mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 09:52:26 +01:00
Merge branch 'dev' into i#1048
This commit is contained in:
commit
70b8e640f0
@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
## Current Master
|
## Current Master
|
||||||
|
|
||||||
|
- Fix rendering bug where the Confirm transaction view would lets you approve transactions when the account has insufficient balance.
|
||||||
|
- Add ability to import accounts in JSON file format (used by Mist, Geth, MyEtherWallet, and more!)
|
||||||
|
|
||||||
## 3.1.2 2017-1-24
|
## 3.1.2 2017-1-24
|
||||||
|
|
||||||
|
45
app/scripts/account-import-strategies/index.js
Normal file
45
app/scripts/account-import-strategies/index.js
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
const Wallet = require('ethereumjs-wallet')
|
||||||
|
const importers = require('ethereumjs-wallet/thirdparty')
|
||||||
|
const ethUtil = require('ethereumjs-util')
|
||||||
|
|
||||||
|
const accountImporter = {
|
||||||
|
|
||||||
|
importAccount(strategy, args) {
|
||||||
|
try {
|
||||||
|
const importer = this.strategies[strategy]
|
||||||
|
const privateKeyHex = importer.apply(null, args)
|
||||||
|
return Promise.resolve(privateKeyHex)
|
||||||
|
} catch (e) {
|
||||||
|
return Promise.reject(e)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
strategies: {
|
||||||
|
'Private Key': (privateKey) => {
|
||||||
|
const stripped = ethUtil.stripHexPrefix(privateKey)
|
||||||
|
return stripped
|
||||||
|
},
|
||||||
|
'JSON File': (input, password) => {
|
||||||
|
let wallet
|
||||||
|
try {
|
||||||
|
wallet = importers.fromEtherWallet(input, password)
|
||||||
|
} catch (e) {
|
||||||
|
console.log('Attempt to import as EtherWallet format failed, trying V3...')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!wallet) {
|
||||||
|
wallet = Wallet.fromV3(input, password, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
return walletToPrivateKey(wallet)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function walletToPrivateKey (wallet) {
|
||||||
|
const privateKeyBuffer = wallet.getPrivateKey()
|
||||||
|
return ethUtil.bufferToHex(privateKeyBuffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = accountImporter
|
@ -14,6 +14,7 @@ const extension = require('./lib/extension')
|
|||||||
const autoFaucet = require('./lib/auto-faucet')
|
const autoFaucet = require('./lib/auto-faucet')
|
||||||
const nodeify = require('./lib/nodeify')
|
const nodeify = require('./lib/nodeify')
|
||||||
const IdStoreMigrator = require('./lib/idStore-migrator')
|
const IdStoreMigrator = require('./lib/idStore-migrator')
|
||||||
|
const accountImporter = require('./account-import-strategies')
|
||||||
const version = require('../manifest.json').version
|
const version = require('../manifest.json').version
|
||||||
|
|
||||||
module.exports = class MetamaskController extends EventEmitter {
|
module.exports = class MetamaskController extends EventEmitter {
|
||||||
@ -140,6 +141,16 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||||||
if (!primaryKeyring) return cb(new Error('MetamaskController - No HD Key Tree found'))
|
if (!primaryKeyring) return cb(new Error('MetamaskController - No HD Key Tree found'))
|
||||||
promiseToCallback(keyringController.addNewAccount(primaryKeyring))(cb)
|
promiseToCallback(keyringController.addNewAccount(primaryKeyring))(cb)
|
||||||
},
|
},
|
||||||
|
importAccountWithStrategy: (strategy, args, cb) => {
|
||||||
|
accountImporter.importAccount(strategy, args)
|
||||||
|
.then((privateKey) => {
|
||||||
|
return keyringController.addNewKeyring('Simple Key Pair', [ privateKey ])
|
||||||
|
})
|
||||||
|
.then(keyring => keyring.getAccounts())
|
||||||
|
.then((accounts) => keyringController.setSelectedAccount(accounts[0]))
|
||||||
|
.then(() => { cb(null, keyringController.fullUpdate()) })
|
||||||
|
.catch((reason) => { cb(reason) })
|
||||||
|
},
|
||||||
setSelectedAccount: nodeify(keyringController.setSelectedAccount).bind(keyringController),
|
setSelectedAccount: nodeify(keyringController.setSelectedAccount).bind(keyringController),
|
||||||
saveAccountLabel: nodeify(keyringController.saveAccountLabel).bind(keyringController),
|
saveAccountLabel: nodeify(keyringController.saveAccountLabel).bind(keyringController),
|
||||||
exportAccount: nodeify(keyringController.exportAccount).bind(keyringController),
|
exportAccount: nodeify(keyringController.exportAccount).bind(keyringController),
|
||||||
|
@ -85,6 +85,7 @@
|
|||||||
"react-markdown": "^2.3.0",
|
"react-markdown": "^2.3.0",
|
||||||
"react-redux": "^4.4.5",
|
"react-redux": "^4.4.5",
|
||||||
"react-select": "^1.0.0-rc.2",
|
"react-select": "^1.0.0-rc.2",
|
||||||
|
"react-simple-file-input": "^1.0.0",
|
||||||
"react-tooltip-component": "^0.3.0",
|
"react-tooltip-component": "^0.3.0",
|
||||||
"readable-stream": "^2.1.2",
|
"readable-stream": "^2.1.2",
|
||||||
"redux": "^3.0.5",
|
"redux": "^3.0.5",
|
||||||
|
@ -6,11 +6,11 @@ import Select from 'react-select'
|
|||||||
|
|
||||||
// Subviews
|
// Subviews
|
||||||
const JsonImportView = require('./json.js')
|
const JsonImportView = require('./json.js')
|
||||||
const SeedImportView = require('./seed.js')
|
|
||||||
const PrivateKeyImportView = require('./private-key.js')
|
const PrivateKeyImportView = require('./private-key.js')
|
||||||
|
|
||||||
const menuItems = [
|
const menuItems = [
|
||||||
'Private Key',
|
'Private Key',
|
||||||
|
'JSON File',
|
||||||
]
|
]
|
||||||
|
|
||||||
module.exports = connect(mapStateToProps)(AccountImportSubview)
|
module.exports = connect(mapStateToProps)(AccountImportSubview)
|
||||||
@ -81,10 +81,10 @@ AccountImportSubview.prototype.renderImportView = function() {
|
|||||||
const current = type || menuItems[0]
|
const current = type || menuItems[0]
|
||||||
|
|
||||||
switch (current) {
|
switch (current) {
|
||||||
case 'HD Key Tree':
|
|
||||||
return h(SeedImportView)
|
|
||||||
case 'Private Key':
|
case 'Private Key':
|
||||||
return h(PrivateKeyImportView)
|
return h(PrivateKeyImportView)
|
||||||
|
case 'JSON File':
|
||||||
|
return h(JsonImportView)
|
||||||
default:
|
default:
|
||||||
return h(JsonImportView)
|
return h(JsonImportView)
|
||||||
}
|
}
|
||||||
|
@ -2,11 +2,15 @@ const inherits = require('util').inherits
|
|||||||
const Component = require('react').Component
|
const Component = require('react').Component
|
||||||
const h = require('react-hyperscript')
|
const h = require('react-hyperscript')
|
||||||
const connect = require('react-redux').connect
|
const connect = require('react-redux').connect
|
||||||
|
const actions = require('../../actions')
|
||||||
|
const FileInput = require('react-simple-file-input').default
|
||||||
|
|
||||||
module.exports = connect(mapStateToProps)(JsonImportSubview)
|
module.exports = connect(mapStateToProps)(JsonImportSubview)
|
||||||
|
|
||||||
function mapStateToProps (state) {
|
function mapStateToProps (state) {
|
||||||
return {}
|
return {
|
||||||
|
error: state.appState.warning,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inherits(JsonImportSubview, Component)
|
inherits(JsonImportSubview, Component)
|
||||||
@ -15,13 +19,80 @@ function JsonImportSubview () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
JsonImportSubview.prototype.render = function () {
|
JsonImportSubview.prototype.render = function () {
|
||||||
|
const { error } = this.props
|
||||||
|
|
||||||
return (
|
return (
|
||||||
h('div', {
|
h('div', {
|
||||||
style: {
|
style: {
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'center',
|
||||||
|
padding: '5px 15px 0px 15px',
|
||||||
},
|
},
|
||||||
}, [
|
}, [
|
||||||
`Upload your json file here!`,
|
|
||||||
|
h('p', 'Used by a variety of different clients'),
|
||||||
|
|
||||||
|
h(FileInput, {
|
||||||
|
readAs: 'text',
|
||||||
|
onLoad: this.onLoad.bind(this),
|
||||||
|
style: {
|
||||||
|
margin: '20px 0px 12px 20px',
|
||||||
|
fontSize: '15px',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
|
||||||
|
h('input.large-input.letter-spacey', {
|
||||||
|
type: 'password',
|
||||||
|
placeholder: 'Enter password',
|
||||||
|
id: 'json-password-box',
|
||||||
|
onKeyPress: this.createKeyringOnEnter.bind(this),
|
||||||
|
style: {
|
||||||
|
width: 260,
|
||||||
|
marginTop: 12,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
|
||||||
|
h('button.primary', {
|
||||||
|
onClick: this.createNewKeychain.bind(this),
|
||||||
|
style: {
|
||||||
|
margin: 12,
|
||||||
|
},
|
||||||
|
}, 'Import'),
|
||||||
|
|
||||||
|
error ? h('span.warning', error) : null,
|
||||||
])
|
])
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
JsonImportSubview.prototype.onLoad = function (event, file) {
|
||||||
|
this.setState({file: file, fileContents: event.target.result})
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonImportSubview.prototype.createKeyringOnEnter = function (event) {
|
||||||
|
if (event.key === 'Enter') {
|
||||||
|
event.preventDefault()
|
||||||
|
this.createNewKeychain()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonImportSubview.prototype.createNewKeychain = function () {
|
||||||
|
const state = this.state
|
||||||
|
const { fileContents } = state
|
||||||
|
|
||||||
|
if (!fileContents) {
|
||||||
|
const message = 'You must select a file to import.'
|
||||||
|
return this.props.dispatch(actions.displayWarning(message))
|
||||||
|
}
|
||||||
|
|
||||||
|
const passwordInput = document.getElementById('json-password-box')
|
||||||
|
const password = passwordInput.value
|
||||||
|
|
||||||
|
if (!password) {
|
||||||
|
const message = 'You must enter a password for the selected file.'
|
||||||
|
return this.props.dispatch(actions.displayWarning(message))
|
||||||
|
}
|
||||||
|
|
||||||
|
this.props.dispatch(actions.importNewAccount('JSON File', [ fileContents, password ]))
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -2,7 +2,6 @@ const inherits = require('util').inherits
|
|||||||
const Component = require('react').Component
|
const Component = require('react').Component
|
||||||
const h = require('react-hyperscript')
|
const h = require('react-hyperscript')
|
||||||
const connect = require('react-redux').connect
|
const connect = require('react-redux').connect
|
||||||
const type = 'Simple Key Pair'
|
|
||||||
const actions = require('../../actions')
|
const actions = require('../../actions')
|
||||||
|
|
||||||
module.exports = connect(mapStateToProps)(PrivateKeyImportView)
|
module.exports = connect(mapStateToProps)(PrivateKeyImportView)
|
||||||
@ -64,6 +63,6 @@ PrivateKeyImportView.prototype.createKeyringOnEnter = function (event) {
|
|||||||
PrivateKeyImportView.prototype.createNewKeychain = function () {
|
PrivateKeyImportView.prototype.createNewKeychain = function () {
|
||||||
const input = document.getElementById('private-key-box')
|
const input = document.getElementById('private-key-box')
|
||||||
const privateKey = input.value
|
const privateKey = input.value
|
||||||
this.props.dispatch(actions.addNewKeyring(type, [ privateKey ]))
|
this.props.dispatch(actions.importNewAccount('Private Key', [ privateKey ]))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,6 +43,7 @@ var actions = {
|
|||||||
createNewVaultAndRestore: createNewVaultAndRestore,
|
createNewVaultAndRestore: createNewVaultAndRestore,
|
||||||
createNewVaultInProgress: createNewVaultInProgress,
|
createNewVaultInProgress: createNewVaultInProgress,
|
||||||
addNewKeyring,
|
addNewKeyring,
|
||||||
|
importNewAccount,
|
||||||
addNewAccount,
|
addNewAccount,
|
||||||
NEW_ACCOUNT_SCREEN: 'NEW_ACCOUNT_SCREEN',
|
NEW_ACCOUNT_SCREEN: 'NEW_ACCOUNT_SCREEN',
|
||||||
navigateToNewAccountScreen,
|
navigateToNewAccountScreen,
|
||||||
@ -278,6 +279,21 @@ function addNewKeyring (type, opts) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function importNewAccount (strategy, args) {
|
||||||
|
return (dispatch) => {
|
||||||
|
dispatch(actions.showLoadingIndication('This may take a while, be patient.'))
|
||||||
|
background.importAccountWithStrategy(strategy, args, (err, newState) => {
|
||||||
|
dispatch(actions.hideLoadingIndication())
|
||||||
|
if (err) return dispatch(actions.displayWarning(err.message))
|
||||||
|
dispatch(actions.updateMetamaskState(newState))
|
||||||
|
dispatch({
|
||||||
|
type: actions.SHOW_ACCOUNT_DETAIL,
|
||||||
|
value: newState.selectedAccount,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function navigateToNewAccountScreen() {
|
function navigateToNewAccountScreen() {
|
||||||
return {
|
return {
|
||||||
type: this.NEW_ACCOUNT_SCREEN,
|
type: this.NEW_ACCOUNT_SCREEN,
|
||||||
@ -628,9 +644,10 @@ function useEtherscanProvider () {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function showLoadingIndication () {
|
function showLoadingIndication (message) {
|
||||||
return {
|
return {
|
||||||
type: actions.SHOW_LOADING,
|
type: actions.SHOW_LOADING,
|
||||||
|
value: message,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,6 +43,7 @@ function mapStateToProps (state) {
|
|||||||
return {
|
return {
|
||||||
// state from plugin
|
// state from plugin
|
||||||
isLoading: state.appState.isLoading,
|
isLoading: state.appState.isLoading,
|
||||||
|
loadingMessage: state.appState.loadingMessage,
|
||||||
isDisclaimerConfirmed: state.metamask.isDisclaimerConfirmed,
|
isDisclaimerConfirmed: state.metamask.isDisclaimerConfirmed,
|
||||||
noActiveNotices: state.metamask.noActiveNotices,
|
noActiveNotices: state.metamask.noActiveNotices,
|
||||||
isInitialized: state.metamask.isInitialized,
|
isInitialized: state.metamask.isInitialized,
|
||||||
@ -64,7 +65,7 @@ function mapStateToProps (state) {
|
|||||||
|
|
||||||
App.prototype.render = function () {
|
App.prototype.render = function () {
|
||||||
var props = this.props
|
var props = this.props
|
||||||
const { isLoading, transForward } = props
|
const { isLoading, loadingMessage, transForward } = props
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
||||||
@ -76,7 +77,7 @@ App.prototype.render = function () {
|
|||||||
},
|
},
|
||||||
}, [
|
}, [
|
||||||
|
|
||||||
h(LoadingIndicator, { isLoading }),
|
h(LoadingIndicator, { isLoading, loadingMessage }),
|
||||||
|
|
||||||
// app bar
|
// app bar
|
||||||
this.renderAppBar(),
|
this.renderAppBar(),
|
||||||
|
@ -12,7 +12,7 @@ function LoadingIndicator () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
LoadingIndicator.prototype.render = function () {
|
LoadingIndicator.prototype.render = function () {
|
||||||
var isLoading = this.props.isLoading
|
const { isLoading, loadingMessage } = this.props
|
||||||
|
|
||||||
return (
|
return (
|
||||||
h(ReactCSSTransitionGroup, {
|
h(ReactCSSTransitionGroup, {
|
||||||
@ -37,8 +37,14 @@ LoadingIndicator.prototype.render = function () {
|
|||||||
h('img', {
|
h('img', {
|
||||||
src: 'images/loading.svg',
|
src: 'images/loading.svg',
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
showMessageIfAny(loadingMessage),
|
||||||
]) : null,
|
]) : null,
|
||||||
])
|
])
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function showMessageIfAny (loadingMessage) {
|
||||||
|
if (!loadingMessage) return null
|
||||||
|
return h('span', loadingMessage)
|
||||||
|
}
|
||||||
|
@ -134,7 +134,7 @@ ConfirmTxScreen.prototype.checkBalanceAgainstTx = function (txData) {
|
|||||||
var address = txData.txParams.from || state.selectedAccount
|
var address = txData.txParams.from || state.selectedAccount
|
||||||
var account = state.accounts[address]
|
var account = state.accounts[address]
|
||||||
var balance = account ? account.balance : '0x0'
|
var balance = account ? account.balance : '0x0'
|
||||||
var maxCost = new BN(txData.maxCost)
|
var maxCost = new BN(txData.maxCost, 16)
|
||||||
|
|
||||||
var balanceBn = new BN(ethUtil.stripHexPrefix(balance), 16)
|
var balanceBn = new BN(ethUtil.stripHexPrefix(balance), 16)
|
||||||
return maxCost.gt(balanceBn)
|
return maxCost.gt(balanceBn)
|
||||||
|
@ -386,6 +386,7 @@ function reduceApp (state, action) {
|
|||||||
case actions.SHOW_LOADING:
|
case actions.SHOW_LOADING:
|
||||||
return extend(appState, {
|
return extend(appState, {
|
||||||
isLoading: true,
|
isLoading: true,
|
||||||
|
loadingMessage: action.value,
|
||||||
})
|
})
|
||||||
|
|
||||||
case actions.HIDE_LOADING:
|
case actions.HIDE_LOADING:
|
||||||
|
Loading…
Reference in New Issue
Block a user