Merge pull request #1022 from MetaMask/i715-AddImportMenu

Add ability to import private keys
feature/default_network_editable
kumavis 8 years ago committed by GitHub
commit 28212d167c
  1. 1
      .eslintrc
  2. 1
      CHANGELOG.md
  3. 3
      app/scripts/keyring-controller.js
  4. 8
      app/scripts/keyrings/simple.js
  5. 7
      app/scripts/metamask-controller.js
  6. 84
      development/states/account-list-with-imported.json
  7. 124
      development/states/compilation-bug.json
  8. 92
      development/states/import-private-key-warning.json
  9. 64
      development/states/import-private-key.json
  10. 66
      development/states/new-account.json
  11. 1
      package.json
  12. 3
      test/integration/lib/first-time.js
  13. 91
      ui/app/accounts/import/index.js
  14. 27
      ui/app/accounts/import/json.js
  15. 69
      ui/app/accounts/import/private-key.js
  16. 30
      ui/app/accounts/import/seed.js
  17. 10
      ui/app/accounts/index.js
  18. 26
      ui/app/actions.js
  19. 11
      ui/app/app.js
  20. 52
      ui/app/components/buy-button-subview.js
  21. 35
      ui/app/components/tab-bar.js
  22. 8
      ui/app/css/lib.css
  23. 2
      ui/app/info.js
  24. 17
      ui/app/reducers/app.js
  25. 2
      ui/app/unlock.js
  26. 1
      ui/css.js

