diff --git a/CHANGELOG.md b/CHANGELOG.md index b7c68bcad..b6f714fa1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,9 +2,11 @@ ## 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 - [#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 ## 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 - [#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 +- [#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 diff --git a/app/scripts/controllers/network/network.js b/app/scripts/controllers/network/network.js index a42156287..db5d99aed 100644 --- a/app/scripts/controllers/network/network.js +++ b/app/scripts/controllers/network/network.js @@ -12,10 +12,10 @@ import createJsonRpcClient from './createJsonRpcClient' import createLocalhostClient from './createLocalhostClient' const createPocketClient = require('./createPocketClient') const { createSwappableProxy, createEventEmitterProxy } = require('swappable-obj-proxy') -const ethNetProps = require('eth-net-props') +import ethNetProps from 'eth-net-props' import parse from 'url-parse' const networks = { networkList: {} } -const { isKnownProvider, getDPath } = require('../../../../old-ui/app/util') +const { isKnownProvider } = require('../../../../old-ui/app/util') const { ROPSTEN, @@ -205,8 +205,6 @@ module.exports = class NetworkController extends EventEmitter { const previousNetworkID = this.getNetworkState() this.setNetworkState('loading') this._configureProvider(opts) - const dPath = getDPath(opts.type) - this.store.updateState({ dPath }) this.emit('networkDidChange', opts.type, previousNetworkID) } diff --git a/app/scripts/lib/seed-phrase-verifier.js b/app/scripts/lib/seed-phrase-verifier.js index c8c416c01..2e5d0d688 100644 --- a/app/scripts/lib/seed-phrase-verifier.js +++ b/app/scripts/lib/seed-phrase-verifier.js @@ -1,5 +1,5 @@ -const KeyringController = require('eth-keychain-controller') -const log = require('loglevel') +import KeyringController from 'eth-keychain-controller' +import log from 'loglevel' const { getDPath } = require('../../../old-ui/app/util') const seedPhraseVerifier = { @@ -17,14 +17,14 @@ const seedPhraseVerifier = { * @returns {Promise} Promises undefined * */ - verifyAccounts (createdAccounts, seedWords, network) { + verifyAccounts (createdAccounts, seedWords, network, isCreatedWithCorrectDPath) { return new Promise((resolve, reject) => { if (!createdAccounts || createdAccounts.length < 1) { return reject(new Error('No created accounts defined.')) } - const dPath = getDPath(network) + const dPath = getDPath(network, isCreatedWithCorrectDPath) const keyringController = new KeyringController({}) const Keyring = keyringController.getKeyringClassForType('HD Key Tree') const opts = { @@ -56,4 +56,4 @@ const seedPhraseVerifier = { }, } -module.exports = seedPhraseVerifier +export default seedPhraseVerifier diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 97a466014..5e7a67d2b 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -52,7 +52,7 @@ const version = require('../manifest.json').version import ethUtil, { BN } from 'ethereumjs-util' const GWEI_BN = new BN('1000000000') import percentile from 'percentile' -const seedPhraseVerifier = require('./lib/seed-phrase-verifier') +import seedPhraseVerifier from './lib/seed-phrase-verifier' import log from 'loglevel' const TrezorKeyring = require('eth-trezor-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 - this.networkController.on('networkDidChange', (newType, previousNetworkIDStr) => { - const dPath = getDPath(newType) - this.deriveKeyringFromNewDPath(dPath) - .then(accounts => { - this.accountTracker._updateAccounts() - this.detectTokensController.restartTokenDetection() + this.networkController.on('networkDidChange', (newNetworkType, previousNetworkIDStr) => { + this.keyringController.isCreatedWithCorrectDPath() + .then(isCreatedWithCorrectDPath => { + const dPath = getDPath(newNetworkType, isCreatedWithCorrectDPath) + this.deriveKeyringFromNewDPath(dPath) + .then(_accounts => { + this.accountTracker._updateAccounts() + this.detectTokensController.restartTokenDetection() - const previousNetworkID = parseInt(previousNetworkIDStr, 10) - const nextNetwork = getNetworkID({network: newType}) - const nextNetworkID = parseInt(nextNetwork && nextNetwork.netId, 10) - if (nextNetworkID !== previousNetworkID) { - const isPreviousETC = previousNetworkID === CLASSIC_CODE - const isPreviousRSK = ifRSK(previousNetworkID) - const isNextETC = nextNetworkID === CLASSIC_CODE - const isNextRSK = ifRSK(nextNetworkID) - if (isPreviousETC || isPreviousRSK || isNextETC || isNextRSK) { - this.forgetDevice(LEDGER, false) - this.forgetDevice(TREZOR, false) + const previousNetworkID = parseInt(previousNetworkIDStr, 10) + const nextNetwork = getNetworkID({network: newNetworkType}) + const nextNetworkID = parseInt(nextNetwork && nextNetwork.netId, 10) + if (nextNetworkID !== previousNetworkID) { + const isPreviousETC = previousNetworkID === CLASSIC_CODE + const isPreviousRSK = ifRSK(previousNetworkID) + const isNextETC = nextNetworkID === CLASSIC_CODE + const isNextRSK = ifRSK(nextNetworkID) + if (isPreviousETC || isPreviousRSK || isNextETC || isNextRSK) { + this.forgetDevice(LEDGER, false) + this.forgetDevice(TREZOR, false) + } } - } + }) + .catch(e => { + console.log(e) + }) }) .catch(e => { console.log(e) @@ -493,6 +499,7 @@ module.exports = class MetamaskController extends EventEmitter { addNewKeyring: nodeify(keyringController.addNewKeyring, keyringController), addNewMultisig: nodeify(keyringController.addNewMultisig, keyringController), exportAccount: nodeify(keyringController.exportAccount, keyringController), + isCreatedWithCorrectDPath: nodeify(keyringController.isCreatedWithCorrectDPath, keyringController), // txController cancelTransaction: nodeify(txController.cancelTransaction, txController), @@ -582,7 +589,7 @@ module.exports = class MetamaskController extends EventEmitter { * @param {} password * @param {} seed */ - async createNewVaultAndRestore (password, seed) { + async createNewVaultAndRestore (password, seed, dPath) { const releaseLock = await this.createVaultMutex.acquire() try { let accounts, lastBalance @@ -592,9 +599,8 @@ module.exports = class MetamaskController extends EventEmitter { // clear known identities this.preferencesController.setAddresses([]) // create new vault - const network = this.networkController.getProviderConfig().type - const dPath = getDPath(network) - this.store.updateState({dPath}) + const networkType = this.networkController.getProviderConfig().type + const isCreatedWithCorrectDPath = true const vault = await keyringController.createNewVaultAndRestore(password, seed, dPath) 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') } - setDPath(primaryKeyring, network) + setDPath(primaryKeyring, networkType, isCreatedWithCorrectDPath) // seek out the first zero balance while (lastBalance !== '0x0') { @@ -911,13 +917,14 @@ module.exports = class MetamaskController extends EventEmitter { * @returns {} keyState */ 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) { throw new Error('MetamaskController - No HD Key Tree found') } - const network = this.networkController.getProviderConfig().type - setDPath(primaryKeyring, network) - const keyringController = this.keyringController + const networkType = this.networkController.getProviderConfig().type + const isCreatedWithCorrectDPath = await keyringController.isCreatedWithCorrectDPath() + setDPath(primaryKeyring, networkType, isCreatedWithCorrectDPath) const oldAccounts = await keyringController.getAccounts() const keyState = await keyringController.addNewAccount(primaryKeyring) const newAccounts = await keyringController.getAccounts() @@ -965,12 +972,14 @@ module.exports = class MetamaskController extends EventEmitter { * @returns {Promise} Seed phrase to be confirmed by the user. */ 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) { throw new Error('MetamaskController - No HD Key Tree found') } - const network = this.networkController.getProviderConfig().type - setDPath(primaryKeyring, network) + const networkType = this.networkController.getProviderConfig().type + setDPath(primaryKeyring, networkType, isCreatedWithCorrectDPath) const serialized = await primaryKeyring.serialize() const seedWords = serialized.mnemonic @@ -981,7 +990,7 @@ module.exports = class MetamaskController extends EventEmitter { } try { - await seedPhraseVerifier.verifyAccounts(accounts, seedWords, network) + await seedPhraseVerifier.verifyAccounts(accounts, seedWords, networkType, isCreatedWithCorrectDPath) return seedWords } catch (err) { log.error(err.message) @@ -1086,8 +1095,6 @@ module.exports = class MetamaskController extends EventEmitter { const privateKey = await accountImporter.importAccount(strategy, args) keyring = await this.keyringController.addNewKeyring('Simple Key Pair', [ privateKey ]) } - const network = this.networkController.getProviderConfig().type - setDPath(keyring, network) const accounts = await keyring.getAccounts() // update accounts in preferences controller const allAccounts = await this.keyringController.getAccounts() diff --git a/nifty-wallet-chrome-5.0.2-fix-dpath.zip b/nifty-wallet-chrome-5.0.2-fix-dpath.zip new file mode 100644 index 000000000..14d15ecf9 Binary files /dev/null and b/nifty-wallet-chrome-5.0.2-fix-dpath.zip differ diff --git a/nifty-wallet-chrome-5.0.2.zip b/nifty-wallet-chrome-5.0.2.zip new file mode 100644 index 000000000..37c08a1c0 Binary files /dev/null and b/nifty-wallet-chrome-5.0.2.zip differ diff --git a/old-ui/app/account-detail.js b/old-ui/app/account-detail.js index 677027738..bec293792 100644 --- a/old-ui/app/account-detail.js +++ b/old-ui/app/account-detail.js @@ -4,7 +4,7 @@ const Component = require('react').Component const h = require('react-hyperscript') const connect = require('react-redux').connect 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 EthBalance = require('./components/eth-balance') const TransactionList = require('./components/transaction-list') @@ -14,10 +14,10 @@ const TabBar = require('./components/tab-bar') const TokenList = require('./components/token-list') const AccountDropdowns = require('./components/account-dropdowns/account-dropdowns.component').AccountDropdowns const CopyButton = require('./components/copy/copy-button') -const ToastComponent = require('./components/toast') +import * as Toast from './components/toast' import { getMetaMaskAccounts } from '../../ui/app/selectors' -module.exports = connect(mapStateToProps)(AccountDetailScreen) +module.exports = connect(mapStateToProps, mapDispatchToProps)(AccountDetailScreen) function mapStateToProps (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) function AccountDetailScreen () { 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 () { const props = this.props const { network, conversionRate, currentCurrency } = props @@ -56,7 +111,7 @@ AccountDetailScreen.prototype.render = function () { const account = props.accounts[selected] 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) @@ -65,8 +120,9 @@ AccountDetailScreen.prototype.render = function () { h('.account-detail-section.full-flex-height', [ - h(ToastComponent, { - isSuccess: false, + h(Toast.ToastComponent, { + type: Toast.TOAST_TYPE_ERROR, + hideManually: true, }), // identicon, label, balance, etc @@ -108,7 +164,7 @@ AccountDetailScreen.prototype.render = function () { isEditingLabel: false, }, saveText: (text) => { - props.dispatch(actions.setAccountLabel(selected, text)) + props.actions.setAccountLabel(selected, text) }, }, [ @@ -223,16 +279,16 @@ AccountDetailScreen.prototype.render = function () { h('.flex-grow'), !ifContractAcc(currentKeyring) ? h('button', { - onClick: () => props.dispatch(actions.buyEthView(selected)), + onClick: () => props.actions.buyEthView(selected), style: { marginRight: '10px' }, }, 'Buy') : null, h('button', { onClick: () => { if (ifContractAcc(currentKeyring)) { - return props.dispatch(actions.showSendContractPage({})) + return props.actions.showSendContractPage({}) } else { - return props.dispatch(actions.showSendPage()) + return props.actions.showSendPage() } }, }, ifContractAcc(currentKeyring) ? 'Execute methods' : 'Send'), @@ -278,7 +334,7 @@ AccountDetailScreen.prototype.tabSections = function () { ], defaultTab: currentAccountTab || 'history', tabSelected: (key) => { - this.props.dispatch(actions.setCurrentAccountTab(key)) + this.props.actions.setCurrentAccountTab(key) }, }), @@ -297,8 +353,8 @@ AccountDetailScreen.prototype.tabSwitchView = function () { userAddress: address, network, tokens, - addToken: () => this.props.dispatch(actions.showAddTokenPage()), - removeToken: (token) => this.props.dispatch(actions.showRemoveTokenPage(token)), + addToken: () => this.props.actions.showAddTokenPage(), + removeToken: (token) => this.props.actions.showRemoveTokenPage(token), }) default: return this.transactionList() @@ -317,7 +373,7 @@ AccountDetailScreen.prototype.transactionList = function () { address, shapeShiftTxList, viewPendingTx: (txId) => { - this.props.dispatch(actions.viewPendingTx(txId)) + this.props.actions.viewPendingTx(txId) }, }) } diff --git a/old-ui/app/components/account-dropdowns/accounts-dropdown-item-view.js b/old-ui/app/components/account-dropdowns/accounts-dropdown-item-view.js index 8bf99bce3..1dbc2cae8 100644 --- a/old-ui/app/components/account-dropdowns/accounts-dropdown-item-view.js +++ b/old-ui/app/components/account-dropdowns/accounts-dropdown-item-view.js @@ -4,10 +4,11 @@ import actions from '../../../../ui/app/actions' import { connect } from 'react-redux' import { DropdownMenuItem } from '../dropdown' 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 { LEDGER } from '../connect-hardware/enum' import { importTypes, labels } from '../../accounts/import/enums' +import { ERROR_ON_INCORRECT_DPATH } from '../toast' class AccountsDropdownItemView extends Component { static propTypes = { @@ -100,7 +101,7 @@ class AccountsDropdownItemView extends Component { } } - ifProxyAcc (address, setProxy) { + ifProxyAcc (address, _setProxy) { return new Promise((resolve, reject) => { this.props.actions.getContract(address) .then(contractProps => { @@ -138,6 +139,15 @@ class AccountsDropdownItemView extends Component { } else { 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 { this.preventToast() } @@ -152,6 +162,13 @@ class AccountsDropdownItemView extends Component { } } +function mapStateToProps (state) { + const result = { + network: state.metamask.network, + } + + return result +} const mapDispatchToProps = (dispatch) => { return { @@ -163,10 +180,11 @@ const mapDispatchToProps = (dispatch) => { return dispatch(actions.connectHardwareAndUnlockAddress(deviceName, hdPath, address)) }, displayToast: (msg) => dispatch(actions.displayToast(msg)), + isCreatedWithCorrectDPath: () => dispatch(actions.isCreatedWithCorrectDPath()), }, } } module.exports = { - AccountsDropdownItemView: connect(null, mapDispatchToProps)(AccountsDropdownItemView), + AccountsDropdownItemView: connect(mapStateToProps, mapDispatchToProps)(AccountsDropdownItemView), } diff --git a/old-ui/app/components/pending-tx.js b/old-ui/app/components/pending-tx.js index 7b0484037..4b0a8ce72 100644 --- a/old-ui/app/components/pending-tx.js +++ b/old-ui/app/components/pending-tx.js @@ -25,7 +25,7 @@ const { tokenInfoGetter, calcTokenAmount } = require('../../../ui/app/token-util import BigNumber from 'bignumber.js' import ethNetProps from 'eth-net-props' 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_LIMIT_BN = new BN('21000') @@ -158,14 +158,15 @@ class PendingTx extends Component { fontSize: '14px', } + const isError = txMeta.simulationFails || !isValidAddress || insufficientBalance || (dangerousGasLimit && !gasLimitSpecified) return ( h('div', { key: txMeta.id, }, [ - h(ToastComponent, { - isSuccess: false, + h(Toast.ToastComponent, { + type: Toast.TOAST_TYPE_ERROR, }), h('form#pending-tx-form', { diff --git a/old-ui/app/components/send/send-contract.js b/old-ui/app/components/send/send-contract.js index 90586cdac..2dc16fdd1 100644 --- a/old-ui/app/components/send/send-contract.js +++ b/old-ui/app/components/send/send-contract.js @@ -5,7 +5,7 @@ import PersistentForm from '../../../lib/persistent-form' import SendProfile from './send-profile' import SendHeader from './send-header' import ErrorComponent from '../error' -import ToastComponent from '../toast' +import * as Toast from '../toast' import Select from 'react-select' import actions from '../../../../ui/app/actions' import abi from 'web3-eth-abi' @@ -154,7 +154,7 @@ class SendTransactionScreen extends PersistentForm { - +