Merge pull request #356 from poanetwork/vb-rsk-dpath-update

Custom derivation paths and access to funds in accounts derived from ETH dPath
This commit is contained in:
Victor Baranov 2020-05-07 18:25:01 +03:00 committed by GitHub
commit 7662c11542
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 250 additions and 103 deletions

View File

@ -2,9 +2,11 @@
## Current Master ## Current Master
- [#356](https://github.com/poanetwork/nifty-wallet/pull/356) - (Backwards-compatibility feature) Custom derivation paths and access to funds in accounts derived from ETH dPath
- [#379](https://github.com/poanetwork/nifty-wallet/pull/379) - (Feature) Ability to set custom nonce of tx - [#379](https://github.com/poanetwork/nifty-wallet/pull/379) - (Feature) Ability to set custom nonce of tx
- [#377](https://github.com/poanetwork/nifty-wallet/pull/377) - (Fix) Sign message screen: do not decode message if it is not hex encoded - [#377](https://github.com/poanetwork/nifty-wallet/pull/377) - (Fix) Sign message screen: do not decode message if it is not hex encoded
- [#364](https://github.com/poanetwork/nifty-wallet/pull/364) - (Fix) notifications order in batch requests - [#364](https://github.com/poanetwork/nifty-wallet/pull/364) - (Fix) notifications order in batch requests
- [#364](https://github.com/poanetwork/nifty-wallet/pull/364) - Fix notifications order in batch requests
## 5.0.3 Fri May 01 2020 ## 5.0.3 Fri May 01 2020
@ -14,6 +16,7 @@
- [#368](https://github.com/poanetwork/nifty-wallet/pull/368) - (Fix) Ability to import Keystore file if it is not secured by password - [#368](https://github.com/poanetwork/nifty-wallet/pull/368) - (Fix) Ability to import Keystore file if it is not secured by password
- [#366](https://github.com/poanetwork/nifty-wallet/pull/366) - (Fix) Increase max token symbol length up to 12 - [#366](https://github.com/poanetwork/nifty-wallet/pull/366) - (Fix) Increase max token symbol length up to 12
- [#363](https://github.com/poanetwork/nifty-wallet/pull/363) - (Fix) token decimals display in pending tx screen - [#363](https://github.com/poanetwork/nifty-wallet/pull/363) - (Fix) token decimals display in pending tx screen
- [#356](https://github.com/poanetwork/nifty-wallet/pull/356) - (Backwards-compatibility feature) Custom derivation paths and access to funds in accounts derived from ETH dPath
## 5.0.2 Thu Apr 16 2020 ## 5.0.2 Thu Apr 16 2020

View File

@ -12,10 +12,10 @@ import createJsonRpcClient from './createJsonRpcClient'
import createLocalhostClient from './createLocalhostClient' import createLocalhostClient from './createLocalhostClient'
const createPocketClient = require('./createPocketClient') const createPocketClient = require('./createPocketClient')
const { createSwappableProxy, createEventEmitterProxy } = require('swappable-obj-proxy') const { createSwappableProxy, createEventEmitterProxy } = require('swappable-obj-proxy')
const ethNetProps = require('eth-net-props') import ethNetProps from 'eth-net-props'
import parse from 'url-parse' import parse from 'url-parse'
const networks = { networkList: {} } const networks = { networkList: {} }
const { isKnownProvider, getDPath } = require('../../../../old-ui/app/util') const { isKnownProvider } = require('../../../../old-ui/app/util')
const { const {
ROPSTEN, ROPSTEN,
@ -205,8 +205,6 @@ module.exports = class NetworkController extends EventEmitter {
const previousNetworkID = this.getNetworkState() const previousNetworkID = this.getNetworkState()
this.setNetworkState('loading') this.setNetworkState('loading')
this._configureProvider(opts) this._configureProvider(opts)
const dPath = getDPath(opts.type)
this.store.updateState({ dPath })
this.emit('networkDidChange', opts.type, previousNetworkID) this.emit('networkDidChange', opts.type, previousNetworkID)
} }

View File

@ -1,5 +1,5 @@
const KeyringController = require('eth-keychain-controller') import KeyringController from 'eth-keychain-controller'
const log = require('loglevel') import log from 'loglevel'
const { getDPath } = require('../../../old-ui/app/util') const { getDPath } = require('../../../old-ui/app/util')
const seedPhraseVerifier = { const seedPhraseVerifier = {
@ -17,14 +17,14 @@ const seedPhraseVerifier = {
* @returns {Promise<void>} Promises undefined * @returns {Promise<void>} Promises undefined
* *
*/ */
verifyAccounts (createdAccounts, seedWords, network) { verifyAccounts (createdAccounts, seedWords, network, isCreatedWithCorrectDPath) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (!createdAccounts || createdAccounts.length < 1) { if (!createdAccounts || createdAccounts.length < 1) {
return reject(new Error('No created accounts defined.')) return reject(new Error('No created accounts defined.'))
} }
const dPath = getDPath(network) const dPath = getDPath(network, isCreatedWithCorrectDPath)
const keyringController = new KeyringController({}) const keyringController = new KeyringController({})
const Keyring = keyringController.getKeyringClassForType('HD Key Tree') const Keyring = keyringController.getKeyringClassForType('HD Key Tree')
const opts = { const opts = {
@ -56,4 +56,4 @@ const seedPhraseVerifier = {
}, },
} }
module.exports = seedPhraseVerifier export default seedPhraseVerifier

View File

@ -52,7 +52,7 @@ const version = require('../manifest.json').version
import ethUtil, { BN } from 'ethereumjs-util' import ethUtil, { BN } from 'ethereumjs-util'
const GWEI_BN = new BN('1000000000') const GWEI_BN = new BN('1000000000')
import percentile from 'percentile' import percentile from 'percentile'
const seedPhraseVerifier = require('./lib/seed-phrase-verifier') import seedPhraseVerifier from './lib/seed-phrase-verifier'
import log from 'loglevel' import log from 'loglevel'
const TrezorKeyring = require('eth-trezor-keyring') const TrezorKeyring = require('eth-trezor-keyring')
const LedgerBridgeKeyring = require('eth-ledger-bridge-keyring') const LedgerBridgeKeyring = require('eth-ledger-bridge-keyring')
@ -169,26 +169,32 @@ module.exports = class MetamaskController extends EventEmitter {
}) })
// ensure accountTracker updates balances after network change // ensure accountTracker updates balances after network change
this.networkController.on('networkDidChange', (newType, previousNetworkIDStr) => { this.networkController.on('networkDidChange', (newNetworkType, previousNetworkIDStr) => {
const dPath = getDPath(newType) this.keyringController.isCreatedWithCorrectDPath()
this.deriveKeyringFromNewDPath(dPath) .then(isCreatedWithCorrectDPath => {
.then(accounts => { const dPath = getDPath(newNetworkType, isCreatedWithCorrectDPath)
this.accountTracker._updateAccounts() this.deriveKeyringFromNewDPath(dPath)
this.detectTokensController.restartTokenDetection() .then(_accounts => {
this.accountTracker._updateAccounts()
this.detectTokensController.restartTokenDetection()
const previousNetworkID = parseInt(previousNetworkIDStr, 10) const previousNetworkID = parseInt(previousNetworkIDStr, 10)
const nextNetwork = getNetworkID({network: newType}) const nextNetwork = getNetworkID({network: newNetworkType})
const nextNetworkID = parseInt(nextNetwork && nextNetwork.netId, 10) const nextNetworkID = parseInt(nextNetwork && nextNetwork.netId, 10)
if (nextNetworkID !== previousNetworkID) { if (nextNetworkID !== previousNetworkID) {
const isPreviousETC = previousNetworkID === CLASSIC_CODE const isPreviousETC = previousNetworkID === CLASSIC_CODE
const isPreviousRSK = ifRSK(previousNetworkID) const isPreviousRSK = ifRSK(previousNetworkID)
const isNextETC = nextNetworkID === CLASSIC_CODE const isNextETC = nextNetworkID === CLASSIC_CODE
const isNextRSK = ifRSK(nextNetworkID) const isNextRSK = ifRSK(nextNetworkID)
if (isPreviousETC || isPreviousRSK || isNextETC || isNextRSK) { if (isPreviousETC || isPreviousRSK || isNextETC || isNextRSK) {
this.forgetDevice(LEDGER, false) this.forgetDevice(LEDGER, false)
this.forgetDevice(TREZOR, false) this.forgetDevice(TREZOR, false)
}
} }
} })
.catch(e => {
console.log(e)
})
}) })
.catch(e => { .catch(e => {
console.log(e) console.log(e)
@ -493,6 +499,7 @@ module.exports = class MetamaskController extends EventEmitter {
addNewKeyring: nodeify(keyringController.addNewKeyring, keyringController), addNewKeyring: nodeify(keyringController.addNewKeyring, keyringController),
addNewMultisig: nodeify(keyringController.addNewMultisig, keyringController), addNewMultisig: nodeify(keyringController.addNewMultisig, keyringController),
exportAccount: nodeify(keyringController.exportAccount, keyringController), exportAccount: nodeify(keyringController.exportAccount, keyringController),
isCreatedWithCorrectDPath: nodeify(keyringController.isCreatedWithCorrectDPath, keyringController),
// txController // txController
cancelTransaction: nodeify(txController.cancelTransaction, txController), cancelTransaction: nodeify(txController.cancelTransaction, txController),
@ -582,7 +589,7 @@ module.exports = class MetamaskController extends EventEmitter {
* @param {} password * @param {} password
* @param {} seed * @param {} seed
*/ */
async createNewVaultAndRestore (password, seed) { async createNewVaultAndRestore (password, seed, dPath) {
const releaseLock = await this.createVaultMutex.acquire() const releaseLock = await this.createVaultMutex.acquire()
try { try {
let accounts, lastBalance let accounts, lastBalance
@ -592,9 +599,8 @@ module.exports = class MetamaskController extends EventEmitter {
// clear known identities // clear known identities
this.preferencesController.setAddresses([]) this.preferencesController.setAddresses([])
// create new vault // create new vault
const network = this.networkController.getProviderConfig().type const networkType = this.networkController.getProviderConfig().type
const dPath = getDPath(network) const isCreatedWithCorrectDPath = true
this.store.updateState({dPath})
const vault = await keyringController.createNewVaultAndRestore(password, seed, dPath) const vault = await keyringController.createNewVaultAndRestore(password, seed, dPath)
const ethQuery = new EthQuery(this.provider) const ethQuery = new EthQuery(this.provider)
@ -606,7 +612,7 @@ module.exports = class MetamaskController extends EventEmitter {
throw new Error('MetamaskController - No HD Key Tree found') throw new Error('MetamaskController - No HD Key Tree found')
} }
setDPath(primaryKeyring, network) setDPath(primaryKeyring, networkType, isCreatedWithCorrectDPath)
// seek out the first zero balance // seek out the first zero balance
while (lastBalance !== '0x0') { while (lastBalance !== '0x0') {
@ -911,13 +917,14 @@ module.exports = class MetamaskController extends EventEmitter {
* @returns {} keyState * @returns {} keyState
*/ */
async addNewAccount () { async addNewAccount () {
const primaryKeyring = this.keyringController.getKeyringsByType('HD Key Tree')[0] const keyringController = this.keyringController
const primaryKeyring = keyringController.getKeyringsByType('HD Key Tree')[0]
if (!primaryKeyring) { if (!primaryKeyring) {
throw new Error('MetamaskController - No HD Key Tree found') throw new Error('MetamaskController - No HD Key Tree found')
} }
const network = this.networkController.getProviderConfig().type const networkType = this.networkController.getProviderConfig().type
setDPath(primaryKeyring, network) const isCreatedWithCorrectDPath = await keyringController.isCreatedWithCorrectDPath()
const keyringController = this.keyringController setDPath(primaryKeyring, networkType, isCreatedWithCorrectDPath)
const oldAccounts = await keyringController.getAccounts() const oldAccounts = await keyringController.getAccounts()
const keyState = await keyringController.addNewAccount(primaryKeyring) const keyState = await keyringController.addNewAccount(primaryKeyring)
const newAccounts = await keyringController.getAccounts() const newAccounts = await keyringController.getAccounts()
@ -965,12 +972,14 @@ module.exports = class MetamaskController extends EventEmitter {
* @returns {Promise<string>} Seed phrase to be confirmed by the user. * @returns {Promise<string>} Seed phrase to be confirmed by the user.
*/ */
async verifySeedPhrase () { async verifySeedPhrase () {
const primaryKeyring = this.keyringController.getKeyringsByType('HD Key Tree')[0] const keyringController = this.keyringController
const isCreatedWithCorrectDPath = await keyringController.isCreatedWithCorrectDPath()
const primaryKeyring = keyringController.getKeyringsByType('HD Key Tree')[0]
if (!primaryKeyring) { if (!primaryKeyring) {
throw new Error('MetamaskController - No HD Key Tree found') throw new Error('MetamaskController - No HD Key Tree found')
} }
const network = this.networkController.getProviderConfig().type const networkType = this.networkController.getProviderConfig().type
setDPath(primaryKeyring, network) setDPath(primaryKeyring, networkType, isCreatedWithCorrectDPath)
const serialized = await primaryKeyring.serialize() const serialized = await primaryKeyring.serialize()
const seedWords = serialized.mnemonic const seedWords = serialized.mnemonic
@ -981,7 +990,7 @@ module.exports = class MetamaskController extends EventEmitter {
} }
try { try {
await seedPhraseVerifier.verifyAccounts(accounts, seedWords, network) await seedPhraseVerifier.verifyAccounts(accounts, seedWords, networkType, isCreatedWithCorrectDPath)
return seedWords return seedWords
} catch (err) { } catch (err) {
log.error(err.message) log.error(err.message)
@ -1086,8 +1095,6 @@ module.exports = class MetamaskController extends EventEmitter {
const privateKey = await accountImporter.importAccount(strategy, args) const privateKey = await accountImporter.importAccount(strategy, args)
keyring = await this.keyringController.addNewKeyring('Simple Key Pair', [ privateKey ]) keyring = await this.keyringController.addNewKeyring('Simple Key Pair', [ privateKey ])
} }
const network = this.networkController.getProviderConfig().type
setDPath(keyring, network)
const accounts = await keyring.getAccounts() const accounts = await keyring.getAccounts()
// update accounts in preferences controller // update accounts in preferences controller
const allAccounts = await this.keyringController.getAccounts() const allAccounts = await this.keyringController.getAccounts()

Binary file not shown.

Binary file not shown.

View File

@ -4,7 +4,7 @@ const Component = require('react').Component
const h = require('react-hyperscript') const h = require('react-hyperscript')
const connect = require('react-redux').connect const connect = require('react-redux').connect
const actions = require('../../ui/app/actions') const actions = require('../../ui/app/actions')
const { getCurrentKeyring, ifContractAcc, valuesFor, toChecksumAddress } = require('./util') const { getCurrentKeyring, ifContractAcc, valuesFor, toChecksumAddress, ifLooseAcc, ifRSK, ifETC } = require('./util')
const Identicon = require('./components/identicon') const Identicon = require('./components/identicon')
const EthBalance = require('./components/eth-balance') const EthBalance = require('./components/eth-balance')
const TransactionList = require('./components/transaction-list') const TransactionList = require('./components/transaction-list')
@ -14,10 +14,10 @@ const TabBar = require('./components/tab-bar')
const TokenList = require('./components/token-list') const TokenList = require('./components/token-list')
const AccountDropdowns = require('./components/account-dropdowns/account-dropdowns.component').AccountDropdowns const AccountDropdowns = require('./components/account-dropdowns/account-dropdowns.component').AccountDropdowns
const CopyButton = require('./components/copy/copy-button') const CopyButton = require('./components/copy/copy-button')
const ToastComponent = require('./components/toast') import * as Toast from './components/toast'
import { getMetaMaskAccounts } from '../../ui/app/selectors' import { getMetaMaskAccounts } from '../../ui/app/selectors'
module.exports = connect(mapStateToProps)(AccountDetailScreen) module.exports = connect(mapStateToProps, mapDispatchToProps)(AccountDetailScreen)
function mapStateToProps (state) { function mapStateToProps (state) {
const accounts = getMetaMaskAccounts(state) const accounts = getMetaMaskAccounts(state)
@ -42,11 +42,66 @@ function mapStateToProps (state) {
} }
} }
function mapDispatchToProps (dispatch) {
return {
actions: {
showSendPage: () => dispatch(actions.showSendPage()),
showSendContractPage: ({ methodSelected, methodABI, inputValues }) => dispatch(actions.showSendContractPage({methodSelected, methodABI, inputValues})),
buyEthView: (selected) => dispatch(actions.buyEthView(selected)),
viewPendingTx: (txId) => dispatch(actions.viewPendingTx(txId)),
setAccountLabel: (account, label) => dispatch(actions.setAccountLabel(account, label)),
showRemoveTokenPage: (token) => dispatch(actions.showRemoveTokenPage(token)),
showAddSuggestedTokenPage: () => dispatch(actions.showAddSuggestedTokenPage()),
showAddTokenPage: () => dispatch(actions.showAddTokenPage()),
setCurrentAccountTab: (key) => dispatch(actions.setCurrentAccountTab(key)),
displayToast: (msg) => dispatch(actions.displayToast(msg)),
isCreatedWithCorrectDPath: () => dispatch(actions.isCreatedWithCorrectDPath()),
},
}
}
inherits(AccountDetailScreen, Component) inherits(AccountDetailScreen, Component)
function AccountDetailScreen () { function AccountDetailScreen () {
Component.call(this) Component.call(this)
} }
AccountDetailScreen.prototype.componentDidMount = function () {
const props = this.props
const { address, network, keyrings, identities } = props
props.actions.isCreatedWithCorrectDPath()
.then(isCreatedWithCorrectDPath => {
if (!isCreatedWithCorrectDPath) {
const currentKeyring = getCurrentKeyring(address, network, keyrings, identities)
if (!ifLooseAcc(currentKeyring) && !ifContractAcc(currentKeyring) && (ifRSK(network) || ifETC(network))) {
props.actions.displayToast(Toast.ERROR_ON_INCORRECT_DPATH)
}
}
})
}
AccountDetailScreen.prototype.componentWillUpdate = function (nextProps) {
const {
network: oldNet,
} = this.props
const {
network: newNet,
} = nextProps
if (oldNet !== newNet) {
const props = this.props
const { address, keyrings, identities } = props
props.actions.isCreatedWithCorrectDPath()
.then(isCreatedWithCorrectDPath => {
if (!isCreatedWithCorrectDPath) {
const currentKeyring = getCurrentKeyring(address, newNet, keyrings, identities)
if (!ifLooseAcc(currentKeyring) && !ifContractAcc(currentKeyring) && (ifRSK(newNet) || ifETC(newNet))) {
props.actions.displayToast(Toast.ERROR_ON_INCORRECT_DPATH)
}
}
})
}
}
AccountDetailScreen.prototype.render = function () { AccountDetailScreen.prototype.render = function () {
const props = this.props const props = this.props
const { network, conversionRate, currentCurrency } = props const { network, conversionRate, currentCurrency } = props
@ -56,7 +111,7 @@ AccountDetailScreen.prototype.render = function () {
const account = props.accounts[selected] const account = props.accounts[selected]
if (Object.keys(props.suggestedTokens).length > 0) { if (Object.keys(props.suggestedTokens).length > 0) {
this.props.dispatch(actions.showAddSuggestedTokenPage()) this.props.actions.showAddSuggestedTokenPage()
} }
const currentKeyring = getCurrentKeyring(props.address, network, props.keyrings, props.identities) const currentKeyring = getCurrentKeyring(props.address, network, props.keyrings, props.identities)
@ -65,8 +120,9 @@ AccountDetailScreen.prototype.render = function () {
h('.account-detail-section.full-flex-height', [ h('.account-detail-section.full-flex-height', [
h(ToastComponent, { h(Toast.ToastComponent, {
isSuccess: false, type: Toast.TOAST_TYPE_ERROR,
hideManually: true,
}), }),
// identicon, label, balance, etc // identicon, label, balance, etc
@ -108,7 +164,7 @@ AccountDetailScreen.prototype.render = function () {
isEditingLabel: false, isEditingLabel: false,
}, },
saveText: (text) => { saveText: (text) => {
props.dispatch(actions.setAccountLabel(selected, text)) props.actions.setAccountLabel(selected, text)
}, },
}, [ }, [
@ -223,16 +279,16 @@ AccountDetailScreen.prototype.render = function () {
h('.flex-grow'), h('.flex-grow'),
!ifContractAcc(currentKeyring) ? h('button', { !ifContractAcc(currentKeyring) ? h('button', {
onClick: () => props.dispatch(actions.buyEthView(selected)), onClick: () => props.actions.buyEthView(selected),
style: { marginRight: '10px' }, style: { marginRight: '10px' },
}, 'Buy') : null, }, 'Buy') : null,
h('button', { h('button', {
onClick: () => { onClick: () => {
if (ifContractAcc(currentKeyring)) { if (ifContractAcc(currentKeyring)) {
return props.dispatch(actions.showSendContractPage({})) return props.actions.showSendContractPage({})
} else { } else {
return props.dispatch(actions.showSendPage()) return props.actions.showSendPage()
} }
}, },
}, ifContractAcc(currentKeyring) ? 'Execute methods' : 'Send'), }, ifContractAcc(currentKeyring) ? 'Execute methods' : 'Send'),
@ -278,7 +334,7 @@ AccountDetailScreen.prototype.tabSections = function () {
], ],
defaultTab: currentAccountTab || 'history', defaultTab: currentAccountTab || 'history',
tabSelected: (key) => { tabSelected: (key) => {
this.props.dispatch(actions.setCurrentAccountTab(key)) this.props.actions.setCurrentAccountTab(key)
}, },
}), }),
@ -297,8 +353,8 @@ AccountDetailScreen.prototype.tabSwitchView = function () {
userAddress: address, userAddress: address,
network, network,
tokens, tokens,
addToken: () => this.props.dispatch(actions.showAddTokenPage()), addToken: () => this.props.actions.showAddTokenPage(),
removeToken: (token) => this.props.dispatch(actions.showRemoveTokenPage(token)), removeToken: (token) => this.props.actions.showRemoveTokenPage(token),
}) })
default: default:
return this.transactionList() return this.transactionList()
@ -317,7 +373,7 @@ AccountDetailScreen.prototype.transactionList = function () {
address, address,
shapeShiftTxList, shapeShiftTxList,
viewPendingTx: (txId) => { viewPendingTx: (txId) => {
this.props.dispatch(actions.viewPendingTx(txId)) this.props.actions.viewPendingTx(txId)
}, },
}) })
} }

View File

@ -4,10 +4,11 @@ import actions from '../../../../ui/app/actions'
import { connect } from 'react-redux' import { connect } from 'react-redux'
import { DropdownMenuItem } from '../dropdown' import { DropdownMenuItem } from '../dropdown'
import Identicon from '../identicon' import Identicon from '../identicon'
import { ifLooseAcc, ifContractAcc, ifHardwareAcc } from '../../util' import { ifLooseAcc, ifContractAcc, ifHardwareAcc, ifRSK, ifETC } from '../../util'
import { getHdPaths, isLedger } from '../connect-hardware/util' import { getHdPaths, isLedger } from '../connect-hardware/util'
import { LEDGER } from '../connect-hardware/enum' import { LEDGER } from '../connect-hardware/enum'
import { importTypes, labels } from '../../accounts/import/enums' import { importTypes, labels } from '../../accounts/import/enums'
import { ERROR_ON_INCORRECT_DPATH } from '../toast'
class AccountsDropdownItemView extends Component { class AccountsDropdownItemView extends Component {
static propTypes = { static propTypes = {
@ -100,7 +101,7 @@ class AccountsDropdownItemView extends Component {
} }
} }
ifProxyAcc (address, setProxy) { ifProxyAcc (address, _setProxy) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.props.actions.getContract(address) this.props.actions.getContract(address)
.then(contractProps => { .then(contractProps => {
@ -138,6 +139,15 @@ class AccountsDropdownItemView extends Component {
} else { } else {
this.preventToast() this.preventToast()
} }
} else if (!ifLooseAcc(keyring) && !ifContractAcc(keyring) && (ifRSK(this.props.network) || ifETC(this.props.network))) {
this.props.actions.isCreatedWithCorrectDPath()
.then(isCreatedWithCorrectDPath => {
if (isCreatedWithCorrectDPath) {
this.preventToast()
} else {
this.props.actions.displayToast(ERROR_ON_INCORRECT_DPATH)
}
})
} else { } else {
this.preventToast() this.preventToast()
} }
@ -152,6 +162,13 @@ class AccountsDropdownItemView extends Component {
} }
} }
function mapStateToProps (state) {
const result = {
network: state.metamask.network,
}
return result
}
const mapDispatchToProps = (dispatch) => { const mapDispatchToProps = (dispatch) => {
return { return {
@ -163,10 +180,11 @@ const mapDispatchToProps = (dispatch) => {
return dispatch(actions.connectHardwareAndUnlockAddress(deviceName, hdPath, address)) return dispatch(actions.connectHardwareAndUnlockAddress(deviceName, hdPath, address))
}, },
displayToast: (msg) => dispatch(actions.displayToast(msg)), displayToast: (msg) => dispatch(actions.displayToast(msg)),
isCreatedWithCorrectDPath: () => dispatch(actions.isCreatedWithCorrectDPath()),
}, },
} }
} }
module.exports = { module.exports = {
AccountsDropdownItemView: connect(null, mapDispatchToProps)(AccountsDropdownItemView), AccountsDropdownItemView: connect(mapStateToProps, mapDispatchToProps)(AccountsDropdownItemView),
} }

View File

@ -25,7 +25,7 @@ const { tokenInfoGetter, calcTokenAmount } = require('../../../ui/app/token-util
import BigNumber from 'bignumber.js' import BigNumber from 'bignumber.js'
import ethNetProps from 'eth-net-props' import ethNetProps from 'eth-net-props'
import { getMetaMaskAccounts } from '../../../ui/app/selectors' import { getMetaMaskAccounts } from '../../../ui/app/selectors'
import ToastComponent from './toast' import * as Toast from './toast'
const MIN_GAS_PRICE_BN = new BN('0') const MIN_GAS_PRICE_BN = new BN('0')
const MIN_GAS_LIMIT_BN = new BN('21000') const MIN_GAS_LIMIT_BN = new BN('21000')
@ -158,14 +158,15 @@ class PendingTx extends Component {
fontSize: '14px', fontSize: '14px',
} }
const isError = txMeta.simulationFails || !isValidAddress || insufficientBalance || (dangerousGasLimit && !gasLimitSpecified) const isError = txMeta.simulationFails || !isValidAddress || insufficientBalance || (dangerousGasLimit && !gasLimitSpecified)
return ( return (
h('div', { h('div', {
key: txMeta.id, key: txMeta.id,
}, [ }, [
h(ToastComponent, { h(Toast.ToastComponent, {
isSuccess: false, type: Toast.TOAST_TYPE_ERROR,
}), }),
h('form#pending-tx-form', { h('form#pending-tx-form', {

View File

@ -5,7 +5,7 @@ import PersistentForm from '../../../lib/persistent-form'
import SendProfile from './send-profile' import SendProfile from './send-profile'
import SendHeader from './send-header' import SendHeader from './send-header'
import ErrorComponent from '../error' import ErrorComponent from '../error'
import ToastComponent from '../toast' import * as Toast from '../toast'
import Select from 'react-select' import Select from 'react-select'
import actions from '../../../../ui/app/actions' import actions from '../../../../ui/app/actions'
import abi from 'web3-eth-abi' import abi from 'web3-eth-abi'
@ -154,7 +154,7 @@ class SendTransactionScreen extends PersistentForm {
<SendProfile /> <SendProfile />
<SendHeader title="Execute Method" /> <SendHeader title="Execute Method" />
<ErrorComponent error={error} /> <ErrorComponent error={error} />
<ToastComponent isSuccess={true} /> <Toast.ToastComponent type={Toast.TOAST_TYPE_SUCCESS} />
<div style={{ padding: '0 30px' }}> <div style={{ padding: '0 30px' }}>
<Select <Select
clearable={false} clearable={false}

View File

@ -15,7 +15,7 @@ import SendProfile from './send-profile'
import SendHeader from './send-header' import SendHeader from './send-header'
import ErrorComponent from '../error' import ErrorComponent from '../error'
import { getMetaMaskAccounts } from '../../../../ui/app/selectors' import { getMetaMaskAccounts } from '../../../../ui/app/selectors'
import ToastComponent from '../toast' import * as Toast from '../toast'
const optionalDataLabelStyle = { const optionalDataLabelStyle = {
background: '#ffffff', background: '#ffffff',
@ -42,7 +42,7 @@ class SendTransactionScreen extends PersistentForm {
return ( return (
<div className="send-screen flex-column flex-grow"> <div className="send-screen flex-column flex-grow">
<ToastComponent isSuccess={false} /> <Toast.ToastComponent type={Toast.TOAST_TYPE_ERROR} />
<SendProfile/> <SendProfile/>
<SendHeader <SendHeader

View File

@ -4,12 +4,18 @@ import { connect } from 'react-redux'
import classnames from 'classnames' import classnames from 'classnames'
import actions from '../../../ui/app/actions' import actions from '../../../ui/app/actions'
const TOAST_TYPE_SUCCESS = 'success'
const TOAST_TYPE_ERROR = 'error'
const ERROR_ON_INCORRECT_DPATH = 'The account is derived from ETH derivation path despite you connected to another chain. If you are ready to switch to correct derivation path, just restore from the same seed phrase.'
class ToastComponent extends Component { class ToastComponent extends Component {
static propTypes = { static propTypes = {
msg: PropTypes.string, msg: PropTypes.string,
toastMsg: PropTypes.string, toastMsg: PropTypes.string,
isSuccess: PropTypes.bool, type: PropTypes.string,
hideToast: PropTypes.func, hideToast: PropTypes.func,
hideManually: PropTypes.bool,
} }
constructor (props) { constructor (props) {
@ -19,10 +25,12 @@ class ToastComponent extends Component {
componentDidUpdate (prevProps) { componentDidUpdate (prevProps) {
if ((!prevProps.msg && this.props.msg) || (!prevProps.toastMsg && this.props.toastMsg)) { if ((!prevProps.msg && this.props.msg) || (!prevProps.toastMsg && this.props.toastMsg)) {
this.timerID = setTimeout(() => { if (!this.props.hideManually) {
this.props.hideToast() this.timerID = setTimeout(() => {
clearTimeout(this.timerID) this.props.hideToast()
}, 4000) clearTimeout(this.timerID)
}, 4000)
}
} }
} }
@ -31,15 +39,23 @@ class ToastComponent extends Component {
clearTimeout(this.timerID) clearTimeout(this.timerID)
} }
_getClass (type) {
switch (type) {
case TOAST_TYPE_SUCCESS:
return 'green'
case TOAST_TYPE_ERROR:
return 'red'
default:
return 'green'
}
}
render () { render () {
let toastMsg = this.props.msg || this.props.toastMsg let toastMsg = this.props.msg || this.props.toastMsg
toastMsg = (toastMsg && toastMsg.message) || toastMsg toastMsg = (toastMsg && toastMsg.message) || toastMsg
return toastMsg ? ( return toastMsg ? (
<div <div
className={classnames('toast', { className={classnames('toast', this._getClass(this.props.type))}
'green': this.props.isSuccess,
'red': !this.props.isSuccess,
})}
onClick={(e) => this.props.hideToast()} onClick={(e) => this.props.hideToast()}
>{toastMsg}</div> >{toastMsg}</div>
) : null ) : null
@ -58,4 +74,9 @@ function mapDispatchToProps (dispatch) {
} }
} }
module.exports = connect(mapStateToProps, mapDispatchToProps)(ToastComponent) module.exports = {
ToastComponent: connect(mapStateToProps, mapDispatchToProps)(ToastComponent),
TOAST_TYPE_SUCCESS,
TOAST_TYPE_ERROR,
ERROR_ON_INCORRECT_DPATH,
}

View File

@ -293,7 +293,7 @@ app sections
color: #ffffff !important; color: #ffffff !important;
font-size: 12px; font-size: 12px;
text-align: center; text-align: center;
padding: 10px; padding: 20px;
width: 357px; width: 357px;
line-height: 14px; line-height: 14px;
position: fixed; position: fixed;

View File

@ -4,6 +4,7 @@ const Component = require('react').Component
const connect = require('react-redux').connect const connect = require('react-redux').connect
const h = require('react-hyperscript') const h = require('react-hyperscript')
const actions = require('../../../../../ui/app/actions') const actions = require('../../../../../ui/app/actions')
const { getDPath } = require('../../../util')
module.exports = connect(mapStateToProps)(RevealSeedConfirmation) module.exports = connect(mapStateToProps)(RevealSeedConfirmation)
@ -16,6 +17,7 @@ function mapStateToProps (state) {
return { return {
warning: state.appState.warning, warning: state.appState.warning,
dPath: state.metamask.dPath, dPath: state.metamask.dPath,
provider: state.metamask.provider,
} }
} }
@ -115,7 +117,9 @@ RevealSeedConfirmation.prototype.checkConfirmation = function (event) {
} }
} }
RevealSeedConfirmation.prototype.revealSeedWords = function () { RevealSeedConfirmation.prototype.revealSeedWords = async function () {
const password = document.getElementById('password-box').value const password = document.getElementById('password-box').value
this.props.dispatch(actions.requestRevealSeed(password, this.props.dPath)) const isCreatedWithCorrectDPath = this.props.dispatch(actions.isCreatedWithCorrectDPath())
const dPath = getDPath(this.props.provider.type, isCreatedWithCorrectDPath)
this.props.dispatch(actions.requestRevealSeed(password, dPath))
} }

View File

@ -3,6 +3,7 @@ const PersistentForm = require('../../../lib/persistent-form')
const connect = require('react-redux').connect const connect = require('react-redux').connect
const h = require('react-hyperscript') const h = require('react-hyperscript')
const actions = require('../../../../ui/app/actions') const actions = require('../../../../ui/app/actions')
const { getDPath } = require('../../util')
module.exports = connect(mapStateToProps)(RestoreVaultScreen) module.exports = connect(mapStateToProps)(RestoreVaultScreen)
@ -15,6 +16,7 @@ function mapStateToProps (state) {
return { return {
warning: state.appState.warning, warning: state.appState.warning,
forgottenPassword: state.appState.forgottenPassword, forgottenPassword: state.appState.forgottenPassword,
provider: state.metamask.provider,
} }
} }
@ -185,5 +187,7 @@ RestoreVaultScreen.prototype.createNewVaultAndRestore = function () {
// submit // submit
this.warning = null this.warning = null
this.props.dispatch(actions.displayWarning(this.warning)) this.props.dispatch(actions.displayWarning(this.warning))
this.props.dispatch(actions.createNewVaultAndRestore(password, seed)) const isCreatedWithCorrectDPath = true
const dPath = getDPath(this.props.provider.type, isCreatedWithCorrectDPath)
this.props.dispatch(actions.createNewVaultAndRestore(password, seed, dPath))
} }

View File

@ -111,11 +111,13 @@ UnlockScreen.prototype.onKeyPress = function (event) {
UnlockScreen.prototype.submitPassword = async function (event) { UnlockScreen.prototype.submitPassword = async function (event) {
const element = event.target const element = event.target
const password = element.value const password = element.value
const props = this.props
// reset input // reset input
element.value = '' element.value = ''
try { try {
const dPath = getDPath(this.props.provider.type) || this.props.dPath const isCreatedWithCorrectDPath = await props.dispatch(actions.isCreatedWithCorrectDPath())
await this.props.dispatch(actions.tryUnlockMetamask(password, dPath)) const dPath = getDPath(props.provider.type, isCreatedWithCorrectDPath) || props.dPath
await props.dispatch(actions.tryUnlockMetamask(password, dPath))
} catch (e) { } catch (e) {
log.error(e) log.error(e)
} }

View File

@ -39,7 +39,7 @@ const {
RSK, RSK,
RSK_TESTNET, RSK_TESTNET,
RSK_TICK, RSK_TICK,
// customDPaths, customDPaths,
} = require('../../app/scripts/controllers/network/enums') } = require('../../app/scripts/controllers/network/enums')
const valueTable = { const valueTable = {
@ -88,6 +88,7 @@ module.exports = {
ifHardwareAcc, ifHardwareAcc,
getAllKeyRingsAccounts, getAllKeyRingsAccounts,
ifRSK, ifRSK,
ifETC,
ifRSKByProviderType, ifRSKByProviderType,
ifPOA, ifPOA,
toChecksumAddress, toChecksumAddress,
@ -432,6 +433,12 @@ function ifRSK (network) {
return numericNet === RSK_CODE || numericNet === RSK_TESTNET_CODE return numericNet === RSK_CODE || numericNet === RSK_TESTNET_CODE
} }
function ifETC (network) {
if (!network) return false
const numericNet = isNaN(network) ? network : parseInt(network)
return numericNet === CLASSIC_CODE
}
function ifRSKByProviderType (type) { function ifRSKByProviderType (type) {
if (!type) return false if (!type) return false
return type === RSK || type === RSK_TESTNET return type === RSK || type === RSK_TESTNET
@ -557,14 +564,16 @@ function getNetworkID ({ network }) {
} }
} }
function getDPath (network) { function getDPath (networkType, isCreatedWithCorrectDPath) {
// todo: return when the robust solution will be ready if (isCreatedWithCorrectDPath) {
return `m/44'/60'/0'/0` return customDPaths[networkType] || `m/44'/60'/0'/0`
// return customDPaths[network] || `m/44'/60'/0'/0` } else {
return `m/44'/60'/0'/0`
}
} }
function setDPath (keyring, network) { function setDPath (keyring, networkType, isCreatedWithCorrectDPath) {
const dPath = getDPath(network) const dPath = getDPath(networkType, isCreatedWithCorrectDPath)
if (dPath && keyring.setHdPath) { if (dPath && keyring.setHdPath) {
keyring.setHdPath(dPath) keyring.setHdPath(dPath)
} }

View File

@ -1,9 +1,7 @@
const path = require('path') const path = require('path')
const Func = require('./func').Functions const Func = require('./func').Functions
const account1 = '0x2E428ABd9313D256d64D1f69fe3929C3BE18fD1f' const account1 = '0x2E428ABd9313D256d64D1f69fe3929C3BE18fD1f'
// todo: const account1RSK = '0x7a9bc05F7441d862d1B83CB724861a9872FF43fe'
// const account1RSK = '0x7a9bc05F7441d862d1B83CB724861a9872FF43fe'
const account1RSK = '0x2E428aBd9313D256d64D1f69fe3929c3Be18Fd1F'
const account2 = '0xd7b7AFeCa35e32594e29504771aC847E2a803742' const account2 = '0xd7b7AFeCa35e32594e29504771aC847E2a803742'
const testsFolder = './test-cases' const testsFolder = './test-cases'
const setup = require(`${testsFolder}/setup.spec`) const setup = require(`${testsFolder}/setup.spec`)

View File

@ -1,8 +1,8 @@
const assert = require('assert') import assert from 'assert'
const clone = require('clone') import clone from 'clone'
const KeyringController = require('eth-keychain-controller') import KeyringController from 'eth-keychain-controller'
import seedPhraseVerifier from '../../../app/scripts/lib/seed-phrase-verifier'
const firstTimeState = require('../../../app/scripts/first-time-state') const firstTimeState = require('../../../app/scripts/first-time-state')
const seedPhraseVerifier = require('../../../app/scripts/lib/seed-phrase-verifier')
const mockEncryptor = require('../../lib/mock-encryptor') const mockEncryptor = require('../../lib/mock-encryptor')
describe('SeedPhraseVerifier', function () { describe('SeedPhraseVerifier', function () {

View File

@ -235,7 +235,7 @@ describe('Actions', () => {
createNewVaultAndRestoreSpy = sinon.stub(background, 'createNewVaultAndRestore') createNewVaultAndRestoreSpy = sinon.stub(background, 'createNewVaultAndRestore')
createNewVaultAndRestoreSpy.callsFake((password, seed, callback) => { createNewVaultAndRestoreSpy.callsFake((password, seed, dPath, callback) => {
callback(new Error('error')) callback(new Error('error'))
}) })

View File

@ -370,6 +370,7 @@ const actions = {
getRequestAccountTabIds, getRequestAccountTabIds,
setOpenMetamaskTabsIDs, setOpenMetamaskTabsIDs,
getOpenMetamaskTabsIds, getOpenMetamaskTabsIds,
isCreatedWithCorrectDPath,
closeCurrentNotificationWindow, closeCurrentNotificationWindow,
closeNotificationWindow, closeNotificationWindow,
} }
@ -406,7 +407,12 @@ function tryUnlockMetamask (password, dPath) {
}) })
.then(() => { .then(() => {
dispatch(actions.unlockSucceeded()) dispatch(actions.unlockSucceeded())
return forceUpdateMetamaskState(dispatch) return updateMetamaskStateFromBackground()
.then(newState => {
newState = Object.assign(newState, {dPath: dPath})
dispatch(actions.updateMetamaskState(newState))
forceUpdateMetamaskState(dispatch)
})
}) })
.catch((err) => { .catch((err) => {
log.error(err) log.error(err)
@ -436,6 +442,21 @@ function tryUnlockMetamask (password, dPath) {
} }
} }
function isCreatedWithCorrectDPath () {
return dispatch => {
return new Promise((resolve, reject) => {
background.isCreatedWithCorrectDPath((err, isCreatedWithCorrectDPath) => {
if (err) {
dispatch(actions.displayWarning(err.message))
return reject(err)
}
resolve(isCreatedWithCorrectDPath)
})
})
}
}
function transitionForward () { function transitionForward () {
return { return {
type: this.TRANSITION_FORWARD, type: this.TRANSITION_FORWARD,
@ -468,7 +489,7 @@ function confirmSeedWords () {
} }
} }
function createNewVaultAndRestore (password, seed) { function createNewVaultAndRestore (password, seed, dPath) {
return (dispatch) => { return (dispatch) => {
dispatch(actions.showLoadingIndication()) dispatch(actions.showLoadingIndication())
log.debug(`background.createNewVaultAndRestore`) log.debug(`background.createNewVaultAndRestore`)
@ -479,7 +500,7 @@ function createNewVaultAndRestore (password, seed) {
return reject(err) return reject(err)
} }
background.createNewVaultAndRestore(password, seed, (err) => { background.createNewVaultAndRestore(password, seed, dPath, (err) => {
if (err) { if (err) {
return reject(err) return reject(err)
} }
@ -491,6 +512,11 @@ function createNewVaultAndRestore (password, seed) {
.then(() => dispatch(actions.unMarkPasswordForgotten())) .then(() => dispatch(actions.unMarkPasswordForgotten()))
.then(() => { .then(() => {
dispatch(actions.showAccountsPage()) dispatch(actions.showAccountsPage())
updateMetamaskStateFromBackground()
.then(newState => {
newState = Object.assign(newState, {dPath: dPath})
dispatch(actions.updateMetamaskState(newState))
})
dispatch(actions.hideLoadingIndication()) dispatch(actions.hideLoadingIndication())
}) })
.catch(err => { .catch(err => {

View File

@ -1,8 +1,8 @@
const extend = require('xtend') import extend from 'xtend'
import log from 'loglevel'
const actions = require('../actions') const actions = require('../actions')
const txHelper = require('../../lib/tx-helper') const txHelper = require('../../lib/tx-helper')
const { customHdPaths } = require('../../../old-ui/app/components/connect-hardware/util.js') const { customHdPaths } = require('../../../old-ui/app/components/connect-hardware/util.js')
const log = require('loglevel')
module.exports = reduceApp module.exports = reduceApp