aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorkumavis <kumavis@users.noreply.github.com>2017-01-19 04:15:58 +0800
committerGitHub <noreply@github.com>2017-01-19 04:15:58 +0800
commit28212d167cbd201f78e0253cf9c6fb676d71cb7a (patch)
treed278415f35a74e26459ca47f10798368e8b91403
parent4a0f330a066ed2a557b4622163221b410b6b6e40 (diff)
parent3273f507f7a9cf33cfdbb4fffa243d75fde98b10 (diff)
downloadtangerine-wallet-browser-28212d167cbd201f78e0253cf9c6fb676d71cb7a.tar
tangerine-wallet-browser-28212d167cbd201f78e0253cf9c6fb676d71cb7a.tar.gz
tangerine-wallet-browser-28212d167cbd201f78e0253cf9c6fb676d71cb7a.tar.bz2
tangerine-wallet-browser-28212d167cbd201f78e0253cf9c6fb676d71cb7a.tar.lz
tangerine-wallet-browser-28212d167cbd201f78e0253cf9c6fb676d71cb7a.tar.xz
tangerine-wallet-browser-28212d167cbd201f78e0253cf9c6fb676d71cb7a.tar.zst
tangerine-wallet-browser-28212d167cbd201f78e0253cf9c6fb676d71cb7a.zip
Merge pull request #1022 from MetaMask/i715-AddImportMenu
Add ability to import private keys
-rw-r--r--.eslintrc1
-rw-r--r--CHANGELOG.md1
-rw-r--r--app/scripts/keyring-controller.js5
-rw-r--r--app/scripts/keyrings/simple.js18
-rw-r--r--app/scripts/metamask-controller.js7
-rw-r--r--development/states/account-list-with-imported.json84
-rw-r--r--development/states/compilation-bug.json124
-rw-r--r--development/states/import-private-key-warning.json92
-rw-r--r--development/states/import-private-key.json64
-rw-r--r--development/states/new-account.json66
-rw-r--r--package.json1
-rw-r--r--test/integration/lib/first-time.js3
-rw-r--r--ui/app/accounts/import/index.js91
-rw-r--r--ui/app/accounts/import/json.js27
-rw-r--r--ui/app/accounts/import/private-key.js69
-rw-r--r--ui/app/accounts/import/seed.js30
-rw-r--r--ui/app/accounts/index.js10
-rw-r--r--ui/app/actions.js26
-rw-r--r--ui/app/app.js11
-rw-r--r--ui/app/components/buy-button-subview.js82
-rw-r--r--ui/app/components/tab-bar.js35
-rw-r--r--ui/app/css/lib.css8
-rw-r--r--ui/app/info.js2
-rw-r--r--ui/app/reducers/app.js17
-rw-r--r--ui/app/unlock.js2
-rw-r--r--ui/css.js1
26 files changed, 828 insertions, 49 deletions
diff --git a/.eslintrc b/.eslintrc
index ffa4cd574..84f65bea4 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -1,5 +1,6 @@
{
"parserOptions": {
+ "sourceType": "module",
"ecmaVersion": 6,
"ecmaFeatures": {
"experimentalObjectRestSpread": true,
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 968fedffb..5ea70e966 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,7 @@
## 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).
## 3.0.1 2017-1-17
diff --git a/app/scripts/keyring-controller.js b/app/scripts/keyring-controller.js
index 4be00a5a5..e609403cc 100644
--- a/app/scripts/keyring-controller.js
+++ b/app/scripts/keyring-controller.js
@@ -234,7 +234,10 @@ module.exports = class KeyringController extends EventEmitter {
addNewKeyring (type, opts) {
const Keyring = this.getKeyringClassForType(type)
const keyring = new Keyring(opts)
- return keyring.getAccounts()
+ return keyring.deserialize(opts)
+ .then(() => {
+ return keyring.getAccounts()
+ })
.then((accounts) => {
this.keyrings.push(keyring)
return this.setupAccounts(accounts)
diff --git a/app/scripts/keyrings/simple.js b/app/scripts/keyrings/simple.js
index d604430b8..46687fcaf 100644
--- a/app/scripts/keyrings/simple.js
+++ b/app/scripts/keyrings/simple.js
@@ -20,13 +20,19 @@ class SimpleKeyring extends EventEmitter {
}
deserialize (privateKeys = []) {
- this.wallets = privateKeys.map((privateKey) => {
- const stripped = ethUtil.stripHexPrefix(privateKey)
- const buffer = new Buffer(stripped, 'hex')
- const wallet = Wallet.fromPrivateKey(buffer)
- return wallet
+ return new Promise((resolve, reject) => {
+ try {
+ this.wallets = privateKeys.map((privateKey) => {
+ const stripped = ethUtil.stripHexPrefix(privateKey)
+ const buffer = new Buffer(stripped, 'hex')
+ const wallet = Wallet.fromPrivateKey(buffer)
+ return wallet
+ })
+ } catch (e) {
+ reject(e)
+ }
+ resolve()
})
- return Promise.resolve()
}
addAccounts (n = 1) {
diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js
index b94b98eac..629216e42 100644
--- a/app/scripts/metamask-controller.js
+++ b/app/scripts/metamask-controller.js
@@ -115,7 +115,12 @@ module.exports = class MetamaskController extends EventEmitter {
.then((newState) => { cb(null, newState) })
.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),
setSelectedAccount: nodeify(keyringController.setSelectedAccount).bind(keyringController),
saveAccountLabel: nodeify(keyringController.saveAccountLabel).bind(keyringController),
diff --git a/development/states/account-list-with-imported.json b/development/states/account-list-with-imported.json
new file mode 100644
index 000000000..e32327743
--- /dev/null
+++ b/development/states/account-list-with-imported.json
@@ -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": {}
+} \ No newline at end of file
diff --git a/development/states/compilation-bug.json b/development/states/compilation-bug.json
new file mode 100644
index 000000000..a9dfc4d4e
--- /dev/null
+++ b/development/states/compilation-bug.json
@@ -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": {}
+} \ No newline at end of file
diff --git a/development/states/import-private-key-warning.json b/development/states/import-private-key-warning.json
new file mode 100644
index 000000000..f4ac99b05
--- /dev/null
+++ b/development/states/import-private-key-warning.json
@@ -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": {}
+} \ No newline at end of file
diff --git a/development/states/import-private-key.json b/development/states/import-private-key.json
new file mode 100644
index 000000000..c70f02a36
--- /dev/null
+++ b/development/states/import-private-key.json
@@ -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": {}
+} \ No newline at end of file
diff --git a/development/states/new-account.json b/development/states/new-account.json
new file mode 100644
index 000000000..8c9be3654
--- /dev/null
+++ b/development/states/new-account.json
@@ -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": {}
+} \ No newline at end of file
diff --git a/package.json b/package.json
index 52708fdab..2c0c30523 100644
--- a/package.json
+++ b/package.json
@@ -83,6 +83,7 @@
"react-hyperscript": "^2.2.2",
"react-markdown": "^2.3.0",
"react-redux": "^4.4.5",
+ "react-select": "^1.0.0-rc.2",
"react-tooltip-component": "^0.3.0",
"readable-stream": "^2.1.2",
"redux": "^3.0.5",
diff --git a/test/integration/lib/first-time.js b/test/integration/lib/first-time.js
index 1811ccbd4..777fcbb7e 100644
--- a/test/integration/lib/first-time.js
+++ b/test/integration/lib/first-time.js
@@ -66,7 +66,8 @@ QUnit.test('agree to terms', function (assert) {
}).then(function() {
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')
lock.click()
diff --git a/ui/app/accounts/import/index.js b/ui/app/accounts/import/index.js
new file mode 100644
index 000000000..18a6b985c
--- /dev/null
+++ b/ui/app/accounts/import/index.js
@@ -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)
+ }
+}
diff --git a/ui/app/accounts/import/json.js b/ui/app/accounts/import/json.js
new file mode 100644
index 000000000..22cf95cfd
--- /dev/null
+++ b/ui/app/accounts/import/json.js
@@ -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!`,
+ ])
+ )
+}
+
diff --git a/ui/app/accounts/import/private-key.js b/ui/app/accounts/import/private-key.js
new file mode 100644
index 000000000..6b988a76b
--- /dev/null
+++ b/ui/app/accounts/import/private-key.js
@@ -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 ]))
+}
+
diff --git a/ui/app/accounts/import/seed.js b/ui/app/accounts/import/seed.js
new file mode 100644
index 000000000..b4a7c0afa
--- /dev/null
+++ b/ui/app/accounts/import/seed.js
@@ -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'),
+ ])
+ )
+}
+
diff --git a/ui/app/accounts/index.js b/ui/app/accounts/index.js
index edb15eafe..e6f376735 100644
--- a/ui/app/accounts/index.js
+++ b/ui/app/accounts/index.js
@@ -73,7 +73,8 @@ AccountsScreen.prototype.render = function () {
const simpleAddress = identity.address.substring(2).toLowerCase()
const keyring = keyrings.find((kr) => {
- return kr.accounts.includes(simpleAddress)
+ return kr.accounts.includes(simpleAddress) ||
+ kr.accounts.includes(identity.address)
})
return h(AccountListItem, {
@@ -154,6 +155,13 @@ AccountsScreen.prototype.addNewAccount = function () {
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 () {
this.props.dispatch(actions.goHome())
}
diff --git a/ui/app/actions.js b/ui/app/actions.js
index 5a3968f82..7934a329a 100644
--- a/ui/app/actions.js
+++ b/ui/app/actions.js
@@ -32,16 +32,20 @@ var actions = {
SHOW_INIT_MENU: 'SHOW_INIT_MENU',
SHOW_NEW_VAULT_SEED: 'SHOW_NEW_VAULT_SEED',
SHOW_INFO_PAGE: 'SHOW_INFO_PAGE',
+ SHOW_IMPORT_PAGE: 'SHOW_IMPORT_PAGE',
unlockMetamask: unlockMetamask,
unlockFailed: unlockFailed,
showCreateVault: showCreateVault,
showRestoreVault: showRestoreVault,
showInitializeMenu: showInitializeMenu,
+ showImportPage,
createNewVaultAndKeychain: createNewVaultAndKeychain,
createNewVaultAndRestore: createNewVaultAndRestore,
createNewVaultInProgress: createNewVaultInProgress,
addNewKeyring,
addNewAccount,
+ NEW_ACCOUNT_SCREEN: 'NEW_ACCOUNT_SCREEN',
+ navigateToNewAccountScreen,
showNewVaultSeed: showNewVaultSeed,
showInfoPage: showInfoPage,
// seed recovery actions
@@ -249,7 +253,21 @@ function requestRevealSeed (password) {
}
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) {
@@ -376,6 +394,12 @@ function showInitializeMenu () {
}
}
+function showImportPage () {
+ return {
+ type: actions.SHOW_IMPORT_PAGE,
+ }
+}
+
function agreeToDisclaimer () {
return (dispatch) => {
dispatch(this.showLoadingIndication())
diff --git a/ui/app/app.js b/ui/app/app.js
index 9efe95874..0e04c334c 100644
--- a/ui/app/app.js
+++ b/ui/app/app.js
@@ -20,6 +20,7 @@ const NoticeScreen = require('./components/notice')
const generateLostAccountsNotice = require('../lib/lost-accounts-notice')
// other views
const ConfigScreen = require('./config')
+const Import = require('./accounts/import')
const InfoScreen = require('./info')
const LoadingIndicator = require('./components/loading')
const SandwichExpando = require('sandwich-expando')
@@ -305,6 +306,13 @@ App.prototype.renderDropdown = function () {
}),
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, {
label: 'Lock',
closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }),
action: () => this.props.dispatch(actions.lockMetamask()),
@@ -411,6 +419,9 @@ App.prototype.renderPrimary = function () {
case 'config':
return h(ConfigScreen, {key: 'config'})
+ case 'import-menu':
+ return h(Import, {key: 'import-menu'})
+
case 'reveal-seed-conf':
return h(RevealSeedConfirmation, {key: 'reveal-seed-conf'})
diff --git a/ui/app/components/buy-button-subview.js b/ui/app/components/buy-button-subview.js
index 35eda647e..afda5bf59 100644
--- a/ui/app/components/buy-button-subview.js
+++ b/ui/app/components/buy-button-subview.js
@@ -7,6 +7,7 @@ const CoinbaseForm = require('./coinbase-form')
const ShapeshiftForm = require('./shapeshift-form')
const extension = require('../../../app/scripts/lib/extension')
const Loading = require('./loading')
+const TabBar = require('./tab-bar')
module.exports = connect(mapStateToProps)(BuyButtonSubview)
@@ -29,7 +30,6 @@ function BuyButtonSubview () {
BuyButtonSubview.prototype.render = function () {
const props = this.props
- const currentForm = props.buyView.formView
const isLoading = props.isSubLoading
return (
@@ -53,43 +53,53 @@ BuyButtonSubview.prototype.render = function () {
h(Loading, { isLoading }),
- h('h3.flex-row.text-transform-uppercase', {
- style: {
- background: '#EBEBEB',
- color: '#AEAEAE',
- paddingTop: '4px',
- justifyContent: 'space-around',
+ h(TabBar, {
+ tabs: [
+ {
+ content: [
+ 'Coinbase',
+ h('a', {
+ onClick: (event) => this.navigateTo('https://github.com/MetaMask/faq/blob/master/COINBASE.md'),
+ }, [
+ h('i.fa.fa-question-circle', {
+ style: {
+ margin: '0px 5px',
+ },
+ }),
+ ]),
+ ],
+ key: 'coinbase',
+ },
+ {
+ content: [
+ 'Shapeshift',
+ h('a', {
+ href: 'https://github.com/MetaMask/faq/blob/master/COINBASE.md',
+ onClick: (event) => this.navigateTo('https://info.shapeshift.io/about'),
+ }, [
+ h('i.fa.fa-question-circle', {
+ style: {
+ margin: '0px 5px',
+ },
+ }),
+ ]),
+ ],
+ 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
+ }
},
- }, [
- h(currentForm.coinbase ? '.activeForm' : '.inactiveForm.pointer', {
- onClick: () => props.dispatch(actions.coinBaseSubview()),
- }, 'Coinbase'),
- h('a', {
- onClick: (event) => this.navigateTo('https://github.com/MetaMask/faq/blob/master/COINBASE.md'),
- }, [
- h('i.fa.fa-question-circle', {
- style: {
- position: 'relative',
- right: '33px',
- },
- }),
- ]),
- h(currentForm.shapeshift ? '.activeForm' : '.inactiveForm.pointer', {
- onClick: () => props.dispatch(actions.shapeShiftSubview(props.provider.type)),
- }, 'Shapeshift'),
+ }),
- h('a', {
- href: 'https://github.com/MetaMask/faq/blob/master/COINBASE.md',
- onClick: (event) => this.navigateTo('https://info.shapeshift.io/about'),
- }, [
- h('i.fa.fa-question-circle', {
- style: {
- position: 'relative',
- right: '28px',
- },
- }),
- ]),
- ]),
this.formVersionSubview(),
])
)
diff --git a/ui/app/components/tab-bar.js b/ui/app/components/tab-bar.js
new file mode 100644
index 000000000..65078e0a4
--- /dev/null
+++ b/ui/app/components/tab-bar.js
@@ -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)
+ }))
+ )
+}
diff --git a/ui/app/css/lib.css b/ui/app/css/lib.css
index abbf8667e..a8df1d115 100644
--- a/ui/app/css/lib.css
+++ b/ui/app/css/lib.css
@@ -23,6 +23,14 @@
flex-direction: column;
}
+.space-between {
+ justify-content: space-between;
+}
+
+.space-around {
+ justify-content: space-around;
+}
+
.flex-column-bottom {
display: flex;
flex-direction: column-reverse;
diff --git a/ui/app/info.js b/ui/app/info.js
index cc753b2ea..e79580be4 100644
--- a/ui/app/info.js
+++ b/ui/app/info.js
@@ -110,7 +110,7 @@ InfoScreen.prototype.render = function () {
onClick (event) { this.navigateTo(event.target.href) },
}, [
h('img.icon-size', {
- src: manifest.icons[128],
+ src: manifest.icons['128'],
style: {
filter: 'grayscale(100%)', /* IE6-9 */
WebkitFilter: 'grayscale(100%)', /* Microsoft Edge and Firefox 35+ */
diff --git a/ui/app/reducers/app.js b/ui/app/reducers/app.js
index dc7344b3e..ae91272cc 100644
--- a/ui/app/reducers/app.js
+++ b/ui/app/reducers/app.js
@@ -99,6 +99,14 @@ function reduceApp (state, action) {
transForward: action.value,
})
+ case actions.SHOW_IMPORT_PAGE:
+ return extend(appState, {
+ currentView: {
+ name: 'import-menu',
+ },
+ transForward: true,
+ })
+
case actions.SHOW_INFO_PAGE:
return extend(appState, {
currentView: {
@@ -128,6 +136,15 @@ function reduceApp (state, action) {
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:
return extend(appState, {
currentView: {
diff --git a/ui/app/unlock.js b/ui/app/unlock.js
index 19f5eaec2..1aee3c5d0 100644
--- a/ui/app/unlock.js
+++ b/ui/app/unlock.js
@@ -26,7 +26,7 @@ UnlockScreen.prototype.render = function () {
const state = this.props
const warning = state.warning
return (
- h('.flex-column.hey-im-here', [
+ h('.flex-column', [
h('.unlock-screen.flex-column.flex-center.flex-grow', [
h(Mascot, {
diff --git a/ui/css.js b/ui/css.js
index 01f317acd..043363cd7 100644
--- a/ui/css.js
+++ b/ui/css.js
@@ -10,6 +10,7 @@ var cssFiles = {
'index.css': fs.readFileSync(path.join(__dirname, '/app/css/index.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-css': fs.readFileSync(path.join(__dirname, '..', 'node_modules', 'react-select', 'dist', 'react-select.css'), 'utf8'),
}
function bundleCss () {