diff --git a/CHANGELOG.md b/CHANGELOG.md index f4c2abd4d..19287f046 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,17 @@ ## Current Master -- Fixes issue where old nicknames were kept around causing errors. +## 4.7.4 Tue Jun 05 2018 + +- Add diagnostic reporting for users with multiple HD keyrings +- Throw explicit error when selected account is unset + +## 4.7.3 Mon Jun 04 2018 + +- Hide token now uses new modal +- Indicate the current selected account on the popup account view +- Reduce height of notice container in onboarding +- Fixes issue where old nicknames were kept around causing errors ## 4.7.2 Sun Jun 03 2018 diff --git a/app/_locales/ja/messages.json b/app/_locales/ja/messages.json index 3a664ec00..75deeaddf 100644 --- a/app/_locales/ja/messages.json +++ b/app/_locales/ja/messages.json @@ -62,6 +62,9 @@ "message": " $1以上 $2以下にして下さい。", "description": "helper for inputting hex as decimal input" }, + "blockiesIdenticon": { + "message": "Blockies Identicon を使用" + }, "borrowDharma": { "message": "Dharmaで借りる(ベータ版)" }, @@ -95,6 +98,9 @@ "confirmTransaction": { "message": "トランザクションの確認" }, + "continue": { + "message": "続行" + }, "continueToCoinbase": { "message": "Coinbaseを開く" }, @@ -359,6 +365,9 @@ "likeToAddTokens": { "message": "トークンを追加しますか?" }, + "links": { + "message": "リンク" + }, "limit": { "message": "リミット" }, @@ -371,12 +380,18 @@ "localhost": { "message": "Localhost 8545" }, + "login": { + "message": "ログイン" + }, "logout": { "message": "ログアウト" }, "loose": { "message": "外部秘密鍵" }, + "max": { + "message": "最大" + }, "mainnet": { "message": "Ethereumメインネットワーク" }, @@ -417,7 +432,7 @@ "message": "新規コントラクト" }, "newPassword": { - "message": "新規パスワード(最低8文字)" + "message": "新規パスワード(最低8文字)" }, "newRecipient": { "message": "新規受取人" @@ -453,6 +468,9 @@ "message": "または", "description": "choice between creating or importing a new account" }, + "password": { + "message": "パスワード" + }, "passwordMismatch": { "message": "パスワードが一致しません。", "description": "in password creation process, the two new password fields did not match" @@ -474,6 +492,9 @@ "popularTokens": { "message": "人気のトークン" }, + "privacyMsg": { + "message": "プライバシーポリシー" + }, "privateKey": { "message": "秘密鍵", "description": "select this type of file to use to import an account" @@ -546,6 +567,12 @@ "message": "ファイルとして保存", "description": "Account export process" }, + "search": { + "message": "検索" + }, + "searchResults": { + "message": "検索結果" + }, "selectService": { "message": "サービスを選択" }, @@ -575,7 +602,7 @@ }, "info": { "message": "情報" - }, + }, "shapeshiftBuy": { "message": "Shapeshiftで交換" }, @@ -609,6 +636,9 @@ "takesTooLong": { "message": "送信に時間がかかりますか?" }, + "terms": { + "message": "利用規約" + }, "testFaucet": { "message": "Faucetをテスト" }, @@ -619,6 +649,9 @@ "message": "ShapeShiftで $1をETHにする", "description": "system will fill in deposit type in start of message" }, + "token": { + "message": "トークン" + }, "tokenAddress": { "message": "トークンアドレス" }, @@ -690,6 +723,12 @@ "warning": { "message": "警告" }, + "welcomeBack": { + "message": "おかえりなさい!" + }, + "welcomeBeta": { + "message": "MetaMask ベータ版へようこそ!" + }, "whatsThis": { "message": "この機能について" }, diff --git a/app/manifest.json b/app/manifest.json index c1f26d2ea..e3a7fd963 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -1,7 +1,7 @@ { "name": "__MSG_appName__", "short_name": "__MSG_appName__", - "version": "4.7.2", + "version": "4.7.4", "manifest_version": 2, "author": "https://metamask.io", "description": "__MSG_appDescription__", diff --git a/app/scripts/controllers/preferences.js b/app/scripts/controllers/preferences.js index 2fe009f9a..8411e3a28 100644 --- a/app/scripts/controllers/preferences.js +++ b/app/scripts/controllers/preferences.js @@ -1,9 +1,7 @@ const ObservableStore = require('obs-store') const normalizeAddress = require('eth-sig-util').normalize const extend = require('xtend') -const notifier = require('../lib/bug-notifier') -const log = require('loglevel') -const { version } = require('../../manifest.json') + class PreferencesController { @@ -34,8 +32,7 @@ class PreferencesController { lostIdentities: {}, }, opts.initState) - this.getFirstTimeInfo = opts.getFirstTimeInfo || null - this.notifier = opts.notifier || notifier + this.diagnostics = opts.diagnostics this.store = new ObservableStore(initState) } @@ -128,17 +125,9 @@ class PreferencesController { if (Object.keys(newlyLost).length > 0) { // Notify our servers: - const uri = 'https://diagnostics.metamask.io/v1/orphanedAccounts' - const firstTimeInfo = this.getFirstTimeInfo ? this.getFirstTimeInfo() : {} - this.notifier.notify(uri, { - accounts: Object.keys(newlyLost), - metadata: { - version, - firstTimeInfo, - }, - }) - .catch(log.error) + if (this.diagnostics) this.diagnostics.reportOrphans(newlyLost) + // store lost accounts for (let key in newlyLost) { lostIdentities[key] = newlyLost[key] } @@ -258,6 +247,7 @@ class PreferencesController { * @return {Promise} */ setAccountLabel (account, label) { + if (!account) throw new Error('setAccountLabel requires a valid address, got ' + String(account)) const address = normalizeAddress(account) const {identities} = this.store.getState() identities[address] = identities[address] || {} diff --git a/app/scripts/controllers/transactions/index.js b/app/scripts/controllers/transactions/index.js index aff5db984..b53947e27 100644 --- a/app/scripts/controllers/transactions/index.js +++ b/app/scripts/controllers/transactions/index.js @@ -10,6 +10,7 @@ const NonceTracker = require('./nonce-tracker') const txUtils = require('./lib/util') const cleanErrorStack = require('../../lib/cleanErrorStack') const log = require('loglevel') +const recipientBlacklistChecker = require('./lib/recipient-blacklist-checker') /** Transaction Controller is an aggregate of sub-controllers and trackers @@ -157,8 +158,11 @@ class TransactionController extends EventEmitter { let txMeta = this.txStateManager.generateTxMeta({ txParams: normalizedTxParams }) this.addTx(txMeta) this.emit('newUnapprovedTx', txMeta) - // add default tx params + try { + // check whether recipient account is blacklisted + recipientBlacklistChecker.checkAccount(txMeta.metamaskNetworkId, normalizedTxParams.to) + // add default tx params txMeta = await this.addTxGasDefaults(txMeta) } catch (error) { console.log(error) diff --git a/app/scripts/controllers/transactions/lib/recipient-blacklist-checker.js b/app/scripts/controllers/transactions/lib/recipient-blacklist-checker.js new file mode 100644 index 000000000..84c6df1f0 --- /dev/null +++ b/app/scripts/controllers/transactions/lib/recipient-blacklist-checker.js @@ -0,0 +1,24 @@ +const Config = require('./recipient-blacklist-config.json') + +/** @module*/ +module.exports = { + checkAccount, +} + +/** + * Checks if a specified account on a specified network is blacklisted. + @param networkId {number} + @param account {string} +*/ +function checkAccount (networkId, account) { + + const mainnetId = 1 + if (networkId !== mainnetId) { + return + } + + const accountToCheck = account.toLowerCase() + if (Config.blacklist.includes(accountToCheck)) { + throw new Error('Recipient is a public account') + } +} diff --git a/app/scripts/controllers/transactions/lib/recipient-blacklist-config.json b/app/scripts/controllers/transactions/lib/recipient-blacklist-config.json new file mode 100644 index 000000000..b348eb72e --- /dev/null +++ b/app/scripts/controllers/transactions/lib/recipient-blacklist-config.json @@ -0,0 +1,14 @@ +{ + "blacklist": [ + "0x627306090abab3a6e1400e9345bc60c78a8bef57", + "0xf17f52151ebef6c7334fad080c5704d77216b732", + "0xc5fdf4076b8f3a5357c5e395ab970b5b54098fef", + "0x821aea9a577a9b44299b9c15c88cf3087f3b5544", + "0x0d1d4e623d10f9fba5db95830f7d3839406c6af2", + "0x2932b7a2355d6fecc4b5c0b6bd44cc31df247a2e", + "0x2191ef87e392377ec08e7c08eb105ef5448eced5", + "0x0f4f2ac550a1b4e2280d04c21cea7ebd822934b5", + "0x6330a553fc93768f612722bb8c2ec78ac90b3bbc", + "0x5aeda56215b167893e80b4fe645ba6d5bab767de" + ] +} diff --git a/app/scripts/lib/bug-notifier.js b/app/scripts/lib/bug-notifier.js deleted file mode 100644 index 4d305b894..000000000 --- a/app/scripts/lib/bug-notifier.js +++ /dev/null @@ -1,22 +0,0 @@ -class BugNotifier { - notify (uri, message) { - return postData(uri, message) - } -} - -function postData(uri, data) { - return fetch(uri, { - body: JSON.stringify(data), // must match 'Content-Type' header - credentials: 'same-origin', // include, same-origin, *omit - headers: { - 'content-type': 'application/json', - }, - method: 'POST', // *GET, POST, PUT, DELETE, etc. - mode: 'cors', // no-cors, cors, *same-origin - }) -} - -const notifier = new BugNotifier() - -module.exports = notifier - diff --git a/app/scripts/lib/diagnostics-reporter.js b/app/scripts/lib/diagnostics-reporter.js new file mode 100644 index 000000000..aa4ca6e26 --- /dev/null +++ b/app/scripts/lib/diagnostics-reporter.js @@ -0,0 +1,71 @@ +class DiagnosticsReporter { + + constructor ({ firstTimeInfo, version }) { + this.firstTimeInfo = firstTimeInfo + this.version = version + } + + async reportOrphans(orphans) { + try { + return await this.submit({ + accounts: Object.keys(orphans), + metadata: { + type: 'orphans', + }, + }) + } catch (err) { + console.error('DiagnosticsReporter - "reportOrphans" encountered an error:') + console.error(err) + } + } + + async reportMultipleKeyrings(rawKeyrings) { + try { + const keyrings = await Promise.all(rawKeyrings.map(async (keyring, index) => { + return { + index, + type: keyring.type, + accounts: await keyring.getAccounts(), + } + })) + return await this.submit({ + accounts: [], + metadata: { + type: 'keyrings', + keyrings, + }, + }) + } catch (err) { + console.error('DiagnosticsReporter - "reportMultipleKeyrings" encountered an error:') + console.error(err) + } + } + + async submit (message) { + try { + // add metadata + message.metadata.version = this.version + message.metadata.firstTimeInfo = this.firstTimeInfo + return await postData(message) + } catch (err) { + console.error('DiagnosticsReporter - "submit" encountered an error:') + throw err + } + } + +} + +function postData(data) { + const uri = 'https://diagnostics.metamask.io/v1/orphanedAccounts' + return fetch(uri, { + body: JSON.stringify(data), // must match 'Content-Type' header + credentials: 'same-origin', // include, same-origin, *omit + headers: { + 'content-type': 'application/json', + }, + method: 'POST', // *GET, POST, PUT, DELETE, etc. + mode: 'cors', // no-cors, cors, *same-origin + }) +} + +module.exports = DiagnosticsReporter diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 80eaafd92..532cfbd61 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -46,6 +46,7 @@ const GWEI_BN = new BN('1000000000') const percentile = require('percentile') const seedPhraseVerifier = require('./lib/seed-phrase-verifier') const cleanErrorStack = require('./lib/cleanErrorStack') +const DiagnosticsReporter = require('./lib/diagnostics-reporter') const log = require('loglevel') module.exports = class MetamaskController extends EventEmitter { @@ -64,6 +65,12 @@ module.exports = class MetamaskController extends EventEmitter { const initState = opts.initState || {} this.recordFirstTimeInfo(initState) + // metamask diagnostics reporter + this.diagnostics = opts.diagnostics || new DiagnosticsReporter({ + firstTimeInfo: initState.firstTimeInfo, + version, + }) + // platform-specific api this.platform = opts.platform @@ -85,7 +92,7 @@ module.exports = class MetamaskController extends EventEmitter { this.preferencesController = new PreferencesController({ initState: initState.PreferencesController, initLangCode: opts.initLangCode, - getFirstTimeInfo: () => initState.firstTimeInfo, + diagnostics: this.diagnostics, }) // currency controller @@ -489,6 +496,12 @@ module.exports = class MetamaskController extends EventEmitter { await this.keyringController.submitPassword(password) const accounts = await this.keyringController.getAccounts() + // verify keyrings + const nonSimpleKeyrings = this.keyringController.keyrings.filter(keyring => keyring.type !== 'Simple Key Pair') + if (nonSimpleKeyrings.length > 1 && this.diagnostics) { + await this.diagnostics.reportMultipleKeyrings(nonSimpleKeyrings) + } + await this.preferencesController.syncAddresses(accounts) return this.keyringController.fullUpdate() } diff --git a/development/states/add-token.json b/development/states/add-token.json index 9c0f16372..84ad5dd4c 100644 --- a/development/states/add-token.json +++ b/development/states/add-token.json @@ -75,9 +75,9 @@ { "type": "HD Key Tree", "accounts": [ - "fdea65c8e26263f6d9a1b5de9555d2931a33b825", - "c5b8dbac4c1d3f152cdeb400e2313f309c410acb", - "2f8d4a878cfa04a6e60d46362f5644deab66572d" + "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825", + "0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb", + "0x2f8d4a878cfa04a6e60d46362f5644deab66572d" ] }, { diff --git a/development/states/confirm-sig-requests.json b/development/states/confirm-sig-requests.json index b51003d11..3c9caafb0 100644 --- a/development/states/confirm-sig-requests.json +++ b/development/states/confirm-sig-requests.json @@ -115,9 +115,9 @@ { "type": "HD Key Tree", "accounts": [ - "fdea65c8e26263f6d9a1b5de9555d2931a33b825", - "c5b8dbac4c1d3f152cdeb400e2313f309c410acb", - "2f8d4a878cfa04a6e60d46362f5644deab66572d" + "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825", + "0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb", + "0x2f8d4a878cfa04a6e60d46362f5644deab66572d" ] }, { diff --git a/development/states/currency-localization.json b/development/states/currency-localization.json index 302e24c11..8c8b18a91 100644 --- a/development/states/currency-localization.json +++ b/development/states/currency-localization.json @@ -76,9 +76,9 @@ { "type": "HD Key Tree", "accounts": [ - "fdea65c8e26263f6d9a1b5de9555d2931a33b825", - "c5b8dbac4c1d3f152cdeb400e2313f309c410acb", - "2f8d4a878cfa04a6e60d46362f5644deab66572d" + "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825", + "0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb", + "0x2f8d4a878cfa04a6e60d46362f5644deab66572d" ] }, { diff --git a/development/states/send-edit.json b/development/states/send-edit.json index 71ccbd96c..b05acbf3b 100644 --- a/development/states/send-edit.json +++ b/development/states/send-edit.json @@ -94,9 +94,9 @@ { "type": "HD Key Tree", "accounts": [ - "fdea65c8e26263f6d9a1b5de9555d2931a33b825", - "c5b8dbac4c1d3f152cdeb400e2313f309c410acb", - "2f8d4a878cfa04a6e60d46362f5644deab66572d" + "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825", + "0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb", + "0x2f8d4a878cfa04a6e60d46362f5644deab66572d" ] }, { diff --git a/development/states/send-new-ui.json b/development/states/send-new-ui.json index 4b2d20730..b457749ee 100644 --- a/development/states/send-new-ui.json +++ b/development/states/send-new-ui.json @@ -76,9 +76,9 @@ { "type": "HD Key Tree", "accounts": [ - "fdea65c8e26263f6d9a1b5de9555d2931a33b825", - "c5b8dbac4c1d3f152cdeb400e2313f309c410acb", - "2f8d4a878cfa04a6e60d46362f5644deab66572d" + "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825", + "0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb", + "0x2f8d4a878cfa04a6e60d46362f5644deab66572d" ] }, { diff --git a/development/states/tx-list-items.json b/development/states/tx-list-items.json index 43e6f4b90..f22fd0a56 100644 --- a/development/states/tx-list-items.json +++ b/development/states/tx-list-items.json @@ -83,9 +83,9 @@ { "type": "HD Key Tree", "accounts": [ - "fdea65c8e26263f6d9a1b5de9555d2931a33b825", - "c5b8dbac4c1d3f152cdeb400e2313f309c410acb", - "2f8d4a878cfa04a6e60d46362f5644deab66572d" + "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825", + "0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb", + "0x2f8d4a878cfa04a6e60d46362f5644deab66572d" ] }, { diff --git a/old-ui/app/components/account-dropdowns.js b/old-ui/app/components/account-dropdowns.js index 53468a1a1..262de6601 100644 --- a/old-ui/app/components/account-dropdowns.js +++ b/old-ui/app/components/account-dropdowns.js @@ -23,9 +23,10 @@ class AccountDropdowns extends Component { renderAccounts () { const { identities, selected, keyrings } = this.props + const accountOrder = keyrings.reduce((list, keyring) => list.concat(keyring.accounts), []) - return Object.keys(identities).map((key, index) => { - const identity = identities[key] + return accountOrder.map((address, index) => { + const identity = identities[address] const isSelected = identity.address === selected const simpleAddress = identity.address.substring(2).toLowerCase() diff --git a/test/unit/app/controllers/metamask-controller-test.js b/test/unit/app/controllers/metamask-controller-test.js index 7ec98766a..0dda4609b 100644 --- a/test/unit/app/controllers/metamask-controller-test.js +++ b/test/unit/app/controllers/metamask-controller-test.js @@ -53,6 +53,9 @@ describe('MetaMaskController', function () { }, initState: clone(firstTimeState), }) + // disable diagnostics + metamaskController.diagnostics = null + // add sinon method spies sandbox.spy(metamaskController.keyringController, 'createNewVaultAndKeychain') sandbox.spy(metamaskController.keyringController, 'createNewVaultAndRestore') }) @@ -72,11 +75,6 @@ describe('MetaMaskController', function () { it('removes any identities that do not correspond to known accounts.', async function () { const fakeAddress = '0xbad0' metamaskController.preferencesController.addAddresses([fakeAddress]) - metamaskController.preferencesController.notifier = { - notify: async () => { - return true - }, - } await metamaskController.submitPassword(password) const identities = Object.keys(metamaskController.preferencesController.store.getState().identities) diff --git a/test/unit/app/controllers/transactions/recipient-blacklist-checker-test.js b/test/unit/app/controllers/transactions/recipient-blacklist-checker-test.js new file mode 100644 index 000000000..56e8d50db --- /dev/null +++ b/test/unit/app/controllers/transactions/recipient-blacklist-checker-test.js @@ -0,0 +1,77 @@ +const assert = require('assert') +const recipientBlackListChecker = require('../../../../../app/scripts/controllers/transactions/lib/recipient-blacklist-checker') +const { + ROPSTEN_CODE, + RINKEYBY_CODE, + KOVAN_CODE, +} = require('../../../../../app/scripts/controllers/network/enums') + +const KeyringController = require('eth-keyring-controller') + +describe('Recipient Blacklist Checker', function () { + + let publicAccounts + + before(async function () { + const damnedMnemonic = 'candy maple cake sugar pudding cream honey rich smooth crumble sweet treat' + const keyringController = new KeyringController({}) + const Keyring = keyringController.getKeyringClassForType('HD Key Tree') + const opts = { + mnemonic: damnedMnemonic, + numberOfAccounts: 10, + } + const keyring = new Keyring(opts) + publicAccounts = await keyring.getAccounts() + }) + + describe('#checkAccount', function () { + it('does not fail on test networks', function () { + let callCount = 0 + const networks = [ROPSTEN_CODE, RINKEYBY_CODE, KOVAN_CODE] + for (let networkId in networks) { + publicAccounts.forEach((account) => { + recipientBlackListChecker.checkAccount(networkId, account) + callCount++ + }) + } + assert.equal(callCount, 30) + }) + + it('fails on mainnet', function () { + const mainnetId = 1 + let callCount = 0 + publicAccounts.forEach((account) => { + try { + recipientBlackListChecker.checkAccount(mainnetId, account) + assert.fail('function should have thrown an error') + } catch (err) { + assert.equal(err.message, 'Recipient is a public account') + } + callCount++ + }) + assert.equal(callCount, 10) + }) + + it('fails for public account - uppercase', function () { + const mainnetId = 1 + const publicAccount = '0X0D1D4E623D10F9FBA5DB95830F7D3839406C6AF2' + try { + recipientBlackListChecker.checkAccount(mainnetId, publicAccount) + assert.fail('function should have thrown an error') + } catch (err) { + assert.equal(err.message, 'Recipient is a public account') + } + }) + + it('fails for public account - lowercase', async function () { + const mainnetId = 1 + const publicAccount = '0x0d1d4e623d10f9fba5db95830f7d3839406c6af2' + try { + await recipientBlackListChecker.checkAccount(mainnetId, publicAccount) + assert.fail('function should have thrown an error') + } catch (err) { + assert.equal(err.message, 'Recipient is a public account') + } + }) + }) +}) diff --git a/test/unit/app/controllers/transactions/tx-controller-test.js b/test/unit/app/controllers/transactions/tx-controller-test.js index 1f32a0f37..9bdfe7c1a 100644 --- a/test/unit/app/controllers/transactions/tx-controller-test.js +++ b/test/unit/app/controllers/transactions/tx-controller-test.js @@ -185,6 +185,23 @@ describe('Transaction Controller', function () { .catch(done) }) + it('should fail if recipient is public', function (done) { + txController.networkStore = new ObservableStore(1) + txController.addUnapprovedTransaction({ from: '0x1678a085c290ebd122dc42cba69373b5953b831d', to: '0x0d1d4e623D10F9FBA5Db95830F7d3839406C6AF2' }) + .catch((err) => { + if (err.message === 'Recipient is a public account') done() + else done(err) + }) + }) + + it('should not fail if recipient is public but not on mainnet', function (done) { + txController.once('newUnapprovedTx', (txMetaFromEmit) => { + assert(txMetaFromEmit, 'txMeta is falsey') + done() + }) + txController.addUnapprovedTransaction({ from: '0x1678a085c290ebd122dc42cba69373b5953b831d', to: '0x0d1d4e623D10F9FBA5Db95830F7d3839406C6AF2' }) + .catch(done) + }) }) describe('#addTxGasDefaults', function () { diff --git a/ui/app/actions.js b/ui/app/actions.js index 465b3d5fe..1edf692b6 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -558,10 +558,12 @@ function importNewAccount (strategy, args) { } dispatch(actions.hideLoadingIndication()) dispatch(actions.updateMetamaskState(newState)) - dispatch({ - type: actions.SHOW_ACCOUNT_DETAIL, - value: newState.selectedAddress, - }) + if (newState.selectedAddress) { + dispatch({ + type: actions.SHOW_ACCOUNT_DETAIL, + value: newState.selectedAddress, + }) + } return newState } } diff --git a/ui/app/app.js b/ui/app/app.js index 09f7fdef4..ec2329463 100644 --- a/ui/app/app.js +++ b/ui/app/app.js @@ -99,7 +99,7 @@ class App extends Component { } = this.props const isLoadingNetwork = network === 'loading' && currentView.name !== 'config' const loadMessage = loadingMessage || isLoadingNetwork ? - this.getConnectingLabel() : null + this.getConnectingLabel(loadingMessage) : null log.debug('Main ui render function') return ( @@ -210,7 +210,10 @@ class App extends Component { } } - getConnectingLabel = function () { + getConnectingLabel = function (loadingMessage) { + if (loadingMessage) { + return loadingMessage + } const { provider } = this.props const providerName = provider.type diff --git a/ui/app/components/account-menu/index.js b/ui/app/components/account-menu/index.js index 7638995ea..f34631ca8 100644 --- a/ui/app/components/account-menu/index.js +++ b/ui/app/components/account-menu/index.js @@ -135,11 +135,12 @@ AccountMenu.prototype.renderAccounts = function () { showAccountDetail, } = this.props - return Object.keys(identities).map((key, index) => { - const identity = identities[key] + const accountOrder = keyrings.reduce((list, keyring) => list.concat(keyring.accounts), []) + return accountOrder.map((address) => { + const identity = identities[address] const isSelected = identity.address === selectedAddress - const balanceValue = accounts[key] ? accounts[key].balance : '' + const balanceValue = accounts[address] ? accounts[address].balance : '' const formattedBalance = balanceValue ? formatBalance(balanceValue, 6) : '...' const simpleAddress = identity.address.substring(2).toLowerCase() diff --git a/ui/app/components/pages/add-token/add-token.component.js b/ui/app/components/pages/add-token/add-token.component.js index 1f4b37b53..bcb93d401 100644 --- a/ui/app/components/pages/add-token/add-token.component.js +++ b/ui/app/components/pages/add-token/add-token.component.js @@ -231,7 +231,7 @@ class AddToken extends Component {
this.handleCustomAddressChange(e.target.value)} @@ -241,7 +241,7 @@ class AddToken extends Component { /> this.handleCustomSymbolChange(e.target.value)} @@ -252,7 +252,7 @@ class AddToken extends Component { /> this.handleCustomDecimalsChange(e.target.value)} diff --git a/ui/app/components/pages/create-account/import-account/json.js b/ui/app/components/pages/create-account/import-account/json.js index 0164417ec..1dc2ba534 100644 --- a/ui/app/components/pages/create-account/import-account/json.js +++ b/ui/app/components/pages/create-account/import-account/json.js @@ -82,18 +82,19 @@ class JsonImportSubview extends Component { } createNewKeychain () { + const { firstAddress, displayWarning, importNewJsonAccount, setSelectedAddress } = this.props const state = this.state if (!state) { const message = this.context.t('validFileImport') - return this.props.displayWarning(message) + return displayWarning(message) } const { fileContents } = state if (!fileContents) { const message = this.context.t('needImportFile') - return this.props.displayWarning(message) + return displayWarning(message) } const passwordInput = document.getElementById('json-password-box') @@ -101,12 +102,19 @@ class JsonImportSubview extends Component { if (!password) { const message = this.context.t('needImportPassword') - return this.props.displayWarning(message) + return displayWarning(message) } - this.props.importNewJsonAccount([ fileContents, password ]) - // JS runtime requires caught rejections but failures are handled by Redux - .catch() + importNewJsonAccount([ fileContents, password ]) + .then(({ selectedAddress }) => { + if (selectedAddress) { + history.push(DEFAULT_ROUTE) + } else { + displayWarning('Error importing account.') + setSelectedAddress(firstAddress) + } + }) + .catch(err => displayWarning(err)) } } @@ -114,14 +122,17 @@ JsonImportSubview.propTypes = { error: PropTypes.string, goHome: PropTypes.func, displayWarning: PropTypes.func, + firstAddress: PropTypes.string, importNewJsonAccount: PropTypes.func, history: PropTypes.object, + setSelectedAddress: PropTypes.func, t: PropTypes.func, } const mapStateToProps = state => { return { error: state.appState.warning, + firstAddress: Object.keys(state.metamask.accounts)[0], } } @@ -130,6 +141,7 @@ const mapDispatchToProps = dispatch => { goHome: () => dispatch(actions.goHome()), displayWarning: warning => dispatch(actions.displayWarning(warning)), importNewJsonAccount: options => dispatch(actions.importNewAccount('JSON File', options)), + setSelectedAddress: (address) => dispatch(actions.setSelectedAddress(address)), } } diff --git a/ui/app/components/pages/create-account/import-account/private-key.js b/ui/app/components/pages/create-account/import-account/private-key.js index e55a47a3c..5df3777da 100644 --- a/ui/app/components/pages/create-account/import-account/private-key.js +++ b/ui/app/components/pages/create-account/import-account/private-key.js @@ -21,6 +21,7 @@ module.exports = compose( function mapStateToProps (state) { return { error: state.appState.warning, + firstAddress: Object.keys(state.metamask.accounts)[0], } } @@ -29,7 +30,8 @@ function mapDispatchToProps (dispatch) { importNewAccount: (strategy, [ privateKey ]) => { return dispatch(actions.importNewAccount(strategy, [ privateKey ])) }, - displayWarning: () => dispatch(actions.displayWarning(null)), + displayWarning: (message) => dispatch(actions.displayWarning(message || null)), + setSelectedAddress: (address) => dispatch(actions.setSelectedAddress(address)), } } @@ -40,7 +42,7 @@ function PrivateKeyImportView () { } PrivateKeyImportView.prototype.render = function () { - const { error } = this.props + const { error, displayWarning } = this.props return ( h('div.new-account-import-form__private-key', [ @@ -60,7 +62,10 @@ PrivateKeyImportView.prototype.render = function () { h('div.new-account-import-form__buttons', {}, [ h('button.btn-default.btn--large.new-account-create-form__button', { - onClick: () => this.props.history.push(DEFAULT_ROUTE), + onClick: () => { + displayWarning(null) + this.props.history.push(DEFAULT_ROUTE) + }, }, [ this.context.t('cancel'), ]), @@ -88,10 +93,16 @@ PrivateKeyImportView.prototype.createKeyringOnEnter = function (event) { PrivateKeyImportView.prototype.createNewKeychain = function () { const input = document.getElementById('private-key-box') const privateKey = input.value - const { importNewAccount, history } = this.props + const { importNewAccount, history, displayWarning, setSelectedAddress, firstAddress } = this.props importNewAccount('Private Key', [ privateKey ]) - // JS runtime requires caught rejections but failures are handled by Redux - .catch() - .then(() => history.push(DEFAULT_ROUTE)) + .then(({ selectedAddress }) => { + if (selectedAddress) { + history.push(DEFAULT_ROUTE) + } else { + displayWarning('Error importing account.') + setSelectedAddress(firstAddress) + } + }) + .catch(err => displayWarning(err)) } diff --git a/ui/app/components/pages/create-account/index.js b/ui/app/components/pages/create-account/index.js index 475261253..6e3b93742 100644 --- a/ui/app/components/pages/create-account/index.js +++ b/ui/app/components/pages/create-account/index.js @@ -22,7 +22,9 @@ class CreateAccountPage extends Component { }), }), onClick: () => history.push(NEW_ACCOUNT_ROUTE), - }, 'Create'), + }, [ + this.context.t('create'), + ]), h('div.new-account__tabs__tab', { className: classnames('new-account__tabs__tab', { @@ -31,14 +33,16 @@ class CreateAccountPage extends Component { }), }), onClick: () => history.push(IMPORT_ACCOUNT_ROUTE), - }, 'Import'), + }, [ + this.context.t('import'), + ]), ]) } render () { return h('div.new-account', {}, [ h('div.new-account__header', [ - h('div.new-account__title', 'New Account'), + h('div.new-account__title', this.context.t('newAccount') ), this.renderTabs(), ]), h('div.new-account__form', [ @@ -62,6 +66,11 @@ class CreateAccountPage extends Component { CreateAccountPage.propTypes = { location: PropTypes.object, history: PropTypes.object, + t: PropTypes.func, +} + +CreateAccountPage.contextTypes = { + t: PropTypes.func, } const mapStateToProps = state => ({ diff --git a/ui/app/components/pages/settings/index.js b/ui/app/components/pages/settings/index.js index 384ae4b41..aee17e0e8 100644 --- a/ui/app/components/pages/settings/index.js +++ b/ui/app/components/pages/settings/index.js @@ -14,8 +14,8 @@ class Config extends Component { return h('div.settings__tabs', [ h(TabBar, { tabs: [ - { content: 'Settings', key: SETTINGS_ROUTE }, - { content: 'Info', key: INFO_ROUTE }, + { content: this.context.t('settings'), key: SETTINGS_ROUTE }, + { content: this.context.t('info'), key: INFO_ROUTE }, ], isActive: key => matchPath(location.pathname, { path: key, exact: true }), onSelect: key => history.push(key), @@ -54,6 +54,11 @@ class Config extends Component { Config.propTypes = { location: PropTypes.object, history: PropTypes.object, + t: PropTypes.func, +} + +Config.contextTypes = { + t: PropTypes.func, } module.exports = Config diff --git a/ui/app/components/pages/unlock-page/unlock-page.component.js b/ui/app/components/pages/unlock-page/unlock-page.component.js index 8bc3897da..a1d3f9181 100644 --- a/ui/app/components/pages/unlock-page/unlock-page.component.js +++ b/ui/app/components/pages/unlock-page/unlock-page.component.js @@ -120,7 +120,7 @@ class UnlockPage extends Component { > this.handleInputChange(event)} diff --git a/ui/app/components/wallet-view.js b/ui/app/components/wallet-view.js index 3b29dacac..da142fad8 100644 --- a/ui/app/components/wallet-view.js +++ b/ui/app/components/wallet-view.js @@ -36,7 +36,6 @@ function mapStateToProps (state) { tokens: state.metamask.tokens, keyrings: state.metamask.keyrings, selectedAddress: selectors.getSelectedAddress(state), - selectedIdentity: selectors.getSelectedIdentity(state), selectedAccount: selectors.getSelectedAccount(state), selectedTokenAddress: state.metamask.selectedTokenAddress, } @@ -99,21 +98,24 @@ WalletView.prototype.render = function () { const { responsiveDisplayClassname, selectedAddress, - selectedIdentity, keyrings, showAccountDetailModal, sidebarOpen, hideSidebar, history, + identities, } = this.props // temporary logs + fake extra wallets // console.log('walletview, selectedAccount:', selectedAccount) const checksummedAddress = checksumAddress(selectedAddress) + if (!selectedAddress) { + throw new Error('selectedAddress should not be ' + String(selectedAddress)) + } + const keyring = keyrings.find((kr) => { - return kr.accounts.includes(selectedAddress) || - kr.accounts.includes(selectedIdentity.address) + return kr.accounts.includes(selectedAddress) }) const type = keyring.type @@ -145,7 +147,7 @@ WalletView.prototype.render = function () { h('span.account-name', { style: {}, }, [ - selectedIdentity.name, + identities[selectedAddress].name, ]), h('button.btn-clear.wallet-view__details-button.allcaps', this.context.t('details')), diff --git a/ui/app/reducers.js b/ui/app/reducers.js index d0d364da9..0b158a778 100644 --- a/ui/app/reducers.js +++ b/ui/app/reducers.js @@ -4,7 +4,6 @@ const copyToClipboard = require('copy-to-clipboard') // // Sub-Reducers take in the complete state and return their sub-state // -const reduceIdentities = require('./reducers/identities') const reduceMetamask = require('./reducers/metamask') const reduceApp = require('./reducers/app') const reduceLocale = require('./reducers/locale') @@ -22,12 +21,6 @@ function rootReducer (state, action) { return action.value } - // - // Identities - // - - state.identities = reduceIdentities(state, action) - // // MetaMask // diff --git a/ui/app/reducers/identities.js b/ui/app/reducers/identities.js deleted file mode 100644 index 341a404e7..000000000 --- a/ui/app/reducers/identities.js +++ /dev/null @@ -1,15 +0,0 @@ -const extend = require('xtend') - -module.exports = reduceIdentities - -function reduceIdentities (state, action) { - // clone + defaults - var idState = extend({ - - }, state.identities) - - switch (action.type) { - default: - return idState - } -} diff --git a/ui/app/welcome-screen.js b/ui/app/welcome-screen.js index 2fa244d9f..63512cd50 100644 --- a/ui/app/welcome-screen.js +++ b/ui/app/welcome-screen.js @@ -14,6 +14,11 @@ class WelcomeScreen extends Component { closeWelcomeScreen: PropTypes.func.isRequired, welcomeScreenSeen: PropTypes.bool, history: PropTypes.object, + t: PropTypes.func, + } + + static contextTypes = { + t: PropTypes.func, } constructor (props) { @@ -45,16 +50,15 @@ class WelcomeScreen extends Component { height: '225', }), - h('div.welcome-screen__info__header', 'Welcome to MetaMask Beta'), + h('div.welcome-screen__info__header', this.context.t('welcomeBeta')), - h('div.welcome-screen__info__copy', 'MetaMask is a secure identity vault for Ethereum.'), + h('div.welcome-screen__info__copy', this.context.t('metamaskDescription')), - h('div.welcome-screen__info__copy', `It allows you to hold ether & tokens, - and serves as your bridge to decentralized applications.`), + h('div.welcome-screen__info__copy', this.context.t('holdEther')), h('button.welcome-screen__button', { onClick: this.initiateAccountCreation, - }, 'Continue'), + }, this.context.t('continue')), ]),