Merge branch 'develop' into double-fired-events-fix
This commit is contained in:
commit
8f0e5877b9
|
@ -1 +1,21 @@
|
|||
<svg height="22" viewBox="0 0 22 22" width="22" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><mask id="a" fill="#fff"><path d="m-.00035 0h11.00025v10.9997h-11.00025z" fill="none"/></mask><g fill="#4a4a4a" fill-rule="evenodd"><path d="m10.9229.6177c-.102-.244-.296-.439-.541-.541-.122-.05-.251-.077-.382-.077h-6c-.552 0-1 .448-1 1 0 .553.448 1 1 1h3.586l-7.293 7.293c-.391.391-.391 1.024 0 1.414.195.196.451.293.707.293s.512-.097.707-.293l7.293-7.293v3.586c0 .553.448 1 1 1s1-.447 1-1v-6c0-.13-.026-.259-.077-.382" mask="url(#a)" transform="translate(11)"/><path d="m19 10c-.552 0-1 .448-1 1v8c0 .551-.449 1-1 1h-14c-.551 0-1-.449-1-1v-14c0-.551.449-1 1-1h8c.552 0 1-.448 1-1s-.448-1-1-1h-8c-1.654 0-3 1.346-3 3v14c0 1.654 1.346 3 3 3h14c1.654 0 3-1.346 3-3v-8c0-.552-.448-1-1-1"/></g></svg>
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="22px" height="22px" viewBox="0 0 22 22" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 48.2 (47327) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>popout</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs>
|
||||
<polygon id="path-1" points="-0.00035 0 10.9999 0 10.9999 10.9997 -0.00035 10.9997"></polygon>
|
||||
</defs>
|
||||
<g id="MetaMascara-Mobile---structured-TOKEN" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" transform="translate(-327.000000, -96.000000)">
|
||||
<g id="popout" transform="translate(327.000000, 96.000000)">
|
||||
<g id="Group-3" transform="translate(11.000000, 0.000000)">
|
||||
<mask id="mask-2" fill="white">
|
||||
<use xlink:href="#path-1"></use>
|
||||
</mask>
|
||||
<g id="Clip-2"></g>
|
||||
<path d="M10.9229,0.6177 C10.8209,0.3737 10.6269,0.1787 10.3819,0.0767 C10.2599,0.0267 10.1309,-0.0003 9.9999,-0.0003 L3.9999,-0.0003 C3.4479,-0.0003 2.9999,0.4477 2.9999,0.9997 C2.9999,1.5527 3.4479,1.9997 3.9999,1.9997 L7.5859,1.9997 L0.2929,9.2927 C-0.0981,9.6837 -0.0981,10.3167 0.2929,10.7067 C0.4879,10.9027 0.7439,10.9997 0.9999,10.9997 C1.2559,10.9997 1.5119,10.9027 1.7069,10.7067 L8.9999,3.4137 L8.9999,6.9997 C8.9999,7.5527 9.4479,7.9997 9.9999,7.9997 C10.5519,7.9997 10.9999,7.5527 10.9999,6.9997 L10.9999,0.9997 C10.9999,0.8697 10.9739,0.7407 10.9229,0.6177" id="Fill-1" fill="#4A4A4A" mask="url(#mask-2)"></path>
|
||||
</g>
|
||||
<path d="M19,10 C18.448,10 18,10.448 18,11 L18,19 C18,19.551 17.551,20 17,20 L3,20 C2.449,20 2,19.551 2,19 L2,5 C2,4.449 2.449,4 3,4 L11,4 C11.552,4 12,3.552 12,3 C12,2.448 11.552,2 11,2 L3,2 C1.346,2 0,3.346 0,5 L0,19 C0,20.654 1.346,22 3,22 L17,22 C18.654,22 20,20.654 20,19 L20,11 C20,10.448 19.552,10 19,10" id="Fill-4" fill="#4A4A4A"></path>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 823 B After Width: | Height: | Size: 1.9 KiB |
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
@ -149,4 +162,5 @@ module.exports = {
|
|||
hexToBn,
|
||||
bnToHex,
|
||||
BnMultiplyByFraction,
|
||||
capitalizeFirstLetter,
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
const extension = require('extensionizer')
|
||||
const explorerLinks = require('eth-net-props').explorerLinks
|
||||
const { capitalizeFirstLetter } = require('../lib/util')
|
||||
|
||||
class ExtensionPlatform {
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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')
|
||||
|
@ -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'})
|
||||
|
|
|
@ -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()
|
||||
|
@ -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'),
|
||||
]
|
||||
),
|
||||
]
|
||||
)
|
||||
}
|
||||
|
@ -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)),
|
||||
},
|
||||
|
|
|
@ -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
|
|
@ -0,0 +1,135 @@
|
|||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import Button from '../../../../ui/app/components/button'
|
||||
|
||||
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 (
|
||||
<button
|
||||
className={`hw-connect__btn${this.state.selectedDevice === 'trezor' ? ' selected' : ''}`}
|
||||
onClick={_ => this.setState({selectedDevice: 'trezor'})}
|
||||
>
|
||||
<img className="hw-connect__btn__img" src="images/trezor-logo.svg"/>
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
renderConnectToLedgerButton () {
|
||||
return (
|
||||
<button
|
||||
className={`hw-connect__btn${this.state.selectedDevice === 'ledger' ? ' selected' : ''}`}
|
||||
onClick={_ => this.setState({selectedDevice: 'ledger'})}
|
||||
>
|
||||
<img className="hw-connect__btn__img" src="images/ledger-logo.svg"/>
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
renderButtons () {
|
||||
return (
|
||||
<div>
|
||||
<div className="hw-connect__btn-wrapper">
|
||||
{this.renderConnectToLedgerButton()}
|
||||
{this.renderConnectToTrezorButton()}
|
||||
</div>
|
||||
<button
|
||||
className={`hw-connect__connect-btn${!this.state.selectedDevice ? ' disabled' : ''}`}
|
||||
onClick={this.connect}
|
||||
>Connect</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
renderUnsupportedBrowser () {
|
||||
return (
|
||||
<div className="new-account-connect-form unsupported-browser">
|
||||
<div className="hw-connect">
|
||||
<h3 className="hw-connect__title">Your Browser is not supported...</h3>
|
||||
<p className="hw-connect__msg">You need to use Nifty Wallet on Google Chrome in order to connect to your Hardware Wallet.</p>
|
||||
</div>
|
||||
<Button
|
||||
type="primary"
|
||||
large={true}
|
||||
onClick={() => global.platform.openWindow({
|
||||
url: 'https://google.com/chrome',
|
||||
})}
|
||||
>Download Google Chrome</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
renderHeader () {
|
||||
return (
|
||||
<div className="hw-connect__header">
|
||||
<p className="hw-connect__header__msg">{`Select a hardware wallet you'd like to use with Nifty Wallet`}</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
getAffiliateLinks () {
|
||||
const links = {
|
||||
trezor: `<a class='hw-connect__get-hw__link' href='https://shop.trezor.io/?a=niftywallet' target='_blank'>Trezor</a>`,
|
||||
ledger: `<a class='hw-connect__get-hw__link' href='https://www.ledger.com/products/ledger-nano-s' target='_blank'>Ledger</a>`,
|
||||
}
|
||||
|
||||
const text = 'Order a Trezor or Ledger and keep your funds in cold storage'
|
||||
const response = text.replace('Trezor', links.trezor).replace('Ledger', links.ledger)
|
||||
|
||||
return (
|
||||
<div className="hw-connect__get-hw__msg" dangerouslySetInnerHTML={{ __html: response }} />
|
||||
)
|
||||
}
|
||||
|
||||
renderTrezorAffiliateLink () {
|
||||
return (
|
||||
<div className="hw-connect__get-hw">
|
||||
<p className="hw-connect__get-hw__msg">Don’t have a hardware wallet?</p>
|
||||
{this.getAffiliateLinks()}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
scrollToTutorial = (e) => {
|
||||
if (this.referenceNode) this.referenceNode.scrollIntoView({behavior: 'smooth'})
|
||||
}
|
||||
|
||||
renderConnectScreen () {
|
||||
return (
|
||||
<div className="new-account-connect-form">
|
||||
{this.renderHeader()}
|
||||
{this.renderButtons()}
|
||||
{this.renderTrezorAffiliateLink()}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
render () {
|
||||
if (this.props.browserSupported) {
|
||||
return this.renderConnectScreen()
|
||||
}
|
||||
return this.renderUnsupportedBrowser()
|
||||
}
|
||||
}
|
||||
|
||||
ConnectScreen.propTypes = {
|
||||
connectToHardwareWallet: PropTypes.func.isRequired,
|
||||
browserSupported: PropTypes.bool.isRequired,
|
||||
}
|
||||
|
||||
module.exports = ConnectScreen
|
||||
|
|
@ -0,0 +1,285 @@
|
|||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { connect } from 'react-redux'
|
||||
import actions from '../../../../ui/app/actions'
|
||||
import ConnectScreen from './connect-screen'
|
||||
import AccountList from './account-list'
|
||||
import { formatBalance } from '../../util'
|
||||
import { getPlatform } from '../../../../app/scripts/lib/util'
|
||||
import { PLATFORM_FIREFOX } from '../../../../app/scripts/lib/enums'
|
||||
|
||||
class ConnectHardwareForm extends Component {
|
||||
constructor (props, context) {
|
||||
super(props)
|
||||
this.state = {
|
||||
error: null,
|
||||
selectedAccount: null,
|
||||
accounts: [],
|
||||
browserSupported: true,
|
||||
unlocked: false,
|
||||
device: null,
|
||||
}
|
||||
}
|
||||
|
||||
componentWillReceiveProps (nextProps) {
|
||||
const { accounts, network } = 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, 4, undefined, network) : '...'
|
||||
return a
|
||||
})
|
||||
this.setState({accounts: newAccounts})
|
||||
}
|
||||
|
||||
|
||||
componentDidMount () {
|
||||
this.checkIfUnlocked()
|
||||
}
|
||||
|
||||
async checkIfUnlocked () {
|
||||
['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])
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// 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: 'You need to make use your last account before you can add a new one.' })
|
||||
}
|
||||
|
||||
showTemporaryAlert () {
|
||||
this.props.showAlert('Hardware wallet connected')
|
||||
// Autohide the alert after 5 seconds
|
||||
setTimeout(_ => {
|
||||
this.props.hideAlert()
|
||||
}, 5000)
|
||||
}
|
||||
|
||||
getPage = (device, page, hdPath) => {
|
||||
this.props
|
||||
.connectHardware(device, page, hdPath)
|
||||
.then(accounts => {
|
||||
if (accounts.length) {
|
||||
|
||||
// If we just loaded the accounts for the first time
|
||||
// (device previously locked) show the global alert
|
||||
if (this.state.accounts.length === 0 && !this.state.unlocked) {
|
||||
this.showTemporaryAlert()
|
||||
}
|
||||
|
||||
const newState = { unlocked: true, device, error: null }
|
||||
// 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, 4, undefined, this.props.network) : '...'
|
||||
return account
|
||||
})
|
||||
|
||||
this.setState(newState)
|
||||
}
|
||||
})
|
||||
.catch(e => {
|
||||
if (e === 'Window blocked') {
|
||||
this.setState({ browserSupported: false, error: null})
|
||||
} else if (e !== 'Window closed') {
|
||||
this.setState({ error: (e.message || e.toString()) })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
onForgetDevice = (device) => {
|
||||
this.props.forgetDevice(device)
|
||||
.then(_ => {
|
||||
this.setState({
|
||||
error: null,
|
||||
selectedAccount: null,
|
||||
accounts: [],
|
||||
unlocked: false,
|
||||
})
|
||||
}).catch(e => {
|
||||
this.setState({ error: (e.message || e.toString()) })
|
||||
})
|
||||
}
|
||||
|
||||
onUnlockAccount = (device) => {
|
||||
|
||||
if (this.state.selectedAccount === null) {
|
||||
this.setState({ error: 'You need to select an account!' })
|
||||
}
|
||||
|
||||
this.props.unlockHardwareWalletAccount(this.state.selectedAccount, device)
|
||||
.then(_ => {
|
||||
this.props.goHome()
|
||||
}).catch(e => {
|
||||
this.setState({ error: (e.message || e.toString()) })
|
||||
})
|
||||
}
|
||||
|
||||
onCancel = () => {
|
||||
this.props.goHome()
|
||||
}
|
||||
|
||||
renderError () {
|
||||
return this.state.error
|
||||
? <span className="error" style={{ display: 'block', textAlign: 'center' }}>{this.state.error}</span>
|
||||
: null
|
||||
}
|
||||
|
||||
renderContent () {
|
||||
if (!this.state.accounts.length) {
|
||||
return (
|
||||
<ConnectScreen
|
||||
connectToHardwareWallet={this.connectToHardwareWallet}
|
||||
browserSupported={this.state.browserSupported}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<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}
|
||||
network={this.props.network}
|
||||
getPage={this.getPage}
|
||||
onUnlockAccount={this.onUnlockAccount}
|
||||
onForgetDevice={this.onForgetDevice}
|
||||
onCancel={this.onCancel}
|
||||
onAccountRestriction={this.onAccountRestriction}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
render () {
|
||||
return (
|
||||
<div style={{width: '100%'}}>
|
||||
<div className="section-title flex-row flex-center">
|
||||
<i className="fa fa-arrow-left fa-lg cursor-pointer"
|
||||
onClick={() => this.props.goHome() }
|
||||
style={{
|
||||
position: 'absolute',
|
||||
left: '30px',
|
||||
}}/>
|
||||
<h2>Connect to hardware wallet</h2>
|
||||
</div>
|
||||
<div style={{overflowY: 'auto', height: '482px'}}>
|
||||
<div style={{padding: '0 30px'}}>
|
||||
{this.renderError()}
|
||||
{this.renderContent()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
ConnectHardwareForm.propTypes = {
|
||||
showImportPage: PropTypes.func,
|
||||
showConnectPage: PropTypes.func,
|
||||
connectHardware: PropTypes.func,
|
||||
checkHardwareStatus: PropTypes.func,
|
||||
forgetDevice: PropTypes.func,
|
||||
showAlert: PropTypes.func,
|
||||
hideAlert: PropTypes.func,
|
||||
unlockHardwareWalletAccount: PropTypes.func,
|
||||
setHardwareWalletDefaultHdPath: PropTypes.func,
|
||||
goHome: PropTypes.func,
|
||||
numberOfExistingAccounts: PropTypes.number,
|
||||
network: PropTypes.string,
|
||||
accounts: PropTypes.object,
|
||||
address: PropTypes.string,
|
||||
defaultHdPaths: PropTypes.object,
|
||||
}
|
||||
|
||||
const mapStateToProps = state => {
|
||||
const {
|
||||
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 {
|
||||
goHome: () => {
|
||||
dispatch(actions.goHome())
|
||||
},
|
||||
setHardwareWalletDefaultHdPath: ({device, path}) => {
|
||||
return dispatch(actions.setHardwareWalletDefaultHdPath({device, path}))
|
||||
},
|
||||
connectHardware: (deviceName, page, hdPath) => {
|
||||
return dispatch(actions.connectHardware(deviceName, page, hdPath))
|
||||
},
|
||||
checkHardwareStatus: (deviceName, hdPath) => {
|
||||
return dispatch(actions.checkHardwareStatus(deviceName, hdPath))
|
||||
},
|
||||
forgetDevice: (deviceName) => {
|
||||
return dispatch(actions.forgetDevice(deviceName))
|
||||
},
|
||||
unlockHardwareWalletAccount: (index, deviceName, hdPath) => {
|
||||
return dispatch(actions.unlockHardwareWalletAccount(index, deviceName, hdPath))
|
||||
},
|
||||
showImportPage: () => dispatch(actions.showImportPage()),
|
||||
showConnectPage: () => dispatch(actions.showConnectPage()),
|
||||
showAlert: (msg) => dispatch(actions.showAlert(msg)),
|
||||
hideAlert: () => dispatch(actions.hideAlert()),
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = connect(mapStateToProps, mapDispatchToProps)(ConnectHardwareForm)
|
|
@ -0,0 +1,310 @@
|
|||
.hw-tutorial {
|
||||
width: 375px;
|
||||
border-top: 1px solid #D2D8DD;
|
||||
border-bottom: 1px solid #D2D8DD;
|
||||
overflow: visible;
|
||||
display: block;
|
||||
padding: 15px 30px;
|
||||
}
|
||||
|
||||
.hw-connect {
|
||||
width: 100%;
|
||||
}
|
||||
.hw-connect__header__title {
|
||||
margin-top: 5px;
|
||||
margin-bottom: 15px;
|
||||
font-size: 22px;
|
||||
}
|
||||
.hw-connect__header__msg {
|
||||
font-size: 14px;
|
||||
color: #9b9b9b;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.hw-connect__btn-wrapper {
|
||||
flex: 1;
|
||||
flex-direction: row;
|
||||
display: flex;
|
||||
}
|
||||
.hw-connect__connect-btn {
|
||||
color: #fff;
|
||||
border: none;
|
||||
width: 100%;
|
||||
min-height: 54px;
|
||||
font-weight: 300;
|
||||
font-size: 14px;
|
||||
margin-bottom: 20px;
|
||||
margin-top: 20px;
|
||||
border-radius: 5px;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
justify-content: center;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.hw-connect__connect-btn.disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: .5;
|
||||
}
|
||||
.hw-connect__btn {
|
||||
background: #fbfbfb;
|
||||
border: 1px solid #e5e5e5;
|
||||
height: 100px;
|
||||
width: 150px;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 5px;
|
||||
}
|
||||
.hw-connect__btn__img {
|
||||
width: 95px;
|
||||
}
|
||||
.hw-connect__btn.selected {
|
||||
border: 2px solid #60DB97;
|
||||
width: 149px;
|
||||
}
|
||||
.hw-connect__btn:first-child {
|
||||
margin-right: 15px;
|
||||
}
|
||||
.hw-connect__btn:last-child {
|
||||
|
||||
}
|
||||
.hw-connect__hdPath {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin-top: 15px;
|
||||
margin-bottom: 30px;
|
||||
font-size: 14px;
|
||||
}
|
||||
.hw-connect__hdPath__title {
|
||||
display: flex;
|
||||
margin-top: 10px;
|
||||
margin-right: 15px;
|
||||
}
|
||||
.hw-connect__hdPath__select {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
}
|
||||
.hw-connect__learn-more {
|
||||
margin-top: 15px;
|
||||
font-size: 14px;
|
||||
color: #5B5D67;
|
||||
line-height: 19px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
.hw-connect__learn-more__arrow {
|
||||
-webkit-transform: rotate(90deg);
|
||||
transform: rotate(90deg);
|
||||
display: block;
|
||||
text-align: center;
|
||||
height: 30px;
|
||||
margin: 0px auto 10px;
|
||||
}
|
||||
.hw-connect__title {
|
||||
padding-top: 10px;
|
||||
font-weight: 400;
|
||||
font-size: 18px;
|
||||
}
|
||||
.hw-connect__unlock-title {
|
||||
padding-top: 10px;
|
||||
font-weight: 400;
|
||||
font-size: 22px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.hw-connect__msg {
|
||||
font-size: 14px;
|
||||
color: #9b9b9b;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.hw-connect__link {
|
||||
color: #2f9ae0;
|
||||
}
|
||||
.hw-connect__footer__title {
|
||||
padding-top: 15px;
|
||||
padding-bottom: 12px;
|
||||
font-weight: 400;
|
||||
font-size: 18px;
|
||||
text-align: center;
|
||||
}
|
||||
.hw-connect__footer__msg {
|
||||
font-size: 14px;
|
||||
color: #9b9b9b;
|
||||
margin-top: 12px;
|
||||
margin-bottom: 27px;
|
||||
width: 100%;
|
||||
display: block;
|
||||
margin-left: 20px;
|
||||
}
|
||||
.hw-connect__footer__link {
|
||||
color: #2f9ae0;
|
||||
margin-left: 5px;
|
||||
}
|
||||
.hw-connect__get-hw {
|
||||
width: 100%;
|
||||
padding-bottom: 10px;
|
||||
padding-top: 10px;
|
||||
}
|
||||
.hw-connect__get-hw__msg {
|
||||
font-size: 14px;
|
||||
color: #9b9b9b;
|
||||
}
|
||||
.hw-connect__get-hw__link {
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
color: #60DB97;
|
||||
cursor: pointer;
|
||||
}
|
||||
.hw-connect__step-asset {
|
||||
margin: 0px auto 20px;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.hw-account-list {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-flow: column;
|
||||
width: 100%;
|
||||
}
|
||||
.hw-account-list__title_wrapper {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex: 1;
|
||||
}
|
||||
.hw-account-list__title {
|
||||
margin-bottom: 23px;
|
||||
align-self: flex-start;
|
||||
color: #5d5d5d;
|
||||
font-family: Nunito Regular;
|
||||
font-size: 16px;
|
||||
line-height: 21px;
|
||||
font-weight: bold;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
}
|
||||
.hw-account-list__device {
|
||||
margin-bottom: 23px;
|
||||
align-self: flex-end;
|
||||
color: #5d5d5d;
|
||||
font-family: Nunito Regular;
|
||||
font-size: 16px;
|
||||
line-height: 21px;
|
||||
font-weight: normal;
|
||||
display: flex;
|
||||
}
|
||||
.hw-account-list__item {
|
||||
font-size: 15px;
|
||||
flex-direction: row;
|
||||
display: flex;
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
.hw-account-list__item:nth-of-type(even) {
|
||||
background-color: rgba(103, 41, 168, 0.1);
|
||||
}
|
||||
.hw-account-list__item:nth-of-type(odd) {
|
||||
background: rgba(103, 41, 168, 0.2);
|
||||
}
|
||||
.hw-account-list__item:hover {
|
||||
background-color: rgba(103, 41, 168, 0.06);
|
||||
}
|
||||
.hw-account-list__item__index {
|
||||
display: flex;
|
||||
width: 24px;
|
||||
}
|
||||
.hw-account-list__item__radio {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
}
|
||||
.hw-account-list__item__radio input {
|
||||
padding: 10px;
|
||||
margin-top: 13px;
|
||||
}
|
||||
.hw-account-list__item__label {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
padding-left: 10px;
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
.hw-account-list__item__balance {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
justify-content: center;
|
||||
font-family: 'Nunito Bold';
|
||||
cursor: pointer;
|
||||
}
|
||||
.hw-account-list__item__link {
|
||||
display: flex;
|
||||
margin-top: 13px;
|
||||
}
|
||||
.hw-account-list__item__link img {
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
}
|
||||
.hw-list-pagination {
|
||||
margin-top: 10px;
|
||||
}
|
||||
.hw-list-pagination__button {
|
||||
height: 19px;
|
||||
float: right;
|
||||
color: #6729a8;
|
||||
font-size: 14px;
|
||||
line-height: 19px;
|
||||
border: none;
|
||||
min-width: 46px;
|
||||
padding: 0px;
|
||||
font-family: Nunito Regular;
|
||||
background: transparent;
|
||||
}
|
||||
.hw-forget-device-container {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
align-items: center;
|
||||
padding: 22px;
|
||||
}
|
||||
.hw-forget-device-container a {
|
||||
color: #60db97;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.new-account-connect-form {
|
||||
flex-flow: column;
|
||||
align-items: center;
|
||||
}
|
||||
.new-account-connect-form.unsupported-browser {
|
||||
height: 210px;
|
||||
}
|
||||
.new-account-connect-form.account-list {
|
||||
height: auto;
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
}
|
||||
.new-account-connect-form__buttons {
|
||||
margin-top: 39px;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.new-account-connect-form__button {
|
||||
width: 150px;
|
||||
min-width: initial;
|
||||
}
|
||||
.new-account-connect-form .btn-primary {
|
||||
background-color: #259DE5;
|
||||
color: #FFFFFF;
|
||||
border: none;
|
||||
width: 100%;
|
||||
min-height: 54px;
|
||||
font-weight: 300;
|
||||
font-size: 14px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.new-account-connect-form__button.unlock {
|
||||
width: 50%;
|
||||
}
|
||||
.new-account-connect-form__button.btn-primary--disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: .5;
|
||||
}
|
|
@ -40,7 +40,7 @@ RevealSeedConfirmation.prototype.render = function () {
|
|||
h('.page-subtitle', 'Reveal Seed Words'),
|
||||
]),
|
||||
|
||||
h('.div', {
|
||||
h('div', {
|
||||
style: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
|
|
|
@ -8,6 +8,7 @@ var cssFiles = {
|
|||
'reset.css': fs.readFileSync(path.join(__dirname, '/app/css/reset.css'), 'utf8'),
|
||||
'lib.css': fs.readFileSync(path.join(__dirname, '/app/css/lib.css'), 'utf8'),
|
||||
'search-token.css': fs.readFileSync(path.join(__dirname, '/app/css/search-token.css'), 'utf8'),
|
||||
'hw.css': fs.readFileSync(path.join(__dirname, '/app/css/hw.css'), 'utf8'),
|
||||
'confirm-add-token.css': fs.readFileSync(path.join(__dirname, '/app/css/confirm-add-token.css'), 'utf8'),
|
||||
'page-container.css': fs.readFileSync(path.join(__dirname, '/app/css/page-container.css'), 'utf8'),
|
||||
'index.css': fs.readFileSync(path.join(__dirname, '/app/css/index.css'), 'utf8'),
|
||||
|
|
|
@ -113,8 +113,8 @@
|
|||
"eth-block-tracker": "^4.0.3",
|
||||
"eth-contract-metadata": "github:MetaMask/eth-contract-metadata#master",
|
||||
"eth-ens-namehash": "^2.0.8",
|
||||
"eth-hd-keyring": "^2.0.0",
|
||||
"eth-json-rpc-filters": "github:poanetwork/eth-json-rpc-filters#3.0.2",
|
||||
"eth-json-rpc-filters": "^3.0.1",
|
||||
"eth-json-rpc-infura": "^3.0.0",
|
||||
"eth-keychain-controller": "^5.0.0",
|
||||
"eth-ledger-bridge-keyring": "^0.1.0",
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
const assert = require('assert')
|
||||
const { sufficientBalance } = require('../../../app/scripts/lib/util')
|
||||
const { sufficientBalance, capitalizeFirstLetter } = require('../../../app/scripts/lib/util')
|
||||
|
||||
|
||||
describe('SufficientBalance', function () {
|
||||
|
@ -39,3 +39,14 @@ describe('SufficientBalance', function () {
|
|||
assert.ok(!result, 'insufficient balance found.')
|
||||
})
|
||||
})
|
||||
|
||||
describe('capitalizeFirstLetter', () => {
|
||||
it('returns correct output with capitalized first letter of the first word', () => {
|
||||
assert.equal('T', capitalizeFirstLetter('t'))
|
||||
assert.equal('Test', capitalizeFirstLetter('test'))
|
||||
assert.equal('Test with multiple words', capitalizeFirstLetter('test with multiple words'))
|
||||
assert.equal('Test with multiple words', capitalizeFirstLetter('Test with multiple words'))
|
||||
assert.equal('', capitalizeFirstLetter(''))
|
||||
assert.equal('', capitalizeFirstLetter())
|
||||
})
|
||||
})
|
||||
|
|
|
@ -71,6 +71,7 @@ var actions = {
|
|||
SHOW_NEW_VAULT_SEED: 'SHOW_NEW_VAULT_SEED',
|
||||
SHOW_INFO_PAGE: 'SHOW_INFO_PAGE',
|
||||
SHOW_IMPORT_PAGE: 'SHOW_IMPORT_PAGE',
|
||||
SHOW_HARDWARE_WALLET_PAGE: 'SHOW_HARDWARE_WALLET_PAGE',
|
||||
SHOW_NEW_ACCOUNT_PAGE: 'SHOW_NEW_ACCOUNT_PAGE',
|
||||
SET_NEW_ACCOUNT_FORM: 'SET_NEW_ACCOUNT_FORM',
|
||||
unlockMetamask: unlockMetamask,
|
||||
|
@ -80,6 +81,7 @@ var actions = {
|
|||
showRestoreVault: showRestoreVault,
|
||||
showInitializeMenu: showInitializeMenu,
|
||||
showImportPage,
|
||||
showConnectHWWalletPage: showConnectHWWalletPage,
|
||||
showNewAccountPage,
|
||||
setNewAccountForm,
|
||||
createNewVaultAndKeychain: createNewVaultAndKeychain,
|
||||
|
@ -1114,8 +1116,9 @@ function sendTx (txData) {
|
|||
log.debug(`actions calling background.approveTransaction`)
|
||||
background.approveTransaction(txData.id, (err) => {
|
||||
if (err) {
|
||||
err = err.message || err.error || err
|
||||
dispatch(actions.txError(err))
|
||||
return log.error(err.message)
|
||||
return log.error(err)
|
||||
}
|
||||
dispatch(actions.completedTx(txData.id))
|
||||
|
||||
|
@ -1149,6 +1152,7 @@ function updateTransaction (txData) {
|
|||
background.updateTransaction(txData, (err) => {
|
||||
dispatch(actions.updateTransactionParams(txData.id, txData.txParams))
|
||||
if (err) {
|
||||
err = err.message || err.error || err
|
||||
dispatch(actions.txError(err))
|
||||
dispatch(actions.goHome())
|
||||
log.error(err.message)
|
||||
|
@ -1180,9 +1184,10 @@ function updateAndApproveTx (txData) {
|
|||
dispatch(actions.clearSend())
|
||||
|
||||
if (err) {
|
||||
err = err.message || err.error || err
|
||||
dispatch(actions.txError(err))
|
||||
dispatch(actions.goHome())
|
||||
log.error(err.message)
|
||||
log.error(err)
|
||||
reject(err)
|
||||
}
|
||||
|
||||
|
@ -1228,7 +1233,7 @@ function updateTransactionParams (id, txParams) {
|
|||
function txError (err) {
|
||||
return {
|
||||
type: actions.TRANSACTION_ERROR,
|
||||
message: err.message,
|
||||
message: (err.message || err.error || err),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1451,6 +1456,12 @@ function showImportPage () {
|
|||
}
|
||||
}
|
||||
|
||||
function showConnectHWWalletPage () {
|
||||
return {
|
||||
type: actions.SHOW_HARDWARE_WALLET_PAGE,
|
||||
}
|
||||
}
|
||||
|
||||
function showNewAccountPage (formToSelect) {
|
||||
return {
|
||||
type: actions.SHOW_NEW_ACCOUNT_PAGE,
|
||||
|
|
|
@ -258,6 +258,15 @@ function reduceApp (state, action) {
|
|||
warning: null,
|
||||
})
|
||||
|
||||
case actions.SHOW_HARDWARE_WALLET_PAGE:
|
||||
return extend(appState, {
|
||||
currentView: {
|
||||
name: 'hardware-wallets-menu',
|
||||
},
|
||||
transForward: true,
|
||||
warning: null,
|
||||
})
|
||||
|
||||
case actions.SHOW_NEW_ACCOUNT_PAGE:
|
||||
return extend(appState, {
|
||||
currentView: {
|
||||
|
|
Loading…
Reference in New Issue