Merge pull request #3007 from alextsg/uat-master-011618

[NewUI] Merge master into uat branch
feature/default_network_editable
Chi Kei Chan 7 years ago committed by GitHub
commit b80ed2c451
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 14
      CHANGELOG.md
  2. 2
      README.md
  3. 10
      app/_locales/ko/messages.json
  4. 1
      app/scripts/controllers/blacklist.js
  5. 7
      app/scripts/controllers/network.js
  6. 78
      app/scripts/controllers/recent-blocks.js
  7. 14
      app/scripts/controllers/transactions.js
  8. 34
      app/scripts/lib/tx-gas-utils.js
  9. 42
      app/scripts/metamask-controller.js
  10. 16
      gulpfile.js
  11. 2
      notices/archive/notice_2.md
  12. 2
      notices/notices.json
  13. 16
      old-ui/app/account-detail.js
  14. 13
      old-ui/app/app.js
  15. 6
      old-ui/app/conf-tx.js
  16. 2
      old-ui/app/css/index.css
  17. 15
      package.json
  18. 15
      test/stub/provider.js
  19. 68
      test/unit/metamask-controller-test.js
  20. 40
      test/unit/tx-controller-test.js
  21. 32
      test/unit/tx-gas-util-test.js
  22. 1
      ui/app/app.js
  23. 7
      ui/app/info.js
  24. 4
      ui/app/keychains/hd/restore-vault.js

@ -2,10 +2,24 @@
## Current Master ## Current Master
## 3.13.5 2018-1-16
- Estimating gas limit for simple ether sends now faster & cheaper, by avoiding VM usage on recipients with no code.
- Add an extra px to address for Firefox clipping.
- Fix Firefox scrollbar.
- Open metamask popup for transaction confirmation before gas estimation finishes and add a loading screen over transaction confirmation.
- Fix bug that prevented eth_signTypedData from signing bytes.
- Further improve gas price estimation.
## 3.13.4 2018-1-9
- Remove recipient field if application initializes a tx with an empty string, or 0x, and tx data. Throw an error with the same condition, but without tx data.
- Improve gas price suggestion to be closer to the lowest that will be accepted.
- Throw an error if a application tries to submit a tx whose value is a decimal, and inform that it should be in wei. - Throw an error if a application tries to submit a tx whose value is a decimal, and inform that it should be in wei.
- Fix bug that prevented updating custom token details. - Fix bug that prevented updating custom token details.
- No longer mark long-pending transactions as failed, since we now have button to retry with higher gas. - No longer mark long-pending transactions as failed, since we now have button to retry with higher gas.
- Fix rounding error when specifying an ether amount that has too much precision. - Fix rounding error when specifying an ether amount that has too much precision.
- Fix bug where incorrectly inputting seed phrase would prevent any future attempts from succeeding.
## 3.13.3 2017-12-14 ## 3.13.3 2017-12-14

@ -4,7 +4,7 @@
## Support ## Support
If you're a user seeking support, [here is our support site](http://metamask.consensyssupport.happyfox.com). If you're a user seeking support, [here is our support site](https://metamask.helpscoutdocs.com/).
## Developing Compatible Dapps ## Developing Compatible Dapps

@ -0,0 +1,10 @@
{
"appName": {
"message": "MetaMask",
"description": "The name of the application"
},
"appDescription": {
"message": "이더리움 계좌 관리",
"description": "The description of the application"
}
}

@ -57,3 +57,4 @@ class BlacklistController {
} }
module.exports = BlacklistController module.exports = BlacklistController

