aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md12
-rw-r--r--app/_locales/ja/messages.json43
-rw-r--r--app/manifest.json2
-rw-r--r--app/scripts/controllers/preferences.js50
-rw-r--r--app/scripts/controllers/transactions/index.js6
-rw-r--r--app/scripts/controllers/transactions/lib/recipient-blacklist-checker.js24
-rw-r--r--app/scripts/controllers/transactions/lib/recipient-blacklist-config.json14
-rw-r--r--app/scripts/lib/diagnostics-reporter.js71
-rw-r--r--app/scripts/metamask-controller.js32
-rw-r--r--development/states/add-token.json6
-rw-r--r--development/states/confirm-sig-requests.json6
-rw-r--r--development/states/currency-localization.json6
-rw-r--r--development/states/send-edit.json6
-rw-r--r--development/states/send-new-ui.json6
-rw-r--r--development/states/tx-list-items.json6
-rw-r--r--mascara/src/app/first-time/index.css17
-rw-r--r--old-ui/app/components/account-dropdowns.js5
-rw-r--r--test/unit/app/controllers/metamask-controller-test.js34
-rw-r--r--test/unit/app/controllers/transactions/recipient-blacklist-checker-test.js77
-rw-r--r--test/unit/app/controllers/transactions/tx-controller-test.js17
-rw-r--r--ui/app/actions.js10
-rw-r--r--ui/app/app.js7
-rw-r--r--ui/app/components/account-menu/index.js7
-rw-r--r--ui/app/components/index.scss2
-rw-r--r--ui/app/components/modals/edit-account-name-modal.js2
-rw-r--r--ui/app/components/modals/hide-token-confirmation-modal.js2
-rw-r--r--ui/app/components/modals/shapeshift-deposit-tx-modal.js2
-rw-r--r--ui/app/components/pages/add-token/add-token.component.js6
-rw-r--r--ui/app/components/pages/create-account/import-account/json.js24
-rw-r--r--ui/app/components/pages/create-account/import-account/private-key.js25
-rw-r--r--ui/app/components/pages/create-account/index.js15
-rw-r--r--ui/app/components/pages/settings/index.js9
-rw-r--r--ui/app/components/pages/unlock-page/unlock-page.component.js2
-rw-r--r--ui/app/components/selected-account/index.js2
-rw-r--r--ui/app/components/selected-account/index.scss38
-rw-r--r--ui/app/components/selected-account/selected-account.component.js60
-rw-r--r--ui/app/components/selected-account/selected-account.container.js13
-rw-r--r--ui/app/components/token-cell.js4
-rw-r--r--ui/app/components/tx-view.js28
-rw-r--r--ui/app/components/wallet-view.js12
-rw-r--r--ui/app/css/itcss/components/account-menu.scss4
-rw-r--r--ui/app/css/itcss/components/hero-balance.scss1
-rw-r--r--ui/app/css/itcss/components/token-list.scss13
-rw-r--r--ui/app/reducers.js7
-rw-r--r--ui/app/reducers/identities.js15
-rw-r--r--ui/app/send-v2.js4
-rw-r--r--ui/app/welcome-screen.js14
47 files changed, 639 insertions, 129 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1cf23ccbe..19287f046 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,18 @@
## Current Master
+## 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
- Fix bug preventing users from logging in. Internally accounts and identities were out of sync.
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 760868ddf..8411e3a28 100644
--- a/app/scripts/controllers/preferences.js
+++ b/app/scripts/controllers/preferences.js
@@ -2,6 +2,7 @@ const ObservableStore = require('obs-store')
const normalizeAddress = require('eth-sig-util').normalize
const extend = require('xtend')
+
class PreferencesController {
/**
@@ -28,7 +29,11 @@ class PreferencesController {
featureFlags: {},
currentLocale: opts.initLangCode,
identities: {},
+ lostIdentities: {},
}, opts.initState)
+
+ this.diagnostics = opts.diagnostics
+
this.store = new ObservableStore(initState)
}
// PUBLIC METHODS
@@ -98,6 +103,50 @@ class PreferencesController {
this.store.updateState({ identities })
}
+ /*
+ * Synchronizes identity entries with known accounts.
+ * Removes any unknown identities, and returns the resulting selected address.
+ *
+ * @param {Array<string>} addresses known to the vault.
+ * @returns {Promise<string>} selectedAddress the selected address.
+ */
+ syncAddresses (addresses) {
+ let { identities, lostIdentities } = this.store.getState()
+
+ let newlyLost = {}
+ Object.keys(identities).forEach((identity) => {
+ if (!addresses.includes(identity)) {
+ newlyLost[identity] = identities[identity]
+ delete identities[identity]
+ }
+ })
+
+ // Identities are no longer present.
+ if (Object.keys(newlyLost).length > 0) {
+
+ // Notify our servers:
+ if (this.diagnostics) this.diagnostics.reportOrphans(newlyLost)
+
+ // store lost accounts
+ for (let key in newlyLost) {
+ lostIdentities[key] = newlyLost[key]
+ }
+ }
+
+ this.store.updateState({ identities, lostIdentities })
+ this.addAddresses(addresses)
+
+ // If the selected account is no longer valid,
+ // select an arbitrary other account:
+ let selected = this.getSelectedAddress()
+ if (!addresses.includes(selected)) {
+ selected = addresses[0]
+ this.setSelectedAddress(selected)
+ }
+
+ return selected
+ }
+
/**
* Setter for the `selectedAddress` property
*
@@ -198,6 +247,7 @@ class PreferencesController {
* @return {Promise<string>}
*/
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/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 96f976568..1bb0af5ee 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,6 +92,7 @@ module.exports = class MetamaskController extends EventEmitter {
this.preferencesController = new PreferencesController({
initState: initState.PreferencesController,
initLangCode: opts.initLangCode,
+ diagnostics: this.diagnostics,
})
// currency controller
@@ -356,7 +364,7 @@ module.exports = class MetamaskController extends EventEmitter {
importAccountWithStrategy: nodeify(this.importAccountWithStrategy, this),
// vault management
- submitPassword: nodeify(keyringController.submitPassword, keyringController),
+ submitPassword: nodeify(this.submitPassword, this),
// network management
setProviderType: nodeify(networkController.setProviderType, networkController),
@@ -474,6 +482,28 @@ module.exports = class MetamaskController extends EventEmitter {
}
}
+ /*
+ * Submits the user's password and attempts to unlock the vault.
+ * Also synchronizes the preferencesController, to ensure its schema
+ * is up to date with known accounts once the vault is decrypted.
+ *
+ * @param {string} password - The user's password
+ * @returns {Promise<object>} - The keyringController update.
+ */
+ async submitPassword (password) {
+ 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()
+ }
+
/**
* @type Identity
* @property {string} name - The account nickname.
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 ae3098ecb..0d4c27116 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 1297a9139..787f8023e 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 d567e3fed..ee787aaee 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/mascara/src/app/first-time/index.css b/mascara/src/app/first-time/index.css
index 25e60b84a..09e7d378d 100644
--- a/mascara/src/app/first-time/index.css
+++ b/mascara/src/app/first-time/index.css
@@ -123,10 +123,6 @@
width: calc(100vw - 80px);
}
- .unique-image {
- width: auto;
- }
-
.create-password__title,
.unique-image__title,
.tou__title,
@@ -148,7 +144,7 @@
height: 100%;
flex-direction: column;
align-items: center;
- justify-content: space-evenly;
+ justify-content: flex-start;
margin-top: 12px;
}
@@ -181,7 +177,6 @@
margin: 0 !important;
padding: 16px 20px !important;
height: 30vh !important;
- width: calc(100% - 48px) !important;
}
.backup-phrase__content-wrapper {
@@ -280,6 +275,12 @@
width: 335px;
}
+@media only screen and (max-width: 575px) {
+ .unique-image__body-text {
+ width: initial;
+ }
+}
+
.unique-image__body-text +
.unique-image__body-text,
.backup-phrase__body-text +
@@ -294,7 +295,7 @@
border-radius: 8px;
background-color: #FFFFFF;
margin: 0 142px 0 0;
- height: 334px;
+ height: 200px;
overflow-y: auto;
color: #757575;
font-family: Roboto;
@@ -679,7 +680,7 @@ button.backup-phrase__confirm-seed-option:hover {
}
.first-time-flow__input {
- width: 350px;
+ max-width: 350px;
}
.first-time-flow__button {
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 4bc16e65e..0dda4609b 100644
--- a/test/unit/app/controllers/metamask-controller-test.js
+++ b/test/unit/app/controllers/metamask-controller-test.js
@@ -45,7 +45,7 @@ describe('MetaMaskController', function () {
encryptor: {
encrypt: function (password, object) {
this.object = object
- return Promise.resolve()
+ return Promise.resolve('mock-encrypted')
},
decrypt: function () {
return Promise.resolve(this.object)
@@ -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')
})
@@ -62,6 +65,31 @@ describe('MetaMaskController', function () {
sandbox.restore()
})
+ describe('submitPassword', function () {
+ const password = 'password'
+
+ beforeEach(async function () {
+ await metamaskController.createNewVaultAndKeychain(password)
+ })
+
+ it('removes any identities that do not correspond to known accounts.', async function () {
+ const fakeAddress = '0xbad0'
+ metamaskController.preferencesController.addAddresses([fakeAddress])
+ await metamaskController.submitPassword(password)
+
+ const identities = Object.keys(metamaskController.preferencesController.store.getState().identities)
+ const addresses = await metamaskController.keyringController.getAccounts()
+
+ identities.forEach((identity) => {
+ assert.ok(addresses.includes(identity), `addresses should include all IDs: ${identity}`)
+ })
+
+ addresses.forEach((address) => {
+ assert.ok(identities.includes(address), `identities should include all Addresses: ${address}`)
+ })
+ })
+ })
+
describe('#getGasPrice', function () {
it('gives the 50th percentile lowest accepted gas price from recentBlocksController', async function () {
@@ -479,7 +507,7 @@ describe('MetaMaskController', function () {
it('errors when signing a message', async function () {
await metamaskController.signPersonalMessage(personalMessages[0].msgParams)
assert.equal(metamaskPersonalMsgs[msgId].status, 'signed')
- assert.equal(metamaskPersonalMsgs[msgId].rawSig, '0x6a1b65e2b8ed53cf398a769fad24738f9fbe29841fe6854e226953542c4b6a173473cb152b6b1ae5f06d601d45dd699a129b0a8ca84e78b423031db5baa734741b')
+ assert.equal(metamaskPersonalMsgs[msgId].rawSig, '0x6a1b65e2b8ed53cf398a769fad24738f9fbe29841fe6854e226953542c4b6a173473cb152b6b1ae5f06d601d45dd699a129b0a8ca84e78b423031db5baa734741b')
})
})
@@ -513,7 +541,7 @@ describe('MetaMaskController', function () {
})
it('sets up controller dnode api for trusted communication', function (done) {
- streamTest = createThoughStream((chunk, enc, cb) => {
+ streamTest = createThoughStream((chunk, enc, cb) => {
assert.equal(chunk.name, 'controller')
cb()
done()
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 a9372d6f3..07d7bd5d1 100644
--- a/ui/app/actions.js
+++ b/ui/app/actions.js
@@ -550,10 +550,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 0e8b907df..7005adb7f 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/index.scss b/ui/app/components/index.scss
index e69acff63..351640f6e 100644
--- a/ui/app/components/index.scss
+++ b/ui/app/components/index.scss
@@ -1,5 +1,7 @@
@import './export-text-container/index';
+@import './selected-account/index';
+
@import './info-box/index';
@import './pages/index';
diff --git a/ui/app/components/modals/edit-account-name-modal.js b/ui/app/components/modals/edit-account-name-modal.js
index 5681a3cad..edced8725 100644
--- a/ui/app/components/modals/edit-account-name-modal.js
+++ b/ui/app/components/modals/edit-account-name-modal.js
@@ -9,7 +9,7 @@ const { getSelectedAccount } = require('../../selectors')
function mapStateToProps (state) {
return {
selectedAccount: getSelectedAccount(state),
- identity: state.appState.modal.modalState.identity,
+ identity: state.appState.modal.modalState.props.identity,
}
}
diff --git a/ui/app/components/modals/hide-token-confirmation-modal.js b/ui/app/components/modals/hide-token-confirmation-modal.js
index 72e9c84eb..1518fa9a0 100644
--- a/ui/app/components/modals/hide-token-confirmation-modal.js
+++ b/ui/app/components/modals/hide-token-confirmation-modal.js
@@ -9,7 +9,7 @@ const Identicon = require('../identicon')
function mapStateToProps (state) {
return {
network: state.metamask.network,
- token: state.appState.modal.modalState.token,
+ token: state.appState.modal.modalState.props.token,
}
}
diff --git a/ui/app/components/modals/shapeshift-deposit-tx-modal.js b/ui/app/components/modals/shapeshift-deposit-tx-modal.js
index 24af5a0de..242c7b89d 100644
--- a/ui/app/components/modals/shapeshift-deposit-tx-modal.js
+++ b/ui/app/components/modals/shapeshift-deposit-tx-modal.js
@@ -8,7 +8,7 @@ const AccountModalContainer = require('./account-modal-container')
function mapStateToProps (state) {
return {
- Qr: state.appState.modal.modalState.Qr,
+ Qr: state.appState.modal.modalState.props.Qr,
}
}
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 {
<div className="add-token__custom-token-form">
<TextField
id="custom-address"
- label="Token Address"
+ label={this.context.t('tokenAddress')}
type="text"
value={customAddress}
onChange={e => this.handleCustomAddressChange(e.target.value)}
@@ -241,7 +241,7 @@ class AddToken extends Component {
/>
<TextField
id="custom-symbol"
- label="Token Symbol"
+ label={this.context.t('tokenSymbol')}
type="text"
value={customSymbol}
onChange={e => this.handleCustomSymbolChange(e.target.value)}
@@ -252,7 +252,7 @@ class AddToken extends Component {
/>
<TextField
id="custom-decimals"
- label="Decimals of Precision"
+ label={this.context.t('decimal')}
type="number"
value={customDecimals}
onChange={e => 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 {
>
<TextField
id="password"
- label="Password"
+ label={this.context.t('password')}
type="password"
value={this.state.password}
onChange={event => this.handleInputChange(event)}
diff --git a/ui/app/components/selected-account/index.js b/ui/app/components/selected-account/index.js
new file mode 100644
index 000000000..eb342181f
--- /dev/null
+++ b/ui/app/components/selected-account/index.js
@@ -0,0 +1,2 @@
+import SelectedAccount from './selected-account.container'
+module.exports = SelectedAccount
diff --git a/ui/app/components/selected-account/index.scss b/ui/app/components/selected-account/index.scss
new file mode 100644
index 000000000..5339a228b
--- /dev/null
+++ b/ui/app/components/selected-account/index.scss
@@ -0,0 +1,38 @@
+.selected-account {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ flex: 1;
+
+ &__name {
+ max-width: 200px;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ white-space: nowrap;
+ text-align: center;
+ }
+
+ &__address {
+ font-size: .75rem;
+ color: $silver-chalice;
+ }
+
+ &__clickable {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ padding: 5px 15px;
+ border-radius: 10px;
+ cursor: pointer;
+
+ &:hover {
+ background-color: #e8e6e8;
+ }
+
+ &:active {
+ background-color: #d9d7da;
+ }
+ }
+}
diff --git a/ui/app/components/selected-account/selected-account.component.js b/ui/app/components/selected-account/selected-account.component.js
new file mode 100644
index 000000000..3386a4196
--- /dev/null
+++ b/ui/app/components/selected-account/selected-account.component.js
@@ -0,0 +1,60 @@
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
+import copyToClipboard from 'copy-to-clipboard'
+
+const Tooltip = require('../tooltip-v2.js')
+
+const addressStripper = (address = '') => {
+ if (address.length < 4) {
+ return address
+ }
+
+ return `${address.slice(0, 4)}...${address.slice(-4)}`
+}
+
+class SelectedAccount extends Component {
+ state = {
+ copied: false,
+ }
+
+ static contextTypes = {
+ t: PropTypes.func,
+ }
+
+ static propTypes = {
+ selectedAddress: PropTypes.string,
+ selectedIdentity: PropTypes.object,
+ }
+
+ render () {
+ const { t } = this.context
+ const { selectedAddress, selectedIdentity } = this.props
+
+ return (
+ <div className="selected-account">
+ <Tooltip
+ position="bottom"
+ title={this.state.copied ? t('copiedExclamation') : t('copyToClipboard')}
+ >
+ <div
+ className="selected-account__clickable"
+ onClick={() => {
+ this.setState({ copied: true })
+ setTimeout(() => this.setState({ copied: false }), 3000)
+ copyToClipboard(selectedAddress)
+ }}
+ >
+ <div className="selected-account__name">
+ { selectedIdentity.name }
+ </div>
+ <div className="selected-account__address">
+ { addressStripper(selectedAddress) }
+ </div>
+ </div>
+ </Tooltip>
+ </div>
+ )
+ }
+}
+
+export default SelectedAccount
diff --git a/ui/app/components/selected-account/selected-account.container.js b/ui/app/components/selected-account/selected-account.container.js
new file mode 100644
index 000000000..f9e061d15
--- /dev/null
+++ b/ui/app/components/selected-account/selected-account.container.js
@@ -0,0 +1,13 @@
+import { connect } from 'react-redux'
+import SelectedAccount from './selected-account.component'
+
+const selectors = require('../../selectors')
+
+const mapStateToProps = state => {
+ return {
+ selectedAddress: selectors.getSelectedAddress(state),
+ selectedIdentity: selectors.getSelectedIdentity(state),
+ }
+}
+
+export default connect(mapStateToProps)(SelectedAccount)
diff --git a/ui/app/components/token-cell.js b/ui/app/components/token-cell.js
index c84117d84..4100d76a5 100644
--- a/ui/app/components/token-cell.js
+++ b/ui/app/components/token-cell.js
@@ -101,8 +101,8 @@ TokenCell.prototype.render = function () {
h('div.token-list-item__balance-ellipsis', null, [
h('div.token-list-item__balance-wrapper', null, [
- h('h3.token-list-item__token-balance', `${string || 0} ${symbol}`),
-
+ h('div.token-list-item__token-balance', `${string || 0}`),
+ h('div.token-list-item__token-symbol', symbol),
showFiat && h('div.token-list-item__fiat-amount', {
style: {},
}, formattedFiat),
diff --git a/ui/app/components/tx-view.js b/ui/app/components/tx-view.js
index 263f992c0..014497fcd 100644
--- a/ui/app/components/tx-view.js
+++ b/ui/app/components/tx-view.js
@@ -12,7 +12,7 @@ const { checksumAddress: toChecksumAddress } = require('../util')
const BalanceComponent = require('./balance-component')
const TxList = require('./tx-list')
-const Identicon = require('./identicon')
+const SelectedAccount = require('./selected-account')
module.exports = compose(
withRouter,
@@ -103,7 +103,7 @@ TxView.prototype.renderButtons = function () {
}
TxView.prototype.render = function () {
- const { selectedAddress, identity, network, isMascara } = this.props
+ const { isMascara } = this.props
return h('div.tx-view.flex-column', {
style: {},
@@ -111,10 +111,12 @@ TxView.prototype.render = function () {
h('div.flex-row.phone-visible', {
style: {
- justifyContent: 'space-between',
+ justifyContent: 'center',
alignItems: 'center',
flex: '0 0 auto',
- margin: '10px',
+ marginBottom: '16px',
+ padding: '5px',
+ borderBottom: '1px solid #e5e5e5',
},
}, [
@@ -127,23 +129,7 @@ TxView.prototype.render = function () {
onClick: () => this.props.sidebarOpen ? this.props.hideSidebar() : this.props.showSidebar(),
}),
- h('.identicon-wrapper.select-none', {
- style: {
- marginLeft: '0.9em',
- },
- }, [
- h(Identicon, {
- diameter: 24,
- address: selectedAddress,
- network,
- }),
- ]),
-
- h('span.account-name', {
- style: {},
- }, [
- identity.name,
- ]),
+ h(SelectedAccount),
!isMascara && h('div.open-in-browser', {
onClick: () => global.platform.openExtensionInBrowser(),
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/css/itcss/components/account-menu.scss b/ui/app/css/itcss/components/account-menu.scss
index 657760ab5..96fba890c 100644
--- a/ui/app/css/itcss/components/account-menu.scss
+++ b/ui/app/css/itcss/components/account-menu.scss
@@ -116,6 +116,10 @@
&__name {
color: $white;
font-size: 18px;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ white-space: nowrap;
+ max-width: 200px;
}
&__balance {
diff --git a/ui/app/css/itcss/components/hero-balance.scss b/ui/app/css/itcss/components/hero-balance.scss
index 69cde8a0f..09d66aedd 100644
--- a/ui/app/css/itcss/components/hero-balance.scss
+++ b/ui/app/css/itcss/components/hero-balance.scss
@@ -6,6 +6,7 @@
justify-content: flex-start;
align-items: center;
flex: 0 0 auto;
+ padding-top: 16px;
}
@media screen and (min-width: $break-large) {
diff --git a/ui/app/css/itcss/components/token-list.scss b/ui/app/css/itcss/components/token-list.scss
index e8de317e3..72fda372f 100644
--- a/ui/app/css/itcss/components/token-list.scss
+++ b/ui/app/css/itcss/components/token-list.scss
@@ -14,10 +14,17 @@ $wallet-balance-breakpoint-range: "screen and (min-width: #{$break-large}) and (
min-width: 0;
&__token-balance {
- font-size: 1.5rem;
+ margin-right: 4px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
+ min-width: 0;
+ max-width: 100%;
+ }
+
+ &__token-balance, &__token-symbol {
+ font-size: 1.5rem;
+ flex: 0 0 auto;
@media #{$wallet-balance-breakpoint-range} {
font-size: 95%;
@@ -66,7 +73,9 @@ $wallet-balance-breakpoint-range: "screen and (min-width: #{$break-large}) and (
}
&__balance-wrapper {
- flex: 1 1 auto;
+ flex: 1;
+ flex-flow: row wrap;
+ display: flex;
min-width: 0;
}
}
diff --git a/ui/app/reducers.js b/ui/app/reducers.js
index f155b2bf3..e3a3077d9 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) {
}
//
- // 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/send-v2.js b/ui/app/send-v2.js
index 4fbe8ff11..612f256df 100644
--- a/ui/app/send-v2.js
+++ b/ui/app/send-v2.js
@@ -224,7 +224,7 @@ SendTransactionScreen.prototype.renderFromRow = function () {
return h('div.send-v2__form-row', [
- h('div.send-v2__form-label', 'From:'),
+ h('div.send-v2__form-label', this.context.t('from')),
h('div.send-v2__form-field', [
h(FromDropdown, {
@@ -396,7 +396,7 @@ SendTransactionScreen.prototype.renderAmountRow = function () {
return h('div.send-v2__form-row', [
h('div.send-v2__form-label', [
- 'Amount:',
+ this.context.t('amount'),
this.renderErrorMessage('amount'),
!errors.amount && gasTotal && h('div.send-v2__amount-max', {
onClick: (event) => {
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')),
]),