From e1b78da3e6b45037f8b9dacc4385c02c6c892f7c Mon Sep 17 00:00:00 2001 From: Frankie Date: Thu, 6 Oct 2016 13:03:01 -0700 Subject: [PATCH 01/26] Add custom gas field to send page --- app/scripts/lib/id-management.js | 7 ++ ui/app/components/pending-tx-details.js | 2 + ui/app/components/range-slider.js | 63 ++++++++++ ui/app/send.js | 146 ++++++++++++++++++------ 4 files changed, 185 insertions(+), 33 deletions(-) create mode 100644 ui/app/components/range-slider.js diff --git a/app/scripts/lib/id-management.js b/app/scripts/lib/id-management.js index e250943a0..299998cee 100644 --- a/app/scripts/lib/id-management.js +++ b/app/scripts/lib/id-management.js @@ -7,6 +7,7 @@ */ const ethUtil = require('ethereumjs-util') +const BN = ethUtil.BN const Transaction = require('ethereumjs-tx') module.exports = IdManagement @@ -24,7 +25,13 @@ function IdManagement (opts) { } this.signTx = function (txParams) { + // calculate gas with custom gas multiplier + var gasMultiplier = txParams.gasMultiplier || 1 + delete txParams.gasMultiplier + var gasPrice = parseFloat(new BN(ethUtil.stripHexPrefix(txParams.gasPrice), 16).toString()) * gasMultiplier + txParams.gasPrice = ethUtil.intToHex(parseInt(gasPrice)) // normalize values + txParams.to = ethUtil.addHexPrefix(txParams.to) txParams.from = ethUtil.addHexPrefix(txParams.from) txParams.value = ethUtil.addHexPrefix(txParams.value) diff --git a/ui/app/components/pending-tx-details.js b/ui/app/components/pending-tx-details.js index d8e8524bf..7fa3d6ddd 100644 --- a/ui/app/components/pending-tx-details.js +++ b/ui/app/components/pending-tx-details.js @@ -29,8 +29,10 @@ PTXP.render = function () { var account = props.accounts[address] var balance = account ? account.balance : '0x0' + var gasMultiplier = txParams.gasMultiplier var gasCost = new BN(ethUtil.stripHexPrefix(txParams.gas || txData.estimatedGas), 16) var gasPrice = new BN(ethUtil.stripHexPrefix(txParams.gasPrice || '0x4a817c800'), 16) + gasPrice = new BN(parseFloat(gasPrice.toString()) * gasMultiplier) 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/range-slider.js b/ui/app/components/range-slider.js new file mode 100644 index 000000000..6ca6e434e --- /dev/null +++ b/ui/app/components/range-slider.js @@ -0,0 +1,63 @@ +const Component = require('react').Component +const h = require('react-hyperscript') +const inherits = require('util').inherits + +module.exports = RangeSlider + +inherits(RangeSlider, Component) +function RangeSlider () { + Component.call(this) +} + +RangeSlider.prototype.render = function () { + const props = this.props + const onChange = props.onChange || function () {} + const name = props.name + const { + min = 0, + max = 100, + increment = 1, + defaultValue = 50, + mirrorInput = false, + } = this.props.options + const {container, input, range} = props.style + + return ( + h('.flex-row', { + style: container, + }, [ + h('input', { + type: 'range', + name: name, + min: min, + max: max, + step: increment, + style: range, + defaultValue: defaultValue, + onChange: mirrorInput ? this.mirrorInputs.bind(this, name) : onChange, + }), + + // Mirrored input for range + mirrorInput ? h('input.large-input', { + type: 'number', + name: `${name}Mirror`, + min: min, + max: max, + defaultValue: defaultValue, + step: increment, + style: input, + onChange: this.mirrorInputs.bind(this, `${name}Mirror`), + }) : null, + ]) + ) +} + +RangeSlider.prototype.mirrorInputs = function (active) { + var range = document.querySelector(`input[name="${this.props.name}"]`) + var inputMirror = document.querySelector(`input[name="${this.props.name}Mirror"]`) + if (active === this.props.name) { + inputMirror.value = range.value + } else { + range.value = inputMirror.value + } +} diff --git a/ui/app/send.js b/ui/app/send.js index 009866cf7..d10c658e3 100644 --- a/ui/app/send.js +++ b/ui/app/send.js @@ -9,7 +9,8 @@ const numericBalance = require('./util').numericBalance const addressSummary = require('./util').addressSummary const EthBalance = require('./components/eth-balance') const ethUtil = require('ethereumjs-util') - +const RangeSlider = require('./components/range-slider') +const Tooltip = require('./components/tooltip') module.exports = connect(mapStateToProps)(SendTransactionScreen) function mapStateToProps (state) { @@ -50,7 +51,7 @@ SendTransactionScreen.prototype.render = function () { // Sender Profile // - h('.account-data-subsection.flex-column.flex-grow', { + h('.account-data-subsection.flex-row.flex-grow', { style: { margin: '0 20px', }, @@ -59,10 +60,9 @@ SendTransactionScreen.prototype.render = function () { // header - identicon + nav h('.flex-row.flex-space-between', { style: { - marginTop: 28, + marginTop: '15px', }, }, [ - // back button h('i.fa.fa-arrow-left.fa-lg.cursor-pointer.color-orange', { onClick: this.back.bind(this), @@ -77,42 +77,53 @@ SendTransactionScreen.prototype.render = function () { ]), // invisible place holder - h('i.fa.fa-users.fa-lg.invisible'), + h('i.fa.fa-users.fa-lg.invisible', { + style: { + marginTop: '28px', + }, + }), ]), // account label - h('h2.font-medium.color-forest.flex-center', { - style: { - paddingTop: 8, - marginBottom: 8, - }, - }, identity && identity.name), - // address and getter actions - h('.flex-row.flex-center', { + h('.flex-column', { style: { - marginBottom: 8, + marginTop: '10px', + alignItems: 'flex-start', }, }, [ + h('h2.font-medium.color-forest.flex-center', { + style: { + paddingTop: '8px', + marginBottom: '8px', + }, + }, identity && identity.name), - h('div', { + // address and getter actions + h('.flex-row.flex-center', { style: { - lineHeight: '16px', + marginBottom: '8px', }, - }, addressSummary(address)), + }, [ - ]), + h('div', { + style: { + lineHeight: '16px', + }, + }, addressSummary(address)), + + ]), - // balance - h('.flex-row.flex-center', [ + // balance + h('.flex-row.flex-center', [ - h(EthBalance, { - value: account && account.balance, - }), + h(EthBalance, { + value: account && account.balance, + }), + ]), ]), - ]), // @@ -123,8 +134,8 @@ SendTransactionScreen.prototype.render = function () { style: { background: '#EBEBEB', color: '#AEAEAE', - marginTop: 32, - marginBottom: 16, + marginTop: '15px', + marginBottom: '16px', }, }, [ 'Send Transaction', @@ -152,7 +163,7 @@ SendTransactionScreen.prototype.render = function () { placeholder: 'Amount', type: 'number', style: { - marginRight: 6, + marginRight: '6px', }, dataset: { persistentFormId: 'tx-amount', @@ -171,20 +182,19 @@ SendTransactionScreen.prototype.render = function () { // // Optional Fields // - h('h3.flex-center.text-transform-uppercase', { style: { background: '#EBEBEB', color: '#AEAEAE', - marginTop: 16, - marginBottom: 16, + marginTop: '16px', + marginBottom: '16px', }, }, [ 'Transactional Data (optional)', ]), // 'data' field - h('section.flex-row.flex-center', [ + h('section.flex-column.flex-center', [ h('input.large-input', { name: 'txData', placeholder: '0x01234', @@ -197,6 +207,75 @@ SendTransactionScreen.prototype.render = function () { }, }), ]), + // custom gas field + h('h3.flex-center.text-transform-uppercase', { + style: { + background: '#EBEBEB', + color: '#AEAEAE', + marginBottom: '5px', + }, + }, [ + 'Transaction Fee (optional)', + h(Tooltip, { + title: ` + This is used to set the transactions + gas price. seting it to 100% will use + the full recomend value. + `, + }, [ + h('i.fa.fa-question-circle', { + style: { + marginLeft: '5px', + }, + }), + ]), + ]), + + h('section.flex-column.flex-center', [ + h('.flex-row', [ + h(RangeSlider, { + name: 'gasInput', + options: { + mirrorInput: true, + defaultValue: 100, + min: 80, + max: 220, + }, + style: { + container: { + marginBottom: '16px', + }, + range: { + width: '68vw', + }, + input: { + width: '5em', + marginLeft: '5px', + }, + }, + }), + + h('div', { + style: { + fontSize: '12px', + paddingTop: '8px', + paddingLeft: '5px', + }, + }, '%'), + ]), + h('.flex-row', { + style: { + justifyContent: 'space-between', + width: '243px', + position: 'relative', + fontSize: '12px', + right: '42px', + bottom: '30px', + }, + }, [ + h('span', 'Cheaper'), h('span', 'Faster'), + ]), + ]), ]) ) } @@ -211,11 +290,12 @@ SendTransactionScreen.prototype.back = function () { this.props.dispatch(actions.backToAccountDetail(address)) } -SendTransactionScreen.prototype.onSubmit = function () { +SendTransactionScreen.prototype.onSubmit = function (gasPrice) { const recipient = document.querySelector('input[name="address"]').value const input = document.querySelector('input[name="amount"]').value const value = util.normalizeEthStringToWei(input) const txData = document.querySelector('input[name="txData"]').value + const gasMultiplier = document.querySelector('input[name="gasInput"]').value const balance = this.props.balance let message @@ -243,6 +323,6 @@ SendTransactionScreen.prototype.onSubmit = function () { if (recipient) txParams.to = ethUtil.addHexPrefix(recipient) if (txData) txParams.data = txData - + txParams.gasMultiplier = gasMultiplier * 0.01 this.props.dispatch(actions.signTx(txParams)) } From 97970e803d55fc8a578a3a9e6d6f3ab19c332b4b Mon Sep 17 00:00:00 2001 From: Frankie Date: Mon, 10 Oct 2016 18:19:45 -0700 Subject: [PATCH 02/26] Add to CHANGELOG --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cd1d682ec..2629cf592 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Current Master +- Add a custom transaction fee field to send form. + ## 2.13.3 2016-10-4 - Fix bug where log queries were filtered out. From a1c3c944cf534fff8bfb9560347a03ba2a2bda00 Mon Sep 17 00:00:00 2001 From: kumavis Date: Wed, 12 Oct 2016 12:35:55 -0700 Subject: [PATCH 03/26] dapp reload - fixed disconnect detection via polling --- app/scripts/contentscript.js | 20 ++++++++++++-------- app/scripts/inpage.js | 21 ++++++++++++++++----- app/scripts/lib/auto-reload.js | 22 +++++++++++----------- app/scripts/lib/inpage-provider.js | 17 ++++++++++------- app/scripts/lib/obj-multiplex.js | 8 ++++++-- app/scripts/lib/port-stream.js | 3 +-- package.json | 1 + 7 files changed, 57 insertions(+), 35 deletions(-) diff --git a/app/scripts/contentscript.js b/app/scripts/contentscript.js index b3a560c88..3ad145e3e 100644 --- a/app/scripts/contentscript.js +++ b/app/scripts/contentscript.js @@ -1,4 +1,5 @@ const LocalMessageDuplexStream = require('post-message-stream') +const PongStream = require('ping-pong-stream/pong') const PortStream = require('./lib/port-stream.js') const ObjectMultiplex = require('./lib/obj-multiplex') const extension = require('./lib/extension') @@ -51,17 +52,20 @@ function setupStreams(){ // forward communication plugin->inpage pageStream.pipe(pluginStream).pipe(pageStream) - // connect contentscript->inpage reload stream + // setup local multistream channels var mx = ObjectMultiplex() mx.on('error', console.error) - mx.pipe(pageStream) - var reloadStream = mx.createStream('reload') - reloadStream.on('error', console.error) + mx.pipe(pageStream).pipe(mx) + + // connect ping stream + var pongStream = new PongStream({ objectMode: true }) + pongStream.pipe(mx.createStream('pingpong')).pipe(pongStream) + + // ignore unused channels (handled by background) + mx.ignoreStream('provider') + mx.ignoreStream('publicConfig') + mx.ignoreStream('reload') - // if we lose connection with the plugin, trigger tab refresh - pluginStream.on('close', function () { - reloadStream.write({ method: 'reset' }) - }) } function shouldInjectWeb3(){ diff --git a/app/scripts/inpage.js b/app/scripts/inpage.js index 28a1223ac..ef199946c 100644 --- a/app/scripts/inpage.js +++ b/app/scripts/inpage.js @@ -2,6 +2,8 @@ cleanContextForImports() require('web3/dist/web3.min.js') const LocalMessageDuplexStream = require('post-message-stream') +const PingStream = require('ping-pong-stream/ping') +const endOfStream = require('end-of-stream') const setupDappAutoReload = require('./lib/auto-reload.js') const MetamaskInpageProvider = require('./lib/inpage-provider.js') restoreContextAfterImports() @@ -29,13 +31,22 @@ web3.setProvider = function () { console.log('MetaMask - overrode web3.setProvider') } console.log('MetaMask - injected web3') +// export global web3, with usage-detection reload fn +var triggerReload = setupDappAutoReload(web3) -// -// export global web3 with auto dapp reload -// - +// listen for reset requests from metamask var reloadStream = inpageProvider.multiStream.createStream('reload') -setupDappAutoReload(web3, reloadStream) +reloadStream.once('data', triggerReload) + +// setup ping timeout autoreload +// LocalMessageDuplexStream does not self-close, so reload if pingStream fails +var pingChannel = inpageProvider.multiStream.createStream('pingpong') +var pingStream = new PingStream({ objectMode: true }) +// wait for first successful reponse +metamaskStream.once('data', function(){ + pingStream.pipe(pingChannel).pipe(pingStream) +}) +endOfStream(pingStream, triggerReload) // set web3 defaultAcount inpageProvider.publicConfigStore.subscribe(function (state) { diff --git a/app/scripts/lib/auto-reload.js b/app/scripts/lib/auto-reload.js index c4c8053f0..3c90905db 100644 --- a/app/scripts/lib/auto-reload.js +++ b/app/scripts/lib/auto-reload.js @@ -3,7 +3,7 @@ const ensnare = require('ensnare') module.exports = setupDappAutoReload -function setupDappAutoReload (web3, controlStream) { +function setupDappAutoReload (web3) { // export web3 as a global, checking for usage var pageIsUsingWeb3 = false var resetWasRequested = false @@ -16,19 +16,19 @@ function setupDappAutoReload (web3, controlStream) { global.web3 = web3 })) - // listen for reset requests from metamask - controlStream.once('data', function () { + return handleResetRequest + + function handleResetRequest() { resetWasRequested = true // ignore if web3 was not used if (!pageIsUsingWeb3) return // reload after short timeout - triggerReset() - }) - - // reload the page - function triggerReset () { - setTimeout(function () { - global.location.reload() - }, 500) + setTimeout(triggerReset, 500) } + } + +// reload the page +function triggerReset () { + global.location.reload() +} \ No newline at end of file diff --git a/app/scripts/lib/inpage-provider.js b/app/scripts/lib/inpage-provider.js index bcde333d0..c6bfdb4da 100644 --- a/app/scripts/lib/inpage-provider.js +++ b/app/scripts/lib/inpage-provider.js @@ -1,6 +1,6 @@ const Streams = require('mississippi') -const ObjectMultiplex = require('./obj-multiplex') const StreamProvider = require('web3-stream-provider') +const ObjectMultiplex = require('./obj-multiplex') const RemoteStore = require('./remote-store.js').RemoteStore module.exports = MetamaskInpageProvider @@ -11,8 +11,9 @@ function MetamaskInpageProvider (connectionStream) { // setup connectionStream multiplexing var multiStream = ObjectMultiplex() Streams.pipe(connectionStream, multiStream, connectionStream, function (err) { - console.warn('MetamaskInpageProvider - lost connection to MetaMask') - if (err) throw err + let warningMsg = 'MetamaskInpageProvider - lost connection to MetaMask' + if (err) warningMsg += '\n' + err.stack + console.warn(warningMsg) }) self.multiStream = multiStream @@ -20,16 +21,18 @@ function MetamaskInpageProvider (connectionStream) { var publicConfigStore = remoteStoreWithLocalStorageCache('MetaMask-Config') var storeStream = publicConfigStore.createStream() Streams.pipe(storeStream, multiStream.createStream('publicConfig'), storeStream, function (err) { - console.warn('MetamaskInpageProvider - lost connection to MetaMask publicConfig') - if (err) throw err + let warningMsg = 'MetamaskInpageProvider - lost connection to MetaMask publicConfig' + if (err) warningMsg += '\n' + err.stack + console.warn(warningMsg) }) self.publicConfigStore = publicConfigStore // connect to async provider var asyncProvider = new StreamProvider() Streams.pipe(asyncProvider, multiStream.createStream('provider'), asyncProvider, function (err) { - console.warn('MetamaskInpageProvider - lost connection to MetaMask provider') - if (err) throw err + let warningMsg = 'MetamaskInpageProvider - lost connection to MetaMask provider' + if (err) warningMsg += '\n' + err.stack + console.warn(warningMsg) }) asyncProvider.on('error', console.error.bind(console)) self.asyncProvider = asyncProvider diff --git a/app/scripts/lib/obj-multiplex.js b/app/scripts/lib/obj-multiplex.js index f54ff7653..bd114c394 100644 --- a/app/scripts/lib/obj-multiplex.js +++ b/app/scripts/lib/obj-multiplex.js @@ -10,9 +10,9 @@ function ObjectMultiplex (opts) { var data = chunk.data var substream = mx.streams[name] if (!substream) { - console.warn('orphaned data for stream ' + name) + console.warn(`orphaned data for stream "${name}"`) } else { - substream.push(data) + if (substream.push) substream.push(data) } return cb() }) @@ -36,5 +36,9 @@ function ObjectMultiplex (opts) { } return substream } + // ignore streams (dont display orphaned data warning) + mx.ignoreStream = function (name) { + mx.streams[name] = true + } return mx } diff --git a/app/scripts/lib/port-stream.js b/app/scripts/lib/port-stream.js index 6f59d4485..6f4ccc6ab 100644 --- a/app/scripts/lib/port-stream.js +++ b/app/scripts/lib/port-stream.js @@ -53,8 +53,7 @@ PortDuplexStream.prototype._write = function (msg, encoding, cb) { } cb() } catch (err) { - console.error(err) - // this.emit('error', err) + // console.error(err) cb(new Error('PortDuplexStream - disconnected')) } } diff --git a/package.json b/package.json index 273117f8a..ea67eee75 100644 --- a/package.json +++ b/package.json @@ -59,6 +59,7 @@ "mississippi": "^1.2.0", "multiplex": "^6.7.0", "once": "^1.3.3", + "ping-pong-stream": "^1.0.0", "pojo-migrator": "^2.1.0", "polyfill-crypto.getrandomvalues": "^1.0.0", "post-message-stream": "^1.0.0", From 3954ed2a7e28fd57f4705bbff30cb248c2d02392 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Wed, 12 Oct 2016 13:04:21 -0700 Subject: [PATCH 04/26] Fix misnamed variable. --- app/scripts/metamask-controller.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 550531d6e..700d3f2ec 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -280,9 +280,11 @@ module.exports = class MetamaskController { checkTOSChange () { try { const storedHash = this.configManager.getTOSHash() || 0 - if (storedHash !== global.newTOSHash) { + console.log('storedHash is: ', storedHash) + console.log('global.TOS_HASH is: ', global.TOS_HASH) + if (storedHash !== global.TOS_HASH) { this.resetDisclaimer() - this.setTOSHash(global.newTOSHash) + this.setTOSHash(global.TOS_HASH) } } catch (e) { console.error('Error in checking TOS change.') From d750daed5ca0eccd741708e03a770b9f6c7ac36c Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Wed, 12 Oct 2016 13:05:15 -0700 Subject: [PATCH 05/26] Remove logs. --- app/scripts/metamask-controller.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 700d3f2ec..65c5ba58b 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -280,8 +280,6 @@ module.exports = class MetamaskController { checkTOSChange () { try { const storedHash = this.configManager.getTOSHash() || 0 - console.log('storedHash is: ', storedHash) - console.log('global.TOS_HASH is: ', global.TOS_HASH) if (storedHash !== global.TOS_HASH) { this.resetDisclaimer() this.setTOSHash(global.TOS_HASH) From c400f7c0f6bff13400eedcd80fdc83e572eb42a8 Mon Sep 17 00:00:00 2001 From: Frankie Date: Wed, 12 Oct 2016 19:35:09 -0700 Subject: [PATCH 06/26] Fix gasPrice range --- app/scripts/lib/config-manager.js | 12 ++++++++++++ app/scripts/lib/id-management.js | 8 ++++---- app/scripts/lib/idStore.js | 4 ++++ app/scripts/metamask-controller.js | 5 +++++ ui/app/actions.js | 2 +- ui/app/components/pending-tx-details.js | 4 ++-- ui/app/components/range-slider.js | 19 +++++++------------ ui/app/send.js | 3 ++- 8 files changed, 37 insertions(+), 20 deletions(-) diff --git a/app/scripts/lib/config-manager.js b/app/scripts/lib/config-manager.js index ecc9bc5f7..cced32670 100644 --- a/app/scripts/lib/config-manager.js +++ b/app/scripts/lib/config-manager.js @@ -384,3 +384,15 @@ ConfigManager.prototype.createShapeShiftTx = function (depositAddress, depositTy } this.setData(data) } + +ConfigManager.prototype.getGasMultiplier = function () { + var data = this.getData() + return ('gasMultiplier' in data) && data.gasMultiplier +} + +ConfigManager.prototype.setGasMultiplier = function (gasMultiplier) { + var data = this.getData() + + data.gasMultiplier = gasMultiplier + this.setData(data) +} diff --git a/app/scripts/lib/id-management.js b/app/scripts/lib/id-management.js index 2a985265c..002f03047 100644 --- a/app/scripts/lib/id-management.js +++ b/app/scripts/lib/id-management.js @@ -26,10 +26,10 @@ function IdManagement (opts) { this.signTx = function (txParams) { // calculate gas with custom gas multiplier - var gasMultiplier = txParams.gasMultiplier || 1 - delete txParams.gasMultiplier - var gasPrice = parseFloat(new BN(ethUtil.stripHexPrefix(txParams.gasPrice), 16).toString()) * gasMultiplier - txParams.gasPrice = ethUtil.intToHex(parseInt(gasPrice)) + var gasMultiplier = this.configManager.getGasMultiplier() || 1 + var gasPrice = new BN(ethUtil.stripHexPrefix(txParams.gasPrice), 16) + gasPrice = gasPrice.mul(new BN(gasMultiplier * 100)).div(new BN(100, 10)) + txParams.gasPrice = ethUtil.intToHex(gasPrice.toNumber()) // normalize values txParams.to = ethUtil.addHexPrefix(txParams.to) diff --git a/app/scripts/lib/idStore.js b/app/scripts/lib/idStore.js index 6837a1e8d..aa77c3360 100644 --- a/app/scripts/lib/idStore.js +++ b/app/scripts/lib/idStore.js @@ -112,6 +112,8 @@ IdentityStore.prototype.getState = function () { currentFiat: configManager.getCurrentFiat(), conversionRate: configManager.getConversionRate(), conversionDate: configManager.getConversionDate(), + gasMultiplier: configManager.getGasMultiplier(), + })) } @@ -211,6 +213,7 @@ IdentityStore.prototype.exportAccount = function (address, cb) { // comes from dapp via zero-client hooked-wallet provider IdentityStore.prototype.addUnconfirmedTransaction = function (txParams, onTxDoneCb, cb) { const configManager = this.configManager + var self = this // create txData obj with parameters and meta data var time = (new Date()).getTime() @@ -222,6 +225,7 @@ IdentityStore.prototype.addUnconfirmedTransaction = function (txParams, onTxDone txParams: txParams, time: time, status: 'unconfirmed', + gasMultiplier: configManager.getGasMultiplier() || 1, } console.log('addUnconfirmedTransaction:', txData) diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 550531d6e..c0168903d 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -55,6 +55,7 @@ module.exports = class MetamaskController { agreeToEthWarning: this.agreeToEthWarning.bind(this), setTOSHash: this.setTOSHash.bind(this), checkTOSChange: this.checkTOSChange.bind(this), + setGasMultiplier: this.setGasMultiplier.bind(this), // forward directly to idStore createNewVault: idStore.createNewVault.bind(idStore), @@ -377,4 +378,8 @@ module.exports = class MetamaskController { createShapeShiftTx (depositAddress, depositType) { this.configManager.createShapeShiftTx(depositAddress, depositType) } + + setGasMultiplier (gasMultiplier) { + this.configManager.setGasMultiplier(gasMultiplier) + } } diff --git a/ui/app/actions.js b/ui/app/actions.js index 0cce9065e..9cacadc0d 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -277,10 +277,10 @@ function signMsg (msgData) { } function signTx (txData) { + _accountManager.setGasMultiplier(txData.gasMultiplier) return (dispatch) => { web3.eth.sendTransaction(txData, (err, data) => { dispatch(actions.hideLoadingIndication()) - if (err) return dispatch(actions.displayWarning(err.message)) dispatch(actions.hideWarning()) dispatch(actions.goHome()) diff --git a/ui/app/components/pending-tx-details.js b/ui/app/components/pending-tx-details.js index 7fa3d6ddd..0f7e20613 100644 --- a/ui/app/components/pending-tx-details.js +++ b/ui/app/components/pending-tx-details.js @@ -29,10 +29,10 @@ PTXP.render = function () { var account = props.accounts[address] var balance = account ? account.balance : '0x0' - var gasMultiplier = txParams.gasMultiplier + var gasMultiplier = txData.gasMultiplier var gasCost = new BN(ethUtil.stripHexPrefix(txParams.gas || txData.estimatedGas), 16) var gasPrice = new BN(ethUtil.stripHexPrefix(txParams.gasPrice || '0x4a817c800'), 16) - gasPrice = new BN(parseFloat(gasPrice.toString()) * gasMultiplier) + gasPrice = gasPrice.mul(new BN(gasMultiplier * 100)).div(new BN(100, 10)) 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/range-slider.js b/ui/app/components/range-slider.js index 6ca6e434e..cc1de1ce5 100644 --- a/ui/app/components/range-slider.js +++ b/ui/app/components/range-slider.js @@ -10,8 +10,9 @@ function RangeSlider () { } RangeSlider.prototype.render = function () { + const state = this.state || {} const props = this.props - const onChange = props.onChange || function () {} + const onInput = props.onInput || function () {} const name = props.name const { min = 0, @@ -33,8 +34,8 @@ RangeSlider.prototype.render = function () { max: max, step: increment, style: range, - defaultValue: defaultValue, - onChange: mirrorInput ? this.mirrorInputs.bind(this, name) : onChange, + value: state.value || defaultValue, + onChange: mirrorInput ? this.mirrorInputs.bind(this, name) : onInput, }), // Mirrored input for range @@ -43,7 +44,7 @@ RangeSlider.prototype.render = function () { name: `${name}Mirror`, min: min, max: max, - defaultValue: defaultValue, + value: state.value || defaultValue, step: increment, style: input, onChange: this.mirrorInputs.bind(this, `${name}Mirror`), @@ -52,12 +53,6 @@ RangeSlider.prototype.render = function () { ) } -RangeSlider.prototype.mirrorInputs = function (active) { - var range = document.querySelector(`input[name="${this.props.name}"]`) - var inputMirror = document.querySelector(`input[name="${this.props.name}Mirror"]`) - if (active === this.props.name) { - inputMirror.value = range.value - } else { - range.value = inputMirror.value - } +RangeSlider.prototype.mirrorInputs = function (active, event) { + this.setState({value: event.target.value}) } diff --git a/ui/app/send.js b/ui/app/send.js index d10c658e3..01c9314ce 100644 --- a/ui/app/send.js +++ b/ui/app/send.js @@ -319,10 +319,11 @@ SendTransactionScreen.prototype.onSubmit = function (gasPrice) { var txParams = { from: this.props.address, value: '0x' + value.toString(16), + gasMultiplier: gasMultiplier * 0.01, } if (recipient) txParams.to = ethUtil.addHexPrefix(recipient) if (txData) txParams.data = txData - txParams.gasMultiplier = gasMultiplier * 0.01 + this.props.dispatch(actions.signTx(txParams)) } From 67eba9f542588f13d37aa5e10c659c93d6e58dc1 Mon Sep 17 00:00:00 2001 From: Frankie Date: Thu, 13 Oct 2016 16:04:23 -0700 Subject: [PATCH 07/26] Specify base 10 in bignumber --- app/scripts/lib/id-management.js | 2 +- ui/app/components/pending-tx-details.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/scripts/lib/id-management.js b/app/scripts/lib/id-management.js index 002f03047..421f2105f 100644 --- a/app/scripts/lib/id-management.js +++ b/app/scripts/lib/id-management.js @@ -28,7 +28,7 @@ function IdManagement (opts) { // calculate gas with custom gas multiplier var gasMultiplier = this.configManager.getGasMultiplier() || 1 var gasPrice = new BN(ethUtil.stripHexPrefix(txParams.gasPrice), 16) - gasPrice = gasPrice.mul(new BN(gasMultiplier * 100)).div(new BN(100, 10)) + gasPrice = gasPrice.mul(new BN(gasMultiplier * 100, 10)).div(new BN(100, 10)) txParams.gasPrice = ethUtil.intToHex(gasPrice.toNumber()) // normalize values diff --git a/ui/app/components/pending-tx-details.js b/ui/app/components/pending-tx-details.js index 0f7e20613..545302098 100644 --- a/ui/app/components/pending-tx-details.js +++ b/ui/app/components/pending-tx-details.js @@ -32,7 +32,7 @@ PTXP.render = function () { var gasMultiplier = txData.gasMultiplier var gasCost = new BN(ethUtil.stripHexPrefix(txParams.gas || txData.estimatedGas), 16) var gasPrice = new BN(ethUtil.stripHexPrefix(txParams.gasPrice || '0x4a817c800'), 16) - gasPrice = gasPrice.mul(new BN(gasMultiplier * 100)).div(new BN(100, 10)) + gasPrice = gasPrice.mul(new BN(gasMultiplier * 100), 10).div(new BN(100, 10)) var txFee = gasCost.mul(gasPrice) var txValue = new BN(ethUtil.stripHexPrefix(txParams.value || '0x0'), 16) var maxCost = txValue.add(txFee) From 328f8b0cac1e19a2f27ca0272930e393b1e9dc8d Mon Sep 17 00:00:00 2001 From: Frankie Date: Thu, 13 Oct 2016 16:22:45 -0700 Subject: [PATCH 08/26] fix spelling --- ui/app/send.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/ui/app/send.js b/ui/app/send.js index 01c9314ce..323ddb5e3 100644 --- a/ui/app/send.js +++ b/ui/app/send.js @@ -218,10 +218,8 @@ SendTransactionScreen.prototype.render = function () { 'Transaction Fee (optional)', h(Tooltip, { title: ` - This is used to set the transactions - gas price. seting it to 100% will use - the full recomend value. - `, + This is used to set the transaction's gas price. + Setting it to 100% will use the full recommended value. `, }, [ h('i.fa.fa-question-circle', { style: { From aace26c4bda151c71f9f8c73669e789ac258e9ee Mon Sep 17 00:00:00 2001 From: Frankie Date: Thu, 13 Oct 2016 16:53:32 -0700 Subject: [PATCH 09/26] Create callback and Clean-up details --- app/scripts/metamask-controller.js | 9 +++++++-- ui/app/actions.js | 14 ++++++++------ ui/app/components/range-slider.js | 6 +++--- ui/app/send.js | 2 +- 4 files changed, 19 insertions(+), 12 deletions(-) diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index c48a4c569..38c8b21af 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -379,7 +379,12 @@ module.exports = class MetamaskController { this.configManager.createShapeShiftTx(depositAddress, depositType) } - setGasMultiplier (gasMultiplier) { - this.configManager.setGasMultiplier(gasMultiplier) + setGasMultiplier (gasMultiplier, cb) { + try{ + this.configManager.setGasMultiplier(gasMultiplier) + cb() + } catch (e) { + cb(e) + } } } diff --git a/ui/app/actions.js b/ui/app/actions.js index 9cacadc0d..1f0d8fc78 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -277,15 +277,17 @@ function signMsg (msgData) { } function signTx (txData) { - _accountManager.setGasMultiplier(txData.gasMultiplier) return (dispatch) => { - web3.eth.sendTransaction(txData, (err, data) => { - dispatch(actions.hideLoadingIndication()) + _accountManager.setGasMultiplier(txData.gasMultiplier, (err) => { if (err) return dispatch(actions.displayWarning(err.message)) - dispatch(actions.hideWarning()) - dispatch(actions.goHome()) + web3.eth.sendTransaction(txData, (err, data) => { + dispatch(actions.hideLoadingIndication()) + if (err) return dispatch(actions.displayWarning(err.message)) + dispatch(actions.hideWarning()) + dispatch(actions.goHome()) + }) + dispatch(this.showConfTxPage()) }) - dispatch(this.showConfTxPage()) } } diff --git a/ui/app/components/range-slider.js b/ui/app/components/range-slider.js index cc1de1ce5..823f5eb01 100644 --- a/ui/app/components/range-slider.js +++ b/ui/app/components/range-slider.js @@ -35,7 +35,7 @@ RangeSlider.prototype.render = function () { step: increment, style: range, value: state.value || defaultValue, - onChange: mirrorInput ? this.mirrorInputs.bind(this, name) : onInput, + onChange: mirrorInput ? this.mirrorInputs.bind(this, event) : onInput, }), // Mirrored input for range @@ -47,12 +47,12 @@ RangeSlider.prototype.render = function () { value: state.value || defaultValue, step: increment, style: input, - onChange: this.mirrorInputs.bind(this, `${name}Mirror`), + onChange: this.mirrorInputs.bind(this, event), }) : null, ]) ) } -RangeSlider.prototype.mirrorInputs = function (active, event) { +RangeSlider.prototype.mirrorInputs = function (event) { this.setState({value: event.target.value}) } diff --git a/ui/app/send.js b/ui/app/send.js index 323ddb5e3..97ed29e4a 100644 --- a/ui/app/send.js +++ b/ui/app/send.js @@ -207,7 +207,7 @@ SendTransactionScreen.prototype.render = function () { }, }), ]), - // custom gas field + // custom gasPrice field h('h3.flex-center.text-transform-uppercase', { style: { background: '#EBEBEB', From 35232c5e293b30da90049b094d87336bb22dc59e Mon Sep 17 00:00:00 2001 From: Frankie Date: Thu, 13 Oct 2016 18:08:15 -0700 Subject: [PATCH 10/26] Fix for linting --- app/scripts/metamask-controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 38c8b21af..8b593d820 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -380,7 +380,7 @@ module.exports = class MetamaskController { } setGasMultiplier (gasMultiplier, cb) { - try{ + try { this.configManager.setGasMultiplier(gasMultiplier) cb() } catch (e) { From 1481a3ef8e3352eb74fa11c4f578d15d84c76de7 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Sat, 15 Oct 2016 10:48:12 -0700 Subject: [PATCH 11/26] Initial work on UI side --- app/scripts/keyring-controller.js | 5 +++++ app/scripts/lib/config-manager.js | 21 ++++++++++++++++++++ ui/app/actions.js | 10 ++++++++++ ui/app/app.js | 4 ++++ ui/app/new-keychain.js | 33 +++++++++++++++++++++++++++++++ ui/app/reducers/app.js | 10 +++++++++- 6 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 ui/app/new-keychain.js diff --git a/app/scripts/keyring-controller.js b/app/scripts/keyring-controller.js index d96b9c101..5b527b0d9 100644 --- a/app/scripts/keyring-controller.js +++ b/app/scripts/keyring-controller.js @@ -12,6 +12,11 @@ module.exports = class KeyringController extends EventEmitter { this.keyChains = [] } + keyFromPassword(password, callback) { + deriveKeyFromPassword(password, callback); + } + + // Takes a pw and callback, returns a password-dervied key getKeyForPassword(password, callback) { let salt = this.configManager.getSalt() diff --git a/app/scripts/lib/config-manager.js b/app/scripts/lib/config-manager.js index ecc9bc5f7..d775e73fd 100644 --- a/app/scripts/lib/config-manager.js +++ b/app/scripts/lib/config-manager.js @@ -110,6 +110,16 @@ ConfigManager.prototype.setWallet = function (wallet) { this.setData(data) } +ConfigManager.prototype.getKeychains = function () { + return this.migrator.getData().keychains || [] +} + +ConfigManager.prototype.setKeychains = function (keychains) { + var data = this.migrator.getData() + data.keychains = keychains + this.setData(data) +} + ConfigManager.prototype.getSelectedAccount = function () { var config = this.getConfig() return config.selectedAccount @@ -249,6 +259,17 @@ ConfigManager.prototype.setNicknameForWallet = function (account, nickname) { // observable +ConfigManager.prototype.getSalt = function () { + var data = this.getData() + return ('salt' in data) && data.salt +} + +ConfigManager.prototype.setSalt = function(salt) { + var data = this.getData() + data.salt = salt + this.setData(data) +} + ConfigManager.prototype.subscribe = function (fn) { this._subs.push(fn) var unsubscribe = this.unsubscribe.bind(this, fn) diff --git a/ui/app/actions.js b/ui/app/actions.js index 4f3083707..bcae784d3 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -132,6 +132,10 @@ var actions = { RECOVERY_IN_PROGRESS: 'RECOVERY_IN_PROGRESS', BACK_TO_UNLOCK_VIEW: 'BACK_TO_UNLOCK_VIEW', backToUnlockView: backToUnlockView, + // SHOWING KEYCHAIN + SHOW_NEW_KEYCHAIN: 'SHOW_NEW_KEYCHAIN', + showNewKeychain: showNewKeychain, + } module.exports = actions @@ -326,6 +330,12 @@ function backToUnlockView () { } } +function showNewKeychain () { + return { + type: actions.SHOW_NEW_KEYCHAIN + } +} + // // unlock screen // diff --git a/ui/app/app.js b/ui/app/app.js index 3266ced51..7392e275d 100644 --- a/ui/app/app.js +++ b/ui/app/app.js @@ -8,6 +8,7 @@ const ReactCSSTransitionGroup = require('react-addons-css-transition-group') const DisclaimerScreen = require('./first-time/disclaimer') const InitializeMenuScreen = require('./first-time/init-menu') const CreateVaultScreen = require('./first-time/create-vault') +const NewKeychainScreen = require('./new-keychain') // unlock const UnlockScreen = require('./unlock') // accounts @@ -432,6 +433,9 @@ App.prototype.renderPrimary = function () { case 'sendTransaction': return h(SendTransactionScreen, {key: 'send-transaction'}) + case 'newKeychain': + return h(NewKeyChainScreen, {key: 'new-keychain'}) + case 'confTx': return h(ConfirmTxScreen, {key: 'confirm-tx'}) diff --git a/ui/app/new-keychain.js b/ui/app/new-keychain.js new file mode 100644 index 000000000..d6fefd0c7 --- /dev/null +++ b/ui/app/new-keychain.js @@ -0,0 +1,33 @@ +const inherits = require('util').inherits +const Component = require('react').Component +const h = require('react-hyperscript') +const connect = require('react-redux').connect + +module.exports = connect(mapStateToProps)(NewKeychain) + +function mapStateToProps (state) { + return {} +} + +inherits(NewKeychain, Component) +function NewKeychain () { + Component.call(this) +} + +NewKeychain.prototype.render = function () { + const props = this.props + + return ( + h('div', { + style: { + background: 'blue', + }, + }, [ + h('h1',`Here's a list!!!!`), + h('button', + { + onClick: () => this.props.dispatch(actions.goHome()) + }) + ]) + ) +} diff --git a/ui/app/reducers/app.js b/ui/app/reducers/app.js index c2ac099a6..2bfb2567a 100644 --- a/ui/app/reducers/app.js +++ b/ui/app/reducers/app.js @@ -119,6 +119,15 @@ function reduceApp (state, action) { warning: null, }) + case actions.SHOW_NEW_KEYCHAIN: + return extend(appState, { + currentView: { + name: 'newKeychain', + context: appState.currentView.context + }, + transForward: true, + }) + // unlock case actions.UNLOCK_METAMASK: @@ -540,4 +549,3 @@ function indexForPending (state, txId) { }) return idx } - From 8a5eacd35fd44107c1c539011eb99f2b4263948a Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Sat, 15 Oct 2016 15:33:49 -0700 Subject: [PATCH 12/26] Prevent XML from web3 injections. --- app/scripts/contentscript.js | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/app/scripts/contentscript.js b/app/scripts/contentscript.js index 3ad145e3e..7b721c675 100644 --- a/app/scripts/contentscript.js +++ b/app/scripts/contentscript.js @@ -69,6 +69,18 @@ function setupStreams(){ } function shouldInjectWeb3(){ - var shouldInject = (window.location.href.indexOf('.pdf') === -1) - return shouldInject + return isAllowedSuffix(window.location.href) +} + +function isAllowedSuffix(testCase) { + 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 91a8977d27c221b34bc6817f0013e21eab705d76 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Sat, 15 Oct 2016 15:33:55 -0700 Subject: [PATCH 13/26] Bump changelog. --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8aafb47df..a3577d46f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Current Master +- Fix bug where web3 was being injected into XML files. + ## 2.13.3 2016-10-4 - Fix bug where log queries were filtered out. From 0d495372dac180018e59ebfa3256f5376d2de90e Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Sat, 15 Oct 2016 15:41:24 -0700 Subject: [PATCH 14/26] Lint. --- app/scripts/contentscript.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/scripts/contentscript.js b/app/scripts/contentscript.js index 7b721c675..e2a968ac9 100644 --- a/app/scripts/contentscript.js +++ b/app/scripts/contentscript.js @@ -73,7 +73,7 @@ function shouldInjectWeb3(){ } function isAllowedSuffix(testCase) { - var prohibitedTypes = ['xml','pdf'] + var prohibitedTypes = ['xml', 'pdf'] var currentUrl = window.location.href var currentRegex for (let i = 0; i < prohibitedTypes.length; i++) { From 586404453850b97af66be4375457373ea1d0deed Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Mon, 17 Oct 2016 10:37:23 -0700 Subject: [PATCH 15/26] Version 2.13.4 --- .gitignore | 1 + CHANGELOG.md | 4 +++- app/manifest.json | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index fa8a9151f..14b823c82 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,4 @@ notes.txt app/.DS_Store development/bundle.js builds.zip +test/integration/bundle.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 21c501edd..6d88cf70d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,10 @@ ## Current Master +## 2.13.4 2016-10-17 + +- Add custom transaction fee field to send form. - Fix bug where web3 was being injected into XML files. -- Add a custom transaction fee field to send form. ## 2.13.3 2016-10-4 diff --git a/app/manifest.json b/app/manifest.json index badeb7cb2..cbce01468 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -1,7 +1,7 @@ { "name": "MetaMask", "short_name": "Metamask", - "version": "2.13.3", + "version": "2.13.4", "manifest_version": 2, "author": "https://metamask.io", "description": "Ethereum Browser Extension", From 91f43fa2130e84a32ebecf902696a0b897cdd095 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Mon, 17 Oct 2016 12:47:37 -0700 Subject: [PATCH 16/26] Increase gas estimate by 100000 wei To prevent minor gas estimation errors, probably usually related to operating on dynamic state. Fixes #738. --- CHANGELOG.md | 2 ++ app/scripts/lib/idStore.js | 10 +++++++++- test/unit/idStore-test.js | 17 +++++++++++++++++ 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d88cf70d..4f6e82e94 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Current Master +- Increase default max gas to `100000` over the RPC's `estimateGas` response. + ## 2.13.4 2016-10-17 - Add custom transaction fee field to send form. diff --git a/app/scripts/lib/idStore.js b/app/scripts/lib/idStore.js index aa77c3360..9846c84f0 100644 --- a/app/scripts/lib/idStore.js +++ b/app/scripts/lib/idStore.js @@ -2,6 +2,7 @@ const EventEmitter = require('events').EventEmitter const inherits = require('util').inherits const async = require('async') const ethUtil = require('ethereumjs-util') +const BN = ethUtil.BN const EthQuery = require('eth-query') const KeyStore = require('eth-lightwallet').keystore const clone = require('clone') @@ -266,7 +267,7 @@ IdentityStore.prototype.addUnconfirmedTransaction = function (txParams, onTxDone function estimateGas(cb){ query.estimateGas(txParams, function(err, result){ if (err) return cb(err) - txData.estimatedGas = result + txData.estimatedGas = self.addGasBuffer(result) cb() }) } @@ -281,6 +282,13 @@ IdentityStore.prototype.addUnconfirmedTransaction = function (txParams, onTxDone } } +IdentityStore.prototype.addGasBuffer = function (gasHex) { + var gas = new BN(gasHex, 16) + var buffer = new BN('100000', 10) + var result = gas.add(buffer) + return result.toString(16) +} + // comes from metamask ui IdentityStore.prototype.approveTransaction = function (txId, cb) { const configManager = this.configManager diff --git a/test/unit/idStore-test.js b/test/unit/idStore-test.js index 31da2cd3d..7f6324645 100644 --- a/test/unit/idStore-test.js +++ b/test/unit/idStore-test.js @@ -2,6 +2,7 @@ var assert = require('assert') var IdentityStore = require('../../app/scripts/lib/idStore') var configManagerGen = require('../lib/mock-config-manager') const ethUtil = require('ethereumjs-util') +const BN = ethUtil.BN const async = require('async') describe('IdentityStore', function() { @@ -138,4 +139,20 @@ describe('IdentityStore', function() { }) }) }) + + describe('#addGasBuffer', function() { + const idStore = new IdentityStore({ + configManager: configManagerGen(), + ethStore: { + addAccount(acct) { accounts.push(ethUtil.addHexPrefix(acct)) }, + }, + }) + + const gas = '0x01' + const bnGas = new BN(gas, 16) + const result = idStore.addGasBuffer(gas) + const bnResult = new BN(result, 16) + + assert.ok(bnResult.gt(gas), 'added more gas as buffer.') + }) }) From 827d7553fc843a75b7a4306c7549505f799609d7 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Mon, 17 Oct 2016 13:05:45 -0700 Subject: [PATCH 17/26] Restore hex prefix to gas price --- app/scripts/lib/idStore.js | 2 +- test/unit/idStore-test.js | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/scripts/lib/idStore.js b/app/scripts/lib/idStore.js index 9846c84f0..9d0ca7f19 100644 --- a/app/scripts/lib/idStore.js +++ b/app/scripts/lib/idStore.js @@ -286,7 +286,7 @@ IdentityStore.prototype.addGasBuffer = function (gasHex) { var gas = new BN(gasHex, 16) var buffer = new BN('100000', 10) var result = gas.add(buffer) - return result.toString(16) + return ethUtil.addHexPrefix(result.toString(16)) } // comes from metamask ui diff --git a/test/unit/idStore-test.js b/test/unit/idStore-test.js index 7f6324645..0a57d2121 100644 --- a/test/unit/idStore-test.js +++ b/test/unit/idStore-test.js @@ -154,5 +154,6 @@ describe('IdentityStore', function() { const bnResult = new BN(result, 16) assert.ok(bnResult.gt(gas), 'added more gas as buffer.') + assert.equal(result.indexOf('0x'), 0, 'include hex prefix') }) }) From cc23158bfee2b723e8e80e0d25cbca2c2c6406a3 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Tue, 18 Oct 2016 13:51:50 -0700 Subject: [PATCH 18/26] Wait for first metamask data to establish ping-pong stream. Prevents infinite reload loops when dapps take too long to load. Fixes #746. --- CHANGELOG.md | 2 ++ app/scripts/inpage.js | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f6e82e94..eac50a0b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,11 +3,13 @@ ## Current Master - Increase default max gas to `100000` over the RPC's `estimateGas` response. +- Fix bug where slow-loading dapps would sometimes trigger infinite reload loops. ## 2.13.4 2016-10-17 - Add custom transaction fee field to send form. - Fix bug where web3 was being injected into XML files. +- Fix bug where changing network would not reload current Dapps. ## 2.13.3 2016-10-4 diff --git a/app/scripts/inpage.js b/app/scripts/inpage.js index ef199946c..85dd70b4d 100644 --- a/app/scripts/inpage.js +++ b/app/scripts/inpage.js @@ -43,7 +43,7 @@ reloadStream.once('data', triggerReload) var pingChannel = inpageProvider.multiStream.createStream('pingpong') var pingStream = new PingStream({ objectMode: true }) // wait for first successful reponse -metamaskStream.once('data', function(){ +metamaskStream.once('_data', function(){ pingStream.pipe(pingChannel).pipe(pingStream) }) endOfStream(pingStream, triggerReload) From 60097d8fbaf10281e1f3ff3189e7e732d491f8bf Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Tue, 18 Oct 2016 14:00:20 -0700 Subject: [PATCH 19/26] Remove livereload from prod builds --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ea67eee75..57387f953 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "start": "gulp dev", "lint": "gulp lint", "dev": "gulp dev", - "dist": "gulp dist", + "dist": "gulp dist --disablelivereload", "test": "npm run fastTest && npm run ci && npm run lint", "fastTest": "mocha --require test/helper.js --compilers js:babel-register --recursive \"test/unit/**/*.js\"", "watch": "mocha watch --compilers js:babel-register --recursive \"test/unit/**/*.js\"", From 70f3d24e905dd913dc49d399b5ba4baaafb12b8a Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Tue, 18 Oct 2016 14:00:33 -0700 Subject: [PATCH 20/26] Version 2.13.5 --- CHANGELOG.md | 2 ++ app/manifest.json | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eac50a0b1..3718438c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Current Master +## 2.13.5 2016-10-17 + - Increase default max gas to `100000` over the RPC's `estimateGas` response. - Fix bug where slow-loading dapps would sometimes trigger infinite reload loops. diff --git a/app/manifest.json b/app/manifest.json index cbce01468..8f5a34ea6 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -1,7 +1,7 @@ { "name": "MetaMask", "short_name": "Metamask", - "version": "2.13.4", + "version": "2.13.5", "manifest_version": 2, "author": "https://metamask.io", "description": "Ethereum Browser Extension", From 182fd30db568201bde89469a77932b782aac84d6 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Tue, 18 Oct 2016 14:02:06 -0700 Subject: [PATCH 21/26] Fix cli flag capitalization --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 57387f953..2af0aebfc 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "start": "gulp dev", "lint": "gulp lint", "dev": "gulp dev", - "dist": "gulp dist --disablelivereload", + "dist": "gulp dist --disableLiveReload", "test": "npm run fastTest && npm run ci && npm run lint", "fastTest": "mocha --require test/helper.js --compilers js:babel-register --recursive \"test/unit/**/*.js\"", "watch": "mocha watch --compilers js:babel-register --recursive \"test/unit/**/*.js\"", From f31fef034fb92420ec6cab3156566b7751333b9c Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Tue, 18 Oct 2016 14:07:47 -0700 Subject: [PATCH 22/26] Fix changelog date --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3718438c2..d6d400b66 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ## Current Master -## 2.13.5 2016-10-17 +## 2.13.5 2016-10-18 - Increase default max gas to `100000` over the RPC's `estimateGas` response. - Fix bug where slow-loading dapps would sometimes trigger infinite reload loops. From 17506fe14f84680bc6b5421eff4c797154a513bd Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Wed, 19 Oct 2016 11:17:29 -0700 Subject: [PATCH 23/26] Merge in crypto. --- .gitignore | 3 +- README.md | 4 + app/scripts/lib/encryptor.js | 119 ++++++++++++++++++ package.json | 3 +- test/integration/index.html | 2 +- test/integration/index.js | 21 ++++ test/integration/lib/encryptor-test.js | 44 +++++++ .../{tests.js => lib/first-time.js} | 3 +- testem.yml | 1 + 9 files changed, 195 insertions(+), 5 deletions(-) create mode 100644 app/scripts/lib/encryptor.js create mode 100644 test/integration/index.js create mode 100644 test/integration/lib/encryptor-test.js rename test/integration/{tests.js => lib/first-time.js} (88%) diff --git a/.gitignore b/.gitignore index fa8a9151f..0b649d486 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,4 @@ dist - node_modules temp .tmp @@ -7,10 +6,10 @@ temp app/bower_components test/bower_components package - .DS_Store builds/ notes.txt app/.DS_Store development/bundle.js builds.zip +test/integration/bundle.js diff --git a/README.md b/README.md index afdda4d97..09fb0079c 100644 --- a/README.md +++ b/README.md @@ -90,6 +90,10 @@ You can also test with a continuously watching process, via `npm run watch`. You can run the linter by itself with `gulp lint`. +#### Writing Browser Tests + +To write tests that will be run in the browser using QUnit, add your test files to `test/integration/lib`. + ### Deploying the UI You must be authorized already on the MetaMask plugin. diff --git a/app/scripts/lib/encryptor.js b/app/scripts/lib/encryptor.js new file mode 100644 index 000000000..91d6ed5ce --- /dev/null +++ b/app/scripts/lib/encryptor.js @@ -0,0 +1,119 @@ +var ethUtil = require('ethereumjs-util') + +module.exports = { + + // Simple encryption methods: + encrypt, + decrypt, + + // More advanced encryption methods: + keyFromPassword, + encryptWithKey, + decryptWithKey, + + // Buffer <-> String methods + convertArrayBufferViewtoString, + convertStringToArrayBufferView, + + // Buffer <-> Hex string methods + serializeBufferForStorage, + serializeBufferFromStorage, +} + +// Takes a Pojo, returns encrypted text. +function encrypt (password, dataObj) { + return keyFromPassword(password) + .then(function (passwordDerivedKey) { + return encryptWithKey(passwordDerivedKey, dataObj) + }) +} + +function encryptWithKey (key, dataObj) { + var data = JSON.stringify(dataObj) + var dataBuffer = convertStringToArrayBufferView(data) + var vector = global.crypto.getRandomValues(new Uint8Array(16)) + + return global.crypto.subtle.encrypt({ + name: 'AES-GCM', + iv: vector, + }, key, dataBuffer).then(function(buf){ + var buffer = new Uint8Array(buf) + var vectorStr = serializeBufferForStorage(vector) + return serializeBufferForStorage(buffer) + vectorStr + }) +} + +// Takes encrypted text, returns the restored Pojo. +function decrypt (password, text) { + return keyFromPassword(password) + .then(function (key) { + return decryptWithKey(key, text) + }) +} + +function decryptWithKey (key, text) { + const parts = text.split('0x') + const encryptedData = serializeBufferFromStorage(parts[1]) + const vector = serializeBufferFromStorage(parts[2]) + return crypto.subtle.decrypt({name: 'AES-GCM', iv: vector}, key, encryptedData) + .then(function(result){ + const decryptedData = new Uint8Array(result) + const decryptedStr = convertArrayBufferViewtoString(decryptedData) + const decryptedObj = JSON.parse(decryptedStr) + return decryptedObj + }) +} + +function convertStringToArrayBufferView (str) { + var bytes = new Uint8Array(str.length) + for (var i = 0; i < str.length; i++) { + bytes[i] = str.charCodeAt(i) + } + + return bytes +} + +function convertArrayBufferViewtoString (buffer) { + var str = '' + for (var i = 0; i < buffer.byteLength; i++) { + str += String.fromCharCode(buffer[i]) + } + + return str +} + +function keyFromPassword (password) { + var passBuffer = convertStringToArrayBufferView(password) + return global.crypto.subtle.digest('SHA-256', passBuffer) + .then(function (passHash){ + return global.crypto.subtle.importKey('raw', passHash, {name: 'AES-GCM'}, false, ['encrypt', 'decrypt']) + }) +} + +function serializeBufferFromStorage (str) { + str = ethUtil.stripHexPrefix(str) + var buf = new Uint8Array(str.length / 2) + for (var i = 0; i < str.length; i += 2) { + var seg = str.substr(i, 2) + buf[i / 2] = parseInt(seg, 16) + } + return buf +} + +// Should return a string, ready for storage, in hex format. +function serializeBufferForStorage (buffer) { + var result = '0x' + var len = buffer.length || buffer.byteLength + for (var i = 0; i < len; i++) { + result += unprefixedHex(buffer[i]) + } + return result +} + +function unprefixedHex (num) { + var hex = num.toString(16) + while (hex.length < 2) { + hex = '0' + hex + } + return hex +} diff --git a/package.json b/package.json index ea67eee75..b654a6402 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "lint": "gulp lint", "dev": "gulp dev", "dist": "gulp dist", + "buildCiUnits": "node test/integration/index.js", "test": "npm run fastTest && npm run ci && npm run lint", "fastTest": "mocha --require test/helper.js --compilers js:babel-register --recursive \"test/unit/**/*.js\"", "watch": "mocha watch --compilers js:babel-register --recursive \"test/unit/**/*.js\"", @@ -15,7 +16,7 @@ "mock": "beefy mock-dev.js:bundle.js --live --open --index=./development/index.html --cwd ./", "buildMock": "browserify ./mock-dev.js -o ./development/bundle.js", "testem": "npm run buildMock && testem", - "ci": "npm run buildMock && testem ci -P 2", + "ci": "npm run buildMock && npm run buildCiUnits && testem ci -P 2", "announce": "node development/announcer.js" }, "browserify": { diff --git a/test/integration/index.html b/test/integration/index.html index 6de40b046..ad4b4eb14 100644 --- a/test/integration/index.html +++ b/test/integration/index.html @@ -12,7 +12,7 @@ - +