@ -1,6 +1,7 @@
const assert = require('assert') const assert = require('assert')
const EventEmitter = require('events') const EventEmitter = require('events')
const createMetamaskProvider = require('web3-provider-engine/zero.js') const createMetamaskProvider = require('web3-provider-engine/zero.js')
const SubproviderFromProvider = require('web3-provider-engine/subproviders/web3.js')
const createInfuraProvider = require('eth-json-rpc-infura/src/createProvider') const createInfuraProvider = require('eth-json-rpc-infura/src/createProvider')
const ObservableStore = require('obs-store') const ObservableStore = require('obs-store')
const ComposedStore = require('obs-store/lib/composed') const ComposedStore = require('obs-store/lib/composed')
@ -161,15 +162,17 @@ module.exports = class NetworkController extends EventEmitter {
_configureInfuraProvider (opts) { _configureInfuraProvider (opts) {
log.info('_configureInfuraProvider', opts) log.info('_configureInfuraProvider', opts)
const blockTrackerProvider = createInfuraProvider({ const infuraProvider = createInfuraProvider({
network: opts.type, network: opts.type,
}) })
const infuraSubprovider = new SubproviderFromProvider(infuraProvider)
const providerParams = extend(this._baseProviderParams, { const providerParams = extend(this._baseProviderParams, {
rpcUrl: opts.rpcUrl, rpcUrl: opts.rpcUrl,
engineParams: { engineParams: {
pollingInterval: 8000, pollingInterval: 8000,
blockTrackerProvider, blockTrackerProvider: infuraProvider,
}, },
dataSubprovider: infuraSubprovider,
}) })
const provider = createMetamaskProvider(providerParams) const provider = createMetamaskProvider(providerParams)
this._setProvider(provider) this._setProvider(provider)

@ -1,11 +1,14 @@
const ObservableStore = require('obs-store') const ObservableStore = require('obs-store')
const extend = require('xtend') const extend = require('xtend')
const BN = require('ethereumjs-util').BN
const EthQuery = require('eth-query')
class RecentBlocksController { class RecentBlocksController {
constructor (opts = {}) { constructor (opts = {}) {
const { blockTracker } = opts const { blockTracker, provider } = opts
this.blockTracker = blockTracker this.blockTracker = blockTracker
this.ethQuery = new EthQuery(provider)
this.historyLength = opts.historyLength || 40 this.historyLength = opts.historyLength || 40
const initState = extend({ const initState = extend({
@ -14,6 +17,7 @@ class RecentBlocksController {
this.store = new ObservableStore(initState) this.store = new ObservableStore(initState)
this.blockTracker.on('block', this.processBlock.bind(this)) this.blockTracker.on('block', this.processBlock.bind(this))
this.backfill()
} }
resetState () { resetState () {
@ -23,22 +27,84 @@ class RecentBlocksController {
} }
processBlock (newBlock) { processBlock (newBlock) {
const block = this.mapTransactionsToPrices(newBlock)
const state = this.store.getState()
state.recentBlocks.push(block)
while (state.recentBlocks.length > this.historyLength) {
state.recentBlocks.shift()
}
this.store.updateState(state)
}
backfillBlock (newBlock) {
const block = this.mapTransactionsToPrices(newBlock)
const state = this.store.getState()
if (state.recentBlocks.length < this.historyLength) {
state.recentBlocks.unshift(block)
}
this.store.updateState(state)
}
mapTransactionsToPrices (newBlock) {
const block = extend(newBlock, { const block = extend(newBlock, {
gasPrices: newBlock.transactions.map((tx) => { gasPrices: newBlock.transactions.map((tx) => {
return tx.gasPrice return tx.gasPrice
}), }),
}) })
delete block.transactions delete block.transactions
return block
}
const state = this.store.getState() async backfill() {
state.recentBlocks.push(block) this.blockTracker.once('block', async (block) => {
let blockNum = block.number
let recentBlocks
let state = this.store.getState()
recentBlocks = state.recentBlocks
while (state.recentBlocks.length > this.historyLength) { while (recentBlocks.length < this.historyLength) {
state.recentBlocks.shift() try {
let blockNumBn = new BN(blockNum.substr(2), 16)
const newNum = blockNumBn.subn(1).toString(10)
const newBlock = await this.getBlockByNumber(newNum)
if (newBlock) {
this.backfillBlock(newBlock)
blockNum = newBlock.number
} }
this.store.updateState(state) state = this.store.getState()
recentBlocks = state.recentBlocks
} catch (e) {
log.error(e)
}
await this.wait()
}
})
} }
async wait () {
return new Promise((resolve) => {
setTimeout(resolve, 100)
})
}
async getBlockByNumber (number) {
const bn = new BN(number)
return new Promise((resolve, reject) => {
this.ethQuery.getBlockByNumber('0x' + bn.toString(16), true, (err, block) => {
if (err) reject(err)
resolve(block)
})
})
}
} }
module.exports = RecentBlocksController module.exports = RecentBlocksController

@ -32,6 +32,7 @@ module.exports = class TransactionController extends EventEmitter {
this.provider = opts.provider this.provider = opts.provider
this.blockTracker = opts.blockTracker this.blockTracker = opts.blockTracker
this.signEthTx = opts.signTransaction this.signEthTx = opts.signTransaction
this.getGasPrice = opts.getGasPrice
this.memStore = new ObservableStore({}) this.memStore = new ObservableStore({})
this.query = new EthQuery(this.provider) this.query = new EthQuery(this.provider)
@ -138,7 +139,6 @@ module.exports = class TransactionController extends EventEmitter {
async newUnapprovedTransaction (txParams) { async newUnapprovedTransaction (txParams) {
log.debug(`MetaMaskController newUnapprovedTransaction ${JSON.stringify(txParams)}`) log.debug(`MetaMaskController newUnapprovedTransaction ${JSON.stringify(txParams)}`)
const initialTxMeta = await this.addUnapprovedTransaction(txParams) const initialTxMeta = await this.addUnapprovedTransaction(txParams)
this.emit('newUnapprovedTx', initialTxMeta)
// listen for tx completion (success, fail) // listen for tx completion (success, fail)
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.txStateManager.once(`${initialTxMeta.id}:finished`, (finishedTxMeta) => { this.txStateManager.once(`${initialTxMeta.id}:finished`, (finishedTxMeta) => {
@ -166,11 +166,16 @@ module.exports = class TransactionController extends EventEmitter {
status: 'unapproved', status: 'unapproved',
metamaskNetworkId: this.getNetwork(), metamaskNetworkId: this.getNetwork(),
txParams: txParams, txParams: txParams,
loadingDefaults: true,
} }
this.addTx(txMeta)
this.emit('newUnapprovedTx', txMeta)
// add default tx params // add default tx params
await this.addTxDefaults(txMeta) await this.addTxDefaults(txMeta)
txMeta.loadingDefaults = false
// save txMeta // save txMeta
this.addTx(txMeta) this.txStateManager.updateTx(txMeta)
return txMeta return txMeta
} }
@ -179,7 +184,10 @@ module.exports = class TransactionController extends EventEmitter {
// ensure value // ensure value
txMeta.gasPriceSpecified = Boolean(txParams.gasPrice) txMeta.gasPriceSpecified = Boolean(txParams.gasPrice)
txMeta.nonceSpecified = Boolean(txParams.nonce) txMeta.nonceSpecified = Boolean(txParams.nonce)
const gasPrice = txParams.gasPrice || await this.query.gasPrice() let gasPrice = txParams.gasPrice
if (!gasPrice) {
gasPrice = this.getGasPrice ? this.getGasPrice() : await this.query.gasPrice()
}
txParams.gasPrice = ethUtil.addHexPrefix(gasPrice.toString(16)) txParams.gasPrice = ethUtil.addHexPrefix(gasPrice.toString(16))
txParams.value = txParams.value || '0x0' txParams.value = txParams.value || '0x0'
// set gasLimit // set gasLimit

@ -4,6 +4,7 @@ const {
BnMultiplyByFraction, BnMultiplyByFraction,
bnToHex, bnToHex,
} = require('./util') } = require('./util')
const SIMPLE_GAS_COST = '0x5208' // Hex for 21000, cost of a simple send.
/* /*
tx-utils are utility methods for Transaction manager tx-utils are utility methods for Transaction manager
@ -37,14 +38,30 @@ module.exports = class txProvideUtil {
async estimateTxGas (txMeta, blockGasLimitHex) { async estimateTxGas (txMeta, blockGasLimitHex) {
const txParams = txMeta.txParams const txParams = txMeta.txParams
// check if gasLimit is already specified // check if gasLimit is already specified
txMeta.gasLimitSpecified = Boolean(txParams.gas) txMeta.gasLimitSpecified = Boolean(txParams.gas)
// if it is, use that value
if (txMeta.gasLimitSpecified) {
return txParams.gas
}
// if recipient has no code, gas is 21k max:
const recipient = txParams.to
const hasRecipient = Boolean(recipient)
const code = await this.query.getCode(recipient)
if (hasRecipient && (!code || code === '0x')) {
txParams.gas = SIMPLE_GAS_COST
txMeta.simpleSend = true // Prevents buffer addition
return SIMPLE_GAS_COST
}
// if not, fall back to block gasLimit // if not, fall back to block gasLimit
if (!txMeta.gasLimitSpecified) {
const blockGasLimitBN = hexToBn(blockGasLimitHex) const blockGasLimitBN = hexToBn(blockGasLimitHex)
const saferGasLimitBN = BnMultiplyByFraction(blockGasLimitBN, 19, 20) const saferGasLimitBN = BnMultiplyByFraction(blockGasLimitBN, 19, 20)
txParams.gas = bnToHex(saferGasLimitBN) txParams.gas = bnToHex(saferGasLimitBN)
}
// run tx // run tx
return await this.query.estimateGas(txParams) return await this.query.estimateGas(txParams)
} }
@ -55,7 +72,7 @@ module.exports = class txProvideUtil {
// if gasLimit was specified and doesnt OOG, // if gasLimit was specified and doesnt OOG,
// use original specified amount // use original specified amount
if (txMeta.gasLimitSpecified) { if (txMeta.gasLimitSpecified || txMeta.simpleSend) {
txMeta.estimatedGas = txParams.gas txMeta.estimatedGas = txParams.gas
return return
} }
@ -81,6 +98,7 @@ module.exports = class txProvideUtil {
} }
async validateTxParams (txParams) { async validateTxParams (txParams) {
this.validateRecipient(txParams)
if ('value' in txParams) { if ('value' in txParams) {
const value = txParams.value.toString() const value = txParams.value.toString()
if (value.includes('-')) { if (value.includes('-')) {
@ -92,4 +110,14 @@ module.exports = class txProvideUtil {
} }
} }
} }
validateRecipient (txParams) {
if (txParams.to === '0x') {
if (txParams.data) {
delete txParams.to
} else {
throw new Error('Invalid recipient address')
}
}
return txParams
}
} }

@ -5,7 +5,6 @@ const Dnode = require('dnode')
const ObservableStore = require('obs-store') const ObservableStore = require('obs-store')
const asStream = require('obs-store/lib/asStream') const asStream = require('obs-store/lib/asStream')
const AccountTracker = require('./lib/account-tracker') const AccountTracker = require('./lib/account-tracker')
const EthQuery = require('eth-query')
const RpcEngine = require('json-rpc-engine') const RpcEngine = require('json-rpc-engine')
const debounce = require('debounce') const debounce = require('debounce')
const createEngineStream = require('json-rpc-middleware-stream/engineStream') const createEngineStream = require('json-rpc-middleware-stream/engineStream')
@ -35,13 +34,15 @@ const accountImporter = require('./account-import-strategies')
const getBuyEthUrl = require('./lib/buy-eth-url') const getBuyEthUrl = require('./lib/buy-eth-url')
const Mutex = require('await-semaphore').Mutex const Mutex = require('await-semaphore').Mutex
const version = require('../manifest.json').version const version = require('../manifest.json').version
const BN = require('ethereumjs-util').BN
const GWEI_BN = new BN('1000000000')
const percentile = require('percentile')
module.exports = class MetamaskController extends EventEmitter { module.exports = class MetamaskController extends EventEmitter {
constructor (opts) { constructor (opts) {
super() super()
this.sendUpdate = debounce(this.privateSendUpdate.bind(this), 200) this.sendUpdate = debounce(this.privateSendUpdate.bind(this), 200)
this.opts = opts this.opts = opts
@ -94,10 +95,9 @@ module.exports = class MetamaskController extends EventEmitter {
this.recentBlocksController = new RecentBlocksController({ this.recentBlocksController = new RecentBlocksController({
blockTracker: this.blockTracker, blockTracker: this.blockTracker,
provider: this.provider,
}) })
// eth data query tools
this.ethQuery = new EthQuery(this.provider)
// account tracker watches balances, nonces, and any code at their address. // account tracker watches balances, nonces, and any code at their address.
this.accountTracker = new AccountTracker({ this.accountTracker = new AccountTracker({
provider: this.provider, provider: this.provider,
@ -138,7 +138,7 @@ module.exports = class MetamaskController extends EventEmitter {
signTransaction: this.keyringController.signTransaction.bind(this.keyringController), signTransaction: this.keyringController.signTransaction.bind(this.keyringController),
provider: this.provider, provider: this.provider,
blockTracker: this.blockTracker, blockTracker: this.blockTracker,
ethQuery: this.ethQuery, getGasPrice: this.getGasPrice.bind(this),
}) })
this.txController.on('newUnapprovedTx', opts.showUnapprovedTx.bind(opts)) this.txController.on('newUnapprovedTx', opts.showUnapprovedTx.bind(opts))
@ -489,6 +489,33 @@ module.exports = class MetamaskController extends EventEmitter {
this.emit('update', this.getState()) this.emit('update', this.getState())
} }
getGasPrice () {
const { recentBlocksController } = this
const { recentBlocks } = recentBlocksController.store.getState()
// Return 1 gwei if no blocks have been observed:
if (recentBlocks.length === 0) {
return '0x' + GWEI_BN.toString(16)
}
const lowestPrices = recentBlocks.map((block) => {
if (!block.gasPrices || block.gasPrices.length < 1) {
return GWEI_BN
}
return block.gasPrices
.map(hexPrefix => hexPrefix.substr(2))
.map(hex => new BN(hex, 16))
.sort((a, b) => {
return a.gt(b) ? 1 : -1
})[0]
})
.map(number => number.div(GWEI_BN).toNumber())
const percentileNum = percentile(50, lowestPrices)
const percentileNumBn = new BN(percentileNum)
return '0x' + percentileNumBn.mul(GWEI_BN).toString(16)
}
// //
// Vault Management // Vault Management
// //
@ -518,10 +545,15 @@ module.exports = class MetamaskController extends EventEmitter {
async createNewVaultAndRestore (password, seed) { async createNewVaultAndRestore (password, seed) {
const release = await this.createVaultMutex.acquire() const release = await this.createVaultMutex.acquire()
try {
const vault = await this.keyringController.createNewVaultAndRestore(password, seed) const vault = await this.keyringController.createNewVaultAndRestore(password, seed)
this.selectFirstIdentity(vault) this.selectFirstIdentity(vault)
release() release()
return vault return vault
} catch (err) {
release()
throw err
}
} }
selectFirstIdentity (vault) { selectFirstIdentity (vault) {

@ -19,6 +19,8 @@ var manifest = require('./app/manifest.json')
var gulpif = require('gulp-if') var gulpif = require('gulp-if')
var replace = require('gulp-replace') var replace = require('gulp-replace')
var mkdirp = require('mkdirp') var mkdirp = require('mkdirp')
var asyncEach = require('async/each')
var exec = require('child_process').exec
var sass = require('gulp-sass') var sass = require('gulp-sass')
var autoprefixer = require('gulp-autoprefixer') var autoprefixer = require('gulp-autoprefixer')
var gulpStylelint = require('gulp-stylelint') var gulpStylelint = require('gulp-stylelint')
@ -161,6 +163,18 @@ gulp.task('copy:watch', function(){
gulp.watch(['./app/{_locales,images}/*', './app/scripts/chromereload.js', './app/*.{html,json}'], gulp.series('copy')) gulp.watch(['./app/{_locales,images}/*', './app/scripts/chromereload.js', './app/*.{html,json}'], gulp.series('copy'))
}) })
// record deps
gulp.task('deps', function (cb) {
exec('npm ls', (err, stdoutOutput, stderrOutput) => {
if (err) return cb(err)
const browsers = ['firefox','chrome','edge','opera']
asyncEach(browsers, (target, done) => {
fs.writeFile(`./dist/${target}/deps.txt`, stdoutOutput, done)
}, cb)
})
})
// lint js // lint js
gulp.task('lint', function () { gulp.task('lint', function () {
@ -289,7 +303,7 @@ gulp.task('apply-prod-environment', function(done) {
gulp.task('dev', gulp.series('build:scss', 'dev:js', 'copy', gulp.parallel('watch:scss', 'copy:watch', 'dev:reload'))) gulp.task('dev', gulp.series('build:scss', 'dev:js', 'copy', gulp.parallel('watch:scss', 'copy:watch', 'dev:reload')))
gulp.task('build', gulp.series('clean', 'build:scss', gulp.parallel('build:js', 'copy'))) gulp.task('build', gulp.series('clean', 'build:scss', gulp.parallel('build:js', 'copy', 'deps')))
gulp.task('dist', gulp.series('apply-prod-environment', 'build', 'zip')) gulp.task('dist', gulp.series('apply-prod-environment', 'build', 'zip'))
// task generators // task generators

@ -4,5 +4,3 @@ When you log in to MetaMask, your current account is visible to every new site y
For your privacy, for now, please sign out of MetaMask when you're done using a site. For your privacy, for now, please sign out of MetaMask when you're done using a site.
Also, by default, you will be signed in to a test network. To use real Ether, you must connect to the main network manually in the top left network menu.

File diff suppressed because one or more lines are too long

@ -78,9 +78,10 @@ AccountDetailScreen.prototype.render = function () {
address: selected, address: selected,
}), }),
]), ]),
h('div.flex-column', { h('flex-column', {
style: { style: {
lineHeight: '10px', lineHeight: '10px',
marginLeft: '15px',
width: '100%', width: '100%',
}, },
}, [ }, [
@ -101,7 +102,7 @@ AccountDetailScreen.prototype.render = function () {
{ {
style: { style: {
display: 'flex', display: 'flex',
justifyContent: 'space-between', justifyContent: 'flex-start',
alignItems: 'center', alignItems: 'center',
}, },
}, },
@ -131,6 +132,8 @@ AccountDetailScreen.prototype.render = function () {
AccountDropdowns, AccountDropdowns,
{ {
style: { style: {
marginRight: '8px',
marginLeft: 'auto',
cursor: 'pointer', cursor: 'pointer',
}, },
selected, selected,
@ -144,6 +147,7 @@ AccountDetailScreen.prototype.render = function () {
]), ]),
h('.flex-row', { h('.flex-row', {
style: { style: {
width: '15em',
justifyContent: 'space-between', justifyContent: 'space-between',
alignItems: 'baseline', alignItems: 'baseline',
}, },
@ -157,12 +161,11 @@ AccountDetailScreen.prototype.render = function () {
textOverflow: 'ellipsis', textOverflow: 'ellipsis',
paddingTop: '3px', paddingTop: '3px',
width: '5em', width: '5em',
height: '15px',
fontSize: '13px', fontSize: '13px',
fontFamily: 'Montserrat Light', fontFamily: 'Montserrat Light',
textRendering: 'geometricPrecision', textRendering: 'geometricPrecision',
marginTop: '15px',
marginBottom: '15px', marginBottom: '15px',
marginLeft: '15px',
color: '#AEAEAE', color: '#AEAEAE',
}, },
}, checksumAddress), }, checksumAddress),
@ -189,7 +192,7 @@ AccountDetailScreen.prototype.render = function () {
}, },
}), }),
h('div', {}, [ h('.flex-grow'),
h('button', { h('button', {
onClick: () => props.dispatch(actions.buyEthView(selected)), onClick: () => props.dispatch(actions.buyEthView(selected)),
@ -200,12 +203,11 @@ AccountDetailScreen.prototype.render = function () {
onClick: () => props.dispatch(actions.showSendPage()), onClick: () => props.dispatch(actions.showSendPage()),
style: { style: {
marginBottom: '20px', marginBottom: '20px',
marginRight: '8px',
}, },
}, 'SEND'), }, 'SEND'),
]), ]),
]),
]), ]),
// subview (tx history, pk export confirm, buy eth warning) // subview (tx history, pk export confirm, buy eth warning)

@ -397,7 +397,7 @@ App.prototype.renderDropdown = function () {
h(DropdownMenuItem, { h(DropdownMenuItem, {
closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }), closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }),
onClick: () => { this.props.dispatch(actions.lockMetamask()) }, onClick: () => { this.props.dispatch(actions.lockMetamask()) },
}, 'Lock'), }, 'Log Out'),
h(DropdownMenuItem, { h(DropdownMenuItem, {
closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }), closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }),
@ -470,11 +470,6 @@ App.prototype.renderPrimary = function () {
}) })
} }
if (props.seedWords) {
log.debug('rendering seed words')
return h(HDCreateVaultComplete, {key: 'HDCreateVaultComplete'})
}
// show initialize screen // show initialize screen
if (!props.isInitialized || props.forgottenPassword) { if (!props.isInitialized || props.forgottenPassword) {
// show current view // show current view
@ -509,6 +504,12 @@ App.prototype.renderPrimary = function () {
} }
} }
// show seed words screen
if (props.seedWords) {
log.debug('rendering seed words')
return h(HDCreateVaultComplete, {key: 'HDCreateVaultComplete'})
}
// show current view // show current view
switch (props.currentView.name) { switch (props.currentView.name) {

@ -4,6 +4,7 @@ const h = require('react-hyperscript')
const connect = require('react-redux').connect const connect = require('react-redux').connect
const actions = require('../../ui/app/actions') const actions = require('../../ui/app/actions')
const NetworkIndicator = require('./components/network') const NetworkIndicator = require('./components/network')
const LoadingIndicator = require('./components/loading')
const txHelper = require('../lib/tx-helper') const txHelper = require('../lib/tx-helper')
const isPopupOrNotification = require('../../app/scripts/lib/is-popup-or-notification') const isPopupOrNotification = require('../../app/scripts/lib/is-popup-or-notification')
@ -60,6 +61,11 @@ ConfirmTxScreen.prototype.render = function () {
h('.flex-column.flex-grow', [ h('.flex-column.flex-grow', [
h(LoadingIndicator, {
isLoading: txData.loadingDefaults,
loadingMessage: 'Estimating transaction cost…',
}),
// subtitle and nav // subtitle and nav
h('.section-title.flex-row.flex-center', [ h('.section-title.flex-row.flex-center', [
!isNotification ? h('i.fa.fa-arrow-left.fa-lg.cursor-pointer', { !isNotification ? h('i.fa.fa-arrow-left.fa-lg.cursor-pointer', {

@ -440,7 +440,9 @@ input.large-input {
.account-detail-section { .account-detail-section {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
overflow-x: hidden;
overflow-y: auto; overflow-y: auto;
max-height: 465px;
flex-direction: inherit; flex-direction: inherit;
} }

@ -77,15 +77,14 @@
"eslint-plugin-react": "^7.4.0", "eslint-plugin-react": "^7.4.0",
"eth-bin-to-ops": "^1.0.1", "eth-bin-to-ops": "^1.0.1",
"eth-block-tracker": "^2.2.0", "eth-block-tracker": "^2.2.0",
"eth-json-rpc-filters": "^1.2.5",
"eth-json-rpc-infura": "^2.0.5",
"eth-keyring-controller": "^2.1.4",
"eth-contract-metadata": "^1.1.5", "eth-contract-metadata": "^1.1.5",
"eth-hd-keyring": "^1.2.1", "eth-hd-keyring": "^1.2.1",
"eth-json-rpc-filters": "^1.2.4",
"eth-json-rpc-infura": "^1.0.2",
"eth-keyring-controller": "^2.1.3",
"eth-phishing-detect": "^1.1.4", "eth-phishing-detect": "^1.1.4",
"eth-query": "^2.1.2", "eth-query": "^2.1.2",
"eth-sig-util": "^1.4.0", "eth-sig-util": "^1.4.2",
"eth-simple-keyring": "^1.2.0",
"eth-token-tracker": "^1.1.4", "eth-token-tracker": "^1.1.4",
"ethereumjs-abi": "^0.6.4", "ethereumjs-abi": "^0.6.4",
"ethereumjs-tx": "^1.3.0", "ethereumjs-tx": "^1.3.0",
@ -130,6 +129,7 @@
"obj-multiplex": "^1.0.0", "obj-multiplex": "^1.0.0",
"obs-store": "^3.0.0", "obs-store": "^3.0.0",
"once": "^1.3.3", "once": "^1.3.3",
"percentile": "^1.2.0",
"ping-pong-stream": "^1.0.0", "ping-pong-stream": "^1.0.0",
"pojo-migrator": "^2.1.0", "pojo-migrator": "^2.1.0",
"polyfill-crypto.getrandomvalues": "^1.0.0", "polyfill-crypto.getrandomvalues": "^1.0.0",
@ -169,7 +169,7 @@
"valid-url": "^1.0.9", "valid-url": "^1.0.9",
"vreme": "^3.0.2", "vreme": "^3.0.2",
"web3": "^0.20.1", "web3": "^0.20.1",
"web3-provider-engine": "^13.4.0", "web3-provider-engine": "^13.5.0",
"web3-stream-provider": "^3.0.1", "web3-stream-provider": "^3.0.1",
"xtend": "^4.0.1" "xtend": "^4.0.1"
}, },
@ -212,6 +212,7 @@
"gulp-util": "^3.0.7", "gulp-util": "^3.0.7",
"gulp-watch": "^4.3.5", "gulp-watch": "^4.3.5",
"gulp-zip": "^4.0.0", "gulp-zip": "^4.0.0",
"gulp-eslint": "^4.0.0",
"isomorphic-fetch": "^2.2.1", "isomorphic-fetch": "^2.2.1",
"jsdom": "^11.1.0", "jsdom": "^11.1.0",
"jsdom-global": "^3.0.2", "jsdom-global": "^3.0.2",
@ -241,7 +242,7 @@
"tape": "^4.5.1", "tape": "^4.5.1",
"testem": "^1.10.3", "testem": "^1.10.3",
"uglifyify": "^4.0.2", "uglifyify": "^4.0.2",
"vinyl-buffer": "^1.0.0", "vinyl-buffer": "^1.0.1",
"vinyl-source-stream": "^2.0.0", "vinyl-source-stream": "^2.0.0",
"watchify": "^3.9.0" "watchify": "^3.9.0"
}, },

@ -5,7 +5,8 @@ module.exports = {
createEngineForTestData, createEngineForTestData,
providerFromEngine, providerFromEngine,
scaffoldMiddleware, scaffoldMiddleware,
createStubedProvider createEthJsQueryStub,
createStubedProvider,
} }
@ -18,6 +19,18 @@ function providerFromEngine (engine) {
return provider return provider
} }
function createEthJsQueryStub (stubProvider) {
return new Proxy({}, {
get: (obj, method) => {
return (...params) => {
return new Promise((resolve, reject) => {
stubProvider.sendAsync({ method: `eth_${method}`, params }, (err, ress) => resolve(ress.result))
})
}
},
})
}
function createStubedProvider (resultStub) { function createStubedProvider (resultStub) {
const engine = createEngineForTestData() const engine = createEngineForTestData()
engine.push(scaffoldMiddleware(resultStub)) engine.push(scaffoldMiddleware(resultStub))

@ -3,6 +3,8 @@ const sinon = require('sinon')
const clone = require('clone') const clone = require('clone')
const MetaMaskController = require('../../app/scripts/metamask-controller') const MetaMaskController = require('../../app/scripts/metamask-controller')
const firstTimeState = require('../../app/scripts/first-time-state') const firstTimeState = require('../../app/scripts/first-time-state')
const BN = require('ethereumjs-util').BN
const GWEI_BN = new BN('1000000000')
describe('MetaMaskController', function () { describe('MetaMaskController', function () {
const noop = () => {} const noop = () => {}
@ -39,17 +41,63 @@ describe('MetaMaskController', function () {
beforeEach(function () { beforeEach(function () {
sinon.spy(metamaskController.keyringController, 'createNewVaultAndKeychain') sinon.spy(metamaskController.keyringController, 'createNewVaultAndKeychain')
sinon.spy(metamaskController.keyringController, 'createNewVaultAndRestore')
}) })
afterEach(function () { afterEach(function () {
metamaskController.keyringController.createNewVaultAndKeychain.restore() metamaskController.keyringController.createNewVaultAndKeychain.restore()
metamaskController.keyringController.createNewVaultAndRestore.restore()
})
describe('#getGasPrice', function () {
it('gives the 50th percentile lowest accepted gas price from recentBlocksController', async function () {
const realRecentBlocksController = metamaskController.recentBlocksController
metamaskController.recentBlocksController = {
store: {
getState: () => {
return {
recentBlocks: [
{ gasPrices: [ '0x3b9aca00', '0x174876e800'] },
{ gasPrices: [ '0x3b9aca00', '0x174876e800'] },
{ gasPrices: [ '0x174876e800', '0x174876e800' ]},
{ gasPrices: [ '0x174876e800', '0x174876e800' ]},
]
}
}
}
}
const gasPrice = metamaskController.getGasPrice()
assert.equal(gasPrice, '0x3b9aca00', 'accurately estimates 50th percentile accepted gas price')
metamaskController.recentBlocksController = realRecentBlocksController
})
it('gives the 1 gwei price if no blocks have been seen.', async function () {
const realRecentBlocksController = metamaskController.recentBlocksController
metamaskController.recentBlocksController = {
store: {
getState: () => {
return {
recentBlocks: []
}
}
}
}
const gasPrice = metamaskController.getGasPrice()
assert.equal(gasPrice, '0x' + GWEI_BN.toString(16), 'defaults to 1 gwei')
metamaskController.recentBlocksController = realRecentBlocksController
})
}) })
describe('#createNewVaultAndKeychain', function () { describe('#createNewVaultAndKeychain', function () {
it('can only create new vault on keyringController once', async function () { it('can only create new vault on keyringController once', async function () {
const selectStub = sinon.stub(metamaskController, 'selectFirstIdentity') const selectStub = sinon.stub(metamaskController, 'selectFirstIdentity')
const password = 'a-fake-password' const password = 'a-fake-password'
const first = await metamaskController.createNewVaultAndKeychain(password) const first = await metamaskController.createNewVaultAndKeychain(password)
@ -60,6 +108,22 @@ describe('MetaMaskController', function () {
selectStub.reset() selectStub.reset()
}) })
}) })
describe('#createNewVaultAndRestore', function () {
it('should be able to call newVaultAndRestore despite a mistake.', async function () {
// const selectStub = sinon.stub(metamaskController, 'selectFirstIdentity')
const password = 'what-what-what'
const wrongSeed = 'debris dizzy just program just float decrease vacant alarm reduce speak stadiu'
const rightSeed = 'debris dizzy just program just float decrease vacant alarm reduce speak stadium'
const first = await metamaskController.createNewVaultAndRestore(password, wrongSeed)
.catch((e) => {
return
}) })
}) const second = await metamaskController.createNewVaultAndRestore(password, rightSeed)
assert(metamaskController.keyringController.createNewVaultAndRestore.calledTwice)
})
})
})
})

@ -5,7 +5,7 @@ const ObservableStore = require('obs-store')
const sinon = require('sinon') const sinon = require('sinon')
const TransactionController = require('../../app/scripts/controllers/transactions') const TransactionController = require('../../app/scripts/controllers/transactions')
const TxGasUtils = require('../../app/scripts/lib/tx-gas-utils') const TxGasUtils = require('../../app/scripts/lib/tx-gas-utils')
const { createStubedProvider } = require('../stub/provider') const { createStubedProvider, createEthJsQueryStub } = require('../stub/provider')
const noop = () => true const noop = () => true
const currentNetworkId = 42 const currentNetworkId = 42
@ -30,6 +30,8 @@ describe('Transaction Controller', function () {
resolve() resolve()
}), }),
}) })
txController.query = createEthJsQueryStub(provider)
txController.txGasUtil.query = createEthJsQueryStub(provider)
txController.nonceTracker.getNonceLock = () => Promise.resolve({ nextNonce: 0, releaseLock: noop }) txController.nonceTracker.getNonceLock = () => Promise.resolve({ nextNonce: 0, releaseLock: noop })
txController.txProviderUtils = new TxGasUtils(txController.provider) txController.txProviderUtils = new TxGasUtils(txController.provider)
}) })
@ -110,22 +112,15 @@ describe('Transaction Controller', function () {
history: [], history: [],
} }
txController.txStateManager._saveTxList([txMeta]) txController.txStateManager._saveTxList([txMeta])
stub = sinon.stub(txController, 'addUnapprovedTransaction').returns(Promise.resolve(txController.txStateManager.addTx(txMeta))) stub = sinon.stub(txController, 'addUnapprovedTransaction').callsFake(() => {
txController.emit('newUnapprovedTx', txMeta)
return Promise.resolve(txController.txStateManager.addTx(txMeta))
}) })
afterEach(function () { afterEach(function () {
txController.txStateManager._saveTxList([]) txController.txStateManager._saveTxList([])
stub.restore() stub.restore()
}) })
it('should emit newUnapprovedTx event and pass txMeta as the first argument', function (done) {
txController.once('newUnapprovedTx', (txMetaFromEmit) => {
assert(txMetaFromEmit, 'txMeta is falsey')
assert.equal(txMetaFromEmit.id, 1, 'the right txMeta was passed')
done()
})
txController.newUnapprovedTransaction(txParams)
.catch(done)
}) })
it('should resolve when finished and status is submitted and resolve with the hash', function (done) { it('should resolve when finished and status is submitted and resolve with the hash', function (done) {
@ -160,8 +155,17 @@ describe('Transaction Controller', function () {
}) })
describe('#addUnapprovedTransaction', function () { describe('#addUnapprovedTransaction', function () {
let addTxDefaults
beforeEach(() => {
addTxDefaults = txController.addTxDefaults
txController.addTxDefaults = function addTxDefaultsStub () { return Promise.resolve() }
})
afterEach(() => {
txController.addTxDefaults = addTxDefaults
})
it('should add an unapproved transaction and return a valid txMeta', function (done) { it('should add an unapproved transaction and return a valid txMeta', function (done) {
const addTxDefaultsStub = sinon.stub(txController, 'addTxDefaults').callsFake(() => Promise.resolve())
txController.addUnapprovedTransaction({}) txController.addUnapprovedTransaction({})
.then((txMeta) => { .then((txMeta) => {
assert(('id' in txMeta), 'should have a id') assert(('id' in txMeta), 'should have a id')
@ -172,10 +176,20 @@ describe('Transaction Controller', function () {
const memTxMeta = txController.txStateManager.getTx(txMeta.id) const memTxMeta = txController.txStateManager.getTx(txMeta.id)
assert.deepEqual(txMeta, memTxMeta, `txMeta should be stored in txController after adding it\n expected: ${txMeta} \n got: ${memTxMeta}`) assert.deepEqual(txMeta, memTxMeta, `txMeta should be stored in txController after adding it\n expected: ${txMeta} \n got: ${memTxMeta}`)
addTxDefaultsStub.restore()
done() done()
}).catch(done) }).catch(done)
}) })
it('should emit newUnapprovedTx event and pass txMeta as the first argument', function (done) {
providerResultStub.eth_gasPrice = '4a817c800'
txController.once('newUnapprovedTx', (txMetaFromEmit) => {
assert(txMetaFromEmit, 'txMeta is falsey')
done()
})
txController.addUnapprovedTransaction({})
.catch(done)
})
}) })
describe('#addTxDefaults', function () { describe('#addTxDefaults', function () {

@ -0,0 +1,32 @@
const assert = require('assert')
const TxGasUtils = require('../../app/scripts/lib/tx-gas-utils')
const { createStubedProvider } = require('../stub/provider')
describe('Tx Gas Util', function () {
let txGasUtil, provider, providerResultStub
beforeEach(function () {
providerResultStub = {}
provider = createStubedProvider(providerResultStub)
txGasUtil = new TxGasUtils({
provider,
})
})
it('removes recipient for txParams with 0x when contract data is provided', function () {
const zeroRecipientandDataTxParams = {
from: '0x1678a085c290ebd122dc42cba69373b5953b831d',
to: '0x',
data: 'bytecode',
}
const sanitizedTxParams = txGasUtil.validateRecipient(zeroRecipientandDataTxParams)
assert.deepEqual(sanitizedTxParams, { from: '0x1678a085c290ebd122dc42cba69373b5953b831d', data: 'bytecode' }, 'no recipient with 0x')
})
it('should error when recipient is 0x', function () {
const zeroRecipientTxParams = {
from: '0x1678a085c290ebd122dc42cba69373b5953b831d',
to: '0x',
}
assert.throws(() => { txGasUtil.validateRecipient(zeroRecipientTxParams) }, Error, 'Invalid recipient address')
})
})

@ -302,7 +302,6 @@ App.prototype.renderAppBar = function () {
) )
} }
App.prototype.renderLoadingIndicator = function ({ isLoading, isLoadingNetwork, loadMessage }) { App.prototype.renderLoadingIndicator = function ({ isLoading, isLoadingNetwork, loadMessage }) {
const { isMascara } = this.props const { isMascara } = this.props

@ -103,9 +103,9 @@ InfoScreen.prototype.render = function () {
[ [
h('div.fa.fa-support', [ h('div.fa.fa-support', [
h('a.info', { h('a.info', {
href: 'https://support.metamask.io', href: 'https://metamask.helpscoutdocs.com/',
target: '_blank', target: '_blank',
}, 'Visit our Support Center'), }, 'Visit our Knowledge Base'),
]), ]),
h('div', [ h('div', [
@ -138,8 +138,7 @@ InfoScreen.prototype.render = function () {
h('div.fa.fa-envelope', [ h('div.fa.fa-envelope', [
h('a.info', { h('a.info', {
target: '_blank', target: '_blank',
style: { width: '85vw' }, href: 'mailto:support@metamask.io?subject=MetaMask Support',
href: 'mailto:help@metamask.io?subject=Feedback',
}, 'Email us!'), }, 'Email us!'),
]), ]),
]), ]),

@ -149,4 +149,8 @@ RestoreVaultScreen.prototype.createNewVaultAndRestore = function () {
this.warning = null this.warning = null
this.props.dispatch(actions.displayWarning(this.warning)) this.props.dispatch(actions.displayWarning(this.warning))
this.props.dispatch(actions.createNewVaultAndRestore(password, seed)) this.props.dispatch(actions.createNewVaultAndRestore(password, seed))
.catch((err) => {
log.error(err.message)
})
} }

Loading…
Cancel
Save