Skip to content

Commit

Permalink
Merge pull request #183 from poanetwork/hw-support
Browse files Browse the repository at this point in the history
(Feature) HD wallets' support
  • Loading branch information
vbaranov committed Nov 21, 2018
2 parents 2eb8c54 + 256e585 commit b2066c4
Show file tree
Hide file tree
Showing 17 changed files with 1,029 additions and 10 deletions.
22 changes: 21 additions & 1 deletion app/images/popout.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion app/scripts/controllers/transactions/tx-state-manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -352,7 +352,7 @@ class TransactionStateManager extends EventEmitter {
setTxStatusFailed (txId, err) {
const txMeta = this.getTx(txId)
txMeta.err = {
message: err.toString(),
message: (err ? (err.message || err.error || err) : '').toString(),
rpc: err.value,
stack: err.stack,
}
Expand Down
14 changes: 14 additions & 0 deletions app/scripts/lib/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,19 @@ function removeListeners (listeners, emitter) {
})
}

/**
* Capitalizes first letter in the first word of the message
* @param {string} msg The input message
* returns {string} The message with capitalized first letter of the first word
**/
function capitalizeFirstLetter (msg) {
if (!msg) {
return ''
}

return msg.charAt(0).toUpperCase() + msg.slice(1)
}

module.exports = {
removeListeners,
applyListeners,
Expand All @@ -149,4 +162,5 @@ module.exports = {
hexToBn,
bnToHex,
BnMultiplyByFraction,
capitalizeFirstLetter,
}
6 changes: 5 additions & 1 deletion app/scripts/metamask-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -821,7 +821,11 @@ module.exports = class MetamaskController extends EventEmitter {
this.accountTracker.removeAccount([address])

// Remove account from the keyring
await this.keyringController.removeAccount(address)
try {
await this.keyringController.removeAccount(address)
} catch (e) {
log.error(e)
}
return address
}

Expand Down
3 changes: 2 additions & 1 deletion app/scripts/platforms/extension.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const extension = require('extensionizer')
const explorerLinks = require('eth-net-props').explorerLinks
const { capitalizeFirstLetter } = require('../lib/util')

class ExtensionPlatform {

Expand Down Expand Up @@ -94,7 +95,7 @@ class ExtensionPlatform {

const nonce = parseInt(txMeta.txParams.nonce, 16)
const title = 'Failed transaction'
const message = `Transaction ${nonce} failed! ${txMeta.err.message}`
const message = `Transaction ${nonce} failed! ${capitalizeFirstLetter(txMeta.err.message)}`
this._showNotification(title, message)
}

Expand Down
5 changes: 5 additions & 0 deletions old-ui/app/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ const ConfirmAddTokenScreen = require('./components/confirm-add-token')
const RemoveTokenScreen = require('./remove-token')
const AddSuggestedTokenScreen = require('./add-suggested-token')
const Import = require('./accounts/import')
import ConnectHardwareForm from './components/connect-hardware/index.js'
const InfoScreen = require('./info')
const AppBar = require('./components/app-bar')
const Loading = require('./components/loading')
Expand Down Expand Up @@ -265,6 +266,10 @@ App.prototype.renderPrimary = function () {
log.debug('rendering import screen')
return h(Import, {key: 'import-menu'})

case 'hardware-wallets-menu':
log.debug('rendering hardware wallet menu screen')
return h(ConnectHardwareForm, {key: 'hardware-wallets-menu'})

case 'reveal-seed-conf':
log.debug('rendering reveal seed confirmation screen')
return h(RevealSeedConfirmation, {key: 'reveal-seed-conf'})
Expand Down
23 changes: 23 additions & 0 deletions old-ui/app/components/account-dropdowns.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ class AccountDropdowns extends Component {

return accountOrder.map((address, index) => {
const identity = identities[address]
if (!identity) {
return null
}
const isSelected = identity.address === selected

const simpleAddress = identity.address.substring(2).toLowerCase()
Expand Down Expand Up @@ -172,6 +175,25 @@ class AccountDropdowns extends Component {
}, 'Import Account'),
]
),
h(
DropdownMenuItem,
{
style: {
padding: '8px 0px',
},
closeMenu: () => {},
onClick: () => actions.showConnectHWWalletPage(),
},
[
h('span', {
style: {
fontSize: '16px',
marginBottom: '5px',
color: '#60db97',
},
}, 'Connect hardware wallet'),
]
),
]
)
}
Expand Down Expand Up @@ -322,6 +344,7 @@ const mapDispatchToProps = (dispatch) => {
showAccountDetail: (address) => dispatch(actions.showAccountDetail(address)),
addNewAccount: () => dispatch(actions.addNewAccount()),
showImportPage: () => dispatch(actions.showImportPage()),
showConnectHWWalletPage: () => dispatch(actions.showConnectHWWalletPage()),
showQrView: (selected, identity) => dispatch(actions.showQrView(selected, identity)),
showDeleteImportedAccount: (identity) => dispatch(actions.showDeleteImportedAccount(identity)),
},
Expand Down
191 changes: 191 additions & 0 deletions old-ui/app/components/connect-hardware/account-list.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import ethNetProps from 'eth-net-props'
import { default as Select } from 'react-select'
import Button from '../../../../ui/app/components/button'
import { capitalizeFirstLetter } from '../../../../app/scripts/lib/util'

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 (
<div>
<h3 className="hw-connect__hdPath__title">Select HD Path</h3>
<p className="hw-connect__msg">{`If you don't see your existing Ledger accounts below, try switching paths to "Legacy (MEW / MyCrypto)"`}</p>
<div className="hw-connect__hdPath">
<Select
className="hw-connect__hdPath__select"
name="hd-path-select"
clearable={false}
value={selectedPath}
options={options}
onChange={(opt) => {
onPathChange(opt.value)
}}
/>
</div>
</div>
)
}

