diff --git a/CHANGELOG.md b/CHANGELOG.md
index 94a4e9b7c..4894867b7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,11 +3,15 @@
## Current Master
- Replace "Unlock" wording with "Log In".
+- Allow adding custom tokens to classic ui when balance is 0
+- Allow editing of symbol and decimal info when adding custom token in new-ui
+- NewUI shapeshift form can select all coins (not just BTC)
## 4.1.3 2018-2-28
- Ensure MetaMask's inpage provider is named MetamaskInpageProvider to keep some sites from breaking.
- Add retry transaction button back into classic ui.
+- Add network dropdown styles to support long custom RPC urls
## 4.1.2 2018-2-28
diff --git a/app/scripts/background.js b/app/scripts/background.js
index 4487ff318..601ae0372 100644
--- a/app/scripts/background.js
+++ b/app/scripts/background.js
@@ -16,6 +16,7 @@ const firstTimeState = require('./first-time-state')
const setupRaven = require('./lib/setupRaven')
const reportFailedTxToSentry = require('./lib/reportFailedTxToSentry')
const setupMetamaskMeshMetrics = require('./lib/setupMetamaskMeshMetrics')
+const EdgeEncryptor = require('./edge-encryptor')
const STORAGE_KEY = 'metamask-config'
@@ -32,6 +33,12 @@ global.METAMASK_NOTIFIER = notificationManager
const release = platform.getVersion()
const raven = setupRaven({ release })
+// browser check if it is Edge - https://stackoverflow.com/questions/9847580/how-to-detect-safari-chrome-ie-firefox-and-opera-browser
+// Internet Explorer 6-11
+const isIE = !!document.documentMode
+// Edge 20+
+const isEdge = !isIE && !!window.StyleMedia
+
let popupIsOpen = false
let openMetamaskTabsIDs = {}
@@ -81,6 +88,7 @@ function setupController (initState) {
initState,
// platform specific api
platform,
+ encryptor: isEdge ? new EdgeEncryptor() : undefined,
})
global.metamaskController = controller
diff --git a/app/scripts/edge-encryptor.js b/app/scripts/edge-encryptor.js
new file mode 100644
index 000000000..24c0c93a8
--- /dev/null
+++ b/app/scripts/edge-encryptor.js
@@ -0,0 +1,69 @@
+const asmcrypto = require('asmcrypto.js')
+const Unibabel = require('browserify-unibabel')
+
+class EdgeEncryptor {
+
+ encrypt (password, dataObject) {
+
+ var salt = this._generateSalt()
+ return this._keyFromPassword(password, salt)
+ .then(function (key) {
+
+ var data = JSON.stringify(dataObject)
+ var dataBuffer = Unibabel.utf8ToBuffer(data)
+ var vector = global.crypto.getRandomValues(new Uint8Array(16))
+ var resultbuffer = asmcrypto.AES_GCM.encrypt(dataBuffer, key, vector)
+
+ var buffer = new Uint8Array(resultbuffer)
+ var vectorStr = Unibabel.bufferToBase64(vector)
+ var vaultStr = Unibabel.bufferToBase64(buffer)
+ return JSON.stringify({
+ data: vaultStr,
+ iv: vectorStr,
+ salt: salt,
+ })
+ })
+ }
+
+ decrypt (password, text) {
+
+ const payload = JSON.parse(text)
+ const salt = payload.salt
+ return this._keyFromPassword(password, salt)
+ .then(function (key) {
+ const encryptedData = Unibabel.base64ToBuffer(payload.data)
+ const vector = Unibabel.base64ToBuffer(payload.iv)
+ return new Promise((resolve, reject) => {
+ var result
+ try {
+ result = asmcrypto.AES_GCM.decrypt(encryptedData, key, vector)
+ } catch (err) {
+ return reject(new Error('Incorrect password'))
+ }
+ const decryptedData = new Uint8Array(result)
+ const decryptedStr = Unibabel.bufferToUtf8(decryptedData)
+ const decryptedObj = JSON.parse(decryptedStr)
+ resolve(decryptedObj)
+ })
+ })
+ }
+
+ _keyFromPassword (password, salt) {
+
+ var passBuffer = Unibabel.utf8ToBuffer(password)
+ var saltBuffer = Unibabel.base64ToBuffer(salt)
+ return new Promise((resolve) => {
+ var key = asmcrypto.PBKDF2_HMAC_SHA256.bytes(passBuffer, saltBuffer, 10000)
+ resolve(key)
+ })
+ }
+
+ _generateSalt (byteCount = 32) {
+ var view = new Uint8Array(byteCount)
+ global.crypto.getRandomValues(view)
+ var b64encoded = btoa(String.fromCharCode.apply(null, view))
+ return b64encoded
+ }
+}
+
+module.exports = EdgeEncryptor
diff --git a/mascara/src/app/first-time/import-seed-phrase-screen.js b/mascara/src/app/first-time/import-seed-phrase-screen.js
index 93c3f9203..de8d675e1 100644
--- a/mascara/src/app/first-time/import-seed-phrase-screen.js
+++ b/mascara/src/app/first-time/import-seed-phrase-screen.js
@@ -1,13 +1,12 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import {connect} from 'react-redux'
-import LoadingScreen from './loading-screen'
+import classnames from 'classnames'
import {
createNewVaultAndRestore,
hideWarning,
displayWarning,
unMarkPasswordForgotten,
- clearNotices,
} from '../../../../ui/app/actions'
class ImportSeedPhraseScreen extends Component {
@@ -17,8 +16,8 @@ class ImportSeedPhraseScreen extends Component {
next: PropTypes.func.isRequired,
createNewVaultAndRestore: PropTypes.func.isRequired,
hideWarning: PropTypes.func.isRequired,
- isLoading: PropTypes.bool.isRequired,
displayWarning: PropTypes.func,
+ leaveImportSeedScreenState: PropTypes.func,
};
state = {
@@ -27,98 +26,130 @@ class ImportSeedPhraseScreen extends Component {
confirmPassword: '',
}
+ parseSeedPhrase = (seedPhrase) => {
+ return seedPhrase
+ .match(/\w+/g)
+ .join(' ')
+ }
+
+ onChange = ({ seedPhrase, password, confirmPassword }) => {
+ const {
+ password: prevPassword,
+ confirmPassword: prevConfirmPassword,
+ } = this.state
+ const { displayWarning, hideWarning } = this.props
+
+ let warning = null
+
+ if (seedPhrase && this.parseSeedPhrase(seedPhrase).split(' ').length !== 12) {
+ warning = 'Seed Phrases are 12 words long'
+ } else if (password && password.length < 8) {
+ warning = 'Passwords require a mimimum length of 8'
+ } else if ((password || prevPassword) !== (confirmPassword || prevConfirmPassword)) {
+ warning = 'Confirmed password does not match'
+ }
+
+ if (warning) {
+ displayWarning(warning)
+ } else {
+ hideWarning()
+ }
+
+ seedPhrase && this.setState({ seedPhrase })
+ password && this.setState({ password })
+ confirmPassword && this.setState({ confirmPassword })
+ }
+
onClick = () => {
- const { password, seedPhrase, confirmPassword } = this.state
- const { createNewVaultAndRestore, next, displayWarning, leaveImportSeedScreenState } = this.props
+ const { password, seedPhrase } = this.state
+ const {
+ createNewVaultAndRestore,
+ next,
+ displayWarning,
+ leaveImportSeedScreenState,
+ } = this.props
- if (seedPhrase.split(' ').length !== 12) {
- this.warning = 'Seed Phrases are 12 words long'
- displayWarning(this.warning)
- return
- }
-
- if (password.length < 8) {
- this.warning = 'Passwords require a mimimum length of 8'
- displayWarning(this.warning)
- return
- }
-
- if (password !== confirmPassword) {
- this.warning = 'Confirmed password does not match'
- displayWarning(this.warning)
- return
- }
- this.warning = null
leaveImportSeedScreenState()
- createNewVaultAndRestore(password, seedPhrase)
+ createNewVaultAndRestore(password, this.parseSeedPhrase(seedPhrase))
.then(next)
}
render () {
- return this.props.isLoading
- ?