aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.circleci/config.yml5
-rw-r--r--CHANGELOG.md4
-rw-r--r--README.md1
-rw-r--r--app/_locales/en/messages.json60
-rw-r--r--app/images/connect-icon.svg11
-rw-r--r--app/scripts/controllers/preferences.js24
-rw-r--r--app/scripts/lib/util.js2
-rw-r--r--app/scripts/metamask-controller.js139
-rw-r--r--app/scripts/platforms/extension.js7
-rw-r--r--docs/trezor-emulator.md25
-rw-r--r--package-lock.json456
-rw-r--r--package.json7
-rw-r--r--ui/app/actions.js107
-rw-r--r--ui/app/components/account-menu/index.js69
-rw-r--r--ui/app/components/modals/confirm-remove-account/confirm-remove-account.component.js93
-rw-r--r--ui/app/components/modals/confirm-remove-account/confirm-remove-account.container.js20
-rw-r--r--ui/app/components/modals/confirm-remove-account/index.js2
-rw-r--r--ui/app/components/modals/index.scss52
-rw-r--r--ui/app/components/modals/modal.js14
-rw-r--r--ui/app/components/pages/create-account/connect-hardware/account-list.js129
-rw-r--r--ui/app/components/pages/create-account/connect-hardware/connect-screen.js78
-rw-r--r--ui/app/components/pages/create-account/connect-hardware/index.js215
-rw-r--r--ui/app/components/pages/create-account/index.js25
-rw-r--r--ui/app/components/pages/create-account/new-account.js2
-rw-r--r--ui/app/css/itcss/components/account-menu.scss18
-rw-r--r--ui/app/css/itcss/components/new-account.scss154
-rw-r--r--ui/app/routes.js2
27 files changed, 1400 insertions, 321 deletions
diff --git a/.circleci/config.yml b/.circleci/config.yml
index 7063f3113..9e74b4a6f 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -100,11 +100,6 @@ jobs:
- image: circleci/node:8.11.3-browsers
steps:
- checkout
- - restore_cache:
- keys:
- - dependency-cache-{{ checksum "package-lock.json" }}
- # fallback to using the latest cache if no exact match is found
- - dependency-cache-
- run:
name: Install npm 6 + deps via npm
command: |
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4ad52b795..7e1c73346 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,7 +2,9 @@
## Current Master
- - Remove rejected transactions from transaction history
+- Remove rejected transactions from transaction history
+- Add Trezor Support
+- Allow to remove accounts (Imported and Hardware Wallets)
## 4.8.0 Thur Jun 14 2018
diff --git a/README.md b/README.md
index 513a1d1cb..b950135b2 100644
--- a/README.md
+++ b/README.md
@@ -81,6 +81,7 @@ To write tests that will be run in the browser using QUnit, add your test files
- [How to add new networks to the Provider Menu](./docs/adding-new-networks.md)
- [How to manage notices that appear when the app starts up](./docs/notices.md)
- [How to port MetaMask to a new platform](./docs/porting_to_new_environment.md)
+- [How to use the TREZOR simulator](./docs/trezor-simulator.md)
- [How to generate a visualization of this repository's development](./docs/development-visualization.md)
[1]: http://www.nomnoml.com/#view/%5B%3Cactor%3Euser%5D%0A%0A%5Bmetamask-ui%7C%0A%20%20%20%5Btools%7C%0A%20%20%20%20%20react%0A%20%20%20%20%20redux%0A%20%20%20%20%20thunk%0A%20%20%20%20%20ethUtils%0A%20%20%20%20%20jazzicon%0A%20%20%20%5D%0A%20%20%20%5Bcomponents%7C%0A%20%20%20%20%20app%0A%20%20%20%20%20account-detail%0A%20%20%20%20%20accounts%0A%20%20%20%20%20locked-screen%0A%20%20%20%20%20restore-vault%0A%20%20%20%20%20identicon%0A%20%20%20%20%20config%0A%20%20%20%20%20info%0A%20%20%20%5D%0A%20%20%20%5Breducers%7C%0A%20%20%20%20%20app%0A%20%20%20%20%20metamask%0A%20%20%20%20%20identities%0A%20%20%20%5D%0A%20%20%20%5Bactions%7C%0A%20%20%20%20%20%5BaccountManager%5D%0A%20%20%20%5D%0A%20%20%20%5Bcomponents%5D%3A-%3E%5Bactions%5D%0A%20%20%20%5Bactions%5D%3A-%3E%5Breducers%5D%0A%20%20%20%5Breducers%5D%3A-%3E%5Bcomponents%5D%0A%5D%0A%0A%5Bweb%20dapp%7C%0A%20%20%5Bui%20code%5D%0A%20%20%5Bweb3%5D%0A%20%20%5Bmetamask-inpage%5D%0A%20%20%0A%20%20%5B%3Cactor%3Eui%20developer%5D%0A%20%20%5Bui%20developer%5D-%3E%5Bui%20code%5D%0A%20%20%5Bui%20code%5D%3C-%3E%5Bweb3%5D%0A%20%20%5Bweb3%5D%3C-%3E%5Bmetamask-inpage%5D%0A%5D%0A%0A%5Bmetamask-background%7C%0A%20%20%5Bprovider-engine%5D%0A%20%20%5Bhooked%20wallet%20subprovider%5D%0A%20%20%5Bid%20store%5D%0A%20%20%0A%20%20%5Bprovider-engine%5D%3C-%3E%5Bhooked%20wallet%20subprovider%5D%0A%20%20%5Bhooked%20wallet%20subprovider%5D%3C-%3E%5Bid%20store%5D%0A%20%20%5Bconfig%20manager%7C%0A%20%20%20%20%5Brpc%20configuration%5D%0A%20%20%20%20%5Bencrypted%20keys%5D%0A%20%20%20%20%5Bwallet%20nicknames%5D%0A%20%20%5D%0A%20%20%0A%20%20%5Bprovider-engine%5D%3C-%5Bconfig%20manager%5D%0A%20%20%5Bid%20store%5D%3C-%3E%5Bconfig%20manager%5D%0A%5D%0A%0A%5Buser%5D%3C-%3E%5Bmetamask-ui%5D%0A%0A%5Buser%5D%3C%3A--%3A%3E%5Bweb%20dapp%5D%0A%0A%5Bmetamask-contentscript%7C%0A%20%20%5Bplugin%20restart%20detector%5D%0A%20%20%5Brpc%20passthrough%5D%0A%5D%0A%0A%5Brpc%20%7C%0A%20%20%5Bethereum%20blockchain%20%7C%0A%20%20%20%20%5Bcontracts%5D%0A%20%20%20%20%5Baccounts%5D%0A%20%20%5D%0A%5D%0A%0A%5Bweb%20dapp%5D%3C%3A--%3A%3E%5Bmetamask-contentscript%5D%0A%5Bmetamask-contentscript%5D%3C-%3E%5Bmetamask-background%5D%0A%5Bmetamask-background%5D%3C-%3E%5Bmetamask-ui%5D%0A%5Bmetamask-background%5D%3C-%3E%5Brpc%5D%0A
diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json
index 35e28c087..564c12f86 100644
--- a/app/_locales/en/messages.json
+++ b/app/_locales/en/messages.json
@@ -11,6 +11,9 @@
"accountName": {
"message": "Account Name"
},
+ "accountSelectionRequired": {
+ "message": "You need to select an account!"
+ },
"address": {
"message": "Address"
},
@@ -80,6 +83,9 @@
"borrowDharma": {
"message": "Borrow With Dharma (Beta)"
},
+ "browserNotSupported": {
+ "message": "Bummer! Your Browser is not supported..."
+ },
"builtInCalifornia": {
"message": "MetaMask is designed and built in California."
},
@@ -110,6 +116,9 @@
"close": {
"message": "Close"
},
+ "chromeRequiredForTrezor":{
+ "message": "You need to use Metamask on Google Chrome in order to connect to your TREZOR device."
+ },
"confirm": {
"message": "Confirm"
},
@@ -125,6 +134,24 @@
"confirmTransaction": {
"message": "Confirm Transaction"
},
+ "connectHardware": {
+ "message": "Connect Hardware Wallet"
+ },
+ "connect": {
+ "message": "Connect"
+ },
+ "connecting": {
+ "message": "Connecting..."
+ },
+ "connectToTrezor": {
+ "message": "Connect to Trezor"
+ },
+ "connectToTrezorHelp": {
+ "message": "Metamask is able to access your TREZOR ethereum accounts. First make sure your device is connected and unlocked."
+ },
+ "connectToTrezorTrouble": {
+ "message": "If you are having trouble, please make sure you are using the latest version of the TREZOR firmware."
+ },
"continue": {
"message": "Continue"
},
@@ -253,6 +280,9 @@
"done": {
"message": "Done"
},
+ "downloadGoogleChrome": {
+ "message": "Download Google Chrome"
+ },
"downloadStateLogs": {
"message": "Download State Logs"
},
@@ -321,6 +351,9 @@
"followTwitter": {
"message": "Follow us on Twitter"
},
+ "forgetDevice": {
+ "message": "Forget this device"
+ },
"from": {
"message": "From"
},
@@ -374,10 +407,16 @@
"message": "Get Ether from a faucet for the $1",
"description": "Displays network name for Ether faucet"
},
+ "getYourTrezor": {
+ "message": "Don't have a TREZOR hardware wallet? Order yours now!"
+ },
"greaterThanMin": {
"message": "must be greater than or equal to $1.",
"description": "helper for inputting hex as decimal input"
},
+ "hardware": {
+ "message": "hardware"
+ },
"here": {
"message": "here",
"description": "as in -click here- for more information (goes with troubleTokenBalances)"
@@ -639,6 +678,9 @@
"popularTokens": {
"message": "Popular Tokens"
},
+ "prev": {
+ "message": "Prev"
+ },
"privacyMsg": {
"message": "Privacy Policy"
},
@@ -724,6 +766,15 @@
"revert": {
"message": "Revert"
},
+ "remove": {
+ "message": "remove"
+ },
+ "removeAccount": {
+ "message": "Remove account"
+ },
+ "removeAccountDescription": {
+ "message": "This account will be removed from your wallet. Please make sure you have the original seed phrase or private key for this imported account before continuing. You can import or create accounts again from the account drop-down. "
+ },
"rinkeby": {
"message": "Rinkeby Test Network"
},
@@ -823,6 +874,9 @@
"searchTokens": {
"message": "Search Tokens"
},
+ "selectAnAddress": {
+ "message": "Select an Address"
+ },
"sendTokensAnywhere": {
"message": "Send Tokens to anyone with an Ethereum account"
},
@@ -944,6 +998,9 @@
"transfers": {
"message": "Transfers"
},
+ "trezorHardwareWallet": {
+ "message": "TREZOR Hardware Wallet"
+ },
"troubleTokenBalances": {
"message": "We had trouble loading your token balances. You can view them ",
"description": "Followed by a link (here) to view token balances"
@@ -975,6 +1032,9 @@
"unknownNetworkId": {
"message": "Unknown network ID"
},
+ "unlock": {
+ "message": "Unlock"
+ },
"unlockMessage": {
"message": "The decentralized web awaits"
},
diff --git a/app/images/connect-icon.svg b/app/images/connect-icon.svg
new file mode 100644
index 000000000..84540999a
--- /dev/null
+++ b/app/images/connect-icon.svg
@@ -0,0 +1,11 @@
+<svg width="288" height="288" xmlns="http://www.w3.org/2000/svg">
+
+ <g>
+ <title>background</title>
+ <rect fill="none" id="canvas_background" height="402" width="582" y="-1" x="-1"/>
+ </g>
+ <g>
+ <title>Layer 1</title>
+ <path fill="#ffffff" id="svg_1" d="m122,25l15,-21c4,-5 10,-5 14,0l16,22c4,5 2,10 -5,10l-12,0l0,118c0,3 3,3 5,1l25,-25c4,-4 6,-10 6,-16l0,-24c-7,0 -12,-5 -12,-12l0,-12c0,-6 5,-12 12,-12l12,0c7,0 12,5 12,12l0,12c0,7 -5,12 -12,12l0,24c0,10 -3,18 -10,25l-31,31c-4,4 -7,6 -7,16l0,49c12,3 21,13 21,26c0,15 -12,27 -27,27s-27,-12 -27,-27c0,-13 9,-23 21,-26l0,-13c0,-10 -3,-13 -7,-17l-31,-31c-6,-6 -10,-14 -10,-24l0,-25c-7,-2 -12,-9 -12,-17c0,-10 8,-18 18,-18s18,8 18,18c0,8 -5,15 -12,17l0,25c0,7 3,12 7,16l25,25c2,2 4,2 4,-1l0,-154l-12,0c-7,0 -9,-5 -4,-11z"/>
+ </g>
+</svg> \ No newline at end of file
diff --git a/app/scripts/controllers/preferences.js b/app/scripts/controllers/preferences.js
index b314745f5..f6250dc16 100644
--- a/app/scripts/controllers/preferences.js
+++ b/app/scripts/controllers/preferences.js
@@ -86,6 +86,30 @@ class PreferencesController {
}
/**
+ * Removes an address from state
+ *
+ * @param {string} address A hex address
+ * @returns {string} the address that was removed
+ */
+ removeAddress (address) {
+ const identities = this.store.getState().identities
+ if (!identities[address]) {
+ throw new Error(`${address} can't be deleted cause it was not found`)
+ }
+ delete identities[address]
+ this.store.updateState({ identities })
+
+ // If the selected account is no longer valid,
+ // select an arbitrary other account:
+ if (address === this.getSelectedAddress()) {
+ const selected = Object.keys(identities)[0]
+ this.setSelectedAddress(selected)
+ }
+ return address
+ }
+
+
+ /**
* Adds addresses to the identities object without removing identities
*
* @param {string[]} addresses An array of hex addresses
diff --git a/app/scripts/lib/util.js b/app/scripts/lib/util.js
index 431d1e59c..51e9036cc 100644
--- a/app/scripts/lib/util.js
+++ b/app/scripts/lib/util.js
@@ -28,7 +28,7 @@ function getStack () {
*
*/
const getEnvironmentType = (url = window.location.href) => {
- if (url.match(/popup.html(?:\?.+)*$/)) {
+ if (url.match(/popup.html(?:#.*)*$/)) {
return ENVIRONMENT_TYPE_POPUP
} else if (url.match(/home.html(?:\?.+)*$/) || url.match(/home.html(?:#.*)*$/)) {
return ENVIRONMENT_TYPE_FULLSCREEN
diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js
index 450113acf..b8b7c38e4 100644
--- a/app/scripts/metamask-controller.js
+++ b/app/scripts/metamask-controller.js
@@ -47,6 +47,7 @@ const percentile = require('percentile')
const seedPhraseVerifier = require('./lib/seed-phrase-verifier')
const cleanErrorStack = require('./lib/cleanErrorStack')
const log = require('loglevel')
+const TrezorKeyring = require('eth-trezor-keyring')
module.exports = class MetamaskController extends EventEmitter {
@@ -124,7 +125,9 @@ module.exports = class MetamaskController extends EventEmitter {
})
// key mgmt
+ const additionalKeyrings = [TrezorKeyring]
this.keyringController = new KeyringController({
+ keyringTypes: additionalKeyrings,
initState: initState.KeyringController,
getNetwork: this.networkController.getNetworkState.bind(this.networkController),
encryptor: opts.encryptor || undefined,
@@ -351,8 +354,17 @@ module.exports = class MetamaskController extends EventEmitter {
verifySeedPhrase: nodeify(this.verifySeedPhrase, this),
clearSeedWordCache: this.clearSeedWordCache.bind(this),
resetAccount: nodeify(this.resetAccount, this),
+ removeAccount: nodeify(this.removeAccount, this),
importAccountWithStrategy: nodeify(this.importAccountWithStrategy, this),
+ // hardware wallets
+ connectHardware: nodeify(this.connectHardware, this),
+ forgetDevice: nodeify(this.forgetDevice, this),
+ checkHardwareStatus: nodeify(this.checkHardwareStatus, this),
+
+ // TREZOR
+ unlockTrezorAccount: nodeify(this.unlockTrezorAccount, this),
+
// vault management
submitPassword: nodeify(this.submitPassword, this),
@@ -509,6 +521,116 @@ module.exports = class MetamaskController extends EventEmitter {
}
//
+ // Hardware
+ //
+
+ /**
+ * Fetch account list from a trezor device.
+ *
+ * @returns [] accounts
+ */
+ async connectHardware (deviceName, page) {
+
+ switch (deviceName) {
+ case 'trezor':
+ const keyringController = this.keyringController
+ const oldAccounts = await keyringController.getAccounts()
+ let keyring = await keyringController.getKeyringsByType(
+ 'Trezor Hardware'
+ )[0]
+ if (!keyring) {
+ keyring = await this.keyringController.addNewKeyring('Trezor Hardware')
+ }
+ let accounts = []
+
+ switch (page) {
+ case -1:
+ accounts = await keyring.getPreviousPage()
+ break
+ case 1:
+ accounts = await keyring.getNextPage()
+ break
+ default:
+ accounts = await keyring.getFirstPage()
+ }
+
+ // Merge with existing accounts
+ // and make sure addresses are not repeated
+ const accountsToTrack = [...new Set(oldAccounts.concat(accounts.map(a => a.address.toLowerCase())))]
+ this.accountTracker.syncWithAddresses(accountsToTrack)
+ return accounts
+
+ default:
+ throw new Error('MetamaskController - Unknown device')
+ }
+ }
+
+ async checkHardwareStatus (deviceName) {
+
+ switch (deviceName) {
+ case 'trezor':
+ const keyringController = this.keyringController
+ const keyring = await keyringController.getKeyringsByType(
+ 'Trezor Hardware'
+ )[0]
+ if (!keyring) {
+ return false
+ }
+ return keyring.isUnlocked()
+ }
+ }
+
+ async forgetDevice (deviceName) {
+
+ switch (deviceName) {
+ case 'trezor':
+ const keyringController = this.keyringController
+ const keyring = await keyringController.getKeyringsByType(
+ 'Trezor Hardware'
+ )[0]
+ if (!keyring) {
+ return false
+ }
+ keyring.forgetDevice()
+ return true
+ }
+ }
+
+ /**
+ * Imports an account from a trezor device.
+ *
+ * @returns {} keyState
+ */
+ async unlockTrezorAccount (index) {
+ const keyringController = this.keyringController
+ const keyring = await keyringController.getKeyringsByType(
+ 'Trezor Hardware'
+ )[0]
+ if (!keyring) {
+ throw new Error('MetamaskController - No Trezor Hardware Keyring found')
+ }
+
+ keyring.setAccountToUnlock(index)
+ const oldAccounts = await keyringController.getAccounts()
+ const keyState = await keyringController.addNewAccount(keyring)
+ const newAccounts = await keyringController.getAccounts()
+ // Assuming the trezor account is the last one
+ const trezorAccount = newAccounts[newAccounts.length - 1]
+ this.preferencesController.setAddresses(newAccounts)
+ newAccounts.forEach(address => {
+ if (!oldAccounts.includes(address)) {
+ this.preferencesController.setSelectedAddress(address)
+ }
+ })
+
+ this.preferencesController.setAccountLabel(trezorAccount, `TREZOR #${parseInt(index, 10) + 1}`)
+ this.preferencesController.setSelectedAddress(trezorAccount)
+ const { identities } = this.preferencesController.store.getState()
+ return { ...keyState, identities }
+ }
+
+
+ //
// Account Management
//
@@ -621,6 +743,23 @@ module.exports = class MetamaskController extends EventEmitter {
}
/**
+ * Removes an account from state / storage.
+ *
+ * @param {string[]} address A hex address
+ *
+ */
+ async removeAccount (address) {
+ // Remove account from the preferences controller
+ this.preferencesController.removeAddress(address)
+ // Remove account from the account tracker controller
+ this.accountTracker.removeAccount(address)
+ // Remove account from the keyring
+ await this.keyringController.removeAccount(address)
+ return address
+ }
+
+
+ /**
* Imports an account with the specified import strategy.
* These are defined in app/scripts/account-import-strategies
* Each strategy represents a different way of serializing an Ethereum key pair.
diff --git a/app/scripts/platforms/extension.js b/app/scripts/platforms/extension.js
index f5cc255d1..f8dd767dc 100644
--- a/app/scripts/platforms/extension.js
+++ b/app/scripts/platforms/extension.js
@@ -17,8 +17,11 @@ class ExtensionPlatform {
return extension.runtime.getManifest().version
}
- openExtensionInBrowser () {
- const extensionURL = extension.runtime.getURL('home.html')
+ openExtensionInBrowser (route = null) {
+ let extensionURL = extension.runtime.getURL('home.html')
+ if (route) {
+ extensionURL += `#${route}`
+ }
this.openWindow({ url: extensionURL })
}
diff --git a/docs/trezor-emulator.md b/docs/trezor-emulator.md
new file mode 100644
index 000000000..8f66ba213
--- /dev/null
+++ b/docs/trezor-emulator.md
@@ -0,0 +1,25 @@
+# Using the TREZOR simulator
+
+You can install the TREZOR emulator and use it with Metamask.
+Here is how:
+
+## 1 - Install the TREZOR Bridge
+
+Download the corresponding bridge for your platform from [this url](https://wallet.trezor.io/data/bridge/latest/index.html)
+
+## 2 - Download and build the simulator
+
+Follow this instructions: https://github.com/trezor/trezor-core/blob/master/docs/build.md
+
+## 3 - Restart the bridge with emulator support (Mac OSx instructions)
+
+`
+ # stop any existing instance of trezord
+ killall trezord
+
+ # start the bridge for the simulator
+ /Applications/Utilities/TREZOR\ Bridge/trezord -e 21324 >> /dev/null 2>&1 &
+
+ # launch the emulator
+ ~/trezor-core/emu.sh
+`
diff --git a/package-lock.json b/package-lock.json
index 300685fc3..d9912c285 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -4224,6 +4224,7 @@
"version": "2.10.1",
"resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz",
"integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=",
+ "dev": true,
"requires": {
"hoek": "2.x.x"
},
@@ -4231,7 +4232,8 @@
"hoek": {
"version": "2.16.3",
"resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz",
- "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0="
+ "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=",
+ "dev": true
}
}
},
@@ -6154,6 +6156,8 @@
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz",
"integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=",
+ "dev": true,
+ "optional": true,
"requires": {
"boom": "2.x.x"
}
@@ -6852,9 +6856,9 @@
"dev": true
},
"yauzl": {
- "version": "2.9.2",
- "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.9.2.tgz",
- "integrity": "sha1-T7G8euH8L1cDe1SvasyP4QMcW3c=",
+ "version": "2.10.0",
+ "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz",
+ "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=",
"dev": true,
"requires": {
"buffer-crc32": "~0.2.3",
@@ -8454,9 +8458,9 @@
}
},
"eth-keyring-controller": {
- "version": "3.1.4",
- "resolved": "https://registry.npmjs.org/eth-keyring-controller/-/eth-keyring-controller-3.1.4.tgz",
- "integrity": "sha512-NNlVB/TBc8p9CblwECjPlUR+7MNQKiBa7tEFxIzZ9MjjNCEYPWDXTm0vJZzuDtVmFxYwIA53UD0QEn0QNxWNEQ==",
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/eth-keyring-controller/-/eth-keyring-controller-3.3.0.tgz",
+ "integrity": "sha512-ZQtWxg0mNSAeQ9JcOaeTKaN5vfr+MbjRhSfeaF1VIWZsQgULpHrbjT6m94qiAFdh7ZngoR7OEpK9PATBH7JNYw==",
"dev": true,
"requires": {
"bip39": "^2.4.0",
@@ -8464,7 +8468,7 @@
"browser-passworder": "^2.0.3",
"eth-hd-keyring": "^1.2.2",
"eth-sig-util": "^1.4.0",
- "eth-simple-keyring": "^1.2.2",
+ "eth-simple-keyring": "^1.3.0",
"ethereumjs-util": "^5.1.2",
"loglevel": "^1.5.0",
"obs-store": "^2.4.1",
@@ -8481,6 +8485,19 @@
"object-assign": "^4.0.0"
}
},
+ "eth-simple-keyring": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/eth-simple-keyring/-/eth-simple-keyring-1.3.0.tgz",
+ "integrity": "sha512-KNB3WXHyY/NfUiVTllsGScJ8AFTR6OGcrjKn4oSYG+HIvhkOoBqMAJ94BR3zGTvzyg5rHywHwT4L2K4+ZPFp5Q==",
+ "dev": true,
+ "requires": {
+ "eth-sig-util": "^1.4.2",
+ "ethereumjs-util": "^5.1.1",
+ "ethereumjs-wallet": "^0.6.0",
+ "events": "^1.1.1",
+ "xtend": "^4.0.1"
+ }
+ },
"ethereumjs-util": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.2.0.tgz",
@@ -8650,13 +8667,12 @@
"resolved": "https://registry.npmjs.org/eth-sig-util/-/eth-sig-util-1.4.2.tgz",
"integrity": "sha1-jZWCAsftuq6Dlwf7pvCf8ydgYhA=",
"requires": {
- "ethereumjs-abi": "git+https://github.com/ethereumjs/ethereumjs-abi.git#00ba8463a7f7a67fcad737ff9c2ebd95643427f7",
"ethereumjs-util": "^5.1.1"
},
"dependencies": {
"ethereumjs-abi": {
"version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#00ba8463a7f7a67fcad737ff9c2ebd95643427f7",
- "from": "git+https://github.com/ethereumjs/ethereumjs-abi.git",
+ "from": "git+https://github.com/ethereumjs/ethereumjs-abi.git#00ba8463a7f7a67fcad737ff9c2ebd95643427f7",
"requires": {
"bn.js": "^4.10.0",
"ethereumjs-util": "^5.0.0"
@@ -8678,36 +8694,6 @@
}
}
},
- "eth-simple-keyring": {
- "version": "1.2.2",
- "resolved": "https://registry.npmjs.org/eth-simple-keyring/-/eth-simple-keyring-1.2.2.tgz",
- "integrity": "sha512-uQVBYshHUOaXVoat1BpLA/QNMCr4hgdFBgwIB7rRmQ+m3vQQAseUsOM+biPDYzq6end+6LjcccElLpQaIZe6dg==",
- "dev": true,
- "requires": {
- "eth-sig-util": "^1.4.2",
- "ethereumjs-util": "^5.1.1",
- "ethereumjs-wallet": "^0.6.0",
- "events": "^1.1.1",
- "xtend": "^4.0.1"
- },
- "dependencies": {
- "ethereumjs-util": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.2.0.tgz",
- "integrity": "sha512-CJAKdI0wgMbQFLlLRtZKGcy/L6pzVRgelIZqRqNbuVFM3K9VEnyfbcvz0ncWMRNCe4kaHWjwRYQcYMucmwsnWA==",
- "dev": true,
- "requires": {
- "bn.js": "^4.11.0",
- "create-hash": "^1.1.2",
- "ethjs-util": "^0.1.3",
- "keccak": "^1.0.2",
- "rlp": "^2.0.0",
- "safe-buffer": "^5.1.1",
- "secp256k1": "^3.0.1"
- }
- }
- }
- },
"eth-token-tracker": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/eth-token-tracker/-/eth-token-tracker-1.1.4.tgz",
@@ -8877,6 +8863,63 @@
}
}
},
+ "eth-trezor-keyring": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/eth-trezor-keyring/-/eth-trezor-keyring-0.1.0.tgz",
+ "integrity": "sha512-7ynDXiXGQOh9CslksJSmGGK726lV9fTnIp2QQnjbZJgR4zJIoSUYQYKvT2wXcxLhVrTUl2hLjwKN9QGqDCMVwA==",
+ "requires": {
+ "eth-sig-util": "^1.4.2",
+ "ethereumjs-tx": "^1.3.4",
+ "ethereumjs-util": "^5.1.5",
+ "events": "^2.0.0",
+ "hdkey": "0.8.0"
+ },
+ "dependencies": {
+ "ethereum-common": {
+ "version": "0.0.18",
+ "resolved": "https://registry.npmjs.org/ethereum-common/-/ethereum-common-0.0.18.tgz",
+ "integrity": "sha1-L9w1dvIykDNYl26znaeDIT/5Uj8="
+ },
+ "ethereumjs-tx": {
+ "version": "1.3.6",
+ "resolved": "https://registry.npmjs.org/ethereumjs-tx/-/ethereumjs-tx-1.3.6.tgz",
+ "integrity": "sha512-wzsEs0mCSLqdDjqSDg6AWh1hyL8H3R/pyZxehkcCXq5MJEFXWz+eJ2jSv+3yEaLy6tXrNP7dmqS3Kyb3zAONkg==",
+ "requires": {
+ "ethereum-common": "^0.0.18",
+ "ethereumjs-util": "^5.0.0"
+ }
+ },
+ "ethereumjs-util": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.2.0.tgz",
+ "integrity": "sha512-CJAKdI0wgMbQFLlLRtZKGcy/L6pzVRgelIZqRqNbuVFM3K9VEnyfbcvz0ncWMRNCe4kaHWjwRYQcYMucmwsnWA==",
+ "requires": {
+ "bn.js": "^4.11.0",
+ "create-hash": "^1.1.2",
+ "ethjs-util": "^0.1.3",
+ "keccak": "^1.0.2",
+ "rlp": "^2.0.0",
+ "safe-buffer": "^5.1.1",
+ "secp256k1": "^3.0.1"
+ }
+ },
+ "events": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/events/-/events-2.1.0.tgz",
+ "integrity": "sha512-3Zmiobend8P9DjmKAty0Era4jV8oJ0yGYe2nJJAxgymF9+N8F2m0hhZiMoWtcfepExzNKZumFU3ksdQbInGWCg=="
+ },
+ "hdkey": {
+ "version": "0.8.0",
+ "resolved": "https://registry.npmjs.org/hdkey/-/hdkey-0.8.0.tgz",
+ "integrity": "sha512-oYsdlK22eobT68N5faWI3776f6tOLyqxLLYwxMx+TP0rkWzuCs0oiOm2VbLWcxdpHFP4LtiRR8udaIX8VkEaZQ==",
+ "requires": {
+ "coinstring": "^2.0.0",
+ "safe-buffer": "^5.1.1",
+ "secp256k1": "^3.0.1"
+ }
+ }
+ }
+ },
"eth-tx-summary": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/eth-tx-summary/-/eth-tx-summary-3.2.1.tgz",
@@ -11795,9 +11838,9 @@
}
},
"ganache-core": {
- "version": "2.1.3",
- "resolved": "https://registry.npmjs.org/ganache-core/-/ganache-core-2.1.3.tgz",
- "integrity": "sha512-fHDXzzcaB+jZC4q3HnQdcSj/otcVnQ6ddyqmtFtcyv29ET/44nXL5++3MmYBmAn3iLxV8PUgL+meUpm1aim9kg==",
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/ganache-core/-/ganache-core-2.1.5.tgz",
+ "integrity": "sha512-PuHUfISgrQknb7JpspxSzhpoYfqBWoOdTBHQj/81gu6YypRUHzD2Z6gZmFDxDzG30MFElEHp8JtexaGdgq9iYw==",
"dev": true,
"requires": {
"abstract-leveldown": "^3.0.0",
@@ -11809,7 +11852,7 @@
"clone": "^2.1.1",
"ethereumjs-account": "~2.0.4",
"ethereumjs-block": "~1.2.2",
- "ethereumjs-tx": "^1.3.0",
+ "ethereumjs-tx": "1.3.4",
"ethereumjs-util": "^5.2.0",
"ethereumjs-vm": "2.3.5",
"ethereumjs-wallet": "~0.6.0",
@@ -11966,6 +12009,24 @@
}
}
},
+ "ethereumjs-tx": {
+ "version": "1.3.4",
+ "resolved": "https://registry.npmjs.org/ethereumjs-tx/-/ethereumjs-tx-1.3.4.tgz",
+ "integrity": "sha512-kOgUd5jC+0tgV7t52UDECMMz9Uf+Lro+6fSpCvzWemtXfMEcwI3EOxf5mVPMRbTFkMMhuERokNNVF3jItAjidg==",
+ "dev": true,
+ "requires": {
+ "ethereum-common": "^0.0.18",
+ "ethereumjs-util": "^5.0.0"
+ },
+ "dependencies": {
+ "ethereum-common": {
+ "version": "0.0.18",
+ "resolved": "https://registry.npmjs.org/ethereum-common/-/ethereum-common-0.0.18.tgz",
+ "integrity": "sha1-L9w1dvIykDNYl26znaeDIT/5Uj8=",
+ "dev": true
+ }
+ }
+ },
"ethereumjs-util": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.2.0.tgz",
@@ -12130,9 +12191,9 @@
"dev": true
},
"ws": {
- "version": "5.2.1",
- "resolved": "https://registry.npmjs.org/ws/-/ws-5.2.1.tgz",
- "integrity": "sha512-2NkHdPKjDBj3CHdnAGNpmlliryKqF+n9MYXX7/wsVC4yqYocKreKNjydPDvT3wShAZnndlM0RytEfTALCDvz7A==",
+ "version": "5.2.2",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-5.2.2.tgz",
+ "integrity": "sha512-jaHFD6PFv6UgoIVda6qZllptQsMlDEJkTQcybzzXDYM1XO9Y8em691FGMPmM46WGyLU4z9KMgQN+qrux/nhlHA==",
"dev": true,
"requires": {
"async-limiter": "~1.0.0"
@@ -12307,12 +12368,16 @@
"generate-function": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz",
- "integrity": "sha1-aFj+fAlpt9TpCTM3ZHrHn2DfvnQ="
+ "integrity": "sha1-aFj+fAlpt9TpCTM3ZHrHn2DfvnQ=",
+ "dev": true,
+ "optional": true
},
"generate-object-property": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz",
"integrity": "sha1-nA4cQDCM6AT0eDYYuTf6iPmdUNA=",
+ "dev": true,
+ "optional": true,
"requires": {
"is-property": "^1.0.0"
}
@@ -13298,21 +13363,6 @@
"resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz",
"integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA="
},
- "assert-plus": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz",
- "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ="
- },
- "aws-sign2": {
- "version": "0.6.0",
- "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz",
- "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8="
- },
- "caseless": {
- "version": "0.11.0",
- "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz",
- "integrity": "sha1-cVuW6phBWTzDMGeSP17GDr2k99c="
- },
"chalk": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.2.tgz",
@@ -13341,79 +13391,16 @@
"is-extendable": "^1.0.1"
}
},
- "form-data": {
- "version": "2.1.4",
- "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz",
- "integrity": "sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE=",
- "requires": {
- "asynckit": "^0.4.0",
- "combined-stream": "^1.0.5",
- "mime-types": "^2.1.12"
- }
- },
"get-stdin": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz",
"integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4="
},
- "har-validator": {
- "version": "2.0.6",
- "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-2.0.6.tgz",
- "integrity": "sha1-zcvAgYgmWtEZtqWnyKtw7s+10n0=",
- "requires": {
- "chalk": "^1.1.1",
- "commander": "^2.9.0",
- "is-my-json-valid": "^2.12.4",
- "pinkie-promise": "^2.0.0"
- },
- "dependencies": {
- "ansi-styles": {
- "version": "2.2.1",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
- "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4="
- },
- "chalk": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
- "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
- "requires": {
- "ansi-styles": "^2.2.1",
- "escape-string-regexp": "^1.0.2",
- "has-ansi": "^2.0.0",
- "strip-ansi": "^3.0.0",
- "supports-color": "^2.0.0"
- }
- },
- "strip-ansi": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
- "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
- "requires": {
- "ansi-regex": "^2.0.0"
- }
- },
- "supports-color": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
- "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc="
- }
- }
- },
"has-flag": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
"integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0="
},
- "http-signature": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz",
- "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=",
- "requires": {
- "assert-plus": "^0.2.0",
- "jsprim": "^1.2.2",
- "sshpk": "^1.7.0"
- }
- },
"is-extendable": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
@@ -13428,9 +13415,9 @@
"integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA=="
},
"node-sass": {
- "version": "4.9.0",
- "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.9.0.tgz",
- "integrity": "sha512-QFHfrZl6lqRU3csypwviz2XLgGNOoWQbo2GOvtsfQqOfL4cy1BtWnhx/XUeAO9LT3ahBzSRXcEO6DdvAH9DzSg==",
+ "version": "4.9.1",
+ "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.9.1.tgz",
+ "integrity": "sha512-m6H1I6cHXsHsJ7BIWdnJsz9S9gVMyh+/H2cOTXgl2/2WqyyWlBcl4PHJcqrXo5RZVCfCUFqOtjPN0+0XbVHR5Q==",
"requires": {
"async-foreach": "^0.1.3",
"chalk": "^1.1.1",
@@ -13447,7 +13434,7 @@
"nan": "^2.10.0",
"node-gyp": "^3.3.1",
"npmlog": "^4.0.0",
- "request": "~2.79.0",
+ "request": "2.87.0",
"sass-graph": "^2.2.4",
"stdout-stream": "^1.4.0",
"true-case-path": "^1.0.2"
@@ -13496,38 +13483,6 @@
"extend-shallow": "^3.0.2"
}
},
- "qs": {
- "version": "6.3.2",
- "resolved": "https://registry.npmjs.org/qs/-/qs-6.3.2.tgz",
- "integrity": "sha1-51vV9uJoEioqDgvaYwslUMFmUCw="
- },
- "request": {
- "version": "2.79.0",
- "resolved": "https://registry.npmjs.org/request/-/request-2.79.0.tgz",
- "integrity": "sha1-Tf5b9r6LjNw3/Pk+BLZVd3InEN4=",
- "requires": {
- "aws-sign2": "~0.6.0",
- "aws4": "^1.2.1",
- "caseless": "~0.11.0",
- "combined-stream": "~1.0.5",
- "extend": "~3.0.0",
- "forever-agent": "~0.6.1",
- "form-data": "~2.1.1",
- "har-validator": "~2.0.6",
- "hawk": "~3.1.3",
- "http-signature": "~1.1.0",
- "is-typedarray": "~1.0.0",
- "isstream": "~0.1.2",
- "json-stringify-safe": "~5.0.1",
- "mime-types": "~2.1.7",
- "oauth-sign": "~0.8.1",
- "qs": "~6.3.0",
- "stringstream": "~0.0.4",
- "tough-cookie": "~2.3.0",
- "tunnel-agent": "~0.4.1",
- "uuid": "^3.0.0"
- }
- },
"strip-ansi": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
@@ -13550,16 +13505,6 @@
"requires": {
"has-flag": "^3.0.0"
}
- },
- "tunnel-agent": {
- "version": "0.4.3",
- "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz",
- "integrity": "sha1-Y3PbdpCf5XDgjXNYM2Xtgop07us="
- },
- "uuid": {
- "version": "3.2.1",
- "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz",
- "integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA=="
}
}
},
@@ -14541,6 +14486,8 @@
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz",
"integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=",
+ "dev": true,
+ "optional": true,
"requires": {
"boom": "2.x.x",
"cryptiles": "2.x.x",
@@ -14551,7 +14498,9 @@
"hoek": {
"version": "2.16.3",
"resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz",
- "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0="
+ "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=",
+ "dev": true,
+ "optional": true
}
}
},
@@ -15848,12 +15797,16 @@
"is-my-ip-valid": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-my-ip-valid/-/is-my-ip-valid-1.0.0.tgz",
- "integrity": "sha512-gmh/eWXROncUzRnIa1Ubrt5b8ep/MGSnfAUI3aRp+sqTCs1tv1Isl8d8F6JmkN3dXKc3ehZMrtiPN9eL03NuaQ=="
+ "integrity": "sha512-gmh/eWXROncUzRnIa1Ubrt5b8ep/MGSnfAUI3aRp+sqTCs1tv1Isl8d8F6JmkN3dXKc3ehZMrtiPN9eL03NuaQ==",
+ "dev": true,
+ "optional": true
},
"is-my-json-valid": {
"version": "2.17.2",
"resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.17.2.tgz",
"integrity": "sha512-IBhBslgngMQN8DDSppmgDv7RNrlFotuuDsKcrCP3+HbFaVivIBU7u9oiiErw8sH4ynx3+gOGQ3q2otkgiSi6kg==",
+ "dev": true,
+ "optional": true,
"requires": {
"generate-function": "^2.0.0",
"generate-object-property": "^1.1.0",
@@ -15977,7 +15930,9 @@
"is-property": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz",
- "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ="
+ "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=",
+ "dev": true,
+ "optional": true
},
"is-redirect": {
"version": "1.0.0",
@@ -16945,7 +16900,9 @@
"jsonpointer": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz",
- "integrity": "sha1-T9kss04OnbPInIYi7PUfm5eMbLk="
+ "integrity": "sha1-T9kss04OnbPInIYi7PUfm5eMbLk=",
+ "dev": true,
+ "optional": true
},
"jsprim": {
"version": "1.4.1",
@@ -17855,9 +17812,9 @@
}
},
"level-sublevel": {
- "version": "6.6.2",
- "resolved": "https://registry.npmjs.org/level-sublevel/-/level-sublevel-6.6.2.tgz",
- "integrity": "sha512-+hptqmFYPKFju9QG4F6scvx3ZXkhrSmmhYui+hPzRn/jiC3DJ6VNZRKsIhGMpeajVBWfRV7XiysUThrJ/7PgXQ==",
+ "version": "6.6.5",
+ "resolved": "https://registry.npmjs.org/level-sublevel/-/level-sublevel-6.6.5.tgz",
+ "integrity": "sha512-SBSR60x+dghhwGUxPKS+BvV1xNqnwsEUBKmnFepPaHJ6VkBXyPK9SImGc3K2BkwBfpxlt7GKkBNlCnrdufsejA==",
"dev": true,
"requires": {
"bytewise": "~1.1.0",
@@ -18690,9 +18647,9 @@
}
},
"log4js": {
- "version": "2.10.0",
- "resolved": "https://registry.npmjs.org/log4js/-/log4js-2.10.0.tgz",
- "integrity": "sha512-NnhN9PjFF9zhxinAjlmDYvkqqrIW+yA3LLJAoTJ3fs6d1zru86OqQHfsxiUcc1kRq3z+faGR4DeyXUfiNbVxKQ==",
+ "version": "2.11.0",
+ "resolved": "https://registry.npmjs.org/log4js/-/log4js-2.11.0.tgz",
+ "integrity": "sha512-z1XdwyGFg8/WGkOyF6DPJjivCWNLKrklGdViywdYnSKOvgtEBo2UyEMZS5sD2mZrQlU3TvO8wDWLc8mzE1ncBQ==",
"dev": true,
"requires": {
"amqplib": "^0.5.2",
@@ -18711,9 +18668,9 @@
},
"dependencies": {
"circular-json": {
- "version": "0.5.4",
- "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.5.4.tgz",
- "integrity": "sha512-vnJA8KS0BfOihugYEUkLRcnmq21FbuivbxgzDLXNs3zIk4KllV4Mx4UuTzBXht9F00C7QfD1YqMXg1zP6EXpig==",
+ "version": "0.5.5",
+ "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.5.5.tgz",
+ "integrity": "sha512-13YaR6kiz0kBNmIVM87Io8Hp7bWOo4r61vkEANy8iH9R9bc6avud/1FT0SBpqR1RpIQADOh/Q+yHZDA1iL6ysA==",
"dev": true
},
"debug": {
@@ -20641,9 +20598,9 @@
}
},
"node-sass": {
- "version": "4.9.0",
- "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.9.0.tgz",
- "integrity": "sha512-QFHfrZl6lqRU3csypwviz2XLgGNOoWQbo2GOvtsfQqOfL4cy1BtWnhx/XUeAO9LT3ahBzSRXcEO6DdvAH9DzSg==",
+ "version": "4.9.1",
+ "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.9.1.tgz",
+ "integrity": "sha512-m6H1I6cHXsHsJ7BIWdnJsz9S9gVMyh+/H2cOTXgl2/2WqyyWlBcl4PHJcqrXo5RZVCfCUFqOtjPN0+0XbVHR5Q==",
"dev": true,
"requires": {
"async-foreach": "^0.1.3",
@@ -20661,30 +20618,12 @@
"nan": "^2.10.0",
"node-gyp": "^3.3.1",
"npmlog": "^4.0.0",
- "request": "~2.79.0",
+ "request": "2.87.0",
"sass-graph": "^2.2.4",
"stdout-stream": "^1.4.0",
"true-case-path": "^1.0.2"
},
"dependencies": {
- "assert-plus": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz",
- "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ=",
- "dev": true
- },
- "aws-sign2": {
- "version": "0.6.0",
- "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz",
- "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8=",
- "dev": true
- },
- "caseless": {
- "version": "0.11.0",
- "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz",
- "integrity": "sha1-cVuW6phBWTzDMGeSP17GDr2k99c=",
- "dev": true
- },
"cross-spawn": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-3.0.1.tgz",
@@ -20695,97 +20634,17 @@
"which": "^1.2.9"
}
},
- "form-data": {
- "version": "2.1.4",
- "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz",
- "integrity": "sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE=",
- "dev": true,
- "requires": {
- "asynckit": "^0.4.0",
- "combined-stream": "^1.0.5",
- "mime-types": "^2.1.12"
- }
- },
"get-stdin": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz",
"integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=",
"dev": true
},
- "har-validator": {
- "version": "2.0.6",
- "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-2.0.6.tgz",
- "integrity": "sha1-zcvAgYgmWtEZtqWnyKtw7s+10n0=",
- "dev": true,
- "requires": {
- "chalk": "^1.1.1",
- "commander": "^2.9.0",
- "is-my-json-valid": "^2.12.4",
- "pinkie-promise": "^2.0.0"
- }
- },
- "http-signature": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz",
- "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=",
- "dev": true,
- "requires": {
- "assert-plus": "^0.2.0",
- "jsprim": "^1.2.2",
- "sshpk": "^1.7.0"
- }
- },
"nan": {
"version": "2.10.0",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.10.0.tgz",
"integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA==",
"dev": true
- },
- "qs": {
- "version": "6.3.2",
- "resolved": "https://registry.npmjs.org/qs/-/qs-6.3.2.tgz",
- "integrity": "sha1-51vV9uJoEioqDgvaYwslUMFmUCw=",
- "dev": true
- },
- "request": {
- "version": "2.79.0",
- "resolved": "https://registry.npmjs.org/request/-/request-2.79.0.tgz",
- "integrity": "sha1-Tf5b9r6LjNw3/Pk+BLZVd3InEN4=",
- "dev": true,
- "requires": {
- "aws-sign2": "~0.6.0",
- "aws4": "^1.2.1",
- "caseless": "~0.11.0",
- "combined-stream": "~1.0.5",
- "extend": "~3.0.0",
- "forever-agent": "~0.6.1",
- "form-data": "~2.1.1",
- "har-validator": "~2.0.6",
- "hawk": "~3.1.3",
- "http-signature": "~1.1.0",
- "is-typedarray": "~1.0.0",
- "isstream": "~0.1.2",
- "json-stringify-safe": "~5.0.1",
- "mime-types": "~2.1.7",
- "oauth-sign": "~0.8.1",
- "qs": "~6.3.0",
- "stringstream": "~0.0.4",
- "tough-cookie": "~2.3.0",
- "tunnel-agent": "~0.4.1",
- "uuid": "^3.0.0"
- }
- },
- "tunnel-agent": {
- "version": "0.4.3",
- "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz",
- "integrity": "sha1-Y3PbdpCf5XDgjXNYM2Xtgop07us=",
- "dev": true
- },
- "uuid": {
- "version": "3.2.1",
- "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz",
- "integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==",
- "dev": true
}
}
},
@@ -28232,6 +28091,8 @@
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz",
"integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=",
+ "dev": true,
+ "optional": true,
"requires": {
"hoek": "2.x.x"
},
@@ -28239,7 +28100,9 @@
"hoek": {
"version": "2.16.3",
"resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz",
- "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0="
+ "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=",
+ "dev": true,
+ "optional": true
}
}
},
@@ -29005,7 +28868,9 @@
"stringstream": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.6.tgz",
- "integrity": "sha512-87GEBAkegbBcweToUrdzf3eLhWNg06FJTebl4BVJz/JgWy8CvEr9dRtX5qWphiynMSQlxxi+QqN0z5T32SLlhA=="
+ "integrity": "sha512-87GEBAkegbBcweToUrdzf3eLhWNg06FJTebl4BVJz/JgWy8CvEr9dRtX5qWphiynMSQlxxi+QqN0z5T32SLlhA==",
+ "dev": true,
+ "optional": true
},
"strip-ansi": {
"version": "3.0.1",
@@ -30764,7 +30629,6 @@
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz",
"integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==",
- "dev": true,
"requires": {
"is-typedarray": "^1.0.0"
}
@@ -31788,7 +31652,6 @@
"resolved": "https://registry.npmjs.org/web3/-/web3-0.20.3.tgz",
"integrity": "sha1-yqRDc9yIFayHZ73ba6cwc5ZMqos=",
"requires": {
- "bignumber.js": "git+https://github.com/frozeman/bignumber.js-nolookahead.git#57692b3ecfc98bbdd6b3a516cb2353652ea49934",
"crypto-js": "^3.1.4",
"utf8": "^2.1.1",
"xhr2": "*",
@@ -31797,7 +31660,7 @@
"dependencies": {
"bignumber.js": {
"version": "git+https://github.com/frozeman/bignumber.js-nolookahead.git#57692b3ecfc98bbdd6b3a516cb2353652ea49934",
- "from": "git+https://github.com/frozeman/bignumber.js-nolookahead.git"
+ "from": "git+https://github.com/frozeman/bignumber.js-nolookahead.git#57692b3ecfc98bbdd6b3a516cb2353652ea49934"
}
}
},
@@ -32296,8 +32159,7 @@
"dev": true,
"requires": {
"underscore": "1.8.3",
- "web3-core-helpers": "1.0.0-beta.34",
- "websocket": "git://github.com/frozeman/WebSocket-Node.git#6c72925e3f8aaaea8dc8450f97627e85263999f2"
+ "web3-core-helpers": "1.0.0-beta.34"
},
"dependencies": {
"underscore": {
@@ -32308,8 +32170,7 @@
},
"websocket": {
"version": "git://github.com/frozeman/WebSocket-Node.git#6c72925e3f8aaaea8dc8450f97627e85263999f2",
- "from": "git://github.com/frozeman/WebSocket-Node.git#browserifyCompatible",
- "dev": true,
+ "from": "git://github.com/frozeman/WebSocket-Node.git#6c72925e3f8aaaea8dc8450f97627e85263999f2",
"requires": {
"debug": "^2.2.0",
"nan": "^2.3.3",
@@ -33665,8 +33526,7 @@
"yaeti": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz",
- "integrity": "sha1-8m9ITXJoTPQr7ft2lwqhYI+/lXc=",
- "dev": true
+ "integrity": "sha1-8m9ITXJoTPQr7ft2lwqhYI+/lXc="
},
"yallist": {
"version": "2.1.2",
diff --git a/package.json b/package.json
index a31262174..1ae4063ed 100644
--- a/package.json
+++ b/package.json
@@ -102,6 +102,7 @@
"eth-query": "^2.1.2",
"eth-sig-util": "^1.4.2",
"eth-token-tracker": "^1.1.4",
+ "eth-trezor-keyring": "^0.1.0",
"ethereumjs-abi": "^0.6.4",
"ethereumjs-tx": "^1.3.0",
"ethereumjs-util": "github:ethereumjs/ethereumjs-util#ac5d0908536b447083ea422b435da27f26615de9",
@@ -233,11 +234,11 @@
"eslint-plugin-mocha": "^5.0.0",
"eslint-plugin-react": "^7.4.0",
"eth-json-rpc-middleware": "^1.6.0",
- "eth-keyring-controller": "^3.1.4",
+ "eth-keyring-controller": "^3.3.0",
"file-loader": "^1.1.11",
"fs-promise": "^2.0.3",
"ganache-cli": "^6.1.0",
- "ganache-core": "^2.1.3",
+ "ganache-core": "^2.1.5",
"geckodriver": "^1.11.0",
"gh-pages": "^1.2.0",
"gifencoder": "^1.1.0",
@@ -273,7 +274,7 @@
"mocha-jsdom": "^1.1.0",
"mocha-sinon": "^2.0.0",
"nock": "^9.0.14",
- "node-sass": "^4.9.0",
+ "node-sass": "^4.9.1",
"nsp": "^3.2.1",
"nyc": "^13.0.0",
"open": "0.0.5",
diff --git a/ui/app/actions.js b/ui/app/actions.js
index 0aeb802f8..3da56ebc2 100644
--- a/ui/app/actions.js
+++ b/ui/app/actions.js
@@ -78,9 +78,14 @@ var actions = {
addNewKeyring,
importNewAccount,
addNewAccount,
+ connectHardware,
+ checkHardwareStatus,
+ forgetDevice,
+ unlockTrezorAccount,
NEW_ACCOUNT_SCREEN: 'NEW_ACCOUNT_SCREEN',
navigateToNewAccountScreen,
resetAccount,
+ removeAccount,
showNewVaultSeed: showNewVaultSeed,
showInfoPage: showInfoPage,
CLOSE_WELCOME_SCREEN: 'CLOSE_WELCOME_SCREEN',
@@ -533,6 +538,26 @@ function resetAccount () {
}
}
+function removeAccount (address) {
+ return dispatch => {
+ dispatch(actions.showLoadingIndication())
+
+ return new Promise((resolve, reject) => {
+ background.removeAccount(address, (err, account) => {
+ dispatch(actions.hideLoadingIndication())
+ if (err) {
+ dispatch(actions.displayWarning(err.message))
+ return reject(err)
+ }
+
+ log.info('Account removed: ' + account)
+ dispatch(actions.showAccountsPage())
+ resolve()
+ })
+ })
+ }
+}
+
function addNewKeyring (type, opts) {
return (dispatch) => {
dispatch(actions.showLoadingIndication())
@@ -599,6 +624,88 @@ function addNewAccount () {
}
}
+function checkHardwareStatus (deviceName) {
+ log.debug(`background.checkHardwareStatus`, deviceName)
+ return (dispatch, getState) => {
+ dispatch(actions.showLoadingIndication())
+ return new Promise((resolve, reject) => {
+ background.checkHardwareStatus(deviceName, (err, unlocked) => {
+ if (err) {
+ log.error(err)
+ dispatch(actions.displayWarning(err.message))
+ return reject(err)
+ }
+
+ dispatch(actions.hideLoadingIndication())
+
+ forceUpdateMetamaskState(dispatch)
+ return resolve(unlocked)
+ })
+ })
+ }
+}
+
+function forgetDevice (deviceName) {
+ log.debug(`background.forgetDevice`, deviceName)
+ return (dispatch, getState) => {
+ dispatch(actions.showLoadingIndication())
+ return new Promise((resolve, reject) => {
+ background.forgetDevice(deviceName, (err, response) => {
+ if (err) {
+ log.error(err)
+ dispatch(actions.displayWarning(err.message))
+ return reject(err)
+ }
+
+ dispatch(actions.hideLoadingIndication())
+
+ forceUpdateMetamaskState(dispatch)
+ return resolve()
+ })
+ })
+ }
+}
+
+function connectHardware (deviceName, page) {
+ log.debug(`background.connectHardware`, deviceName, page)
+ return (dispatch, getState) => {
+ dispatch(actions.showLoadingIndication())
+ return new Promise((resolve, reject) => {
+ background.connectHardware(deviceName, page, (err, accounts) => {
+ if (err) {
+ log.error(err)
+ dispatch(actions.displayWarning(err.message))
+ return reject(err)
+ }
+
+ dispatch(actions.hideLoadingIndication())
+
+ forceUpdateMetamaskState(dispatch)
+ return resolve(accounts)
+ })
+ })
+ }
+}
+
+function unlockTrezorAccount (index) {
+ log.debug(`background.unlockTrezorAccount`, index)
+ return (dispatch, getState) => {
+ dispatch(actions.showLoadingIndication())
+ return new Promise((resolve, reject) => {
+ background.unlockTrezorAccount(index, (err, accounts) => {
+ if (err) {
+ log.error(err)
+ dispatch(actions.displayWarning(err.message))
+ return reject(err)
+ }
+
+ dispatch(actions.hideLoadingIndication())
+ return resolve()
+ })
+ })
+ }
+}
+
function showInfoPage () {
return {
type: actions.SHOW_INFO_PAGE,
diff --git a/ui/app/components/account-menu/index.js b/ui/app/components/account-menu/index.js
index f34631ca8..fc48b60f3 100644
--- a/ui/app/components/account-menu/index.js
+++ b/ui/app/components/account-menu/index.js
@@ -9,11 +9,17 @@ const actions = require('../../actions')
const { Menu, Item, Divider, CloseArea } = require('../dropdowns/components/menu')
const Identicon = require('../identicon')
const { formatBalance } = require('../../util')
+const { ENVIRONMENT_TYPE_POPUP } = require('../../../../app/scripts/lib/enums')
+const { getEnvironmentType } = require('../../../../app/scripts/lib/util')
+const Tooltip = require('../tooltip')
+
+
const {
SETTINGS_ROUTE,
INFO_ROUTE,
NEW_ACCOUNT_ROUTE,
IMPORT_ACCOUNT_ROUTE,
+ CONNECT_HARDWARE_ROUTE,
DEFAULT_ROUTE,
} = require('../../routes')
@@ -63,6 +69,9 @@ function mapDispatchToProps (dispatch) {
dispatch(actions.hideSidebar())
dispatch(actions.toggleAccountMenu())
},
+ showRemoveAccountConfirmationModal: (identity) => {
+ return dispatch(actions.showModal({ name: 'CONFIRM_REMOVE_ACCOUNT', identity }))
+ },
}
}
@@ -106,6 +115,18 @@ AccountMenu.prototype.render = function () {
icon: h('img.account-menu__item-icon', { src: 'images/import-account.svg' }),
text: this.context.t('importAccount'),
}),
+ h(Item, {
+ onClick: () => {
+ toggleAccountMenu()
+ if (getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_POPUP) {
+ global.platform.openExtensionInBrowser(CONNECT_HARDWARE_ROUTE)
+ } else {
+ history.push(CONNECT_HARDWARE_ROUTE)
+ }
+ },
+ icon: h('img.account-menu__item-icon', { src: 'images/connect-icon.svg' }),
+ text: this.context.t('connectHardware'),
+ }),
h(Divider),
h(Item, {
onClick: () => {
@@ -136,7 +157,8 @@ AccountMenu.prototype.renderAccounts = function () {
} = this.props
const accountOrder = keyrings.reduce((list, keyring) => list.concat(keyring.accounts), [])
- return accountOrder.map((address) => {
+ return accountOrder.filter(address => !!identities[address]).map((address) => {
+
const identity = identities[address]
const isSelected = identity.address === selectedAddress
@@ -170,16 +192,53 @@ AccountMenu.prototype.renderAccounts = function () {
h('div.account-menu__balance', formattedBalance),
]),
- this.indicateIfLoose(keyring),
+ this.renderKeyringType(keyring),
+ this.renderRemoveAccount(keyring, identity),
],
)
})
}
-AccountMenu.prototype.indicateIfLoose = function (keyring) {
+AccountMenu.prototype.renderRemoveAccount = function (keyring, identity) {
+ // Any account that's not from the HD wallet Keyring can be removed
+ const type = keyring.type
+ const isRemovable = type !== 'HD Key Tree'
+ if (isRemovable) {
+ return h(Tooltip, {
+ title: this.context.t('removeAccount'),
+ position: 'bottom',
+ }, [
+ h('a.remove-account-icon', {
+ onClick: (e) => this.removeAccount(e, identity),
+ }, ''),
+ ])
+ }
+ return null
+}
+
+AccountMenu.prototype.removeAccount = function (e, identity) {
+ e.preventDefault()
+ e.stopPropagation()
+ const { showRemoveAccountConfirmationModal } = this.props
+ showRemoveAccountConfirmationModal(identity)
+}
+
+AccountMenu.prototype.renderKeyringType = function (keyring) {
try { // Sometimes keyrings aren't loaded yet:
const type = keyring.type
- const isLoose = type !== 'HD Key Tree'
- return isLoose ? h('.keyring-label.allcaps', this.context.t('imported')) : null
+ let label
+ switch (type) {
+ case 'Trezor Hardware':
+ label = this.context.t('hardware')
+ break
+ case 'Simple Key Pair':
+ label = this.context.t('imported')
+ break
+ default:
+ label = ''
+ }
+
+ return label !== '' ? h('.keyring-label.allcaps', label) : null
+
} catch (e) { return }
}
diff --git a/ui/app/components/modals/confirm-remove-account/confirm-remove-account.component.js b/ui/app/components/modals/confirm-remove-account/confirm-remove-account.component.js
new file mode 100644
index 000000000..5a9f0f289
--- /dev/null
+++ b/ui/app/components/modals/confirm-remove-account/confirm-remove-account.component.js
@@ -0,0 +1,93 @@
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
+import Button from '../../button'
+import { addressSummary } from '../../../util'
+import Identicon from '../../identicon'
+import genAccountLink from '../../../../lib/account-link'
+
+class ConfirmRemoveAccount extends Component {
+ static propTypes = {
+ hideModal: PropTypes.func.isRequired,
+ removeAccount: PropTypes.func.isRequired,
+ identity: PropTypes.object.isRequired,
+ network: PropTypes.string.isRequired,
+ }
+
+ static contextTypes = {
+ t: PropTypes.func,
+ }
+
+ handleRemove () {
+ this.props.removeAccount(this.props.identity.address)
+ .then(() => this.props.hideModal())
+ }
+
+ renderSelectedAccount () {
+ const { identity } = this.props
+ return (
+ <div className="modal-container__account">
+ <div className="modal-container__account__identicon">
+ <Identicon
+ address={identity.address}
+ diameter={32}
+ />
+ </div>
+ <div className="modal-container__account__name">
+ <span className="modal-container__account__label">Name</span>
+ <span className="account_value">{identity.name}</span>
+ </div>
+ <div className="modal-container__account__address">
+ <span className="modal-container__account__label">Public Address</span>
+ <span className="account_value">{ addressSummary(identity.address, 4, 4) }</span>
+ </div>
+ <div className="modal-container__account__link">
+ <a
+ className=""
+ href={genAccountLink(identity.address, this.props.network)}
+ target={'_blank'}
+ title={this.context.t('etherscanView')}
+ >
+ <img src="images/popout.svg" />
+ </a>
+ </div>
+ </div>
+ )
+ }
+
+ render () {
+ const { t } = this.context
+
+ return (
+ <div className="modal-container">
+ <div className="modal-container__content">
+ <div className="modal-container__title">
+ { `${t('removeAccount')}` }?
+ </div>
+ { this.renderSelectedAccount() }
+ <div className="modal-container__description">
+ { t('removeAccountDescription') }
+ <a className="modal-container__link" rel="noopener noreferrer" target="_blank" href="https://consensys.zendesk.com/hc/en-us/articles/360004180111-What-are-imported-accounts-New-UI-">{ t('learnMore') }</a>
+ </div>
+ </div>
+ <div className="modal-container__footer">
+ <Button
+ type="default"
+ className="modal-container__footer-button"
+ onClick={() => this.props.hideModal()}
+ >
+ { t('nevermind') }
+ </Button>
+ <Button
+ type="secondary"
+ className="modal-container__footer-button"
+ onClick={() => this.handleRemove()}
+ >
+ { t('remove') }
+ </Button>
+ </div>
+ </div>
+ )
+ }
+}
+
+export default ConfirmRemoveAccount
diff --git a/ui/app/components/modals/confirm-remove-account/confirm-remove-account.container.js b/ui/app/components/modals/confirm-remove-account/confirm-remove-account.container.js
new file mode 100644
index 000000000..4b194c995
--- /dev/null
+++ b/ui/app/components/modals/confirm-remove-account/confirm-remove-account.container.js
@@ -0,0 +1,20 @@
+import { connect } from 'react-redux'
+import ConfirmRemoveAccount from './confirm-remove-account.component'
+
+const { hideModal, removeAccount } = require('../../../actions')
+
+const mapStateToProps = state => {
+ return {
+ identity: state.appState.modal.modalState.props.identity,
+ network: state.metamask.network,
+ }
+}
+
+const mapDispatchToProps = dispatch => {
+ return {
+ hideModal: () => dispatch(hideModal()),
+ removeAccount: (address) => dispatch(removeAccount(address)),
+ }
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(ConfirmRemoveAccount)
diff --git a/ui/app/components/modals/confirm-remove-account/index.js b/ui/app/components/modals/confirm-remove-account/index.js
new file mode 100644
index 000000000..9763fbe05
--- /dev/null
+++ b/ui/app/components/modals/confirm-remove-account/index.js
@@ -0,0 +1,2 @@
+import ConfirmRemoveAccount from './confirm-remove-account.container'
+module.exports = ConfirmRemoveAccount
diff --git a/ui/app/components/modals/index.scss b/ui/app/components/modals/index.scss
index 160911c10..e198cca44 100644
--- a/ui/app/components/modals/index.scss
+++ b/ui/app/components/modals/index.scss
@@ -20,6 +20,58 @@
font-size: .875rem;
}
+ &__account {
+ border: 1px solid #b7b7b7;
+ border-radius: 4px;
+ padding: 10px;
+ display: flex;
+ margin-top: 10px;
+ margin-bottom: 20px;
+ width: 100%;
+
+ &__identicon {
+ margin-right: 10px;
+ }
+
+ &__name,
+ &__address {
+ margin-right: 10px;
+ font-size: 14px;
+ }
+
+ &__name {
+ width: 100px;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+
+ &__label {
+ font-size: 11px;
+ display: block;
+ color: #9b9b9b;
+ }
+
+ &__link {
+ margin-top: 14px;
+
+ img {
+ width: 15px;
+ height: 15px;
+ }
+ }
+
+ @media screen and (max-width: 575px) {
+ &__name {
+ width: 90px;
+ }
+ }
+ }
+
+ &__link {
+ color: #2f9ae0;
+ }
+
&__content {
overflow-y: auto;
flex: 1;
diff --git a/ui/app/components/modals/modal.js b/ui/app/components/modals/modal.js
index 973438b6b..f59825ed1 100644
--- a/ui/app/components/modals/modal.js
+++ b/ui/app/components/modals/modal.js
@@ -20,6 +20,7 @@ const HideTokenConfirmationModal = require('./hide-token-confirmation-modal')
const CustomizeGasModal = require('../customize-gas-modal')
const NotifcationModal = require('./notification-modal')
const ConfirmResetAccount = require('./confirm-reset-account')
+const ConfirmRemoveAccount = require('./confirm-remove-account')
const TransactionConfirmed = require('./transaction-confirmed')
const WelcomeBeta = require('./welcome-beta')
const Notification = require('./notification')
@@ -243,6 +244,19 @@ const MODALS = {
},
},
+ CONFIRM_REMOVE_ACCOUNT: {
+ contents: h(ConfirmRemoveAccount),
+ mobileModalStyle: {
+ ...modalContainerMobileStyle,
+ },
+ laptopModalStyle: {
+ ...modalContainerLaptopStyle,
+ },
+ contentStyle: {
+ borderRadius: '8px',
+ },
+ },
+
NEW_ACCOUNT: {
contents: [
h(NewAccountModal, {}, []),
diff --git a/ui/app/components/pages/create-account/connect-hardware/account-list.js b/ui/app/components/pages/create-account/connect-hardware/account-list.js
new file mode 100644
index 000000000..3bd6a00a7
--- /dev/null
+++ b/ui/app/components/pages/create-account/connect-hardware/account-list.js
@@ -0,0 +1,129 @@
+const { Component } = require('react')
+const PropTypes = require('prop-types')
+const h = require('react-hyperscript')
+const genAccountLink = require('../../../../../lib/account-link.js')
+
+class AccountList extends Component {
+ constructor (props, context) {
+ super(props)
+ }
+
+ renderAccounts () {
+ return h('div.hw-account-list', [
+ h('div.hw-account-list__title_wrapper', [
+ h('div.hw-account-list__title', {}, [this.context.t('selectAnAddress')]),
+ h('div.hw-account-list__device', {}, ['Trezor - ETH']),
+ ]),
+ this.props.accounts.map((a, i) => {
+
+ return h('div.hw-account-list__item', { key: a.address }, [
+ h('span.hw-account-list__item__index', a.index + 1),
+ h('div.hw-account-list__item__radio', [
+ h('input', {
+ type: 'radio',
+ name: 'selectedAccount',
+ id: `address-${i}`,
+ value: a.index,
+ onChange: (e) => this.props.onAccountChange(e.target.value),
+ checked: this.props.selectedAccount === a.index.toString(),
+ }),
+ h(
+ 'label.hw-account-list__item__label',
+ {
+ htmlFor: `address-${i}`,
+ },
+ `${a.address.slice(0, 4)}...${a.address.slice(-4)}`
+ ),
+ ]),
+ h('span.hw-account-list__item__balance', `${a.balance}`),
+ h(
+ 'a.hw-account-list__item__link',
+ {
+ href: genAccountLink(a.address, this.props.network),
+ target: '_blank',
+ title: this.context.t('etherscanView'),
+ },
+ h('img', { src: 'images/popout.svg' })
+ ),
+ ])
+ }),
+ ])
+ }
+
+ renderPagination () {
+ return h('div.hw-list-pagination', [
+ h(
+ 'button.btn-primary.hw-list-pagination__button',
+ {
+ onClick: () => this.props.getPage(-1),
+ },
+ `< ${this.context.t('prev')}`
+ ),
+
+ h(
+ 'button.btn-primary.hw-list-pagination__button',
+ {
+ onClick: () => this.props.getPage(1),
+ },
+ `${this.context.t('next')} >`
+ ),
+ ])
+ }
+
+ renderButtons () {
+ return h('div.new-account-create-form__buttons', {}, [
+ h(
+ 'button.btn-default.btn--large.new-account-create-form__button',
+ {
+ onClick: this.props.onCancel.bind(this),
+ },
+ [this.context.t('cancel')]
+ ),
+
+ h(
+ `button.btn-primary.btn--large.new-account-create-form__button ${this.props.selectedAccount === null ? '.btn-primary--disabled' : ''}`,
+ {
+ onClick: this.props.onUnlockAccount.bind(this),
+ },
+ [this.context.t('unlock')]
+ ),
+ ])
+ }
+
+ renderForgetDevice () {
+ return h('div.hw-forget-device-container', {}, [
+ h('a', {
+ onClick: this.props.onForgetDevice.bind(this),
+ }, this.context.t('forgetDevice')),
+ ])
+ }
+
+ render () {
+ return h('div', {}, [
+ this.renderAccounts(),
+ this.renderPagination(),
+ this.renderButtons(),
+ this.renderForgetDevice(),
+ ])
+ }
+
+}
+
+
+AccountList.propTypes = {
+ accounts: PropTypes.array.isRequired,
+ onAccountChange: PropTypes.func.isRequired,
+ onForgetDevice: PropTypes.func.isRequired,
+ getPage: PropTypes.func.isRequired,
+ network: PropTypes.string,
+ selectedAccount: PropTypes.string,
+ history: PropTypes.object,
+ onUnlockAccount: PropTypes.func,
+ onCancel: PropTypes.func,
+}
+
+AccountList.contextTypes = {
+ t: PropTypes.func,
+}
+
+module.exports = AccountList
diff --git a/ui/app/components/pages/create-account/connect-hardware/connect-screen.js b/ui/app/components/pages/create-account/connect-hardware/connect-screen.js
new file mode 100644
index 000000000..8d9980b10
--- /dev/null
+++ b/ui/app/components/pages/create-account/connect-hardware/connect-screen.js
@@ -0,0 +1,78 @@
+const { Component } = require('react')
+const PropTypes = require('prop-types')
+const h = require('react-hyperscript')
+
+class ConnectScreen extends Component {
+ constructor (props, context) {
+ super(props)
+ }
+
+ renderUnsupportedBrowser () {
+ return (
+ h('div', {}, [
+ h('div.hw-connect', [
+ h('h3.hw-connect__title', {}, this.context.t('browserNotSupported')),
+ h('p.hw-connect__msg', {}, this.context.t('chromeRequiredForTrezor')),
+ ]),
+ h(
+ 'button.btn-primary.btn--large',
+ {
+ onClick: () => global.platform.openWindow({
+ url: 'https://google.com/chrome',
+ }),
+ },
+ this.context.t('downloadGoogleChrome')
+ ),
+ ])
+ )
+ }
+
+ renderConnectScreen () {
+ return (
+ h('div', {}, [
+ h('div.hw-connect', [
+ h('h3.hw-connect__title', {}, this.context.t('trezorHardwareWallet')),
+ h('p.hw-connect__msg', {}, this.context.t('connectToTrezorHelp')),
+ h('p.hw-connect__msg', {}, [
+ this.context.t('connectToTrezorTrouble'),
+ h('a.hw-connect__link', {
+ href: 'https://support.metamask.io/',
+ target: '_blank',
+ }, ` ${this.context.t('learnMore')}`),
+ ]),
+ ]),
+ h(
+ 'button.btn-primary.btn--large',
+ { onClick: this.props.connectToTrezor.bind(this) },
+ this.props.btnText
+ ),
+ h('div.hw-connect__get-trezor', {}, [
+ h('a', {
+ href: 'https://shop.trezor.io/?a=metamask',
+ target: '_blank',
+ }, this.context.t('getYourTrezor')),
+ ]),
+ ])
+ )
+ }
+
+ render () {
+ if (this.props.browserSupported) {
+ return this.renderConnectScreen()
+ }
+ return this.renderUnsupportedBrowser()
+ }
+}
+
+ConnectScreen.propTypes = {
+ connectToTrezor: PropTypes.func.isRequired,
+ btnText: PropTypes.string.isRequired,
+ browserSupported: PropTypes.bool.isRequired,
+}
+
+ConnectScreen.contextTypes = {
+ t: PropTypes.func,
+}
+
+module.exports = ConnectScreen
+
diff --git a/ui/app/components/pages/create-account/connect-hardware/index.js b/ui/app/components/pages/create-account/connect-hardware/index.js
new file mode 100644
index 000000000..dc9907f31
--- /dev/null
+++ b/ui/app/components/pages/create-account/connect-hardware/index.js
@@ -0,0 +1,215 @@
+const { Component } = require('react')
+const PropTypes = require('prop-types')
+const h = require('react-hyperscript')
+const connect = require('react-redux').connect
+const actions = require('../../../../actions')
+const ConnectScreen = require('./connect-screen')
+const AccountList = require('./account-list')
+const { DEFAULT_ROUTE } = require('../../../../routes')
+const { formatBalance } = require('../../../../util')
+
+class ConnectHardwareForm extends Component {
+ constructor (props, context) {
+ super(props)
+ this.state = {
+ error: null,
+ btnText: context.t('connectToTrezor'),
+ selectedAccount: null,
+ accounts: [],
+ browserSupported: true,
+ }
+ }
+
+ componentWillReceiveProps (nextProps) {
+ const { accounts } = nextProps
+ const newAccounts = this.state.accounts.map(a => {
+ const normalizedAddress = a.address.toLowerCase()
+ const balanceValue = accounts[normalizedAddress] && accounts[normalizedAddress].balance || null
+ a.balance = balanceValue ? formatBalance(balanceValue, 6) : '...'
+ return a
+ })
+ this.setState({accounts: newAccounts})
+ }
+
+
+ async componentDidMount () {
+ const unlocked = await this.props.checkHardwareStatus('trezor')
+ if (unlocked) {
+ this.getPage(0)
+ }
+ }
+
+ connectToTrezor = () => {
+ if (this.state.accounts.length) {
+ return null
+ }
+ this.setState({ btnText: this.context.t('connecting')})
+ this.getPage(0)
+ }
+
+ onAccountChange = (account) => {
+ this.setState({selectedAccount: account.toString(), error: null})
+ }
+
+ getPage = (page) => {
+ this.props
+ .connectHardware('trezor', page)
+ .then(accounts => {
+ if (accounts.length) {
+ const newState = {}
+ // Default to the first account
+ if (this.state.selectedAccount === null) {
+ accounts.forEach((a, i) => {
+ if (a.address.toLowerCase() === this.props.address) {
+ newState.selectedAccount = a.index.toString()
+ }
+ })
+ // If the page doesn't contain the selected account, let's deselect it
+ } else if (!accounts.filter(a => a.index.toString() === this.state.selectedAccount).length) {
+ newState.selectedAccount = null
+ }
+
+
+ // Map accounts with balances
+ newState.accounts = accounts.map(account => {
+ const normalizedAddress = account.address.toLowerCase()
+ const balanceValue = this.props.accounts[normalizedAddress] && this.props.accounts[normalizedAddress].balance || null
+ account.balance = balanceValue ? formatBalance(balanceValue, 6) : '...'
+ return account
+ })
+
+ this.setState(newState)
+ }
+ })
+ .catch(e => {
+ if (e === 'Window blocked') {
+ this.setState({ browserSupported: false })
+ }
+ this.setState({ btnText: this.context.t('connectToTrezor') })
+ })
+ }
+
+ onForgetDevice = () => {
+ this.props.forgetDevice('trezor')
+ .then(_ => {
+ this.setState({
+ error: null,
+ btnText: this.context.t('connectToTrezor'),
+ selectedAccount: null,
+ accounts: [],
+ })
+ }).catch(e => {
+ this.setState({ error: e.toString() })
+ })
+ }
+
+ onUnlockAccount = () => {
+
+ if (this.state.selectedAccount === null) {
+ this.setState({ error: this.context.t('accountSelectionRequired') })
+ }
+
+ this.props.unlockTrezorAccount(this.state.selectedAccount)
+ .then(_ => {
+ this.props.history.push(DEFAULT_ROUTE)
+ }).catch(e => {
+ this.setState({ error: e.toString() })
+ })
+ }
+
+ onCancel = () => {
+ this.props.history.push(DEFAULT_ROUTE)
+ }
+
+ renderError () {
+ return this.state.error
+ ? h('span.error', { style: { marginBottom: 40 } }, this.state.error)
+ : null
+ }
+
+ renderContent () {
+ if (!this.state.accounts.length) {
+ return h(ConnectScreen, {
+ connectToTrezor: this.connectToTrezor,
+ btnText: this.state.btnText,
+ browserSupported: this.state.browserSupported,
+ })
+ }
+
+ return h(AccountList, {
+ accounts: this.state.accounts,
+ selectedAccount: this.state.selectedAccount,
+ onAccountChange: this.onAccountChange,
+ network: this.props.network,
+ getPage: this.getPage,
+ history: this.props.history,
+ onUnlockAccount: this.onUnlockAccount,
+ onForgetDevice: this.onForgetDevice,
+ onCancel: this.onCancel,
+ })
+ }
+
+ render () {
+ return h('div.new-account-create-form', [
+ this.renderError(),
+ this.renderContent(),
+ ])
+ }
+}
+
+ConnectHardwareForm.propTypes = {
+ hideModal: PropTypes.func,
+ showImportPage: PropTypes.func,
+ showConnectPage: PropTypes.func,
+ connectHardware: PropTypes.func,
+ checkHardwareStatus: PropTypes.func,
+ forgetDevice: PropTypes.func,
+ unlockTrezorAccount: PropTypes.func,
+ numberOfExistingAccounts: PropTypes.number,
+ history: PropTypes.object,
+ t: PropTypes.func,
+ network: PropTypes.string,
+ accounts: PropTypes.object,
+ address: PropTypes.string,
+}
+
+const mapStateToProps = state => {
+ const {
+ metamask: { network, selectedAddress, identities = {}, accounts = [] },
+ } = state
+ const numberOfExistingAccounts = Object.keys(identities).length
+
+ return {
+ network,
+ accounts,
+ address: selectedAddress,
+ numberOfExistingAccounts,
+ }
+}
+
+const mapDispatchToProps = dispatch => {
+ return {
+ connectHardware: (deviceName, page) => {
+ return dispatch(actions.connectHardware(deviceName, page))
+ },
+ checkHardwareStatus: (deviceName) => {
+ return dispatch(actions.checkHardwareStatus(deviceName))
+ },
+ forgetDevice: (deviceName) => {
+ return dispatch(actions.forgetDevice(deviceName))
+ },
+ unlockTrezorAccount: index => {
+ return dispatch(actions.unlockTrezorAccount(index))
+ },
+ showImportPage: () => dispatch(actions.showImportPage()),
+ showConnectPage: () => dispatch(actions.showConnectPage()),
+ }
+}
+
+ConnectHardwareForm.contextTypes = {
+ t: PropTypes.func,
+}
+
+module.exports = connect(mapStateToProps, mapDispatchToProps)(
+ ConnectHardwareForm
+)
diff --git a/ui/app/components/pages/create-account/index.js b/ui/app/components/pages/create-account/index.js
index 5681e43a9..d3de1ea01 100644
--- a/ui/app/components/pages/create-account/index.js
+++ b/ui/app/components/pages/create-account/index.js
@@ -8,7 +8,12 @@ const { getCurrentViewContext } = require('../../../selectors')
const classnames = require('classnames')
const NewAccountCreateForm = require('./new-account')
const NewAccountImportForm = require('./import-account')
-const { NEW_ACCOUNT_ROUTE, IMPORT_ACCOUNT_ROUTE } = require('../../../routes')
+const ConnectHardwareForm = require('./connect-hardware')
+const {
+ NEW_ACCOUNT_ROUTE,
+ IMPORT_ACCOUNT_ROUTE,
+ CONNECT_HARDWARE_ROUTE,
+} = require('../../../routes')
class CreateAccountPage extends Component {
renderTabs () {
@@ -36,6 +41,19 @@ class CreateAccountPage extends Component {
}, [
this.context.t('import'),
]),
+ h(
+ 'div.new-account__tabs__tab',
+ {
+ className: classnames('new-account__tabs__tab', {
+ 'new-account__tabs__selected': matchPath(location.pathname, {
+ path: CONNECT_HARDWARE_ROUTE,
+ exact: true,
+ }),
+ }),
+ onClick: () => history.push(CONNECT_HARDWARE_ROUTE),
+ },
+ this.context.t('connect')
+ ),
])
}
@@ -57,6 +75,11 @@ class CreateAccountPage extends Component {
path: IMPORT_ACCOUNT_ROUTE,
component: NewAccountImportForm,
}),
+ h(Route, {
+ exact: true,
+ path: CONNECT_HARDWARE_ROUTE,
+ component: ConnectHardwareForm,
+ }),
]),
]),
])
diff --git a/ui/app/components/pages/create-account/new-account.js b/ui/app/components/pages/create-account/new-account.js
index 9c94990e0..402b8f03b 100644
--- a/ui/app/components/pages/create-account/new-account.js
+++ b/ui/app/components/pages/create-account/new-account.js
@@ -62,6 +62,7 @@ class NewAccountCreateForm extends Component {
NewAccountCreateForm.propTypes = {
hideModal: PropTypes.func,
showImportPage: PropTypes.func,
+ showConnectPage: PropTypes.func,
createAccount: PropTypes.func,
numberOfExistingAccounts: PropTypes.number,
history: PropTypes.object,
@@ -92,6 +93,7 @@ const mapDispatchToProps = dispatch => {
})
},
showImportPage: () => dispatch(actions.showImportPage()),
+ showConnectPage: () => dispatch(actions.showConnectPage()),
}
}
diff --git a/ui/app/css/itcss/components/account-menu.scss b/ui/app/css/itcss/components/account-menu.scss
index 96fba890c..b14753e23 100644
--- a/ui/app/css/itcss/components/account-menu.scss
+++ b/ui/app/css/itcss/components/account-menu.scss
@@ -72,6 +72,7 @@
background-color: $dusty-gray;
color: $black;
font-weight: normal;
+ letter-spacing: .5px;
}
}
@@ -84,6 +85,23 @@
@media screen and (max-width: 575px) {
padding: 12px 14px;
}
+
+ .remove-account-icon {
+ width: 15px;
+ margin-left: 10px;
+ height: 15px;
+ }
+
+ &:hover {
+ .remove-account-icon::after {
+ content: '\00D7';
+ font-size: 25px;
+ color: $white;
+ cursor: pointer;
+ position: absolute;
+ margin-top: -5px;
+ }
+ }
}
&__account-info {
diff --git a/ui/app/css/itcss/components/new-account.scss b/ui/app/css/itcss/components/new-account.scss
index 293579058..a44fab3be 100644
--- a/ui/app/css/itcss/components/new-account.scss
+++ b/ui/app/css/itcss/components/new-account.scss
@@ -28,7 +28,6 @@
&__tab {
height: 54px;
- width: 75px;
padding: 15px 10px;
color: $dusty-gray;
font-family: Roboto;
@@ -38,10 +37,6 @@
cursor: pointer;
}
- &__tab:first-of-type {
- margin-right: 20px;
- }
-
&__tab:hover {
color: $black;
border-bottom: none;
@@ -158,6 +153,151 @@
}
}
+.hw-connect {
+ &__title {
+ padding-top: 10px;
+ font-weight: 500;
+ }
+
+ &__msg {
+ font-size: 14px;
+ color: #9b9b9b;
+ margin-top: 15px;
+ margin-bottom: 15px;
+ }
+
+ &__link {
+ color: #2f9ae0;
+ }
+
+ &__get-trezor {
+ display: flex;
+ flex-flow: column;
+ align-items: center;
+ padding: 30px 30px 0;
+ width: 305px;
+
+ a {
+ font-size: 14px;
+ text-align: center;
+ color: #2f9ae0;
+ cursor: pointer;
+ }
+ }
+}
+
+.hw-account-list {
+ display: flex;
+ flex: 1;
+ flex-flow: column;
+ width: 100%;
+
+ &__title_wrapper {
+ display: flex;
+ flex-direction: row;
+ flex: 1;
+ }
+
+ &__title {
+ margin-bottom: 23px;
+ align-self: flex-start;
+ color: $scorpion;
+ font-family: Roboto;
+ font-size: 16px;
+ line-height: 21px;
+ font-weight: bold;
+ display: flex;
+ flex: 1;
+ }
+
+ &__device {
+ margin-bottom: 23px;
+ align-self: flex-end;
+ color: $scorpion;
+ font-family: Roboto;
+ font-size: 16px;
+ line-height: 21px;
+ font-weight: normal;
+ display: flex;
+ }
+
+ &__item {
+ font-size: 15px;
+ flex-direction: row;
+ display: flex;
+ padding: 10px;
+ }
+
+ &__item:nth-of-type(even) {
+ background-color: #fbfbfb;
+ }
+
+ &__item:nth-of-type(odd) {
+ background: rgba(0, 0, 0, 0.03);
+ }
+
+ &__item:hover {
+ background-color: rgba(0, 0, 0, 0.06);
+ }
+
+ &__item__index {
+ display: flex;
+ width: 28px;
+ }
+
+ &__item__radio {
+ display: flex;
+ flex: 1;
+ }
+
+ &__item__label {
+ margin-left: 10px;
+ display: flex;
+ flex: 1;
+ }
+
+ &__item__balance {
+ display: flex;
+ flex: 1;
+ justify-content: center;
+ }
+
+ &__item__link {
+ display: flex;
+ }
+
+ &__item__link img {
+ width: 15px;
+ height: 15px;
+ }
+}
+
+.hw-list-pagination {
+ display: flex;
+ align-self: flex-end;
+ margin-top: 10px;
+
+ &__button {
+ height: 25px;
+ flex: initial;
+ min-width: 90px;
+ font-size: 12px;
+ }
+}
+
+.hw-forget-device-container {
+ display: flex;
+ flex-flow: column;
+ align-items: center;
+ padding: 30px 30px 0;
+
+ a {
+ color: #2f9ae0;
+ font-size: 14px;
+ cursor: pointer;
+ }
+}
+
.new-account-create-form {
display: flex;
flex-flow: column;
@@ -197,4 +337,8 @@
width: 150px;
min-width: initial;
}
+
+ &__button.btn-primary--disabled {
+ cursor: 'not-allowed';
+ }
}
diff --git a/ui/app/routes.js b/ui/app/routes.js
index 7ac606b1a..ec0cc9027 100644
--- a/ui/app/routes.js
+++ b/ui/app/routes.js
@@ -9,6 +9,7 @@ const ADD_TOKEN_ROUTE = '/add-token'
const CONFIRM_ADD_TOKEN_ROUTE = '/confirm-add-token'
const NEW_ACCOUNT_ROUTE = '/new-account'
const IMPORT_ACCOUNT_ROUTE = '/new-account/import'
+const CONNECT_HARDWARE_ROUTE = '/new-account/connect'
const SEND_ROUTE = '/send'
const NOTICE_ROUTE = '/notice'
const WELCOME_ROUTE = '/welcome'
@@ -41,6 +42,7 @@ module.exports = {
CONFIRM_ADD_TOKEN_ROUTE,
NEW_ACCOUNT_ROUTE,
IMPORT_ACCOUNT_ROUTE,
+ CONNECT_HARDWARE_ROUTE,
SEND_ROUTE,
NOTICE_ROUTE,
WELCOME_ROUTE,