renderHeader = () => {
const { device } = this.props
return (
<div className="hw-connect">
<h3 className="hw-connect">
<h3 className="hw-connect__unlock-title">{`Unlock ${capitalizeFirstLetter(device)}`}</h3>
{device.toLowerCase() === 'ledger' ? this.renderHdPathSelector() : null}
<p className="hw-connect__msg">Select the account to view in Nifty Wallet</p>
</h3>
</div>
)
}

renderAccounts = () => {
const rows = []
this.props.accounts.forEach((a, i) => {
rows.push(
<div className="hw-account-list__item" key={a.address}>
<div className="hw-account-list__item__radio">
<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()}
/>
<label className="hw-account-list__item__label" htmlFor={`address-${i}`}>
{`${a.address.slice(0, 4)}...${a.address.slice(-4)}`}
<span
className="hw-account-list__item__balance"
onClick={(event) => {
event.preventDefault()
global.platform.openWindow({
url: ethNetProps.explorerLinks.getExplorerAccountLinkFor(a.address, this.props.network),
})
}}
>{`${a.balance}`}</span>
</label>
</div>
</div>
)
})

return (
<div className="hw-account-list">{rows}</div>
)
}

renderPagination = () => {
return (
<div className="hw-list-pagination">
<button
className="hw-list-pagination__button"
onClick={this.goToNextPage}
>{`Next >`}</button>
<button
className="hw-list-pagination__button"
onClick={this.goToPreviousPage}
>{`< Prev`}</button>
</div>
)
}

renderButtons = () => {
const disabled = this.props.selectedAccount === null
const buttonProps = {}
if (disabled) {
buttonProps.disabled = true
}

return (
<div className="new-account-connect-form__buttons">
<Button
type="default"
large={true}
className="new-account-connect-form__button btn-violet"
onClick={this.props.onCancel.bind(this)}
>Cancel</Button>
<Button
type="primary"
large={true}
className="new-account-connect-form__button unlock"
disabled={disabled}
onClick={this.props.onUnlockAccount.bind(this, this.props.device)}
>Unlock</Button>
</div>
)
}

renderForgetDevice = () => {
return (
<div className="hw-forget-device-container">
<a onClick={this.props.onForgetDevice.bind(this, this.props.device)}>Forget this device</a>
</div>
)
}

render = () => {
return (
<div className="new-account-connect-form.account-list">
{this.renderHeader()}
{this.renderAccounts()}
{this.renderPagination()}
{this.renderButtons()}
{this.renderForgetDevice()}
</div>
)
}

}

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,
getPage: PropTypes.func.isRequired,
network: PropTypes.string,
selectedAccount: PropTypes.string,
history: PropTypes.object,
onUnlockAccount: PropTypes.func,
onCancel: PropTypes.func,
onAccountRestriction: PropTypes.func,
}

module.exports = AccountList
Loading

0 comments on commit b2066c4

Please sign in to comment.