aboutsummaryrefslogtreecommitdiffstats
path: root/ui
diff options
context:
space:
mode:
authorDan Finlay <542863+danfinlay@users.noreply.github.com>2018-08-18 02:26:38 +0800
committerGitHub <noreply@github.com>2018-08-18 02:26:38 +0800
commitc7f5436143a87369ab29c019a1c988c20a98d810 (patch)
treea92c329bc53f99f88fdd0a72e522dc94f4dfb83e /ui
parent755369e4e48e71b04b4dbb9cb702353c09b66e28 (diff)
parentbd904c86f6f27eb50e0633f46af6831869b99893 (diff)
downloadtangerine-wallet-browser-c7f5436143a87369ab29c019a1c988c20a98d810.tar
tangerine-wallet-browser-c7f5436143a87369ab29c019a1c988c20a98d810.tar.gz
tangerine-wallet-browser-c7f5436143a87369ab29c019a1c988c20a98d810.tar.bz2
tangerine-wallet-browser-c7f5436143a87369ab29c019a1c988c20a98d810.tar.lz
tangerine-wallet-browser-c7f5436143a87369ab29c019a1c988c20a98d810.tar.xz
tangerine-wallet-browser-c7f5436143a87369ab29c019a1c988c20a98d810.tar.zst
tangerine-wallet-browser-c7f5436143a87369ab29c019a1c988c20a98d810.zip
Merge pull request #5050 from MetaMask/ledger-support
Add Ledger hardware wallet support
Diffstat (limited to 'ui')
-rw-r--r--ui/app/actions.js29
-rw-r--r--ui/app/components/account-menu/index.js1
-rw-r--r--ui/app/components/modals/account-details-modal.js16
-rw-r--r--ui/app/components/pages/create-account/connect-hardware/account-list.js73
-rw-r--r--ui/app/components/pages/create-account/connect-hardware/connect-screen.js87
-rw-r--r--ui/app/components/pages/create-account/connect-hardware/index.js91
-rw-r--r--ui/app/components/wallet-view.js16
-rw-r--r--ui/app/css/itcss/components/new-account.scss105
-rw-r--r--ui/app/reducers/app.js13
-rw-r--r--ui/app/selectors/confirm-transaction.js2
10 files changed, 356 insertions, 77 deletions
diff --git a/ui/app/actions.js b/ui/app/actions.js
index bd5d25327..6bcc64e17 100644
--- a/ui/app/actions.js
+++ b/ui/app/actions.js
@@ -91,7 +91,7 @@ var actions = {
connectHardware,
checkHardwareStatus,
forgetDevice,
- unlockTrezorAccount,
+ unlockHardwareWalletAccount,
NEW_ACCOUNT_SCREEN: 'NEW_ACCOUNT_SCREEN',
navigateToNewAccountScreen,
resetAccount,
@@ -235,6 +235,8 @@ var actions = {
UPDATE_TOKENS: 'UPDATE_TOKENS',
setRpcTarget: setRpcTarget,
setProviderType: setProviderType,
+ SET_HARDWARE_WALLET_DEFAULT_HD_PATH: 'SET_HARDWARE_WALLET_DEFAULT_HD_PATH',
+ setHardwareWalletDefaultHdPath,
updateProviderType,
// loading overlay
SHOW_LOADING: 'SHOW_LOADING_INDICATION',
@@ -639,12 +641,12 @@ function addNewAccount () {
}
}
-function checkHardwareStatus (deviceName) {
- log.debug(`background.checkHardwareStatus`, deviceName)
+function checkHardwareStatus (deviceName, hdPath) {
+ log.debug(`background.checkHardwareStatus`, deviceName, hdPath)
return (dispatch, getState) => {
dispatch(actions.showLoadingIndication())
return new Promise((resolve, reject) => {
- background.checkHardwareStatus(deviceName, (err, unlocked) => {
+ background.checkHardwareStatus(deviceName, hdPath, (err, unlocked) => {
if (err) {
log.error(err)
dispatch(actions.displayWarning(err.message))
@@ -681,12 +683,12 @@ function forgetDevice (deviceName) {
}
}
-function connectHardware (deviceName, page) {
- log.debug(`background.connectHardware`, deviceName, page)
+function connectHardware (deviceName, page, hdPath) {
+ log.debug(`background.connectHardware`, deviceName, page, hdPath)
return (dispatch, getState) => {
dispatch(actions.showLoadingIndication())
return new Promise((resolve, reject) => {
- background.connectHardware(deviceName, page, (err, accounts) => {
+ background.connectHardware(deviceName, page, hdPath, (err, accounts) => {
if (err) {
log.error(err)
dispatch(actions.displayWarning(err.message))
@@ -702,12 +704,12 @@ function connectHardware (deviceName, page) {
}
}
-function unlockTrezorAccount (index) {
- log.debug(`background.unlockTrezorAccount`, index)
+function unlockHardwareWalletAccount (index, deviceName, hdPath) {
+ log.debug(`background.unlockHardwareWalletAccount`, index, deviceName, hdPath)
return (dispatch, getState) => {
dispatch(actions.showLoadingIndication())
return new Promise((resolve, reject) => {
- background.unlockTrezorAccount(index, (err, accounts) => {
+ background.unlockHardwareWalletAccount(index, deviceName, hdPath, (err, accounts) => {
if (err) {
log.error(err)
dispatch(actions.displayWarning(err.message))
@@ -1854,6 +1856,13 @@ function showLoadingIndication (message) {
}
}
+function setHardwareWalletDefaultHdPath ({ device, path }) {
+ return {
+ type: actions.SET_HARDWARE_WALLET_DEFAULT_HD_PATH,
+ value: {device, path},
+ }
+}
+
function hideLoadingIndication () {
return {
type: actions.HIDE_LOADING,
diff --git a/ui/app/components/account-menu/index.js b/ui/app/components/account-menu/index.js
index 9c063d31e..bcada41e3 100644
--- a/ui/app/components/account-menu/index.js
+++ b/ui/app/components/account-menu/index.js
@@ -229,6 +229,7 @@ AccountMenu.prototype.renderKeyringType = function (keyring) {
let label
switch (type) {
case 'Trezor Hardware':
+ case 'Ledger Hardware':
label = this.context.t('hardware')
break
case 'Simple Key Pair':
diff --git a/ui/app/components/modals/account-details-modal.js b/ui/app/components/modals/account-details-modal.js
index 5607cf051..cc90cf578 100644
--- a/ui/app/components/modals/account-details-modal.js
+++ b/ui/app/components/modals/account-details-modal.js
@@ -14,6 +14,7 @@ function mapStateToProps (state) {
return {
network: state.metamask.network,
selectedIdentity: getSelectedIdentity(state),
+ keyrings: state.metamask.keyrings,
}
}
@@ -50,9 +51,20 @@ AccountDetailsModal.prototype.render = function () {
network,
showExportPrivateKeyModal,
setAccountLabel,
+ keyrings,
} = this.props
const { name, address } = selectedIdentity
+ const keyring = keyrings.find((kr) => {
+ return kr.accounts.includes(address)
+ })
+
+ let exportPrivateKeyFeatureEnabled = true
+ // This feature is disabled for hardware wallets
+ if (keyring.type.search('Hardware') !== -1) {
+ exportPrivateKeyFeatureEnabled = false
+ }
+
return h(AccountModalContainer, {}, [
h(EditableLabel, {
className: 'account-modal__name',
@@ -73,9 +85,9 @@ AccountDetailsModal.prototype.render = function () {
}, this.context.t('etherscanView')),
// Holding on redesign for Export Private Key functionality
- h('button.btn-primary.account-modal__button', {
+ exportPrivateKeyFeatureEnabled ? h('button.btn-primary.account-modal__button', {
onClick: () => showExportPrivateKeyModal(),
- }, this.context.t('exportPrivateKey')),
+ }, this.context.t('exportPrivateKey')) : null,
])
}
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
index c722d1f55..488a189ea 100644
--- a/ui/app/components/pages/create-account/connect-hardware/account-list.js
+++ b/ui/app/components/pages/create-account/connect-hardware/account-list.js
@@ -2,16 +2,75 @@ const { Component } = require('react')
const PropTypes = require('prop-types')
const h = require('react-hyperscript')
const genAccountLink = require('../../../../../lib/account-link.js')
+const Select = require('react-select').default
class AccountList extends Component {
constructor (props, context) {
super(props)
}
+ getHdPaths () {
+ return [
+ {
+ label: `Ledger Live`,
+ value: `m/44'/60'/0'/0/0`,
+ },
+ {
+ label: `Legacy (MEW / MyCrypto)`,
+ value: `m/44'/60'/0'`,
+ },
+ ]
+ }
+
+ goToNextPage = () => {
+ // If we have < 5 accounts, it's restricted by BIP-44
+ if (this.props.accounts.length === 5) {
+ this.props.getPage(this.props.device, 1, this.props.selectedPath)
+ } else {
+ this.props.onAccountRestriction()
+ }
+ }
+
+ goToPreviousPage = () => {
+ this.props.getPage(this.props.device, -1, this.props.selectedPath)
+ }
+
+ renderHdPathSelector () {
+ const { onPathChange, selectedPath } = this.props
+
+ const options = this.getHdPaths()
+ return h('div', [
+ h('h3.hw-connect__hdPath__title', {}, this.context.t('selectHdPath')),
+ h('p.hw-connect__msg', {}, this.context.t('selectPathHelp')),
+ h('div.hw-connect__hdPath', [
+ h(Select, {
+ className: 'hw-connect__hdPath__select',
+ name: 'hd-path-select',
+ clearable: false,
+ value: selectedPath,
+ options,
+ onChange: (opt) => {
+ onPathChange(opt.value)
+ },
+ }),
+ ]),
+ ])
+ }
+
+ capitalizeDevice (device) {
+ return device.slice(0, 1).toUpperCase() + device.slice(1)
+ }
+
renderHeader () {
+ const { device } = this.props
return (
h('div.hw-connect', [
- h('h3.hw-connect__title', {}, this.context.t('selectAnAccount')),
+
+ h('h3.hw-connect__unlock-title', {}, `${this.context.t('unlock')} ${this.capitalizeDevice(device)}`),
+
+ device.toLowerCase() === 'ledger' ? this.renderHdPathSelector() : null,
+
+ h('h3.hw-connect__hdPath__title', {}, this.context.t('selectAnAccount')),
h('p.hw-connect__msg', {}, this.context.t('selectAnAccountHelp')),
])
)
@@ -61,7 +120,7 @@ class AccountList extends Component {
h(
'button.hw-list-pagination__button',
{
- onClick: () => this.props.getPage(-1),
+ onClick: this.goToPreviousPage,
},
`< ${this.context.t('prev')}`
),
@@ -69,7 +128,7 @@ class AccountList extends Component {
h(
'button.hw-list-pagination__button',
{
- onClick: () => this.props.getPage(1),
+ onClick: this.goToNextPage,
},
`${this.context.t('next')} >`
),
@@ -95,7 +154,7 @@ class AccountList extends Component {
h(
`button.btn-primary.btn--large.new-account-connect-form__button.unlock ${disabled ? '.btn-primary--disabled' : ''}`,
{
- onClick: this.props.onUnlockAccount.bind(this),
+ onClick: this.props.onUnlockAccount.bind(this, this.props.device),
...buttonProps,
},
[this.context.t('unlock')]
@@ -106,7 +165,7 @@ class AccountList extends Component {
renderForgetDevice () {
return h('div.hw-forget-device-container', {}, [
h('a', {
- onClick: this.props.onForgetDevice.bind(this),
+ onClick: this.props.onForgetDevice.bind(this, this.props.device),
}, this.context.t('forgetDevice')),
])
}
@@ -125,6 +184,9 @@ class AccountList extends Component {
AccountList.propTypes = {
+ onPathChange: PropTypes.func.isRequired,
+ selectedPath: PropTypes.string.isRequired,
+ device: PropTypes.string.isRequired,
accounts: PropTypes.array.isRequired,
onAccountChange: PropTypes.func.isRequired,
onForgetDevice: PropTypes.func.isRequired,
@@ -134,6 +196,7 @@ AccountList.propTypes = {
history: PropTypes.object,
onUnlockAccount: PropTypes.func,
onCancel: PropTypes.func,
+ onAccountRestriction: PropTypes.func,
}
AccountList.contextTypes = {
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
index cb2b86595..b3dfa4ee2 100644
--- a/ui/app/components/pages/create-account/connect-hardware/connect-screen.js
+++ b/ui/app/components/pages/create-account/connect-hardware/connect-screen.js
@@ -5,6 +5,52 @@ const h = require('react-hyperscript')
class ConnectScreen extends Component {
constructor (props, context) {
super(props)
+ this.state = {
+ selectedDevice: null,
+ }
+ }
+
+ connect = () => {
+ if (this.state.selectedDevice) {
+ this.props.connectToHardwareWallet(this.state.selectedDevice)
+ }
+ return null
+ }
+
+ renderConnectToTrezorButton () {
+ return h(
+ `button.hw-connect__btn${this.state.selectedDevice === 'trezor' ? '.selected' : ''}`,
+ { onClick: _ => this.setState({selectedDevice: 'trezor'}) },
+ h('img.hw-connect__btn__img', {
+ src: 'images/trezor-logo.svg',
+ })
+ )
+ }
+
+ renderConnectToLedgerButton () {
+ return h(
+ `button.hw-connect__btn${this.state.selectedDevice === 'ledger' ? '.selected' : ''}`,
+ { onClick: _ => this.setState({selectedDevice: 'ledger'}) },
+ h('img.hw-connect__btn__img', {
+ src: 'images/ledger-logo.svg',
+ })
+ )
+ }
+
+ renderButtons () {
+ return (
+ h('div', {}, [
+ h('div.hw-connect__btn-wrapper', {}, [
+ this.renderConnectToLedgerButton(),
+ this.renderConnectToTrezorButton(),
+ ]),
+ h(
+ `button.hw-connect__connect-btn${!this.state.selectedDevice ? '.disabled' : ''}`,
+ { onClick: this.connect },
+ this.context.t('connect')
+ ),
+ ])
+ )
}
renderUnsupportedBrowser () {
@@ -12,7 +58,7 @@ class ConnectScreen extends Component {
h('div.new-account-connect-form.unsupported-browser', {}, [
h('div.hw-connect', [
h('h3.hw-connect__title', {}, this.context.t('browserNotSupported')),
- h('p.hw-connect__msg', {}, this.context.t('chromeRequiredForTrezor')),
+ h('p.hw-connect__msg', {}, this.context.t('chromeRequiredForHardwareWallets')),
]),
h(
'button.btn-primary.btn--large',
@@ -30,29 +76,31 @@ class ConnectScreen extends Component {
renderHeader () {
return (
h('div.hw-connect__header', {}, [
- h('h3.hw-connect__header__title', {}, this.context.t(`hardwareSupport`)),
- h('p.hw-connect__header__msg', {}, this.context.t(`hardwareSupportMsg`)),
+ h('h3.hw-connect__header__title', {}, this.context.t(`hardwareWallets`)),
+ h('p.hw-connect__header__msg', {}, this.context.t(`hardwareWalletsMsg`)),
])
)
}
+ getAffiliateLinks () {
+ const links = {
+ trezor: `<a class='hw-connect__get-hw__link' href='https://shop.trezor.io/?a=metamask' target='_blank'>Trezor</a>`,
+ ledger: `<a class='hw-connect__get-hw__link' href='https://www.ledger.com/products/ledger-nano-s?r=17c4991a03fa&tracker=MY_TRACKER' target='_blank'>Ledger</a>`,
+ }
+
+ const text = this.context.t('orderOneHere')
+ const response = text.replace('Trezor', links.trezor).replace('Ledger', links.ledger)
+
+ return h('div.hw-connect__get-hw__msg', { dangerouslySetInnerHTML: {__html: response }})
+ }
+
renderTrezorAffiliateLink () {
- return h('div.hw-connect__get-trezor', {}, [
- h('p.hw-connect__get-trezor__msg', {}, this.context.t(`dontHaveATrezorWallet`)),
- h('a.hw-connect__get-trezor__link', {
- href: 'https://shop.trezor.io/?a=metamask',
- target: '_blank',
- }, this.context.t('orderOneHere')),
+ return h('div.hw-connect__get-hw', {}, [
+ h('p.hw-connect__get-hw__msg', {}, this.context.t(`dontHaveAHardwareWallet`)),
+ this.getAffiliateLinks(),
])
}
- renderConnectToTrezorButton () {
- return h(
- 'button.btn-primary.btn--large',
- { onClick: this.props.connectToTrezor.bind(this) },
- this.props.btnText
- )
- }
scrollToTutorial = (e) => {
if (this.referenceNode) this.referenceNode.scrollIntoView({behavior: 'smooth'})
@@ -102,7 +150,7 @@ class ConnectScreen extends Component {
return (
h('div.hw-connect__footer', {}, [
h('h3.hw-connect__footer__title', {}, this.context.t(`readyToConnect`)),
- this.renderConnectToTrezorButton(),
+ this.renderButtons(),
h('p.hw-connect__footer__msg', {}, [
this.context.t(`havingTroubleConnecting`),
h('a.hw-connect__footer__link', {
@@ -118,8 +166,8 @@ class ConnectScreen extends Component {
return (
h('div.new-account-connect-form', {}, [
this.renderHeader(),
+ this.renderButtons(),
this.renderTrezorAffiliateLink(),
- this.renderConnectToTrezorButton(),
this.renderLearnMore(),
this.renderTutorialSteps(),
this.renderFooter(),
@@ -136,8 +184,7 @@ class ConnectScreen extends Component {
}
ConnectScreen.propTypes = {
- connectToTrezor: PropTypes.func.isRequired,
- btnText: PropTypes.string.isRequired,
+ connectToHardwareWallet: PropTypes.func.isRequired,
browserSupported: PropTypes.bool.isRequired,
}
diff --git a/ui/app/components/pages/create-account/connect-hardware/index.js b/ui/app/components/pages/create-account/connect-hardware/index.js
index 3f66e7098..547df5223 100644
--- a/ui/app/components/pages/create-account/connect-hardware/index.js
+++ b/ui/app/components/pages/create-account/connect-hardware/index.js
@@ -7,17 +7,19 @@ const ConnectScreen = require('./connect-screen')
const AccountList = require('./account-list')
const { DEFAULT_ROUTE } = require('../../../../routes')
const { formatBalance } = require('../../../../util')
+const { getPlatform } = require('../../../../../../app/scripts/lib/util')
+const { PLATFORM_FIREFOX } = require('../../../../../../app/scripts/lib/enums')
class ConnectHardwareForm extends Component {
constructor (props, context) {
super(props)
this.state = {
error: null,
- btnText: context.t('connectToTrezor'),
selectedAccount: null,
accounts: [],
browserSupported: true,
unlocked: false,
+ device: null,
}
}
@@ -38,25 +40,44 @@ class ConnectHardwareForm extends Component {
}
async checkIfUnlocked () {
- const unlocked = await this.props.checkHardwareStatus('trezor')
- if (unlocked) {
- this.setState({unlocked: true})
- this.getPage(0)
- }
+ ['trezor', 'ledger'].forEach(async device => {
+ const unlocked = await this.props.checkHardwareStatus(device, this.props.defaultHdPaths[device])
+ if (unlocked) {
+ this.setState({unlocked: true})
+ this.getPage(device, 0, this.props.defaultHdPaths[device])
+ }
+ })
}
- connectToTrezor = () => {
+ connectToHardwareWallet = (device) => {
+ // None of the hardware wallets are supported
+ // At least for now
+ if (getPlatform() === PLATFORM_FIREFOX) {
+ this.setState({ browserSupported: false, error: null})
+ return null
+ }
+
if (this.state.accounts.length) {
return null
}
- this.setState({ btnText: this.context.t('connecting')})
- this.getPage(0)
+
+ // Default values
+ this.getPage(device, 0, this.props.defaultHdPaths[device])
+ }
+
+ onPathChange = (path) => {
+ this.props.setHardwareWalletDefaultHdPath({device: this.state.device, path})
+ this.getPage(this.state.device, 0, path)
}
onAccountChange = (account) => {
this.setState({selectedAccount: account.toString(), error: null})
}
+ onAccountRestriction = () => {
+ this.setState({error: this.context.t('ledgerAccountRestriction') })
+ }
+
showTemporaryAlert () {
this.props.showAlert(this.context.t('hardwareWalletConnected'))
// Autohide the alert after 5 seconds
@@ -65,9 +86,9 @@ class ConnectHardwareForm extends Component {
}, 5000)
}
- getPage = (page) => {
+ getPage = (device, page, hdPath) => {
this.props
- .connectHardware('trezor', page)
+ .connectHardware(device, page, hdPath)
.then(accounts => {
if (accounts.length) {
@@ -77,7 +98,7 @@ class ConnectHardwareForm extends Component {
this.showTemporaryAlert()
}
- const newState = { unlocked: true }
+ const newState = { unlocked: true, device, error: null }
// Default to the first account
if (this.state.selectedAccount === null) {
accounts.forEach((a, i) => {
@@ -104,18 +125,18 @@ class ConnectHardwareForm extends Component {
})
.catch(e => {
if (e === 'Window blocked') {
- this.setState({ browserSupported: false })
+ this.setState({ browserSupported: false, error: null})
+ } else if (e !== 'Window closed') {
+ this.setState({ error: e.toString() })
}
- this.setState({ btnText: this.context.t('connectToTrezor') })
})
}
- onForgetDevice = () => {
- this.props.forgetDevice('trezor')
+ onForgetDevice = (device) => {
+ this.props.forgetDevice(device)
.then(_ => {
this.setState({
error: null,
- btnText: this.context.t('connectToTrezor'),
selectedAccount: null,
accounts: [],
unlocked: false,
@@ -125,13 +146,13 @@ class ConnectHardwareForm extends Component {
})
}
- onUnlockAccount = () => {
+ onUnlockAccount = (device) => {
if (this.state.selectedAccount === null) {
this.setState({ error: this.context.t('accountSelectionRequired') })
}
- this.props.unlockTrezorAccount(this.state.selectedAccount)
+ this.props.unlockHardwareWalletAccount(this.state.selectedAccount, device)
.then(_ => {
this.props.history.push(DEFAULT_ROUTE)
}).catch(e => {
@@ -145,20 +166,22 @@ class ConnectHardwareForm extends Component {
renderError () {
return this.state.error
- ? h('span.error', { style: { marginBottom: 40 } }, this.state.error)
+ ? h('span.error', { style: { margin: '20px 20px 10px', display: 'block', textAlign: 'center' } }, this.state.error)
: null
}
renderContent () {
if (!this.state.accounts.length) {
return h(ConnectScreen, {
- connectToTrezor: this.connectToTrezor,
- btnText: this.state.btnText,
+ connectToHardwareWallet: this.connectToHardwareWallet,
browserSupported: this.state.browserSupported,
})
}
return h(AccountList, {
+ onPathChange: this.onPathChange,
+ selectedPath: this.props.defaultHdPaths[this.state.device],
+ device: this.state.device,
accounts: this.state.accounts,
selectedAccount: this.state.selectedAccount,
onAccountChange: this.onAccountChange,
@@ -168,6 +191,7 @@ class ConnectHardwareForm extends Component {
onUnlockAccount: this.onUnlockAccount,
onForgetDevice: this.onForgetDevice,
onCancel: this.onCancel,
+ onAccountRestriction: this.onAccountRestriction,
})
}
@@ -188,13 +212,15 @@ ConnectHardwareForm.propTypes = {
forgetDevice: PropTypes.func,
showAlert: PropTypes.func,
hideAlert: PropTypes.func,
- unlockTrezorAccount: PropTypes.func,
+ unlockHardwareWalletAccount: PropTypes.func,
+ setHardwareWalletDefaultHdPath: PropTypes.func,
numberOfExistingAccounts: PropTypes.number,
history: PropTypes.object,
t: PropTypes.func,
network: PropTypes.string,
accounts: PropTypes.object,
address: PropTypes.string,
+ defaultHdPaths: PropTypes.object,
}
const mapStateToProps = state => {
@@ -202,28 +228,35 @@ const mapStateToProps = state => {
metamask: { network, selectedAddress, identities = {}, accounts = [] },
} = state
const numberOfExistingAccounts = Object.keys(identities).length
+ const {
+ appState: { defaultHdPaths },
+ } = state
return {
network,
accounts,
address: selectedAddress,
numberOfExistingAccounts,
+ defaultHdPaths,
}
}
const mapDispatchToProps = dispatch => {
return {
- connectHardware: (deviceName, page) => {
- return dispatch(actions.connectHardware(deviceName, page))
+ setHardwareWalletDefaultHdPath: ({device, path}) => {
+ return dispatch(actions.setHardwareWalletDefaultHdPath({device, path}))
+ },
+ connectHardware: (deviceName, page, hdPath) => {
+ return dispatch(actions.connectHardware(deviceName, page, hdPath))
},
- checkHardwareStatus: (deviceName) => {
- return dispatch(actions.checkHardwareStatus(deviceName))
+ checkHardwareStatus: (deviceName, hdPath) => {
+ return dispatch(actions.checkHardwareStatus(deviceName, hdPath))
},
forgetDevice: (deviceName) => {
return dispatch(actions.forgetDevice(deviceName))
},
- unlockTrezorAccount: index => {
- return dispatch(actions.unlockTrezorAccount(index))
+ unlockHardwareWalletAccount: (index, deviceName, hdPath) => {
+ return dispatch(actions.unlockHardwareWalletAccount(index, deviceName, hdPath))
},
showImportPage: () => dispatch(actions.showImportPage()),
showConnectPage: () => dispatch(actions.showConnectPage()),
diff --git a/ui/app/components/wallet-view.js b/ui/app/components/wallet-view.js
index 20c2be0f1..8e092364c 100644
--- a/ui/app/components/wallet-view.js
+++ b/ui/app/components/wallet-view.js
@@ -118,8 +118,18 @@ WalletView.prototype.render = function () {
return kr.accounts.includes(selectedAddress)
})
- const type = keyring.type
- const isLoose = type !== 'HD Key Tree'
+ let label = ''
+ let type
+ if (keyring) {
+ type = keyring.type
+ if (type !== 'HD Key Tree') {
+ if (type.toLowerCase().search('hardware') !== -1) {
+ label = this.context.t('hardware')
+ } else {
+ label = this.context.t('imported')
+ }
+ }
+ }
return h('div.wallet-view.flex-column' + (responsiveDisplayClassname || ''), {
style: {},
@@ -133,7 +143,7 @@ WalletView.prototype.render = function () {
onClick: hideSidebar,
}),
- h('div.wallet-view__keyring-label.allcaps', isLoose ? this.context.t('imported') : ''),
+ h('div.wallet-view__keyring-label.allcaps', label),
h('div.flex-column.flex-center.wallet-view__name-container', {
style: { margin: '0 auto' },
diff --git a/ui/app/css/itcss/components/new-account.scss b/ui/app/css/itcss/components/new-account.scss
index b12afb124..e4c7a4e0d 100644
--- a/ui/app/css/itcss/components/new-account.scss
+++ b/ui/app/css/itcss/components/new-account.scss
@@ -162,19 +162,99 @@
}
.hw-connect {
+ width: 100%;
+
&__header {
&__title {
margin-top: 5px;
margin-bottom: 15px;
font-size: 22px;
- text-align: center;
}
&__msg {
font-size: 14px;
color: #9b9b9b;
margin-top: 10px;
- margin-bottom: 0px;
+ margin-bottom: 20px;
+ }
+ }
+
+ &__btn-wrapper {
+ flex: 1;
+ flex-direction: row;
+ display: flex;
+ }
+
+ &__connect-btn {
+ background-color: #259De5;
+ color: #fff;
+ border: none;
+ width: 315px;
+ min-height: 54px;
+ font-weight: 300;
+ font-size: 14px;
+ margin-bottom: 20px;
+ margin-top: 20px;
+ border-radius: 5px;
+ display: flex;
+ flex: 1;
+ margin-left: 20px;
+ margin-right: 20px;
+ justify-content: center;
+ text-transform: uppercase;
+ }
+
+ &__connect-btn.disabled {
+ cursor: not-allowed;
+ opacity: .5;
+ }
+
+ &__btn {
+ background: #fbfbfb;
+ border: 1px solid #e5e5e5;
+ height: 100px;
+ width: 150px;
+ flex: 1;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 5px;
+
+ &__img {
+ width: 95px;
+ }
+ }
+
+ &__btn.selected {
+ border: 2px solid #00a8ee;
+ width: 149px;
+ }
+
+ &__btn:first-child {
+ margin-right: 15px;
+ margin-left: 20px;
+ }
+
+ &__btn:last-child {
+ margin-right: 20px;
+ }
+
+ &__hdPath {
+ display: flex;
+ flex-direction: row;
+ margin-top: 15px;
+ margin-bottom: 30px;
+ font-size: 14px;
+
+ &__title {
+ display: flex;
+ margin-top: 10px;
+ margin-right: 15px;
+ }
+
+ &__select {
+ display: flex;
+ flex: 1;
}
}
@@ -201,6 +281,13 @@
font-size: 18px;
}
+ &__unlock-title {
+ padding-top: 10px;
+ font-weight: 400;
+ font-size: 22px;
+ margin-bottom: 15px;
+ }
+
&__msg {
font-size: 14px;
color: #9b9b9b;
@@ -213,8 +300,6 @@
}
&__footer {
- width: 100%;
-
&__title {
padding-top: 15px;
padding-bottom: 12px;
@@ -228,6 +313,9 @@
color: #9b9b9b;
margin-top: 12px;
margin-bottom: 27px;
+ width: 100%;
+ display: block;
+ margin-left: 20px;
}
&__link {
@@ -236,10 +324,10 @@
}
}
- &__get-trezor {
+ &__get-hw {
width: 100%;
- padding-bottom: 20px;
- padding-top: 20px;
+ padding-bottom: 10px;
+ padding-top: 10px;
&__msg {
font-size: 14px;
@@ -390,6 +478,8 @@
&.account-list {
height: auto;
+ padding-left: 20px;
+ padding-right: 20px;
}
&__buttons {
@@ -412,6 +502,7 @@
min-height: 54px;
font-weight: 300;
font-size: 14px;
+ margin-bottom: 20px
}
&__button.unlock {
diff --git a/ui/app/reducers/app.js b/ui/app/reducers/app.js
index 98d467163..c246e7904 100644
--- a/ui/app/reducers/app.js
+++ b/ui/app/reducers/app.js
@@ -67,6 +67,10 @@ function reduceApp (state, action) {
isMouseUser: false,
gasIsLoading: false,
networkNonce: null,
+ defaultHdPaths: {
+ trezor: `m/44'/60'/0'/0`,
+ ledger: `m/44'/60'/0'/0/0`,
+ },
}, state.appState)
switch (action.type) {
@@ -525,6 +529,15 @@ function reduceApp (state, action) {
warning: '',
})
+ case actions.SET_HARDWARE_WALLET_DEFAULT_HD_PATH:
+ const { device, path } = action.value
+ const newDefaults = {...appState.defaultHdPaths}
+ newDefaults[device] = path
+
+ return extend(appState, {
+ defaultHdPaths: newDefaults,
+ })
+
case actions.SHOW_LOADING:
return extend(appState, {
isLoading: true,
diff --git a/ui/app/selectors/confirm-transaction.js b/ui/app/selectors/confirm-transaction.js
index aa1fc5404..6e760c429 100644
--- a/ui/app/selectors/confirm-transaction.js
+++ b/ui/app/selectors/confirm-transaction.js
@@ -159,7 +159,7 @@ export const approveTokenAmountAndToAddressSelector = createSelector(
if (tokenDecimals) {
tokenAmount = calcTokenAmount(value, tokenDecimals)
}
-
+
tokenAmount = roundExponential(tokenAmount)
}