mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 09:52:26 +01:00
Merge pull request #3007 from alextsg/uat-master-011618
[NewUI] Merge master into uat branch
This commit is contained in:
commit
b80ed2c451
14
CHANGELOG.md
14
CHANGELOG.md
@ -2,10 +2,24 @@
|
||||
|
||||
## 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.
|
||||
- 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.
|
||||
- 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
|
||||
|
||||
|
@ -4,7 +4,7 @@
|
||||
|
||||
## 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
|
||||
|
||||
|
10
app/_locales/ko/messages.json
Normal file
10
app/_locales/ko/messages.json
Normal file
@ -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
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
const assert = require('assert')
|
||||
const EventEmitter = require('events')
|
||||
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 ObservableStore = require('obs-store')
|
||||
const ComposedStore = require('obs-store/lib/composed')
|
||||
@ -161,15 +162,17 @@ module.exports = class NetworkController extends EventEmitter {
|
||||
|
||||
_configureInfuraProvider (opts) {
|
||||
log.info('_configureInfuraProvider', opts)
|
||||
const blockTrackerProvider = createInfuraProvider({
|
||||
const infuraProvider = createInfuraProvider({
|
||||
network: opts.type,
|
||||
})
|
||||
const infuraSubprovider = new SubproviderFromProvider(infuraProvider)
|
||||
const providerParams = extend(this._baseProviderParams, {
|
||||
rpcUrl: opts.rpcUrl,
|
||||
engineParams: {
|
||||
pollingInterval: 8000,
|
||||
blockTrackerProvider,
|
||||
blockTrackerProvider: infuraProvider,
|
||||
},
|
||||
dataSubprovider: infuraSubprovider,
|
||||
})
|
||||
const provider = createMetamaskProvider(providerParams)
|
||||
this._setProvider(provider)
|
||||
|
@ -1,11 +1,14 @@
|
||||
const ObservableStore = require('obs-store')
|
||||
const extend = require('xtend')
|
||||
const BN = require('ethereumjs-util').BN
|
||||
const EthQuery = require('eth-query')
|
||||
|
||||
class RecentBlocksController {
|
||||
|
||||
constructor (opts = {}) {
|
||||
const { blockTracker } = opts
|
||||
const { blockTracker, provider } = opts
|
||||
this.blockTracker = blockTracker
|
||||
this.ethQuery = new EthQuery(provider)
|
||||
this.historyLength = opts.historyLength || 40
|
||||
|
||||
const initState = extend({
|
||||
@ -14,6 +17,7 @@ class RecentBlocksController {
|
||||
this.store = new ObservableStore(initState)
|
||||
|
||||
this.blockTracker.on('block', this.processBlock.bind(this))
|
||||
this.backfill()
|
||||
}
|
||||
|
||||
resetState () {
|
||||
@ -23,12 +27,7 @@ class RecentBlocksController {
|
||||
}
|
||||
|
||||
processBlock (newBlock) {
|
||||
const block = extend(newBlock, {
|
||||
gasPrices: newBlock.transactions.map((tx) => {
|
||||
return tx.gasPrice
|
||||
}),
|
||||
})
|
||||
delete block.transactions
|
||||
const block = this.mapTransactionsToPrices(newBlock)
|
||||
|
||||
const state = this.store.getState()
|
||||
state.recentBlocks.push(block)
|
||||
@ -39,6 +38,73 @@ class RecentBlocksController {
|
||||
|
||||
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, {
|
||||
gasPrices: newBlock.transactions.map((tx) => {
|
||||
return tx.gasPrice
|
||||
}),
|
||||
})
|
||||
delete block.transactions
|
||||
return block
|
||||
}
|
||||
|
||||
async backfill() {
|
||||
this.blockTracker.once('block', async (block) => {
|
||||
let blockNum = block.number
|
||||
let recentBlocks
|
||||
let state = this.store.getState()
|
||||
recentBlocks = state.recentBlocks
|
||||
|
||||
while (recentBlocks.length < this.historyLength) {
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
|
@ -32,6 +32,7 @@ module.exports = class TransactionController extends EventEmitter {
|
||||
this.provider = opts.provider
|
||||
this.blockTracker = opts.blockTracker
|
||||
this.signEthTx = opts.signTransaction
|
||||
this.getGasPrice = opts.getGasPrice
|
||||
|
||||
this.memStore = new ObservableStore({})
|
||||
this.query = new EthQuery(this.provider)
|
||||
@ -138,7 +139,6 @@ module.exports = class TransactionController extends EventEmitter {
|
||||
async newUnapprovedTransaction (txParams) {
|
||||
log.debug(`MetaMaskController newUnapprovedTransaction ${JSON.stringify(txParams)}`)
|
||||
const initialTxMeta = await this.addUnapprovedTransaction(txParams)
|
||||
this.emit('newUnapprovedTx', initialTxMeta)
|
||||
// listen for tx completion (success, fail)
|
||||
return new Promise((resolve, reject) => {
|
||||
this.txStateManager.once(`${initialTxMeta.id}:finished`, (finishedTxMeta) => {
|
||||
@ -166,11 +166,16 @@ module.exports = class TransactionController extends EventEmitter {
|
||||
status: 'unapproved',
|
||||
metamaskNetworkId: this.getNetwork(),
|
||||
txParams: txParams,
|
||||
loadingDefaults: true,
|
||||
}
|
||||
this.addTx(txMeta)
|
||||
this.emit('newUnapprovedTx', txMeta)
|
||||
// add default tx params
|
||||
await this.addTxDefaults(txMeta)
|
||||
|
||||
txMeta.loadingDefaults = false
|
||||
// save txMeta
|
||||
this.addTx(txMeta)
|
||||
this.txStateManager.updateTx(txMeta)
|
||||
return txMeta
|
||||
}
|
||||
|
||||
@ -179,7 +184,10 @@ module.exports = class TransactionController extends EventEmitter {
|
||||
// ensure value
|
||||
txMeta.gasPriceSpecified = Boolean(txParams.gasPrice)
|
||||
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.value = txParams.value || '0x0'
|
||||
// set gasLimit
|
||||
|
@ -4,6 +4,7 @@ const {
|
||||
BnMultiplyByFraction,
|
||||
bnToHex,
|
||||
} = require('./util')
|
||||
const SIMPLE_GAS_COST = '0x5208' // Hex for 21000, cost of a simple send.
|
||||
|
||||
/*
|
||||
tx-utils are utility methods for Transaction manager
|
||||
@ -37,14 +38,30 @@ module.exports = class txProvideUtil {
|
||||
|
||||
async estimateTxGas (txMeta, blockGasLimitHex) {
|
||||
const txParams = txMeta.txParams
|
||||
|
||||
// check if gasLimit is already specified
|
||||
txMeta.gasLimitSpecified = Boolean(txParams.gas)
|
||||
// if not, fallback to block gasLimit
|
||||
if (!txMeta.gasLimitSpecified) {
|
||||
const blockGasLimitBN = hexToBn(blockGasLimitHex)
|
||||
const saferGasLimitBN = BnMultiplyByFraction(blockGasLimitBN, 19, 20)
|
||||
txParams.gas = bnToHex(saferGasLimitBN)
|
||||
|
||||
// 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
|
||||
const blockGasLimitBN = hexToBn(blockGasLimitHex)
|
||||
const saferGasLimitBN = BnMultiplyByFraction(blockGasLimitBN, 19, 20)
|
||||
txParams.gas = bnToHex(saferGasLimitBN)
|
||||
|
||||
// run tx
|
||||
return await this.query.estimateGas(txParams)
|
||||
}
|
||||
@ -55,7 +72,7 @@ module.exports = class txProvideUtil {
|
||||
|
||||
// if gasLimit was specified and doesnt OOG,
|
||||
// use original specified amount
|
||||
if (txMeta.gasLimitSpecified) {
|
||||
if (txMeta.gasLimitSpecified || txMeta.simpleSend) {
|
||||
txMeta.estimatedGas = txParams.gas
|
||||
return
|
||||
}
|
||||
@ -81,6 +98,7 @@ module.exports = class txProvideUtil {
|
||||
}
|
||||
|
||||
async validateTxParams (txParams) {
|
||||
this.validateRecipient(txParams)
|
||||
if ('value' in txParams) {
|
||||
const value = txParams.value.toString()
|
||||
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 asStream = require('obs-store/lib/asStream')
|
||||
const AccountTracker = require('./lib/account-tracker')
|
||||
const EthQuery = require('eth-query')
|
||||
const RpcEngine = require('json-rpc-engine')
|
||||
const debounce = require('debounce')
|
||||
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 Mutex = require('await-semaphore').Mutex
|
||||
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 {
|
||||
|
||||
constructor (opts) {
|
||||
super()
|
||||
|
||||
|
||||
this.sendUpdate = debounce(this.privateSendUpdate.bind(this), 200)
|
||||
|
||||
this.opts = opts
|
||||
@ -94,10 +95,9 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
|
||||
this.recentBlocksController = new RecentBlocksController({
|
||||
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.
|
||||
this.accountTracker = new AccountTracker({
|
||||
provider: this.provider,
|
||||
@ -138,7 +138,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
signTransaction: this.keyringController.signTransaction.bind(this.keyringController),
|
||||
provider: this.provider,
|
||||
blockTracker: this.blockTracker,
|
||||
ethQuery: this.ethQuery,
|
||||
getGasPrice: this.getGasPrice.bind(this),
|
||||
})
|
||||
this.txController.on('newUnapprovedTx', opts.showUnapprovedTx.bind(opts))
|
||||
|
||||
@ -489,6 +489,33 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
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
|
||||
//
|
||||
@ -518,10 +545,15 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
|
||||
async createNewVaultAndRestore (password, seed) {
|
||||
const release = await this.createVaultMutex.acquire()
|
||||
const vault = await this.keyringController.createNewVaultAndRestore(password, seed)
|
||||
this.selectFirstIdentity(vault)
|
||||
release()
|
||||
return vault
|
||||
try {
|
||||
const vault = await this.keyringController.createNewVaultAndRestore(password, seed)
|
||||
this.selectFirstIdentity(vault)
|
||||
release()
|
||||
return vault
|
||||
} catch (err) {
|
||||
release()
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
selectFirstIdentity (vault) {
|
||||
|
16
gulpfile.js
16
gulpfile.js
@ -19,6 +19,8 @@ var manifest = require('./app/manifest.json')
|
||||
var gulpif = require('gulp-if')
|
||||
var replace = require('gulp-replace')
|
||||
var mkdirp = require('mkdirp')
|
||||
var asyncEach = require('async/each')
|
||||
var exec = require('child_process').exec
|
||||
var sass = require('gulp-sass')
|
||||
var autoprefixer = require('gulp-autoprefixer')
|
||||
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'))
|
||||
})
|
||||
|
||||
// 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
|
||||
|
||||
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('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'))
|
||||
|
||||
// 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.
|
||||
|
||||
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,
|
||||
}),
|
||||
]),
|
||||
h('div.flex-column', {
|
||||
h('flex-column', {
|
||||
style: {
|
||||
lineHeight: '10px',
|
||||
marginLeft: '15px',
|
||||
width: '100%',
|
||||
},
|
||||
}, [
|
||||
@ -101,7 +102,7 @@ AccountDetailScreen.prototype.render = function () {
|
||||
{
|
||||
style: {
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
justifyContent: 'flex-start',
|
||||
alignItems: 'center',
|
||||
},
|
||||
},
|
||||
@ -131,6 +132,8 @@ AccountDetailScreen.prototype.render = function () {
|
||||
AccountDropdowns,
|
||||
{
|
||||
style: {
|
||||
marginRight: '8px',
|
||||
marginLeft: 'auto',
|
||||
cursor: 'pointer',
|
||||
},
|
||||
selected,
|
||||
@ -144,6 +147,7 @@ AccountDetailScreen.prototype.render = function () {
|
||||
]),
|
||||
h('.flex-row', {
|
||||
style: {
|
||||
width: '15em',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'baseline',
|
||||
},
|
||||
@ -157,12 +161,11 @@ AccountDetailScreen.prototype.render = function () {
|
||||
textOverflow: 'ellipsis',
|
||||
paddingTop: '3px',
|
||||
width: '5em',
|
||||
height: '15px',
|
||||
fontSize: '13px',
|
||||
fontFamily: 'Montserrat Light',
|
||||
textRendering: 'geometricPrecision',
|
||||
marginTop: '15px',
|
||||
marginBottom: '15px',
|
||||
marginLeft: '15px',
|
||||
color: '#AEAEAE',
|
||||
},
|
||||
}, checksumAddress),
|
||||
@ -189,21 +192,20 @@ AccountDetailScreen.prototype.render = function () {
|
||||
},
|
||||
}),
|
||||
|
||||
h('div', {}, [
|
||||
h('.flex-grow'),
|
||||
|
||||
h('button', {
|
||||
onClick: () => props.dispatch(actions.buyEthView(selected)),
|
||||
style: { marginRight: '10px' },
|
||||
}, 'BUY'),
|
||||
h('button', {
|
||||
onClick: () => props.dispatch(actions.buyEthView(selected)),
|
||||
style: { marginRight: '10px' },
|
||||
}, 'BUY'),
|
||||
|
||||
h('button', {
|
||||
onClick: () => props.dispatch(actions.showSendPage()),
|
||||
style: {
|
||||
marginBottom: '20px',
|
||||
},
|
||||
}, 'SEND'),
|
||||
|
||||
]),
|
||||
h('button', {
|
||||
onClick: () => props.dispatch(actions.showSendPage()),
|
||||
style: {
|
||||
marginBottom: '20px',
|
||||
marginRight: '8px',
|
||||
},
|
||||
}, 'SEND'),
|
||||
|
||||
]),
|
||||
]),
|
||||
|
@ -397,7 +397,7 @@ App.prototype.renderDropdown = function () {
|
||||
h(DropdownMenuItem, {
|
||||
closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }),
|
||||
onClick: () => { this.props.dispatch(actions.lockMetamask()) },
|
||||
}, 'Lock'),
|
||||
}, 'Log Out'),
|
||||
|
||||
h(DropdownMenuItem, {
|
||||
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
|
||||
if (!props.isInitialized || props.forgottenPassword) {
|
||||
// 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
|
||||
switch (props.currentView.name) {
|
||||
|
||||
|
@ -4,6 +4,7 @@ const h = require('react-hyperscript')
|
||||
const connect = require('react-redux').connect
|
||||
const actions = require('../../ui/app/actions')
|
||||
const NetworkIndicator = require('./components/network')
|
||||
const LoadingIndicator = require('./components/loading')
|
||||
const txHelper = require('../lib/tx-helper')
|
||||
const isPopupOrNotification = require('../../app/scripts/lib/is-popup-or-notification')
|
||||
|
||||
@ -60,6 +61,11 @@ ConfirmTxScreen.prototype.render = function () {
|
||||
|
||||
h('.flex-column.flex-grow', [
|
||||
|
||||
h(LoadingIndicator, {
|
||||
isLoading: txData.loadingDefaults,
|
||||
loadingMessage: 'Estimating transaction cost…',
|
||||
}),
|
||||
|
||||
// subtitle and nav
|
||||
h('.section-title.flex-row.flex-center', [
|
||||
!isNotification ? h('i.fa.fa-arrow-left.fa-lg.cursor-pointer', {
|
||||
|
@ -440,7 +440,9 @@ input.large-input {
|
||||
.account-detail-section {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
max-height: 465px;
|
||||
flex-direction: inherit;
|
||||
}
|
||||
|
||||
|
15
package.json
15
package.json
@ -77,15 +77,14 @@
|
||||
"eslint-plugin-react": "^7.4.0",
|
||||
"eth-bin-to-ops": "^1.0.1",
|
||||
"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-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-query": "^2.1.2",
|
||||
"eth-sig-util": "^1.4.0",
|
||||
"eth-simple-keyring": "^1.2.0",
|
||||
"eth-sig-util": "^1.4.2",
|
||||
"eth-token-tracker": "^1.1.4",
|
||||
"ethereumjs-abi": "^0.6.4",
|
||||
"ethereumjs-tx": "^1.3.0",
|
||||
@ -130,6 +129,7 @@
|
||||
"obj-multiplex": "^1.0.0",
|
||||
"obs-store": "^3.0.0",
|
||||
"once": "^1.3.3",
|
||||
"percentile": "^1.2.0",
|
||||
"ping-pong-stream": "^1.0.0",
|
||||
"pojo-migrator": "^2.1.0",
|
||||
"polyfill-crypto.getrandomvalues": "^1.0.0",
|
||||
@ -169,7 +169,7 @@
|
||||
"valid-url": "^1.0.9",
|
||||
"vreme": "^3.0.2",
|
||||
"web3": "^0.20.1",
|
||||
"web3-provider-engine": "^13.4.0",
|
||||
"web3-provider-engine": "^13.5.0",
|
||||
"web3-stream-provider": "^3.0.1",
|
||||
"xtend": "^4.0.1"
|
||||
},
|
||||
@ -212,6 +212,7 @@
|
||||
"gulp-util": "^3.0.7",
|
||||
"gulp-watch": "^4.3.5",
|
||||
"gulp-zip": "^4.0.0",
|
||||
"gulp-eslint": "^4.0.0",
|
||||
"isomorphic-fetch": "^2.2.1",
|
||||
"jsdom": "^11.1.0",
|
||||
"jsdom-global": "^3.0.2",
|
||||
@ -241,7 +242,7 @@
|
||||
"tape": "^4.5.1",
|
||||
"testem": "^1.10.3",
|
||||
"uglifyify": "^4.0.2",
|
||||
"vinyl-buffer": "^1.0.0",
|
||||
"vinyl-buffer": "^1.0.1",
|
||||
"vinyl-source-stream": "^2.0.0",
|
||||
"watchify": "^3.9.0"
|
||||
},
|
||||
|
@ -5,7 +5,8 @@ module.exports = {
|
||||
createEngineForTestData,
|
||||
providerFromEngine,
|
||||
scaffoldMiddleware,
|
||||
createStubedProvider
|
||||
createEthJsQueryStub,
|
||||
createStubedProvider,
|
||||
}
|
||||
|
||||
|
||||
@ -18,6 +19,18 @@ function providerFromEngine (engine) {
|
||||
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) {
|
||||
const engine = createEngineForTestData()
|
||||
engine.push(scaffoldMiddleware(resultStub))
|
||||
|
@ -3,6 +3,8 @@ const sinon = require('sinon')
|
||||
const clone = require('clone')
|
||||
const MetaMaskController = require('../../app/scripts/metamask-controller')
|
||||
const firstTimeState = require('../../app/scripts/first-time-state')
|
||||
const BN = require('ethereumjs-util').BN
|
||||
const GWEI_BN = new BN('1000000000')
|
||||
|
||||
describe('MetaMaskController', function () {
|
||||
const noop = () => {}
|
||||
@ -39,17 +41,63 @@ describe('MetaMaskController', function () {
|
||||
|
||||
beforeEach(function () {
|
||||
sinon.spy(metamaskController.keyringController, 'createNewVaultAndKeychain')
|
||||
sinon.spy(metamaskController.keyringController, 'createNewVaultAndRestore')
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
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 () {
|
||||
it('can only create new vault on keyringController once', async function () {
|
||||
|
||||
const selectStub = sinon.stub(metamaskController, 'selectFirstIdentity')
|
||||
|
||||
|
||||
const password = 'a-fake-password'
|
||||
|
||||
const first = await metamaskController.createNewVaultAndKeychain(password)
|
||||
@ -60,6 +108,22 @@ describe('MetaMaskController', function () {
|
||||
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 TransactionController = require('../../app/scripts/controllers/transactions')
|
||||
const TxGasUtils = require('../../app/scripts/lib/tx-gas-utils')
|
||||
const { createStubedProvider } = require('../stub/provider')
|
||||
const { createStubedProvider, createEthJsQueryStub } = require('../stub/provider')
|
||||
|
||||
const noop = () => true
|
||||
const currentNetworkId = 42
|
||||
@ -30,6 +30,8 @@ describe('Transaction Controller', function () {
|
||||
resolve()
|
||||
}),
|
||||
})
|
||||
txController.query = createEthJsQueryStub(provider)
|
||||
txController.txGasUtil.query = createEthJsQueryStub(provider)
|
||||
txController.nonceTracker.getNonceLock = () => Promise.resolve({ nextNonce: 0, releaseLock: noop })
|
||||
txController.txProviderUtils = new TxGasUtils(txController.provider)
|
||||
})
|
||||
@ -110,23 +112,16 @@ describe('Transaction Controller', function () {
|
||||
history: [],
|
||||
}
|
||||
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 () {
|
||||
txController.txStateManager._saveTxList([])
|
||||
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) {
|
||||
txController.once('newUnapprovedTx', (txMetaFromEmit) => {
|
||||
@ -160,8 +155,17 @@ describe('Transaction Controller', 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) {
|
||||
const addTxDefaultsStub = sinon.stub(txController, 'addTxDefaults').callsFake(() => Promise.resolve())
|
||||
txController.addUnapprovedTransaction({})
|
||||
.then((txMeta) => {
|
||||
assert(('id' in txMeta), 'should have a id')
|
||||
@ -172,10 +176,20 @@ describe('Transaction Controller', function () {
|
||||
|
||||
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}`)
|
||||
addTxDefaultsStub.restore()
|
||||
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 () {
|
||||
|
32
test/unit/tx-gas-util-test.js
Normal file
32
test/unit/tx-gas-util-test.js
Normal file
@ -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 }) {
|
||||
const { isMascara } = this.props
|
||||
|
||||
|
@ -103,9 +103,9 @@ InfoScreen.prototype.render = function () {
|
||||
[
|
||||
h('div.fa.fa-support', [
|
||||
h('a.info', {
|
||||
href: 'https://support.metamask.io',
|
||||
href: 'https://metamask.helpscoutdocs.com/',
|
||||
target: '_blank',
|
||||
}, 'Visit our Support Center'),
|
||||
}, 'Visit our Knowledge Base'),
|
||||
]),
|
||||
|
||||
h('div', [
|
||||
@ -138,8 +138,7 @@ InfoScreen.prototype.render = function () {
|
||||
h('div.fa.fa-envelope', [
|
||||
h('a.info', {
|
||||
target: '_blank',
|
||||
style: { width: '85vw' },
|
||||
href: 'mailto:help@metamask.io?subject=Feedback',
|
||||
href: 'mailto:support@metamask.io?subject=MetaMask Support',
|
||||
}, 'Email us!'),
|
||||
]),
|
||||
]),
|
||||
|
@ -149,4 +149,8 @@ RestoreVaultScreen.prototype.createNewVaultAndRestore = function () {
|
||||
this.warning = null
|
||||
this.props.dispatch(actions.displayWarning(this.warning))
|
||||
this.props.dispatch(actions.createNewVaultAndRestore(password, seed))
|
||||
.catch((err) => {
|
||||
log.error(err.message)
|
||||
})
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user