@ -1,5 +1,6 @@
{ {
"parserOptions": { "parserOptions": {
"sourceType": "module",
"ecmaVersion": 6, "ecmaVersion": 6,
"ecmaFeatures": { "ecmaFeatures": {
"experimentalObjectRestSpread": true, "experimentalObjectRestSpread": true,

@ -2,6 +2,7 @@
## Current Master ## Current Master
- Add ability to import accounts by private key.
- Fixed bug that returned the wrong transaction hashes on private networks that had not implemented EIP 155 replay protection (like TestRPC). - Fixed bug that returned the wrong transaction hashes on private networks that had not implemented EIP 155 replay protection (like TestRPC).
## 3.0.1 2017-1-17 ## 3.0.1 2017-1-17

@ -234,7 +234,10 @@ module.exports = class KeyringController extends EventEmitter {
addNewKeyring (type, opts) { addNewKeyring (type, opts) {
const Keyring = this.getKeyringClassForType(type) const Keyring = this.getKeyringClassForType(type)
const keyring = new Keyring(opts) const keyring = new Keyring(opts)
return keyring.deserialize(opts)
.then(() => {
return keyring.getAccounts() return keyring.getAccounts()
})
.then((accounts) => { .then((accounts) => {
this.keyrings.push(keyring) this.keyrings.push(keyring)
return this.setupAccounts(accounts) return this.setupAccounts(accounts)

@ -20,13 +20,19 @@ class SimpleKeyring extends EventEmitter {
} }
deserialize (privateKeys = []) { deserialize (privateKeys = []) {
return new Promise((resolve, reject) => {
try {
this.wallets = privateKeys.map((privateKey) => { this.wallets = privateKeys.map((privateKey) => {
const stripped = ethUtil.stripHexPrefix(privateKey) const stripped = ethUtil.stripHexPrefix(privateKey)
const buffer = new Buffer(stripped, 'hex') const buffer = new Buffer(stripped, 'hex')
const wallet = Wallet.fromPrivateKey(buffer) const wallet = Wallet.fromPrivateKey(buffer)
return wallet return wallet
}) })
return Promise.resolve() } catch (e) {
reject(e)
}
resolve()
})
} }
addAccounts (n = 1) { addAccounts (n = 1) {

@ -115,7 +115,12 @@ module.exports = class MetamaskController extends EventEmitter {
.then((newState) => { cb(null, newState) }) .then((newState) => { cb(null, newState) })
.catch((reason) => { cb(reason) }) .catch((reason) => { cb(reason) })
}, },
addNewKeyring: nodeify(keyringController.addNewKeyring).bind(keyringController), addNewKeyring: (type, opts, cb) => {
keyringController.addNewKeyring(type, opts)
.then(() => keyringController.fullUpdate())
.then((newState) => { cb(null, newState) })
.catch((reason) => { cb(reason) })
},
addNewAccount: nodeify(keyringController.addNewAccount).bind(keyringController), addNewAccount: nodeify(keyringController.addNewAccount).bind(keyringController),
setSelectedAccount: nodeify(keyringController.setSelectedAccount).bind(keyringController), setSelectedAccount: nodeify(keyringController.setSelectedAccount).bind(keyringController),
saveAccountLabel: nodeify(keyringController.saveAccountLabel).bind(keyringController), saveAccountLabel: nodeify(keyringController.saveAccountLabel).bind(keyringController),

@ -0,0 +1,84 @@
{
"metamask": {
"isInitialized": true,
"isUnlocked": true,
"rpcTarget": "https://rawtestrpc.metamask.io/",
"identities": {
"0x58bda1f9d87dc7d2bcc6f7c2513efc9d03fca683": {
"address": "0x58bda1f9d87dc7d2bcc6f7c2513efc9d03fca683",
"name": "Account 1"
},
"0x9858e7d8b79fc3e6d989636721584498926da38a": {
"address": "0x9858e7d8b79fc3e6d989636721584498926da38a",
"name": "Imported Account"
}
},
"unconfTxs": {},
"currentFiat": "USD",
"conversionRate": 10.19458075,
"conversionDate": 1484696373,
"noActiveNotices": true,
"network": "3",
"accounts": {
"0x58bda1f9d87dc7d2bcc6f7c2513efc9d03fca683": {
"code": "0x",
"balance": "0x0",
"nonce": "0x0",
"address": "0x58bda1f9d87dc7d2bcc6f7c2513efc9d03fca683"
},
"0x9858e7d8b79fc3e6d989636721584498926da38a": {
"code": "0x",
"balance": "0x0",
"nonce": "0x0",
"address": "0x9858e7d8b79fc3e6d989636721584498926da38a"
}
},
"transactions": [],
"provider": {
"type": "testnet"
},
"selectedAccount": "0x9858e7d8b79fc3e6d989636721584498926da38a",
"selectedAccountTxList": [],
"isDisclaimerConfirmed": true,
"unconfMsgs": {},
"messages": [],
"shapeShiftTxList": [],
"keyringTypes": [
"Simple Key Pair",
"HD Key Tree"
],
"keyrings": [
{
"type": "HD Key Tree",
"accounts": [
"58bda1f9d87dc7d2bcc6f7c2513efc9d03fca683"
]
},
{
"type": "Simple Key Pair",
"accounts": [
"0x9858e7d8b79fc3e6d989636721584498926da38a"
]
}
],
"lostAccounts": [],
"seedWords": null
},
"appState": {
"menuOpen": false,
"currentView": {
"name": "accounts"
},
"accountDetail": {
"subview": "transactions",
"accountExport": "none",
"privateKey": ""
},
"transForward": true,
"isLoading": false,
"warning": null,
"scrollToBottom": false,
"forgottenPassword": false
},
"identities": {}
}

@ -0,0 +1,124 @@
{
"metamask": {
"isInitialized": true,
"isUnlocked": true,
"rpcTarget": "https://rawtestrpc.metamask.io/",
"identities": {
"0xac39b311dceb2a4b2f5d8461c1cdaf756f4f7ae9": {
"address": "0xac39b311dceb2a4b2f5d8461c1cdaf756f4f7ae9",
"name": "Account 1"
},
"0xd7c0cd9e7d2701c710d64fc492c7086679bdf7b4": {
"address": "0xd7c0cd9e7d2701c710d64fc492c7086679bdf7b4",
"name": "Account 2"
},
"0x1acfb961c5a8268eac8e09d6241a26cbeff42241": {
"address": "0x1acfb961c5a8268eac8e09d6241a26cbeff42241",
"name": "Account 3"
},
"0xabc2bca51709b8615147352c62420f547a63a00c": {
"address": "0xabc2bca51709b8615147352c62420f547a63a00c",
"name": "Account 4"
}
},
"unconfTxs": {
"7992944905869041": {
"id": 7992944905869041,
"txParams": {
"from": "0xac39b311dceb2a4b2f5d8461c1cdaf756f4f7ae9",
"value": "0x0",
"data": "0x606060405234610000575b60da806100186000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630dbe671f14603c575b6000565b3460005760466088565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b600060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16815600a165627a7a72305820a99dfa6091771f518dd1ae8d1ee347bae3304dffd98fd24b1b99a8380bc60a750029",
"gas": "0x1af75",
"metamaskId": 7992944905869041,
"metamaskNetworkId": "3"
},
"time": 1482279685589,
"status": "unconfirmed",
"gasMultiplier": 1,
"metamaskNetworkId": "3",
"gasLimitSpecified": true,
"estimatedGas": "0x1af75",
"simulationFails": true
}
},
"currentFiat": "USD",
"conversionRate": 7.69158136,
"conversionDate": 1482279663,
"noActiveNotices": true,
"network": "3",
"accounts": {
"0xac39b311dceb2a4b2f5d8461c1cdaf756f4f7ae9": {
"code": "0x",
"nonce": "0x3",
"balance": "0x11f646fe14c9c000",
"address": "0xac39b311dceb2a4b2f5d8461c1cdaf756f4f7ae9"
},
"0xd7c0cd9e7d2701c710d64fc492c7086679bdf7b4": {
"code": "0x",
"nonce": "0x0",
"balance": "0x0",
"address": "0xd7c0cd9e7d2701c710d64fc492c7086679bdf7b4"
},
"0x1acfb961c5a8268eac8e09d6241a26cbeff42241": {
"code": "0x",
"balance": "0x0",
"nonce": "0x0",
"address": "0x1acfb961c5a8268eac8e09d6241a26cbeff42241"
},
"0xabc2bca51709b8615147352c62420f547a63a00c": {
"code": "0x",
"balance": "0x0",
"nonce": "0x0",
"address": "0xabc2bca51709b8615147352c62420f547a63a00c"
}
},
"transactions": [
{
"id": 7992944905869041,
"txParams": {
"from": "0xac39b311dceb2a4b2f5d8461c1cdaf756f4f7ae9",
"value": "0x0",
"data": "0x606060405234610000575b60da806100186000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630dbe671f14603c575b6000565b3460005760466088565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b600060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16815600a165627a7a72305820a99dfa6091771f518dd1ae8d1ee347bae3304dffd98fd24b1b99a8380bc60a750029",
"gas": "0x1af75",
"metamaskId": 7992944905869041,
"metamaskNetworkId": "3"
},
"time": 1482279685589,
"status": "unconfirmed",
"gasMultiplier": 1,
"metamaskNetworkId": "3",
"gasLimitSpecified": true,
"estimatedGas": "0x1af75",
"simulationFails": true
}
],
"provider": {
"type": "testnet"
},
"selectedAccount": "0xac39b311dceb2a4b2f5d8461c1cdaf756f4f7ae9",
"seedWords": false,
"isDisclaimerConfirmed": true,
"unconfMsgs": {},
"messages": [],
"shapeShiftTxList": [],
"keyringTypes": [
"Simple Key Pair",
"HD Key Tree"
],
"lostAccounts": []
},
"appState": {
"menuOpen": false,
"currentView": {
"name": "confTx",
"context": 0
},
"accountDetail": {
"subview": "transactions"
},
"transForward": true,
"isLoading": false,
"warning": null
},
"identities": {}
}

@ -0,0 +1,92 @@
{
"metamask": {
"isInitialized": true,
"isUnlocked": true,
"rpcTarget": "https://rawtestrpc.metamask.io/",
"identities": {
"0x01208723ba84e15da2e71656544a2963b0c06d40": {
"address": "0x01208723ba84e15da2e71656544a2963b0c06d40",
"name": "Account 1"
}
},
"unconfTxs": {},
"currentFiat": "USD",
"conversionRate": 10.1219126,
"conversionDate": 1484695442,
"noActiveNotices": true,
"network": "3",
"accounts": {
"0x01208723ba84e15da2e71656544a2963b0c06d40": {
"nonce": "0x0",
"balance": "0x0",
"code": "0x",
"address": "0x01208723ba84e15da2e71656544a2963b0c06d40"
}
},
"transactions": [],
"provider": {
"type": "testnet"
},
"selectedAccount": "0x01208723ba84e15da2e71656544a2963b0c06d40",
"selectedAccountTxList": [],
"seedWords": false,
"isDisclaimerConfirmed": true,
"unconfMsgs": {},
"messages": [],
"shapeShiftTxList": [],
"keyringTypes": [
"Simple Key Pair",
"HD Key Tree"
],
"keyrings": [
{
"type": "Simple Key Pair",
"accounts": []
},
{
"type": "Simple Key Pair",
"accounts": []
},
{
"type": "Simple Key Pair",
"accounts": []
},
{
"type": "Simple Key Pair",
"accounts": []
},
{
"type": "Simple Key Pair",
"accounts": []
},
{
"type": "Simple Key Pair",
"accounts": []
},
{
"type": "Simple Key Pair",
"accounts": []
},
{
"type": "HD Key Tree",
"accounts": [
"01208723ba84e15da2e71656544a2963b0c06d40"
]
}
],
"lostAccounts": []
},
"appState": {
"menuOpen": false,
"currentView": {
"name": "import-menu"
},
"accountDetail": {
"subview": "transactions"
},
"transForward": true,
"isLoading": false,
"warning": "Invalid hex string"
},
"identities": {}
}

@ -0,0 +1,64 @@
{
"metamask": {
"isInitialized": true,
"isUnlocked": true,
"rpcTarget": "https://rawtestrpc.metamask.io/",
"identities": {
"0x01208723ba84e15da2e71656544a2963b0c06d40": {
"address": "0x01208723ba84e15da2e71656544a2963b0c06d40",
"name": "Account 1"
}
},
"unconfTxs": {},
"currentFiat": "USD",
"conversionRate": 10.10788584,
"conversionDate": 1484694362,
"noActiveNotices": true,
"network": "3",
"accounts": {
"0x01208723ba84e15da2e71656544a2963b0c06d40": {
"balance": "0x0",
"code": "0x",
"nonce": "0x0",
"address": "0x01208723ba84e15da2e71656544a2963b0c06d40"
}
},
"transactions": [],
"provider": {
"type": "testnet"
},
"selectedAccount": "0x01208723ba84e15da2e71656544a2963b0c06d40",
"selectedAccountTxList": [],
"seedWords": null,
"isDisclaimerConfirmed": true,
"unconfMsgs": {},
"messages": [],
"shapeShiftTxList": [],
"keyringTypes": [
"Simple Key Pair",
"HD Key Tree"
],
"keyrings": [
{
"type": "HD Key Tree",
"accounts": [
"01208723ba84e15da2e71656544a2963b0c06d40"
]
}
],
"lostAccounts": []
},
"appState": {
"menuOpen": false,
"currentView": {
"name": "import-menu"
},
"accountDetail": {
"subview": "transactions"
},
"transForward": true,
"isLoading": false,
"warning": null
},
"identities": {}
}

@ -0,0 +1,66 @@
{
"metamask": {
"isInitialized": true,
"isUnlocked": true,
"rpcTarget": "https://rawtestrpc.metamask.io/",
"identities": {
"0xa6ef573d60594731178b7f85d80da13cc2af52dd": {
"address": "0xa6ef573d60594731178b7f85d80da13cc2af52dd",
"name": "Dan! 1"
},
"0xf9f52e84ad2c9122caa87478d27041ddaa215666": {
"address": "0xf9f52e84ad2c9122caa87478d27041ddaa215666",
"name": "Account 2"
}
},
"unconfTxs": {},
"currentFiat": "USD",
"conversionRate": 10.92067835,
"conversionDate": 1478282884,
"network": null,
"accounts": {
"0xa6ef573d60594731178b7f85d80da13cc2af52dd": {
"balance": "0x00",
"nonce": "0x100000",
"code": "0x",
"address": "0xa6ef573d60594731178b7f85d80da13cc2af52dd"
},
"0xf9f52e84ad2c9122caa87478d27041ddaa215666": {
"balance": "0x00",
"nonce": "0x100000",
"code": "0x",
"address": "0xf9f52e84ad2c9122caa87478d27041ddaa215666"
}
},
"transactions": [],
"provider": {
"type": "testnet"
},
"selectedAccount": "0xa6ef573d60594731178b7f85d80da13cc2af52dd",
"isConfirmed": true,
"unconfMsgs": {},
"messages": [],
"selectedAddress": "0xa6ef573d60594731178b7f85d80da13cc2af52dd",
"shapeShiftTxList": [],
"keyringTypes": [
"Simple Key Pair",
"HD Key Tree"
]
},
"appState": {
"menuOpen": false,
"currentView": {
"name": "new-account"
},
"accountDetail": {
"subview": "transactions"
},
"transForward": true,
"isLoading": false,
"warning": null,
"forgottenPassword": null,
"detailView": {},
"scrollToBottom": false
},
"identities": {}
}

@ -83,6 +83,7 @@
"react-hyperscript": "^2.2.2", "react-hyperscript": "^2.2.2",
"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-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",

@ -66,7 +66,8 @@ QUnit.test('agree to terms', function (assert) {
}).then(function() { }).then(function() {
var sandwich = app.find('.menu-droppo')[0] var sandwich = app.find('.menu-droppo')[0]
var lock = sandwich.children[2] var children = sandwich.children
var lock = children[children.length - 2]
assert.ok(lock, 'Lock menu item found') assert.ok(lock, 'Lock menu item found')
lock.click() lock.click()

@ -0,0 +1,91 @@
const inherits = require('util').inherits
const Component = require('react').Component
const h = require('react-hyperscript')
const connect = require('react-redux').connect
import Select from 'react-select'
// Subviews
const JsonImportView = require('./json.js')
const SeedImportView = require('./seed.js')
const PrivateKeyImportView = require('./private-key.js')
const menuItems = [
'Private Key',
]
module.exports = connect(mapStateToProps)(AccountImportSubview)
function mapStateToProps (state) {
return {
menuItems,
}
}
inherits(AccountImportSubview, Component)
function AccountImportSubview () {
Component.call(this)
}
AccountImportSubview.prototype.render = function () {
const props = this.props
const state = this.state || {}
const { menuItems } = props
const { type } = state
return (
h('div', {
style: {
},
}, [
h('div', {
style: {
padding: '10px',
color: 'rgb(174, 174, 174)',
},
}, [
h('h3', { style: { padding: '3px' } }, 'SELECT TYPE'),
h('style', `
.has-value.Select--single > .Select-control .Select-value .Select-value-label, .Select-value-label {
color: rgb(174,174,174);
}
`),
h(Select, {
name: 'import-type-select',
clearable: false,
value: type || menuItems[0],
options: menuItems.map((type) => {
return {
value: type,
label: type,
}
}),
onChange: (opt) => {
this.setState({ type: opt.value })
},
}),
]),
this.renderImportView(),
])
)
}
AccountImportSubview.prototype.renderImportView = function() {
const props = this.props
const state = this.state || {}
const { type } = state
const { menuItems } = props
const current = type || menuItems[0]
switch (current) {
case 'HD Key Tree':
return h(SeedImportView)
case 'Private Key':
return h(PrivateKeyImportView)
default:
return h(JsonImportView)
}
}

@ -0,0 +1,27 @@
const inherits = require('util').inherits
const Component = require('react').Component
const h = require('react-hyperscript')
const connect = require('react-redux').connect
module.exports = connect(mapStateToProps)(JsonImportSubview)
function mapStateToProps (state) {
return {}
}
inherits(JsonImportSubview, Component)
function JsonImportSubview () {
Component.call(this)
}
JsonImportSubview.prototype.render = function () {
return (
h('div', {
style: {
},
}, [
`Upload your json file here!`,
])
)
}

@ -0,0 +1,69 @@
const inherits = require('util').inherits
const Component = require('react').Component
const h = require('react-hyperscript')
const connect = require('react-redux').connect
const type = 'Simple Key Pair'
const actions = require('../../actions')
module.exports = connect(mapStateToProps)(PrivateKeyImportView)
function mapStateToProps (state) {
return {
error: state.appState.warning,
}
}
inherits(PrivateKeyImportView, Component)
function PrivateKeyImportView () {
Component.call(this)
}
PrivateKeyImportView.prototype.render = function () {
const { error } = this.props
return (
h('div', {
style: {
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
padding: '5px 15px 0px 15px',
},
}, [
h('span', 'Paste your private key string here'),
h('input.large-input.letter-spacey', {
type: 'password',
id: 'private-key-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,
])
)
}
PrivateKeyImportView.prototype.createKeyringOnEnter = function (event) {
if (event.key === 'Enter') {
event.preventDefault()
this.createNewKeychain()
}
}
PrivateKeyImportView.prototype.createNewKeychain = function () {
const input = document.getElementById('private-key-box')
const privateKey = input.value
this.props.dispatch(actions.addNewKeyring(type, [ privateKey ]))
}

@ -0,0 +1,30 @@
const inherits = require('util').inherits
const Component = require('react').Component
const h = require('react-hyperscript')
const connect = require('react-redux').connect
module.exports = connect(mapStateToProps)(SeedImportSubview)
function mapStateToProps (state) {
return {}
}
inherits(SeedImportSubview, Component)
function SeedImportSubview () {
Component.call(this)
}
SeedImportSubview.prototype.render = function () {
return (
h('div', {
style: {
},
}, [
`Paste your seed phrase here!`,
h('textarea'),
h('br'),
h('button', 'Submit'),
])
)
}

@ -73,7 +73,8 @@ AccountsScreen.prototype.render = function () {
const simpleAddress = identity.address.substring(2).toLowerCase() const simpleAddress = identity.address.substring(2).toLowerCase()
const keyring = keyrings.find((kr) => { const keyring = keyrings.find((kr) => {
return kr.accounts.includes(simpleAddress) return kr.accounts.includes(simpleAddress) ||
kr.accounts.includes(identity.address)
}) })
return h(AccountListItem, { return h(AccountListItem, {
@ -154,6 +155,13 @@ AccountsScreen.prototype.addNewAccount = function () {
this.props.dispatch(actions.addNewAccount(0)) this.props.dispatch(actions.addNewAccount(0))
} }
/* An optional view proposed in this design:
* https://consensys.quip.com/zZVrAysM5znY
AccountsScreen.prototype.addNewAccount = function () {
this.props.dispatch(actions.navigateToNewAccountScreen())
}
*/
AccountsScreen.prototype.goHome = function () { AccountsScreen.prototype.goHome = function () {
this.props.dispatch(actions.goHome()) this.props.dispatch(actions.goHome())
} }

@ -32,16 +32,20 @@ var actions = {
SHOW_INIT_MENU: 'SHOW_INIT_MENU', SHOW_INIT_MENU: 'SHOW_INIT_MENU',
SHOW_NEW_VAULT_SEED: 'SHOW_NEW_VAULT_SEED', SHOW_NEW_VAULT_SEED: 'SHOW_NEW_VAULT_SEED',
SHOW_INFO_PAGE: 'SHOW_INFO_PAGE', SHOW_INFO_PAGE: 'SHOW_INFO_PAGE',
SHOW_IMPORT_PAGE: 'SHOW_IMPORT_PAGE',
unlockMetamask: unlockMetamask, unlockMetamask: unlockMetamask,
unlockFailed: unlockFailed, unlockFailed: unlockFailed,
showCreateVault: showCreateVault, showCreateVault: showCreateVault,
showRestoreVault: showRestoreVault, showRestoreVault: showRestoreVault,
showInitializeMenu: showInitializeMenu, showInitializeMenu: showInitializeMenu,
showImportPage,
createNewVaultAndKeychain: createNewVaultAndKeychain, createNewVaultAndKeychain: createNewVaultAndKeychain,
createNewVaultAndRestore: createNewVaultAndRestore, createNewVaultAndRestore: createNewVaultAndRestore,
createNewVaultInProgress: createNewVaultInProgress, createNewVaultInProgress: createNewVaultInProgress,
addNewKeyring, addNewKeyring,
addNewAccount, addNewAccount,
NEW_ACCOUNT_SCREEN: 'NEW_ACCOUNT_SCREEN',
navigateToNewAccountScreen,
showNewVaultSeed: showNewVaultSeed, showNewVaultSeed: showNewVaultSeed,
showInfoPage: showInfoPage, showInfoPage: showInfoPage,
// seed recovery actions // seed recovery actions
@ -249,7 +253,21 @@ function requestRevealSeed (password) {
} }
function addNewKeyring (type, opts) { function addNewKeyring (type, opts) {
return callBackgroundThenUpdate(background.addNewKeyring, type, opts) return (dispatch) => {
dispatch(actions.showLoadingIndication())
background.addNewKeyring(type, opts, (err, newState) => {
dispatch(actions.hideLoadingIndication())
if (err) return dispatch(actions.displayWarning(err.message))
dispatch(actions.updateMetamaskState(newState))
dispatch(actions.showAccountsPage())
})
}
}
function navigateToNewAccountScreen() {
return {
type: this.NEW_ACCOUNT_SCREEN,
}
} }
function addNewAccount (ringNumber = 0) { function addNewAccount (ringNumber = 0) {
@ -376,6 +394,12 @@ function showInitializeMenu () {
} }
} }
function showImportPage () {
return {
type: actions.SHOW_IMPORT_PAGE,
}
}
function agreeToDisclaimer () { function agreeToDisclaimer () {
return (dispatch) => { return (dispatch) => {
dispatch(this.showLoadingIndication()) dispatch(this.showLoadingIndication())

@ -20,6 +20,7 @@ const NoticeScreen = require('./components/notice')
const generateLostAccountsNotice = require('../lib/lost-accounts-notice') const generateLostAccountsNotice = require('../lib/lost-accounts-notice')
// other views // other views
const ConfigScreen = require('./config') const ConfigScreen = require('./config')
const Import = require('./accounts/import')
const InfoScreen = require('./info') const InfoScreen = require('./info')
const LoadingIndicator = require('./components/loading') const LoadingIndicator = require('./components/loading')
const SandwichExpando = require('sandwich-expando') const SandwichExpando = require('sandwich-expando')
@ -304,6 +305,13 @@ App.prototype.renderDropdown = function () {
icon: h('i.fa.fa-gear.fa-lg'), icon: h('i.fa.fa-gear.fa-lg'),
}), }),
h(DropMenuItem, {
label: 'Import Account',
closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }),
action: () => this.props.dispatch(actions.showImportPage()),
icon: h('i.fa.fa-arrow-circle-o-up.fa-lg'),
}),
h(DropMenuItem, { h(DropMenuItem, {
label: 'Lock', label: 'Lock',
closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }), closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }),
@ -411,6 +419,9 @@ App.prototype.renderPrimary = function () {
case 'config': case 'config':
return h(ConfigScreen, {key: 'config'}) return h(ConfigScreen, {key: 'config'})
case 'import-menu':
return h(Import, {key: 'import-menu'})
case 'reveal-seed-conf': case 'reveal-seed-conf':
return h(RevealSeedConfirmation, {key: 'reveal-seed-conf'}) return h(RevealSeedConfirmation, {key: 'reveal-seed-conf'})

@ -7,6 +7,7 @@ const CoinbaseForm = require('./coinbase-form')
const ShapeshiftForm = require('./shapeshift-form') const ShapeshiftForm = require('./shapeshift-form')
const extension = require('../../../app/scripts/lib/extension') const extension = require('../../../app/scripts/lib/extension')
const Loading = require('./loading') const Loading = require('./loading')
const TabBar = require('./tab-bar')
module.exports = connect(mapStateToProps)(BuyButtonSubview) module.exports = connect(mapStateToProps)(BuyButtonSubview)
@ -29,7 +30,6 @@ function BuyButtonSubview () {
BuyButtonSubview.prototype.render = function () { BuyButtonSubview.prototype.render = function () {
const props = this.props const props = this.props
const currentForm = props.buyView.formView
const isLoading = props.isSubLoading const isLoading = props.isSubLoading
return ( return (
@ -53,43 +53,53 @@ BuyButtonSubview.prototype.render = function () {
h(Loading, { isLoading }), h(Loading, { isLoading }),
h('h3.flex-row.text-transform-uppercase', { h(TabBar, {
style: { tabs: [
background: '#EBEBEB', {
color: '#AEAEAE', content: [
paddingTop: '4px', 'Coinbase',
justifyContent: 'space-around',
},
}, [
h(currentForm.coinbase ? '.activeForm' : '.inactiveForm.pointer', {
onClick: () => props.dispatch(actions.coinBaseSubview()),
}, 'Coinbase'),
h('a', { h('a', {
onClick: (event) => this.navigateTo('https://github.com/MetaMask/faq/blob/master/COINBASE.md'), onClick: (event) => this.navigateTo('https://github.com/MetaMask/faq/blob/master/COINBASE.md'),
}, [ }, [
h('i.fa.fa-question-circle', { h('i.fa.fa-question-circle', {
style: { style: {
position: 'relative', margin: '0px 5px',
right: '33px',
}, },
}), }),
]), ]),
h(currentForm.shapeshift ? '.activeForm' : '.inactiveForm.pointer', { ],
onClick: () => props.dispatch(actions.shapeShiftSubview(props.provider.type)), key: 'coinbase',
}, 'Shapeshift'), },
{
content: [
'Shapeshift',
h('a', { h('a', {
href: 'https://github.com/MetaMask/faq/blob/master/COINBASE.md', href: 'https://github.com/MetaMask/faq/blob/master/COINBASE.md',
onClick: (event) => this.navigateTo('https://info.shapeshift.io/about'), onClick: (event) => this.navigateTo('https://info.shapeshift.io/about'),
}, [ }, [
h('i.fa.fa-question-circle', { h('i.fa.fa-question-circle', {
style: { style: {
position: 'relative', margin: '0px 5px',
right: '28px',
}, },
}), }),
]), ]),
]), ],
key: 'shapeshift',
},
],
defaultTab: 'coinbase',
tabSelected: (key) => {
switch (key) {
case 'coinbase':
props.dispatch(actions.coinBaseSubview())
break
case 'shapeshift':
props.dispatch(actions.shapeShiftSubview(props.provider.type))
break
}
},
}),
this.formVersionSubview(), this.formVersionSubview(),
]) ])
) )

@ -0,0 +1,35 @@
const Component = require('react').Component
const h = require('react-hyperscript')
const inherits = require('util').inherits
module.exports = TabBar
inherits(TabBar, Component)
function TabBar () {
Component.call(this)
}
TabBar.prototype.render = function () {
const props = this.props
const state = this.state || {}
const { tabs = [], defaultTab, tabSelected } = props
const { subview = defaultTab } = state
return (
h('.flex-row.space-around.text-transform-uppercase', {
style: {
background: '#EBEBEB',
color: '#AEAEAE',
paddingTop: '4px',
},
}, tabs.map((tab) => {
const { key, content } = tab
return h(subview === key ? '.activeForm' : '.inactiveForm.pointer', {
onClick: () => {
this.setState({ subview: key })
tabSelected(key)
},
}, content)
}))
)
}

@ -23,6 +23,14 @@
flex-direction: column; flex-direction: column;
} }
.space-between {
justify-content: space-between;
}
.space-around {
justify-content: space-around;
}
.flex-column-bottom { .flex-column-bottom {
display: flex; display: flex;
flex-direction: column-reverse; flex-direction: column-reverse;

@ -110,7 +110,7 @@ InfoScreen.prototype.render = function () {
onClick (event) { this.navigateTo(event.target.href) }, onClick (event) { this.navigateTo(event.target.href) },
}, [ }, [
h('img.icon-size', { h('img.icon-size', {
src: manifest.icons[128], src: manifest.icons['128'],
style: { style: {
filter: 'grayscale(100%)', /* IE6-9 */ filter: 'grayscale(100%)', /* IE6-9 */
WebkitFilter: 'grayscale(100%)', /* Microsoft Edge and Firefox 35+ */ WebkitFilter: 'grayscale(100%)', /* Microsoft Edge and Firefox 35+ */

@ -99,6 +99,14 @@ function reduceApp (state, action) {
transForward: action.value, transForward: action.value,
}) })
case actions.SHOW_IMPORT_PAGE:
return extend(appState, {
currentView: {
name: 'import-menu',
},
transForward: true,
})
case actions.SHOW_INFO_PAGE: case actions.SHOW_INFO_PAGE:
return extend(appState, { return extend(appState, {
currentView: { currentView: {
@ -128,6 +136,15 @@ function reduceApp (state, action) {
isLoading: false, isLoading: false,
}) })
case actions.NEW_ACCOUNT_SCREEN:
return extend(appState, {
currentView: {
name: 'new-account',
context: appState.currentView.context,
},
transForward: true,
})
case actions.SHOW_SEND_PAGE: case actions.SHOW_SEND_PAGE:
return extend(appState, { return extend(appState, {
currentView: { currentView: {

@ -26,7 +26,7 @@ UnlockScreen.prototype.render = function () {
const state = this.props const state = this.props
const warning = state.warning const warning = state.warning
return ( return (
h('.flex-column.hey-im-here', [ h('.flex-column', [
h('.unlock-screen.flex-column.flex-center.flex-grow', [ h('.unlock-screen.flex-column.flex-center.flex-grow', [
h(Mascot, { h(Mascot, {

@ -10,6 +10,7 @@ var cssFiles = {
'index.css': fs.readFileSync(path.join(__dirname, '/app/css/index.css'), 'utf8'), 'index.css': fs.readFileSync(path.join(__dirname, '/app/css/index.css'), 'utf8'),
'transitions.css': fs.readFileSync(path.join(__dirname, '/app/css/transitions.css'), 'utf8'), 'transitions.css': fs.readFileSync(path.join(__dirname, '/app/css/transitions.css'), 'utf8'),
'react-tooltip-component.css': fs.readFileSync(path.join(__dirname, '..', 'node_modules', 'react-tooltip-component', 'dist', 'react-tooltip-component.css'), 'utf8'), 'react-tooltip-component.css': fs.readFileSync(path.join(__dirname, '..', 'node_modules', 'react-tooltip-component', 'dist', 'react-tooltip-component.css'), 'utf8'),
'react-css': fs.readFileSync(path.join(__dirname, '..', 'node_modules', 'react-select', 'dist', 'react-select.css'), 'utf8'),
} }
function bundleCss () { function bundleCss () {

Loading…
Cancel
Save