aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md4
-rw-r--r--app/scripts/controllers/infura.js42
-rw-r--r--app/scripts/controllers/transactions.js20
-rw-r--r--app/scripts/keyring-controller.js2
-rw-r--r--app/scripts/metamask-controller.js15
-rw-r--r--app/scripts/migrations/015.js38
-rw-r--r--app/scripts/migrations/index.js1
-rw-r--r--gulpfile.js14
-rw-r--r--package.json2
-rw-r--r--test/unit/infura-controller-test.js34
-rw-r--r--ui/app/accounts/import/index.js9
-rw-r--r--ui/app/add-token.js14
-rw-r--r--ui/app/components/ens-input.js2
-rw-r--r--ui/app/components/pending-tx.js6
-rw-r--r--ui/app/keychains/hd/create-vault-complete.js2
-rw-r--r--ui/app/send.js4
16 files changed, 169 insertions, 40 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4c8522f02..b405ddc6c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,7 @@
## Current Master
+- No longer stop rebroadcasting transactions
- Add list of popular tokens held to the account detail view.
- Add ability to add Tokens to token list.
- Add a warning to JSON file import.
@@ -9,6 +10,9 @@
- Fix bug where slowly mined txs would sometimes be incorrectly marked as failed.
- Fix bug where badge count did not reflect personal_sign pending messages.
- Seed word confirmation wording is now scarier.
+- Fix error for invalid seed words.
+- Prevent users from submitting two duplicate transactions by disabling submit.
+- Allow Dapps to specify gas price as hex string.
## 3.7.8 2017-6-12
diff --git a/app/scripts/controllers/infura.js b/app/scripts/controllers/infura.js
new file mode 100644
index 000000000..98375b446
--- /dev/null
+++ b/app/scripts/controllers/infura.js
@@ -0,0 +1,42 @@
+const ObservableStore = require('obs-store')
+const extend = require('xtend')
+
+// every ten minutes
+const POLLING_INTERVAL = 300000
+
+class InfuraController {
+
+ constructor (opts = {}) {
+ const initState = extend({
+ infuraNetworkStatus: {},
+ }, opts.initState)
+ this.store = new ObservableStore(initState)
+ }
+
+ //
+ // PUBLIC METHODS
+ //
+
+ // Responsible for retrieving the status of Infura's nodes. Can return either
+ // ok, degraded, or down.
+ checkInfuraNetworkStatus () {
+ return fetch('https://api.infura.io/v1/status/metamask')
+ .then(response => response.json())
+ .then((parsedResponse) => {
+ this.store.updateState({
+ infuraNetworkStatus: parsedResponse,
+ })
+ })
+ }
+
+ scheduleInfuraNetworkCheck () {
+ if (this.conversionInterval) {
+ clearInterval(this.conversionInterval)
+ }
+ this.conversionInterval = setInterval(() => {
+ this.checkInfuraNetworkStatus()
+ }, POLLING_INTERVAL)
+ }
+}
+
+module.exports = InfuraController
diff --git a/app/scripts/controllers/transactions.js b/app/scripts/controllers/transactions.js
index f6dea34e7..52251d66e 100644
--- a/app/scripts/controllers/transactions.js
+++ b/app/scripts/controllers/transactions.js
@@ -8,8 +8,6 @@ const TxProviderUtil = require('../lib/tx-utils')
const createId = require('../lib/random-id')
const denodeify = require('denodeify')
-const RETRY_LIMIT = 200
-
module.exports = class TransactionController extends EventEmitter {
constructor (opts) {
super()
@@ -152,13 +150,15 @@ module.exports = class TransactionController extends EventEmitter {
const txParams = txMeta.txParams
// ensure value
txParams.value = txParams.value || '0x0'
- this.query.gasPrice((err, gasPrice) => {
- if (err) return cb(err)
- // set gasPrice
- txParams.gasPrice = gasPrice
- // set gasLimit
- this.txProviderUtils.analyzeGasUsage(txMeta, cb)
- })
+ if (!txParams.gasPrice) {
+ this.query.gasPrice((err, gasPrice) => {
+ if (err) return cb(err)
+ // set gasPrice
+ txParams.gasPrice = gasPrice
+ })
+ }
+ // set gasLimit
+ this.txProviderUtils.analyzeGasUsage(txMeta, cb)
}
getUnapprovedTxList () {
@@ -435,8 +435,6 @@ module.exports = class TransactionController extends EventEmitter {
// Only auto-submit already-signed txs:
if (!('rawTx' in txMeta)) return cb()
- if (txMeta.retryCount > RETRY_LIMIT) return
-
// Increment a try counter.
txMeta.retryCount++
const rawTx = txMeta.rawTx
diff --git a/app/scripts/keyring-controller.js b/app/scripts/keyring-controller.js
index 5b3c80e40..2edc8060e 100644
--- a/app/scripts/keyring-controller.js
+++ b/app/scripts/keyring-controller.js
@@ -87,7 +87,7 @@ class KeyringController extends EventEmitter {
}
if (!bip39.validateMnemonic(seed)) {
- return Promise.reject('Seed phrase is invalid.')
+ return Promise.reject(new Error('Seed phrase is invalid.'))
}
this.clearKeyrings()
diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js
index 39d22f278..782641b3f 100644
--- a/app/scripts/metamask-controller.js
+++ b/app/scripts/metamask-controller.js
@@ -15,6 +15,7 @@ const CurrencyController = require('./controllers/currency')
const NoticeController = require('./notice-controller')
const ShapeShiftController = require('./controllers/shapeshift')
const AddressBookController = require('./controllers/address-book')
+const InfuraController = require('./controllers/infura')
const MessageManager = require('./lib/message-manager')
const PersonalMessageManager = require('./lib/personal-message-manager')
const TransactionController = require('./controllers/transactions')
@@ -44,8 +45,8 @@ module.exports = class MetamaskController extends EventEmitter {
this.store = new ObservableStore(initState)
// network store
-
this.networkController = new NetworkController(initState.NetworkController)
+
// config manager
this.configManager = new ConfigManager({
store: this.store,
@@ -63,6 +64,13 @@ module.exports = class MetamaskController extends EventEmitter {
this.currencyController.updateConversionRate()
this.currencyController.scheduleConversionInterval()
+ // infura controller
+ this.infuraController = new InfuraController({
+ initState: initState.InfuraController,
+ })
+ this.infuraController.scheduleInfuraNetworkCheck()
+
+
// rpc provider
this.provider = this.initializeProvider()
@@ -147,6 +155,9 @@ module.exports = class MetamaskController extends EventEmitter {
this.networkController.store.subscribe((state) => {
this.store.updateState({ NetworkController: state })
})
+ this.infuraController.store.subscribe((state) => {
+ this.store.updateState({ InfuraController: state })
+ })
// manual mem state subscriptions
this.networkController.store.subscribe(this.sendUpdate.bind(this))
@@ -160,6 +171,7 @@ module.exports = class MetamaskController extends EventEmitter {
this.currencyController.store.subscribe(this.sendUpdate.bind(this))
this.noticeController.memStore.subscribe(this.sendUpdate.bind(this))
this.shapeshiftController.store.subscribe(this.sendUpdate.bind(this))
+ this.infuraController.store.subscribe(this.sendUpdate.bind(this))
}
//
@@ -237,6 +249,7 @@ module.exports = class MetamaskController extends EventEmitter {
this.addressBookController.store.getState(),
this.currencyController.store.getState(),
this.noticeController.memStore.getState(),
+ this.infuraController.store.getState(),
// config manager
this.configManager.getConfig(),
this.shapeshiftController.store.getState(),
diff --git a/app/scripts/migrations/015.js b/app/scripts/migrations/015.js
new file mode 100644
index 000000000..4b839580b
--- /dev/null
+++ b/app/scripts/migrations/015.js
@@ -0,0 +1,38 @@
+const version = 15
+
+/*
+
+This migration sets transactions with the 'Gave up submitting tx.' err message
+to a 'failed' stated
+
+*/
+
+const clone = require('clone')
+
+module.exports = {
+ version,
+
+ migrate: function (originalVersionedData) {
+ const versionedData = clone(originalVersionedData)
+ versionedData.meta.version = version
+ try {
+ const state = versionedData.data
+ const newState = transformState(state)
+ versionedData.data = newState
+ } catch (err) {
+ console.warn(`MetaMask Migration #${version}` + err.stack)
+ }
+ return Promise.resolve(versionedData)
+ },
+}
+
+function transformState (state) {
+ const newState = state
+ const transactions = newState.TransactionController.transactions
+ newState.TransactionController.transactions = transactions.map((txMeta) => {
+ if (!txMeta.err) return txMeta
+ else if (txMeta.err.message === 'Gave up submitting tx.') txMeta.status = 'failed'
+ return txMeta
+ })
+ return newState
+}
diff --git a/app/scripts/migrations/index.js b/app/scripts/migrations/index.js
index fb1ad7863..651ee6a9c 100644
--- a/app/scripts/migrations/index.js
+++ b/app/scripts/migrations/index.js
@@ -25,4 +25,5 @@ module.exports = [
require('./012'),
require('./013'),
require('./014'),
+ require('./015'),
]
diff --git a/gulpfile.js b/gulpfile.js
index 3f235396c..cc723704a 100644
--- a/gulpfile.js
+++ b/gulpfile.js
@@ -20,7 +20,7 @@ var gulpif = require('gulp-if')
var replace = require('gulp-replace')
var mkdirp = require('mkdirp')
-var disableLiveReload = gutil.env.disableLiveReload
+var disableDebugTools = gutil.env.disableDebugTools
var debug = gutil.env.debug
// browser reload
@@ -121,7 +121,7 @@ gulp.task('manifest:production', function() {
'./dist/chrome/manifest.json',
'./dist/edge/manifest.json',
],{base: './dist/'})
- .pipe(gulpif(disableLiveReload,jsoneditor(function(json) {
+ .pipe(gulpif(!debug,jsoneditor(function(json) {
json.background.scripts = ["scripts/background.js"]
return json
})))
@@ -138,7 +138,7 @@ const staticFiles = [
var copyStrings = staticFiles.map(staticFile => `copy:${staticFile}`)
copyStrings.push('copy:contractImages')
-if (!disableLiveReload) {
+if (debug) {
copyStrings.push('copy:reload')
}
@@ -234,7 +234,7 @@ function copyTask(opts){
destinations.forEach(function(destination) {
stream = stream.pipe(gulp.dest(destination))
})
- stream.pipe(gulpif(!disableLiveReload,livereload()))
+ stream.pipe(gulpif(debug,livereload()))
return stream
}
@@ -314,16 +314,16 @@ function bundleTask(opts) {
.pipe(buffer())
// sourcemaps
// loads map from browserify file
- .pipe(sourcemaps.init({loadMaps: true}))
+ .pipe(gulpif(debug, sourcemaps.init({loadMaps: true})))
// writes .map file
- .pipe(sourcemaps.write('./'))
+ .pipe(gulpif(debug, sourcemaps.write('./')))
// write completed bundles
.pipe(gulp.dest('./dist/firefox/scripts'))
.pipe(gulp.dest('./dist/chrome/scripts'))
.pipe(gulp.dest('./dist/edge/scripts'))
.pipe(gulp.dest('./dist/opera/scripts'))
// finally, trigger live reload
- .pipe(gulpif(!disableLiveReload, livereload()))
+ .pipe(gulpif(debug, livereload()))
)
}
diff --git a/package.json b/package.json
index 01c256ef3..3f62923f8 100644
--- a/package.json
+++ b/package.json
@@ -7,7 +7,7 @@
"start": "npm run dev",
"dev": "gulp dev --debug",
"disc": "gulp disc --debug",
- "dist": "npm install && gulp dist --disableLiveReload",
+ "dist": "npm install && gulp dist",
"test": "npm run lint && npm run test-unit && npm run test-integration",
"test-unit": "METAMASK_ENV=test mocha --require test/helper.js --recursive \"test/unit/**/*.js\"",
"test-integration": "npm run buildMock && npm run buildCiUnits && testem ci -P 2",
diff --git a/test/unit/infura-controller-test.js b/test/unit/infura-controller-test.js
new file mode 100644
index 000000000..7a2a114f9
--- /dev/null
+++ b/test/unit/infura-controller-test.js
@@ -0,0 +1,34 @@
+// polyfill fetch
+global.fetch = function () {return Promise.resolve({
+ json: () => { return Promise.resolve({"mainnet": "ok", "ropsten": "degraded", "kovan": "down", "rinkeby": "ok"}) },
+ })
+}
+const assert = require('assert')
+const InfuraController = require('../../app/scripts/controllers/infura')
+
+describe('infura-controller', function () {
+ var infuraController
+
+ beforeEach(function () {
+ infuraController = new InfuraController()
+ })
+
+ describe('network status queries', function () {
+ describe('#checkInfuraNetworkStatus', function () {
+ it('should return an object reflecting the network statuses', function (done) {
+ this.timeout(15000)
+ infuraController.checkInfuraNetworkStatus()
+ .then(() => {
+ const networkStatus = infuraController.store.getState().infuraNetworkStatus
+ assert.equal(Object.keys(networkStatus).length, 4)
+ assert.equal(networkStatus.mainnet, 'ok')
+ assert.equal(networkStatus.ropsten, 'degraded')
+ assert.equal(networkStatus.kovan, 'down')
+ })
+ .then(() => done())
+ .catch(done)
+
+ })
+ })
+ })
+})
diff --git a/ui/app/accounts/import/index.js b/ui/app/accounts/import/index.js
index a0f0f9bdb..97b387229 100644
--- a/ui/app/accounts/import/index.js
+++ b/ui/app/accounts/import/index.js
@@ -2,6 +2,7 @@ const inherits = require('util').inherits
const Component = require('react').Component
const h = require('react-hyperscript')
const connect = require('react-redux').connect
+const actions = require('../../actions')
import Select from 'react-select'
// Subviews
@@ -37,6 +38,14 @@ AccountImportSubview.prototype.render = function () {
style: {
},
}, [
+ h('.section-title.flex-row.flex-center', [
+ h('i.fa.fa-arrow-left.fa-lg.cursor-pointer', {
+ onClick: (event) => {
+ props.dispatch(actions.goHome())
+ },
+ }),
+ h('h2.page-subtitle', 'Import Accounts'),
+ ]),
h('div', {
style: {
padding: '10px',
diff --git a/ui/app/add-token.js b/ui/app/add-token.js
index f21184270..b303b5c0d 100644
--- a/ui/app/add-token.js
+++ b/ui/app/add-token.js
@@ -142,13 +142,7 @@ AddTokenScreen.prototype.render = function () {
if (!valid) return
const { address, symbol, decimals } = this.state
- this.checkIfToken(address.trim())
- .then(() => {
- this.props.dispatch(actions.addToken(address.trim(), symbol.trim(), decimals))
- })
- .catch((reason) => {
- this.setState({ warning: 'Not a valid token address.' })
- })
+ this.props.dispatch(actions.addToken(address.trim(), symbol.trim(), decimals))
},
}, 'Add'),
]),
@@ -208,12 +202,6 @@ AddTokenScreen.prototype.validateInputs = function () {
return isValid
}
-AddTokenScreen.prototype.checkIfToken = async function (address) {
- const contract = this.TokenContract.at(address)
- const result = await contract.balance(address)
- return result[0].toString()
-}
-
AddTokenScreen.prototype.attemptToAutoFillTokenParams = async function (address) {
const contract = this.TokenContract.at(address)
diff --git a/ui/app/components/ens-input.js b/ui/app/components/ens-input.js
index 16c50db84..3a33ebf74 100644
--- a/ui/app/components/ens-input.js
+++ b/ui/app/components/ens-input.js
@@ -41,7 +41,6 @@ EnsInput.prototype.render = function () {
this.checkName()
},
})
-
return h('div', {
style: { width: '100%' },
}, [
@@ -55,6 +54,7 @@ EnsInput.prototype.render = function () {
return h('option', {
value: identity.address,
label: identity.name,
+ key: identity.address,
})
}),
// Corresponds to previously sent-to addresses.
diff --git a/ui/app/components/pending-tx.js b/ui/app/components/pending-tx.js
index 4b1a00eca..f33a5d948 100644
--- a/ui/app/components/pending-tx.js
+++ b/ui/app/components/pending-tx.js
@@ -27,6 +27,7 @@ function PendingTx () {
this.state = {
valid: true,
txData: null,
+ submitting: false,
}
}
@@ -316,7 +317,7 @@ PendingTx.prototype.render = function () {
type: 'submit',
value: 'ACCEPT',
style: { marginLeft: '10px' },
- disabled: insufficientBalance || !this.state.valid || !isValidAddress,
+ disabled: insufficientBalance || !this.state.valid || !isValidAddress || this.state.submitting,
}),
h('button.cancel.btn-red', {
@@ -412,11 +413,12 @@ PendingTx.prototype.onSubmit = function (event) {
event.preventDefault()
const txMeta = this.gatherTxMeta()
const valid = this.checkValidity()
- this.setState({ valid })
+ this.setState({ valid, submitting: true })
if (valid && this.verifyGasParams()) {
this.props.sendTransaction(txMeta, event)
} else {
this.props.dispatch(actions.displayWarning('Invalid Gas Parameters'))
+ this.setState({ submitting: false })
}
}
diff --git a/ui/app/keychains/hd/create-vault-complete.js b/ui/app/keychains/hd/create-vault-complete.js
index 9741155f7..a318a9b50 100644
--- a/ui/app/keychains/hd/create-vault-complete.js
+++ b/ui/app/keychains/hd/create-vault-complete.js
@@ -20,7 +20,7 @@ function mapStateToProps (state) {
CreateVaultCompleteScreen.prototype.render = function () {
var state = this.props
- var seed = state.seed || state.cachedSeed
+ var seed = state.seed || state.cachedSeed || ''
return (
diff --git a/ui/app/send.js b/ui/app/send.js
index fd6994145..a21a219eb 100644
--- a/ui/app/send.js
+++ b/ui/app/send.js
@@ -189,7 +189,7 @@ SendTransactionScreen.prototype.render = function () {
style: {
textTransform: 'uppercase',
},
- }, 'Send'),
+ }, 'Next'),
]),
@@ -244,7 +244,7 @@ SendTransactionScreen.prototype.recipientDidChange = function (recipient, nickna
SendTransactionScreen.prototype.onSubmit = function () {
const state = this.state || {}
- const recipient = state.recipient || document.querySelector('input[name="address"]').value
+ const recipient = state.recipient || document.querySelector('input[name="address"]').value.replace(/^[.\s]+|[.\s]+$/g, '')
const nickname = state.nickname || ' '
const input = document.querySelector('input[name="amount"]').value
const value = util.normalizeEthStringToWei(input)