Merge branch 'develop' of github.com:MetaMask/metamask-extension into network-remove-provider-engine

feature/default_network_editable
kumavis 7 years ago
commit 9d77b0a196
  1. 35
      CHANGELOG.md
  2. 14
      MISSION.md
  3. 12
      README.md
  4. 23
      app/_locales/en/messages.json
  5. 17
      app/images/check-icon.svg
  6. 5
      app/manifest.json
  7. 12
      app/scripts/background.js
  8. 74
      app/scripts/controllers/preferences.js
  9. 8
      app/scripts/controllers/transactions/index.js
  10. 9
      app/scripts/controllers/transactions/tx-state-manager.js
  11. 17
      app/scripts/controllers/user-actions.js
  12. 24
      app/scripts/lib/cleanErrorStack.js
  13. 1
      app/scripts/lib/createErrorMiddleware.js
  14. 71
      app/scripts/lib/diagnostics-reporter.js
  15. 11
      app/scripts/lib/get-first-preferred-lang-code.js
  16. 39
      app/scripts/metamask-controller.js
  17. 2
      app/scripts/migrations/026.js
  18. 28
      docs/publishing.md
  19. 2
      mascara/src/app/first-time/create-password-screen.js
  20. 2
      mascara/src/app/first-time/import-seed-phrase-screen.js
  21. 17
      mascara/src/app/first-time/index.css
  22. 111
      package-lock.json
  23. 5
      package.json
  24. 7
      test/e2e/metamask.spec.js
  25. 10
      test/integration/lib/add-token.js
  26. 6
      test/integration/lib/confirm-sig-requests.js
  27. 6
      test/integration/lib/send-new-ui.js
  28. 12
      test/screens/new-ui.js
  29. 27
      test/unit/app/controllers/metamask-controller-test.js
  30. 24
      ui/app/actions.js
  31. 3
      ui/app/app.js
  32. 23
      ui/app/components/button/button.component.js
  33. 19
      ui/app/components/button/button.stories.js
  34. 2
      ui/app/components/customize-gas-modal/index.js
  35. 4
      ui/app/components/index.scss
  36. 6
      ui/app/components/loading-screen/loading-screen.component.js
  37. 54
      ui/app/components/modals/confirm-reset-account/confirm-reset-account.component.js
  38. 13
      ui/app/components/modals/confirm-reset-account/confirm-reset-account.container.js
  39. 2
      ui/app/components/modals/confirm-reset-account/index.js
  40. 2
      ui/app/components/modals/deposit-ether-modal.js
  41. 2
      ui/app/components/modals/edit-account-name-modal.js
  42. 6
      ui/app/components/modals/export-private-key-modal.js
  43. 2
      ui/app/components/modals/hide-token-confirmation-modal.js
  44. 52
      ui/app/components/modals/index.scss
  45. 71
      ui/app/components/modals/modal.js
  46. 46
      ui/app/components/modals/notification-modals/confirm-reset-account.js
  47. 2
      ui/app/components/modals/notification/index.js
  48. 30
      ui/app/components/modals/notification/notification.component.js
  49. 38
      ui/app/components/modals/notification/notification.container.js
  50. 2
      ui/app/components/modals/shapeshift-deposit-tx-modal.js
  51. 2
      ui/app/components/modals/transaction-confirmed/index.js
  52. 24
      ui/app/components/modals/transaction-confirmed/transaction-confirmed.component.js
  53. 2
      ui/app/components/modals/welcome-beta/index.js
  54. 23
      ui/app/components/modals/welcome-beta/welcome-beta.component.js
  55. 2
      ui/app/components/pages/add-token/add-token.component.js
  56. 4
      ui/app/components/pages/add-token/token-list/token-list-placeholder/index.scss
  57. 2
      ui/app/components/pages/add-token/token-list/token-list-placeholder/token-list-placeholder.component.js
  58. 2
      ui/app/components/pages/confirm-add-token/confirm-add-token.component.js
  59. 2
      ui/app/components/pages/create-account/import-account/index.js
  60. 2
      ui/app/components/pages/create-account/import-account/json.js
  61. 4
      ui/app/components/pages/create-account/import-account/private-key.js
  62. 4
      ui/app/components/pages/create-account/new-account.js
  63. 6
      ui/app/components/pages/keychains/reveal-seed.js
  64. 8
      ui/app/components/pages/settings/settings.js
  65. 20
      ui/app/components/pages/unlock-page/unlock-page.component.js
  66. 60
      ui/app/components/pending-tx/confirm-send-ether.js
  67. 57
      ui/app/components/pending-tx/confirm-send-token.js
  68. 5
      ui/app/components/pending-tx/index.js
  69. 2
      ui/app/components/selected-account/index.js
  70. 38
      ui/app/components/selected-account/index.scss
  71. 60
      ui/app/components/selected-account/selected-account.component.js
  72. 13
      ui/app/components/selected-account/selected-account.container.js
  73. 2
      ui/app/components/shapeshift-form.js
  74. 4
      ui/app/components/signature-request.js
  75. 47
      ui/app/components/text-field/text-field.component.js
  76. 29
      ui/app/components/text-field/text-field.stories.js
  77. 4
      ui/app/components/token-cell.js
  78. 10
      ui/app/components/tx-list-item.js
  79. 28
      ui/app/components/tx-view.js
  80. 12
      ui/app/components/wallet-view.js
  81. 57
      ui/app/conf-tx.js
  82. 4
      ui/app/css/itcss/components/account-menu.scss
  83. 49
      ui/app/css/itcss/components/buttons.scss
  84. 1
      ui/app/css/itcss/components/hero-balance.scss
  85. 4
      ui/app/css/itcss/components/loading-overlay.scss
  86. 13
      ui/app/css/itcss/components/token-list.scss
  87. 19
      ui/app/reducers/app.js
  88. 2
      ui/app/send-v2.js

@ -2,6 +2,41 @@
## Current Master ## Current Master
- 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.
- Fix support links to point to new support system (Zendesk)
- Fix bug in migration #26 ( moving account nicknames to preferences )
- Clears account nicknames on restore from seedPhrase
## 4.7.1 Fri Jun 01 2018
- Fix bug where errors were not returned to Dapps.
## 4.7.0 Wed May 30 2018
- Fix Brave support
- Adds error messages when passwords don't match in onboarding flow.
- Adds modal notification if a retry in the process of being confirmed is dropped.
- New unlock screen design.
- Design improvements to the add token screen.
- Fix inconsistencies in confirm screen between extension and browser window modes.
- Fix scrolling in deposit ether modal.
- Fix styling of app spinner.
- Font weight changed from 300 to 400.
- New reveal screen design.
- Styling improvements to labels in first time flow and signature request headers.
## 4.6.1 Mon Apr 30 2018
- Fix bug where sending a transaction resulted in an infinite spinner
- Allow transactions with a 0 gwei gas price
- Handle encoding errors in ERC20 symbol + digits
- Fix ShapeShift forms (new + old ui)
- Fix sourcemaps
## 4.6.0 Thu Apr 26 2018 ## 4.6.0 Thu Apr 26 2018
- Correctly format currency conversion for locally selected preferred currency. - Correctly format currency conversion for locally selected preferred currency.

@ -0,0 +1,14 @@
# MetaMask Philosophy
## Mission
Making it safe and easy for the most people to use the decentralized web to the greatest degree that is empowering to them.
## Vision
To realize the highest goals achievable for the human race with the twin powers of peer to peer networks and cryptography. To empower users to hold and use their own keys on these new networks as securely and intelligibly as possible, enabling a new world of peer to peer agreements and economies, in hopes that we may collectively overcome the many great problems that we face together, through the power of strong cooperation.
## Strategy
We provide software for users to manage accounts, for sites to easily propose actions to users, and for users to coherently review actions before approving them. We build on this rapidly evolving set of protocols with the goal of empowering the most people to the greatest degree, and aspire to continuously evolve our offering to pursue that goal.

@ -1,12 +1,16 @@
# MetaMask Browser Extension # MetaMask Browser Extension
[![Build Status](https://circleci.com/gh/MetaMask/metamask-extension.svg?style=shield&circle-token=a1ddcf3cd38e29267f254c9c59d556d513e3a1fd)](https://circleci.com/gh/MetaMask/metamask-extension) [![Coverage Status](https://coveralls.io/repos/github/MetaMask/metamask-extension/badge.svg?branch=master)](https://coveralls.io/github/MetaMask/metamask-extension?branch=master) [![Greenkeeper badge](https://badges.greenkeeper.io/MetaMask/metamask-extension.svg)](https://greenkeeper.io/) [![Stories in Ready](https://badge.waffle.io/MetaMask/metamask-extension.png?label=in%20progress&title=waffle.io)](https://waffle.io/MetaMask/metamask-extension) [![Build Status](https://circleci.com/gh/MetaMask/metamask-extension.svg?style=shield&circle-token=a1ddcf3cd38e29267f254c9c59d556d513e3a1fd)](https://circleci.com/gh/MetaMask/metamask-extension) [![Coverage Status](https://coveralls.io/repos/github/MetaMask/metamask-extension/badge.svg?branch=master)](https://coveralls.io/github/MetaMask/metamask-extension?branch=master) [![Greenkeeper badge](https://badges.greenkeeper.io/MetaMask/metamask-extension.svg)](https://greenkeeper.io/) [![Stories in Ready](https://badge.waffle.io/MetaMask/metamask-extension.png?label=in%20progress&title=waffle.io)](https://waffle.io/MetaMask/metamask-extension)
[Internal documentation](./docs/jsdocs)
## Support ## Support
If you're a user seeking support, [here is our support site](https://metamask.helpscoutdocs.com/). If you're a user seeking support, [here is our support site](https://metamask.helpscoutdocs.com/).
## Introduction
[Mission Statement](./MISSION.md)
[Internal documentation](./docs/jsdocs)
## Developing Compatible Dapps ## Developing Compatible Dapps
If you're a web dapp developer, we've got two types of guides for you: If you're a web dapp developer, we've got two types of guides for you:
@ -23,7 +27,9 @@ If you're a web dapp developer, we've got two types of guides for you:
## Building locally ## Building locally
- Install [Node.js](https://nodejs.org/en/) version 6.3.1 or later. - Install [Node.js](https://nodejs.org/en/) version 6.3.1 or later.
- Install local dependencies with `npm install`. - Install dependencies:
- For node versions up to and including 9, install local dependencies with `npm install`.
- For node versions 10 and later, install [Yarn](https://yarnpkg.com/lang/en/docs/install/) and use `yarn install`.
- Install gulp globally with `npm install -g gulp-cli`. - Install gulp globally with `npm install -g gulp-cli`.
- Build the project to the `./dist/` folder with `gulp build`. - Build the project to the `./dist/` folder with `gulp build`.
- Optionally, to rebuild on file changes, run `gulp dev`. - Optionally, to rebuild on file changes, run `gulp dev`.

@ -402,6 +402,9 @@
"infoHelp": { "infoHelp": {
"message": "Info & Help" "message": "Info & Help"
}, },
"initialTransactionConfirmed": {
"message": "Your initial transaction was confirmed by the network. Click OK to go back."
},
"insufficientFunds": { "insufficientFunds": {
"message": "Insufficient funds." "message": "Insufficient funds."
}, },
@ -520,6 +523,9 @@
"networks": { "networks": {
"message": "Networks" "message": "Networks"
}, },
"nevermind": {
"message": "Nevermind"
},
"newAccount": { "newAccount": {
"message": "New Account" "message": "New Account"
}, },
@ -634,9 +640,15 @@
"rejected": { "rejected": {
"message": "Rejected" "message": "Rejected"
}, },
"reset": {
"message": "Reset"
},
"resetAccount": { "resetAccount": {
"message": "Reset Account" "message": "Reset Account"
}, },
"resetAccountDescription": {
"message": "Resetting your account will clear your transaction history."
},
"restoreFromSeed": { "restoreFromSeed": {
"message": "Restore account?" "message": "Restore account?"
}, },
@ -676,6 +688,9 @@
"ropsten": { "ropsten": {
"message": "Ropsten Test Network" "message": "Ropsten Test Network"
}, },
"rpc": {
"message": "Custom RPC"
},
"currentRpc": { "currentRpc": {
"message": "Current RPC" "message": "Current RPC"
}, },
@ -701,10 +716,10 @@
"save": { "save": {
"message": "Save" "message": "Save"
}, },
"reprice_title": { "speedUpTitle": {
"message": "Reprice Transaction" "message": "Speed Up Transaction"
}, },
"reprice_subtitle": { "speedUpSubtitle": {
"message": "Increase your gas price to attempt to overwrite and speed up your transaction" "message": "Increase your gas price to attempt to overwrite and speed up your transaction"
}, },
"saveAsCsvFile": { "saveAsCsvFile": {
@ -891,7 +906,7 @@
"message": "Welcome to the New UI (Beta)" "message": "Welcome to the New UI (Beta)"
}, },
"uiWelcomeMessage": { "uiWelcomeMessage": {
"message": "You are now using the new Metamask UI. Take a look around, try out new features like sending tokens, and let us know if you have any issues." "message": "You are now using the new Metamask UI."
}, },
"unapproved": { "unapproved": {
"message": "Unapproved" "message": "Unapproved"

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="100px" height="100px" viewBox="0 0 100 100" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: sketchtool 50.2 (55047) - http://www.bohemiancoding.com/sketch -->
<title>76BCDB09-52B0-41CB-908F-12F9087A2F1B</title>
<desc>Created with sketchtool.</desc>
<defs></defs>
<g id="Confirm-TX-screen" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="confirmed-alert" transform="translate(-144.000000, -53.000000)" stroke="#61BA00" stroke-width="4">
<g id="Group-17-Copy" transform="translate(22.000000, 20.000000)">
<g id="check-icon" transform="translate(124.000000, 35.000000)">
<circle id="Oval-5" cx="48" cy="48" r="48"></circle>
<polyline id="Path-3" stroke-linecap="round" points="29.76 52.8 41.0023819 64.32 71.04 34.56"></polyline>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

@ -1,7 +1,7 @@
{ {
"name": "__MSG_appName__", "name": "__MSG_appName__",
"short_name": "__MSG_appName__", "short_name": "__MSG_appName__",
"version": "4.6.0", "version": "4.7.2",
"manifest_version": 2, "manifest_version": 2,
"author": "https://metamask.io", "author": "https://metamask.io",
"description": "__MSG_appDescription__", "description": "__MSG_appDescription__",
@ -67,6 +67,9 @@
"externally_connectable": { "externally_connectable": {
"matches": [ "matches": [
"https://metamask.io/*" "https://metamask.io/*"
],
"ids": [
"*"
] ]
} }
} }

@ -309,6 +309,7 @@ function setupController (initState, initLangCode) {
// connect to other contexts // connect to other contexts
// //
extension.runtime.onConnect.addListener(connectRemote) extension.runtime.onConnect.addListener(connectRemote)
extension.runtime.onConnectExternal.addListener(connectExternal)
const metamaskInternalProcessHash = { const metamaskInternalProcessHash = {
[ENVIRONMENT_TYPE_POPUP]: true, [ENVIRONMENT_TYPE_POPUP]: true,
@ -335,9 +336,9 @@ function setupController (initState, initLangCode) {
function connectRemote (remotePort) { function connectRemote (remotePort) {
const processName = remotePort.name const processName = remotePort.name
const isMetaMaskInternalProcess = metamaskInternalProcessHash[processName] const isMetaMaskInternalProcess = metamaskInternalProcessHash[processName]
const portStream = new PortStream(remotePort)
if (isMetaMaskInternalProcess) { if (isMetaMaskInternalProcess) {
const portStream = new PortStream(remotePort)
// communication with popup // communication with popup
controller.isClientOpen = true controller.isClientOpen = true
controller.setupTrustedCommunication(portStream, 'MetaMask') controller.setupTrustedCommunication(portStream, 'MetaMask')
@ -370,11 +371,16 @@ function setupController (initState, initLangCode) {
}) })
} }
} else { } else {
// communication with page connectExternal(remotePort)
}
}
// communication with page or other extension
function connectExternal(remotePort) {
const originDomain = urlUtil.parse(remotePort.sender.url).hostname const originDomain = urlUtil.parse(remotePort.sender.url).hostname
const portStream = new PortStream(remotePort)
controller.setupUntrustedCommunication(portStream, originDomain) controller.setupUntrustedCommunication(portStream, originDomain)
} }
}
// //
// User Interface setup // User Interface setup

@ -2,6 +2,7 @@ const ObservableStore = require('obs-store')
const normalizeAddress = require('eth-sig-util').normalize const normalizeAddress = require('eth-sig-util').normalize
const extend = require('xtend') const extend = require('xtend')
class PreferencesController { class PreferencesController {
/** /**
@ -28,7 +29,11 @@ class PreferencesController {
featureFlags: {}, featureFlags: {},
currentLocale: opts.initLangCode, currentLocale: opts.initLangCode,
identities: {}, identities: {},
lostIdentities: {},
}, opts.initState) }, opts.initState)
this.diagnostics = opts.diagnostics
this.store = new ObservableStore(initState) this.store = new ObservableStore(initState)
} }
// PUBLIC METHODS // PUBLIC METHODS
@ -63,6 +68,13 @@ class PreferencesController {
this.store.updateState({ currentLocale: key }) this.store.updateState({ currentLocale: key })
} }
/**
* Updates identities to only include specified addresses. Removes identities
* not included in addresses array
*
* @param {string[]} addresses An array of hex addresses
*
*/
setAddresses (addresses) { setAddresses (addresses) {
const oldIdentities = this.store.getState().identities const oldIdentities = this.store.getState().identities
const identities = addresses.reduce((ids, address, index) => { const identities = addresses.reduce((ids, address, index) => {
@ -73,6 +85,68 @@ class PreferencesController {
this.store.updateState({ identities }) this.store.updateState({ identities })
} }
/**
* Adds addresses to the identities object without removing identities
*
* @param {string[]} addresses An array of hex addresses
*
*/
addAddresses (addresses) {
const identities = this.store.getState().identities
addresses.forEach((address) => {
// skip if already exists
if (identities[address]) return
// add missing identity
const identityCount = Object.keys(identities).length
identities[address] = { name: `Account ${identityCount + 1}`, address }
})
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 * Setter for the `selectedAddress` property
* *

@ -8,6 +8,7 @@ const TxGasUtil = require('./tx-gas-utils')
const PendingTransactionTracker = require('./pending-tx-tracker') const PendingTransactionTracker = require('./pending-tx-tracker')
const NonceTracker = require('./nonce-tracker') const NonceTracker = require('./nonce-tracker')
const txUtils = require('./lib/util') const txUtils = require('./lib/util')
const cleanErrorStack = require('../../lib/cleanErrorStack')
const log = require('loglevel') const log = require('loglevel')
/** /**
@ -123,6 +124,7 @@ class TransactionController extends EventEmitter {
@param txParams {object} - txParams for the transaction @param txParams {object} - txParams for the transaction
@param opts {object} - with the key origin to put the origin on the txMeta @param opts {object} - with the key origin to put the origin on the txMeta
*/ */
async newUnapprovedTransaction (txParams, opts = {}) { async newUnapprovedTransaction (txParams, opts = {}) {
log.debug(`MetaMaskController newUnapprovedTransaction ${JSON.stringify(txParams)}`) log.debug(`MetaMaskController newUnapprovedTransaction ${JSON.stringify(txParams)}`)
const initialTxMeta = await this.addUnapprovedTransaction(txParams) const initialTxMeta = await this.addUnapprovedTransaction(txParams)
@ -135,11 +137,11 @@ class TransactionController extends EventEmitter {
case 'submitted': case 'submitted':
return resolve(finishedTxMeta.hash) return resolve(finishedTxMeta.hash)
case 'rejected': case 'rejected':
return reject(new Error('MetaMask Tx Signature: User denied transaction signature.')) return reject(cleanErrorStack(new Error('MetaMask Tx Signature: User denied transaction signature.')))
case 'failed': case 'failed':
return reject(new Error(finishedTxMeta.err.message)) return reject(cleanErrorStack(new Error(finishedTxMeta.err.message)))
default: default:
return reject(new Error(`MetaMask Tx Signature: Unknown problem: ${JSON.stringify(finishedTxMeta.txParams)}`)) return reject(cleanErrorStack(new Error(`MetaMask Tx Signature: Unknown problem: ${JSON.stringify(finishedTxMeta.txParams)}`)))
} }
}) })
}) })

@ -2,6 +2,7 @@ const extend = require('xtend')
const EventEmitter = require('events') const EventEmitter = require('events')
const ObservableStore = require('obs-store') const ObservableStore = require('obs-store')
const ethUtil = require('ethereumjs-util') const ethUtil = require('ethereumjs-util')
const log = require('loglevel')
const txStateHistoryHelper = require('./lib/tx-state-history-helper') const txStateHistoryHelper = require('./lib/tx-state-history-helper')
const createId = require('../../lib/random-id') const createId = require('../../lib/random-id')
const { getFinalStates } = require('./lib/util') const { getFinalStates } = require('./lib/util')
@ -398,13 +399,19 @@ class TransactionStateManager extends EventEmitter {
_setTxStatus (txId, status) { _setTxStatus (txId, status) {
const txMeta = this.getTx(txId) const txMeta = this.getTx(txId)
txMeta.status = status txMeta.status = status
setTimeout(() => {
try {
this.updateTx(txMeta, `txStateManager: setting status to ${status}`)
this.emit(`${txMeta.id}:${status}`, txId) this.emit(`${txMeta.id}:${status}`, txId)
this.emit(`tx:status-update`, txId, status) this.emit(`tx:status-update`, txId, status)
if (['submitted', 'rejected', 'failed'].includes(status)) { if (['submitted', 'rejected', 'failed'].includes(status)) {
this.emit(`${txMeta.id}:finished`, txMeta) this.emit(`${txMeta.id}:finished`, txMeta)
} }
this.updateTx(txMeta, `txStateManager: setting status to ${status}`)
this.emit('update:badge') this.emit('update:badge')
} catch (error) {
log.error(error)
}
})
} }
/** /**

@ -0,0 +1,17 @@
const MessageManager = require('./lib/message-manager')
const PersonalMessageManager = require('./lib/personal-message-manager')
const TypedMessageManager = require('./lib/typed-message-manager')
class UserActionController {
constructor (opts = {}) {
this.messageManager = new MessageManager()
this.personalMessageManager = new PersonalMessageManager()
this.typedMessageManager = new TypedMessageManager()
}
}
module.exports = UserActionController

@ -0,0 +1,24 @@
/**
* Returns error without stack trace for better UI display
* @param {Error} err - error
* @returns {Error} Error with clean stack trace.
*/
function cleanErrorStack(err){
var name = err.name
name = (name === undefined) ? 'Error' : String(name)
var msg = err.message
msg = (msg === undefined) ? '' : String(msg)
if (name === '') {
err.stack = err.message
} else if (msg === '') {
err.stack = err.name
} else {
err.stack = err.name + ': ' + err.message
}
return err
}
module.exports = cleanErrorStack

@ -59,6 +59,7 @@ function createErrorMiddleware ({ override = true } = {}) {
if (!error) { return done() } if (!error) { return done() }
sanitizeRPCError(error) sanitizeRPCError(error)
log.error(`MetaMask - RPC Error: ${error.message}`, error) log.error(`MetaMask - RPC Error: ${error.message}`, error)
done()
}) })
} }
} }

@ -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

@ -2,6 +2,12 @@ const extension = require('extensionizer')
const promisify = require('pify') const promisify = require('pify')
const allLocales = require('../../_locales/index.json') const allLocales = require('../../_locales/index.json')
const isSupported = extension.i18n && extension.i18n.getAcceptLanguages
const getPreferredLocales = isSupported ? promisify(
extension.i18n.getAcceptLanguages,
{ errorFirst: false }
) : async () => []
const existingLocaleCodes = allLocales.map(locale => locale.code.toLowerCase().replace('_', '-')) const existingLocaleCodes = allLocales.map(locale => locale.code.toLowerCase().replace('_', '-'))
/** /**
@ -12,10 +18,7 @@ const existingLocaleCodes = allLocales.map(locale => locale.code.toLowerCase().r
* *
*/ */
async function getFirstPreferredLangCode () { async function getFirstPreferredLangCode () {
const userPreferredLocaleCodes = await promisify( const userPreferredLocaleCodes = await getPreferredLocales()
extension.i18n.getAcceptLanguages,
{ errorFirst: false }
)()
const firstPreferredLangCode = userPreferredLocaleCodes const firstPreferredLangCode = userPreferredLocaleCodes
.map(code => code.toLowerCase()) .map(code => code.toLowerCase())
.find(code => existingLocaleCodes.includes(code)) .find(code => existingLocaleCodes.includes(code))

@ -45,6 +45,8 @@ const BN = require('ethereumjs-util').BN
const GWEI_BN = new BN('1000000000') const GWEI_BN = new BN('1000000000')
const percentile = require('percentile') const percentile = require('percentile')
const seedPhraseVerifier = require('./lib/seed-phrase-verifier') const seedPhraseVerifier = require('./lib/seed-phrase-verifier')
const cleanErrorStack = require('./lib/cleanErrorStack')
const DiagnosticsReporter = require('./lib/diagnostics-reporter')
const log = require('loglevel') const log = require('loglevel')
module.exports = class MetamaskController extends EventEmitter { module.exports = class MetamaskController extends EventEmitter {
@ -63,6 +65,12 @@ module.exports = class MetamaskController extends EventEmitter {
const initState = opts.initState || {} const initState = opts.initState || {}
this.recordFirstTimeInfo(initState) this.recordFirstTimeInfo(initState)
// metamask diagnostics reporter
this.diagnostics = opts.diagnostics || new DiagnosticsReporter({
firstTimeInfo: initState.firstTimeInfo,
version,
})
// platform-specific api // platform-specific api
this.platform = opts.platform this.platform = opts.platform
@ -84,6 +92,7 @@ module.exports = class MetamaskController extends EventEmitter {
this.preferencesController = new PreferencesController({ this.preferencesController = new PreferencesController({
initState: initState.PreferencesController, initState: initState.PreferencesController,
initLangCode: opts.initLangCode, initLangCode: opts.initLangCode,
diagnostics: this.diagnostics,
}) })
// currency controller // currency controller
@ -139,6 +148,8 @@ module.exports = class MetamaskController extends EventEmitter {
const address = addresses[0] const address = addresses[0]
this.preferencesController.setSelectedAddress(address) this.preferencesController.setSelectedAddress(address)
} }
// ensure preferences + identities controller know about all addresses
this.preferencesController.addAddresses(addresses)
this.accountTracker.syncWithAddresses(addresses) this.accountTracker.syncWithAddresses(addresses)
}) })
@ -348,7 +359,7 @@ module.exports = class MetamaskController extends EventEmitter {
importAccountWithStrategy: nodeify(this.importAccountWithStrategy, this), importAccountWithStrategy: nodeify(this.importAccountWithStrategy, this),
// vault management // vault management
submitPassword: nodeify(keyringController.submitPassword, keyringController), submitPassword: nodeify(this.submitPassword, this),
// network management // network management
setProviderType: nodeify(networkController.setProviderType, networkController), setProviderType: nodeify(networkController.setProviderType, networkController),
@ -450,7 +461,11 @@ module.exports = class MetamaskController extends EventEmitter {
async createNewVaultAndRestore (password, seed) { async createNewVaultAndRestore (password, seed) {
const release = await this.createVaultMutex.acquire() const release = await this.createVaultMutex.acquire()
try { try {
// clear known identities
this.preferencesController.setAddresses([])
// create new vault
const vault = await this.keyringController.createNewVaultAndRestore(password, seed) const vault = await this.keyringController.createNewVaultAndRestore(password, seed)
// set new identities
const accounts = await this.keyringController.getAccounts() const accounts = await this.keyringController.getAccounts()
this.preferencesController.setAddresses(accounts) this.preferencesController.setAddresses(accounts)
this.selectFirstIdentity() this.selectFirstIdentity()
@ -462,6 +477,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 * @type Identity
* @property {string} name - The account nickname. * @property {string} name - The account nickname.

@ -27,7 +27,7 @@ module.exports = {
function transformState (state) { function transformState (state) {
if (!state.KeyringController || !state.PreferencesController) { if (!state.KeyringController || !state.PreferencesController) {
return return state
} }
if (!state.KeyringController.walletNicknames) { if (!state.KeyringController.walletNicknames) {

@ -2,18 +2,32 @@
When publishing a new version of MetaMask, we follow this procedure: When publishing a new version of MetaMask, we follow this procedure:
## Preparation
We try to ensure certain criteria are met before deploying:
- Deploy early in the week, to give time for emergency responses to unforeseen bugs.
- Deploy early in the day, for the same reason.
- Make sure at least one member of the support team is "on duty" to watch for new user issues coming through the support system.
- Roll out incrementally when possible, to a small number of users first, and gradually to more users.
## Incrementing Version & Changelog ## Incrementing Version & Changelog
Version can be automatically incremented [using our bump script](./bumping-version.md). Version can be automatically incremented [using our bump script](./bumping-version.md).
npm run version:bump $BUMP_TYPE` where `$BUMP_TYPE` is one of `major`, `minor`, or `patch`. npm run version:bump $BUMP_TYPE` where `$BUMP_TYPE` is one of `major`, `minor`, or `patch`.
## Publishing ## Building
1. `npm run dist` to generate the latest build. While we develop on the main `develop` branch, our production version is maintained on the `master` branch.
2. Publish to chrome store.
- Visit [the chrome developer dashboard](https://chrome.google.com/webstore/developer/dashboard?authuser=2). With each pull request, the @MetaMaskBot will comment with a build of that new pull request, so after bumping the version on `develop`, open a pull request against `master`, and once the pull request is reviewed and merged, you can download those builds for publication.
3. Publish to firefox addon marketplace.
4. Post on Github releases page. ## Publishing
5. `npm run announce`, post that announcement in our public places.
1. Publish to chrome store.
2. Visit [the chrome developer dashboard](https://chrome.google.com/webstore/developer/dashboard?authuser=2).
3. Publish to [firefox addon marketplace](http://addons.mozilla.org/en-us/firefox/addon/ether-metamask).
4. Publish to [Opera store](https://addons.opera.com/en/extensions/details/metamask/).
5. Post on [Github releases](https://github.com/MetaMask/metamask-extension/releases) page.
6. Run the `npm run announce` script, and post that announcement in our public places.

@ -143,6 +143,7 @@ class CreatePasswordScreen extends Component {
autoComplete="new-password" autoComplete="new-password"
margin="normal" margin="normal"
fullWidth fullWidth
largeLabel
/> />
<TextField <TextField
id="confirm-password" id="confirm-password"
@ -155,6 +156,7 @@ class CreatePasswordScreen extends Component {
autoComplete="confirm-password" autoComplete="confirm-password"
margin="normal" margin="normal"
fullWidth fullWidth
largeLabel
/> />
<button <button
className="first-time-flow__button" className="first-time-flow__button"

@ -146,6 +146,7 @@ class ImportSeedPhraseScreen extends Component {
error={passwordError} error={passwordError}
autoComplete="new-password" autoComplete="new-password"
margin="normal" margin="normal"
largeLabel
/> />
<TextField <TextField
id="confirm-password" id="confirm-password"
@ -157,6 +158,7 @@ class ImportSeedPhraseScreen extends Component {
error={confirmPasswordError} error={confirmPasswordError}
autoComplete="confirm-password" autoComplete="confirm-password"
margin="normal" margin="normal"
largeLabel
/> />
<button <button
className="first-time-flow__button" className="first-time-flow__button"

@ -123,10 +123,6 @@
width: calc(100vw - 80px); width: calc(100vw - 80px);
} }
.unique-image {
width: auto;
}
.create-password__title, .create-password__title,
.unique-image__title, .unique-image__title,
.tou__title, .tou__title,
@ -148,7 +144,7 @@
height: 100%; height: 100%;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
justify-content: space-evenly; justify-content: flex-start;
margin-top: 12px; margin-top: 12px;
} }
@ -181,7 +177,6 @@
margin: 0 !important; margin: 0 !important;
padding: 16px 20px !important; padding: 16px 20px !important;
height: 30vh !important; height: 30vh !important;
width: calc(100% - 48px) !important;
} }
.backup-phrase__content-wrapper { .backup-phrase__content-wrapper {
@ -280,6 +275,12 @@
width: 335px; width: 335px;
} }
@media only screen and (max-width: 575px) {
.unique-image__body-text {
width: initial;
}
}
.unique-image__body-text + .unique-image__body-text +
.unique-image__body-text, .unique-image__body-text,
.backup-phrase__body-text + .backup-phrase__body-text +
@ -294,7 +295,7 @@
border-radius: 8px; border-radius: 8px;
background-color: #FFFFFF; background-color: #FFFFFF;
margin: 0 142px 0 0; margin: 0 142px 0 0;
height: 334px; height: 200px;
overflow-y: auto; overflow-y: auto;
color: #757575; color: #757575;
font-family: Roboto; font-family: Roboto;
@ -679,7 +680,7 @@ button.backup-phrase__confirm-seed-option:hover {
} }
.first-time-flow__input { .first-time-flow__input {
width: 350px; max-width: 350px;
} }
.first-time-flow__button { .first-time-flow__button {

111
package-lock.json generated

@ -1500,7 +1500,8 @@
}, },
"dependencies": { "dependencies": {
"bignumber.js": { "bignumber.js": {
"version": "git+https://github.com/debris/bignumber.js.git#94d7146671b9719e00a09c29b01a691bc85048c2" "version": "git+https://github.com/debris/bignumber.js.git#94d7146671b9719e00a09c29b01a691bc85048c2",
"from": "git+https://github.com/debris/bignumber.js.git#94d7146671b9719e00a09c29b01a691bc85048c2"
}, },
"chai": { "chai": {
"version": "3.5.0", "version": "3.5.0",
@ -7149,6 +7150,12 @@
"domelementtype": "1.3.0" "domelementtype": "1.3.0"
} }
}, },
"dot-only-hunter": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/dot-only-hunter/-/dot-only-hunter-1.0.3.tgz",
"integrity": "sha1-9k0h7b5v8xFJlfEGGmGpNcMAIEs=",
"dev": true
},
"dotenv": { "dotenv": {
"version": "5.0.1", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-5.0.1.tgz", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-5.0.1.tgz",
@ -8248,20 +8255,20 @@
} }
}, },
"eth-keyring-controller": { "eth-keyring-controller": {
"version": "3.1.1", "version": "3.1.4",
"resolved": "https://registry.npmjs.org/eth-keyring-controller/-/eth-keyring-controller-3.1.1.tgz", "resolved": "https://registry.npmjs.org/eth-keyring-controller/-/eth-keyring-controller-3.1.4.tgz",
"integrity": "sha512-Z9HTzrop/V4Ld8Wq7uwetKecfWIyx25/uL8aFoZxV3kegZGoXaWoRmNy+4oW0WNLp4BcJ1lk6QfsGEdlymGjmA==", "integrity": "sha512-NNlVB/TBc8p9CblwECjPlUR+7MNQKiBa7tEFxIzZ9MjjNCEYPWDXTm0vJZzuDtVmFxYwIA53UD0QEn0QNxWNEQ==",
"requires": { "requires": {
"bip39": "2.4.0", "bip39": "^2.4.0",
"bluebird": "3.5.1", "bluebird": "^3.5.0",
"browser-passworder": "2.0.3", "browser-passworder": "^2.0.3",
"eth-hd-keyring": "1.2.2", "eth-hd-keyring": "^1.2.2",
"eth-sig-util": "1.4.2", "eth-sig-util": "^1.4.0",
"eth-simple-keyring": "1.2.1", "eth-simple-keyring": "^1.2.2",
"ethereumjs-util": "5.2.0", "ethereumjs-util": "^5.1.2",
"loglevel": "1.6.0", "loglevel": "^1.5.0",
"obs-store": "2.4.1", "obs-store": "^2.4.1",
"promise-filter": "1.1.0" "promise-filter": "^1.1.0"
}, },
"dependencies": { "dependencies": {
"babelify": { "babelify": {
@ -8269,8 +8276,8 @@
"resolved": "https://registry.npmjs.org/babelify/-/babelify-7.3.0.tgz", "resolved": "https://registry.npmjs.org/babelify/-/babelify-7.3.0.tgz",
"integrity": "sha1-qlau3nBn/XvVSWZu4W3ChQh+iOU=", "integrity": "sha1-qlau3nBn/XvVSWZu4W3ChQh+iOU=",
"requires": { "requires": {
"babel-core": "6.26.0", "babel-core": "^6.0.14",
"object-assign": "4.1.1" "object-assign": "^4.0.0"
} }
}, },
"ethereumjs-util": { "ethereumjs-util": {
@ -8278,13 +8285,13 @@
"resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.2.0.tgz", "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.2.0.tgz",
"integrity": "sha512-CJAKdI0wgMbQFLlLRtZKGcy/L6pzVRgelIZqRqNbuVFM3K9VEnyfbcvz0ncWMRNCe4kaHWjwRYQcYMucmwsnWA==", "integrity": "sha512-CJAKdI0wgMbQFLlLRtZKGcy/L6pzVRgelIZqRqNbuVFM3K9VEnyfbcvz0ncWMRNCe4kaHWjwRYQcYMucmwsnWA==",
"requires": { "requires": {
"bn.js": "4.11.8", "bn.js": "^4.11.0",
"create-hash": "1.1.3", "create-hash": "^1.1.2",
"ethjs-util": "0.1.4", "ethjs-util": "^0.1.3",
"keccak": "1.4.0", "keccak": "^1.0.2",
"rlp": "2.0.0", "rlp": "^2.0.0",
"safe-buffer": "5.1.1", "safe-buffer": "^5.1.1",
"secp256k1": "3.4.0" "secp256k1": "^3.0.1"
} }
}, },
"obs-store": { "obs-store": {
@ -8292,11 +8299,11 @@
"resolved": "https://registry.npmjs.org/obs-store/-/obs-store-2.4.1.tgz", "resolved": "https://registry.npmjs.org/obs-store/-/obs-store-2.4.1.tgz",
"integrity": "sha512-wpA8G4uSn8cnCKZ0pFTvqsamvy0Sm1hR2ot0Qonbfj5yBMwdAp/eD4vDI+U/ZCbV1hb2V5GapL8YKUdGCvahgg==", "integrity": "sha512-wpA8G4uSn8cnCKZ0pFTvqsamvy0Sm1hR2ot0Qonbfj5yBMwdAp/eD4vDI+U/ZCbV1hb2V5GapL8YKUdGCvahgg==",
"requires": { "requires": {
"babel-preset-es2015": "6.24.1", "babel-preset-es2015": "^6.22.0",
"babelify": "7.3.0", "babelify": "^7.3.0",
"readable-stream": "2.3.3", "readable-stream": "^2.2.2",
"through2": "2.0.3", "through2": "^2.0.3",
"xtend": "4.0.1" "xtend": "^4.0.1"
} }
} }
} }
@ -8343,6 +8350,7 @@
"dependencies": { "dependencies": {
"ethereumjs-abi": { "ethereumjs-abi": {
"version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#4ea2fdfed09e8f99117d9362d17c6b01b64a2bcf", "version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#4ea2fdfed09e8f99117d9362d17c6b01b64a2bcf",
"from": "git+https://github.com/ethereumjs/ethereumjs-abi.git",
"requires": { "requires": {
"bn.js": "4.11.8", "bn.js": "4.11.8",
"ethereumjs-util": "5.1.3" "ethereumjs-util": "5.1.3"
@ -8365,15 +8373,15 @@
} }
}, },
"eth-simple-keyring": { "eth-simple-keyring": {
"version": "1.2.1", "version": "1.2.2",
"resolved": "https://registry.npmjs.org/eth-simple-keyring/-/eth-simple-keyring-1.2.1.tgz", "resolved": "https://registry.npmjs.org/eth-simple-keyring/-/eth-simple-keyring-1.2.2.tgz",
"integrity": "sha1-bXs1LcWppQINYfafryHvsvY2P0U=", "integrity": "sha512-uQVBYshHUOaXVoat1BpLA/QNMCr4hgdFBgwIB7rRmQ+m3vQQAseUsOM+biPDYzq6end+6LjcccElLpQaIZe6dg==",
"requires": { "requires": {
"eth-sig-util": "1.4.2", "eth-sig-util": "^1.4.2",
"ethereumjs-util": "5.2.0", "ethereumjs-util": "^5.1.1",
"ethereumjs-wallet": "0.6.0", "ethereumjs-wallet": "^0.6.0",
"events": "1.1.1", "events": "^1.1.1",
"xtend": "4.0.1" "xtend": "^4.0.1"
}, },
"dependencies": { "dependencies": {
"ethereumjs-util": { "ethereumjs-util": {
@ -8381,13 +8389,13 @@
"resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.2.0.tgz", "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.2.0.tgz",
"integrity": "sha512-CJAKdI0wgMbQFLlLRtZKGcy/L6pzVRgelIZqRqNbuVFM3K9VEnyfbcvz0ncWMRNCe4kaHWjwRYQcYMucmwsnWA==", "integrity": "sha512-CJAKdI0wgMbQFLlLRtZKGcy/L6pzVRgelIZqRqNbuVFM3K9VEnyfbcvz0ncWMRNCe4kaHWjwRYQcYMucmwsnWA==",
"requires": { "requires": {
"bn.js": "4.11.8", "bn.js": "^4.11.0",
"create-hash": "1.1.3", "create-hash": "^1.1.2",
"ethjs-util": "0.1.4", "ethjs-util": "^0.1.3",
"keccak": "1.4.0", "keccak": "^1.0.2",
"rlp": "2.0.0", "rlp": "^2.0.0",
"safe-buffer": "5.1.1", "safe-buffer": "^5.1.1",
"secp256k1": "3.4.0" "secp256k1": "^3.0.1"
} }
} }
} }
@ -8574,7 +8582,7 @@
"eth-query": "2.1.2", "eth-query": "2.1.2",
"ethereumjs-block": "1.7.0", "ethereumjs-block": "1.7.0",
"ethereumjs-tx": "1.3.3", "ethereumjs-tx": "1.3.3",
"ethereumjs-util": "github:ethereumjs/ethereumjs-util#ac5d0908536b447083ea422b435da27f26615de9", "ethereumjs-util": "^5.0.1",
"ethereumjs-vm": "2.3.5", "ethereumjs-vm": "2.3.5",
"through2": "2.0.3", "through2": "2.0.3",
"treeify": "1.1.0", "treeify": "1.1.0",
@ -8681,7 +8689,7 @@
"async": "2.6.0", "async": "2.6.0",
"ethereum-common": "0.2.0", "ethereum-common": "0.2.0",
"ethereumjs-tx": "1.3.3", "ethereumjs-tx": "1.3.3",
"ethereumjs-util": "github:ethereumjs/ethereumjs-util#ac5d0908536b447083ea422b435da27f26615de9", "ethereumjs-util": "^5.0.0",
"merkle-patricia-tree": "2.3.0" "merkle-patricia-tree": "2.3.0"
} }
}, },
@ -8691,7 +8699,7 @@
"integrity": "sha1-7OBR0+/b53GtKlGNYWMsoqt17Ls=", "integrity": "sha1-7OBR0+/b53GtKlGNYWMsoqt17Ls=",
"requires": { "requires": {
"ethereum-common": "0.0.18", "ethereum-common": "0.0.18",
"ethereumjs-util": "github:ethereumjs/ethereumjs-util#ac5d0908536b447083ea422b435da27f26615de9" "ethereumjs-util": "^5.0.0"
}, },
"dependencies": { "dependencies": {
"ethereum-common": { "ethereum-common": {
@ -8703,6 +8711,7 @@
}, },
"ethereumjs-util": { "ethereumjs-util": {
"version": "github:ethereumjs/ethereumjs-util#ac5d0908536b447083ea422b435da27f26615de9", "version": "github:ethereumjs/ethereumjs-util#ac5d0908536b447083ea422b435da27f26615de9",
"from": "github:ethereumjs/ethereumjs-util#ac5d0908536b447083ea422b435da27f26615de9",
"requires": { "requires": {
"bn.js": "4.11.8", "bn.js": "4.11.8",
"create-hash": "1.1.3", "create-hash": "1.1.3",
@ -11192,6 +11201,7 @@
"dependencies": { "dependencies": {
"async-eventemitter": { "async-eventemitter": {
"version": "github:ahultgren/async-eventemitter#fa06e39e56786ba541c180061dbf2c0a5bbf951c", "version": "github:ahultgren/async-eventemitter#fa06e39e56786ba541c180061dbf2c0a5bbf951c",
"from": "async-eventemitter@github:ahultgren/async-eventemitter#fa06e39e56786ba541c180061dbf2c0a5bbf951c",
"requires": { "requires": {
"async": "2.6.0" "async": "2.6.0"
} }
@ -11884,6 +11894,7 @@
}, },
"gulp": { "gulp": {
"version": "github:gulpjs/gulp#71c094a51c7972d26f557899ddecab0210ef3776", "version": "github:gulpjs/gulp#71c094a51c7972d26f557899ddecab0210ef3776",
"from": "github:gulpjs/gulp#4.0",
"requires": { "requires": {
"glob-watcher": "4.0.0", "glob-watcher": "4.0.0",
"gulp-cli": "2.0.1", "gulp-cli": "2.0.1",
@ -15533,6 +15544,7 @@
"dependencies": { "dependencies": {
"async-eventemitter": { "async-eventemitter": {
"version": "github:ahultgren/async-eventemitter#fa06e39e56786ba541c180061dbf2c0a5bbf951c", "version": "github:ahultgren/async-eventemitter#fa06e39e56786ba541c180061dbf2c0a5bbf951c",
"from": "async-eventemitter@github:ahultgren/async-eventemitter#fa06e39e56786ba541c180061dbf2c0a5bbf951c",
"requires": { "requires": {
"async": "2.6.0" "async": "2.6.0"
} }
@ -18307,7 +18319,7 @@
"integrity": "sha512-LKd2OoIT9Re/OG38zXbd5pyHIk2IfcOUczCwkYXl5iJIbufg9nqpweh66VfPwMkUlrEvc7YVvtQdmSrB9V9TkQ==", "integrity": "sha512-LKd2OoIT9Re/OG38zXbd5pyHIk2IfcOUczCwkYXl5iJIbufg9nqpweh66VfPwMkUlrEvc7YVvtQdmSrB9V9TkQ==",
"requires": { "requires": {
"async": "1.5.2", "async": "1.5.2",
"ethereumjs-util": "github:ethereumjs/ethereumjs-util#ac5d0908536b447083ea422b435da27f26615de9", "ethereumjs-util": "^5.0.0",
"level-ws": "0.0.0", "level-ws": "0.0.0",
"levelup": "1.3.9", "levelup": "1.3.9",
"memdown": "1.4.1", "memdown": "1.4.1",
@ -31329,7 +31341,8 @@
}, },
"dependencies": { "dependencies": {
"bignumber.js": { "bignumber.js": {
"version": "git+https://github.com/frozeman/bignumber.js-nolookahead.git#57692b3ecfc98bbdd6b3a516cb2353652ea49934" "version": "git+https://github.com/frozeman/bignumber.js-nolookahead.git#57692b3ecfc98bbdd6b3a516cb2353652ea49934",
"from": "git+https://github.com/frozeman/bignumber.js-nolookahead.git"
} }
} }
}, },
@ -31687,6 +31700,7 @@
"dependencies": { "dependencies": {
"async-eventemitter": { "async-eventemitter": {
"version": "github:ahultgren/async-eventemitter#fa06e39e56786ba541c180061dbf2c0a5bbf951c", "version": "github:ahultgren/async-eventemitter#fa06e39e56786ba541c180061dbf2c0a5bbf951c",
"from": "async-eventemitter@github:ahultgren/async-eventemitter#fa06e39e56786ba541c180061dbf2c0a5bbf951c",
"requires": { "requires": {
"async": "2.6.0" "async": "2.6.0"
} }
@ -31777,6 +31791,7 @@
}, },
"websocket": { "websocket": {
"version": "git://github.com/frozeman/WebSocket-Node.git#7004c39c42ac98875ab61126e5b4a925430f592c", "version": "git://github.com/frozeman/WebSocket-Node.git#7004c39c42ac98875ab61126e5b4a925430f592c",
"from": "websocket@git://github.com/frozeman/WebSocket-Node.git#7004c39c42ac98875ab61126e5b4a925430f592c",
"requires": { "requires": {
"debug": "2.6.9", "debug": "2.6.9",
"nan": "2.8.0", "nan": "2.8.0",

@ -9,7 +9,7 @@
"dist": "gulp dist", "dist": "gulp dist",
"doc": "jsdoc -c development/tools/.jsdoc.json", "doc": "jsdoc -c development/tools/.jsdoc.json",
"test": "npm run test:unit && npm run test:integration && npm run lint", "test": "npm run test:unit && npm run test:integration && npm run lint",
"test:unit": "cross-env METAMASK_ENV=test mocha --exit --require test/setup.js --recursive \"test/unit/**/*.js\"", "test:unit": "cross-env METAMASK_ENV=test mocha --exit --require test/setup.js --recursive \"test/unit/**/*.js\" && dot-only-hunter",
"test:single": "cross-env METAMASK_ENV=test mocha --require test/helper.js", "test:single": "cross-env METAMASK_ENV=test mocha --require test/helper.js",
"test:integration": "npm run test:integration:build && npm run test:flat && npm run test:mascara", "test:integration": "npm run test:integration:build && npm run test:flat && npm run test:mascara",
"test:integration:build": "gulp build:scss", "test:integration:build": "gulp build:scss",
@ -100,7 +100,7 @@
"eth-json-rpc-filters": "^2.1.1", "eth-json-rpc-filters": "^2.1.1",
"eth-json-rpc-infura": "^3.0.0", "eth-json-rpc-infura": "^3.0.0",
"eth-json-rpc-middleware": "^2.2.2", "eth-json-rpc-middleware": "^2.2.2",
"eth-keyring-controller": "^3.1.1", "eth-keyring-controller": "^3.1.4",
"eth-phishing-detect": "^1.1.4", "eth-phishing-detect": "^1.1.4",
"eth-query": "^2.1.2", "eth-query": "^2.1.2",
"eth-sig-util": "^1.4.2", "eth-sig-util": "^1.4.2",
@ -223,6 +223,7 @@
"css-loader": "^0.28.11", "css-loader": "^0.28.11",
"deep-freeze-strict": "^1.1.1", "deep-freeze-strict": "^1.1.1",
"del": "^3.0.0", "del": "^3.0.0",
"dot-only-hunter": "^1.0.3",
"envify": "^4.0.0", "envify": "^4.0.0",
"enzyme": "^3.3.0", "enzyme": "^3.3.0",
"enzyme-adapter-react-15": "^1.0.5", "enzyme-adapter-react-15": "^1.0.5",

@ -121,7 +121,14 @@ describe('Metamask popup page', function () {
await delay(300) await delay(300)
}) })
it('adds a second account', async function () {
await driver.findElement(By.css('#app-content > div > div.full-width > div > div:nth-child(2) > span > div')).click()
await delay(300)
await driver.findElement(By.css('#app-content > div > div.full-width > div > div:nth-child(2) > span > div > div > span > div > li:nth-child(3) > span')).click()
})
it('shows account address', async function () { it('shows account address', async function () {
await delay(300)
accountAddress = await driver.findElement(By.css('#app-content > div > div.app-primary.from-left > div > div > div:nth-child(1) > flex-column > div.flex-row > div')).getText() accountAddress = await driver.findElement(By.css('#app-content > div > div.app-primary.from-left > div > div > div:nth-child(1) > flex-column > div.flex-row > div')).getText()
}) })

@ -43,7 +43,7 @@ async function runAddTokenFlowTest (assert, done) {
assert.equal(addTokenTitle[0].textContent, 'Add Tokens', 'add token title is correct') assert.equal(addTokenTitle[0].textContent, 'Add Tokens', 'add token title is correct')
// Cancel Add Token // Cancel Add Token
const cancelAddTokenButton = await queryAsync($, 'button.btn-secondary--lg.page-container__footer-button') const cancelAddTokenButton = await queryAsync($, 'button.btn-default.btn--large.page-container__footer-button')
assert.ok(cancelAddTokenButton[0], 'cancel add token button present') assert.ok(cancelAddTokenButton[0], 'cancel add token button present')
cancelAddTokenButton.click() cancelAddTokenButton.click()
@ -75,15 +75,15 @@ async function runAddTokenFlowTest (assert, done) {
tokenWrapper[0].click() tokenWrapper[0].click()
// Click Next button // Click Next button
let nextButton = await queryAsync($, 'button.btn-primary--lg') let nextButton = await queryAsync($, 'button.btn-primary.btn--large')
assert.equal(nextButton[0].textContent, 'Next', 'next button rendered') assert.equal(nextButton[0].textContent, 'Next', 'next button rendered')
nextButton[0].click() nextButton[0].click()
// Confirm Add token // Confirm Add token
const confirmAddToken = await queryAsync($, '.confirm-add-token') const confirmAddToken = await queryAsync($, '.confirm-add-token')
assert.ok(confirmAddToken[0], 'confirm add token rendered') assert.ok(confirmAddToken[0], 'confirm add token rendered')
assert.ok($('button.btn-primary--lg')[0], 'confirm add token button found') assert.ok($('button.btn-primary.btn--large')[0], 'confirm add token button found')
$('button.btn-primary--lg')[0].click() $('button.btn-primary.btn--large')[0].click()
// Verify added token image // Verify added token image
let heroBalance = await queryAsync($, '.hero-balance') let heroBalance = await queryAsync($, '.hero-balance')
@ -120,7 +120,7 @@ async function runAddTokenFlowTest (assert, done) {
const errorMessage = await queryAsync($, '#custom-symbol-helper-text') const errorMessage = await queryAsync($, '#custom-symbol-helper-text')
assert.ok(errorMessage[0], 'error rendered') assert.ok(errorMessage[0], 'error rendered')
$('button.btn-secondary--lg')[0].click() $('button.btn-default.btn--large')[0].click()
// await timeout(100000) // await timeout(100000)

@ -38,7 +38,7 @@ async function runConfirmSigRequestsTest(assert, done) {
let confirmSigRowValue = await queryAsync($, '.request-signature__row-value') let confirmSigRowValue = await queryAsync($, '.request-signature__row-value')
assert.equal(confirmSigRowValue[0].textContent, '0x879a053d4800c6354e76c7985a865d2922c82fb5b3f4577b2fe08b998954f2e0') assert.equal(confirmSigRowValue[0].textContent, '0x879a053d4800c6354e76c7985a865d2922c82fb5b3f4577b2fe08b998954f2e0')
let confirmSigSignButton = await queryAsync($, 'button.btn-primary--lg') let confirmSigSignButton = await queryAsync($, 'button.btn-primary.btn--large')
confirmSigSignButton[0].click() confirmSigSignButton[0].click()
confirmSigHeadline = await queryAsync($, '.request-signature__headline') confirmSigHeadline = await queryAsync($, '.request-signature__headline')
@ -47,7 +47,7 @@ async function runConfirmSigRequestsTest(assert, done) {
confirmSigRowValue = await queryAsync($, '.request-signature__row-value') confirmSigRowValue = await queryAsync($, '.request-signature__row-value')
assert.ok(confirmSigRowValue[0].textContent.match(/^\#\sTerms\sof\sUse/)) assert.ok(confirmSigRowValue[0].textContent.match(/^\#\sTerms\sof\sUse/))
confirmSigSignButton = await queryAsync($, 'button.btn-primary--lg') confirmSigSignButton = await queryAsync($, 'button.btn-primary.btn--large')
confirmSigSignButton[0].click() confirmSigSignButton[0].click()
confirmSigHeadline = await queryAsync($, '.request-signature__headline') confirmSigHeadline = await queryAsync($, '.request-signature__headline')
@ -57,7 +57,7 @@ async function runConfirmSigRequestsTest(assert, done) {
assert.equal(confirmSigRowValue[0].textContent, 'Hi, Alice!') assert.equal(confirmSigRowValue[0].textContent, 'Hi, Alice!')
assert.equal(confirmSigRowValue[1].textContent, '1337') assert.equal(confirmSigRowValue[1].textContent, '1337')
confirmSigSignButton = await queryAsync($, 'button.btn-primary--lg') confirmSigSignButton = await queryAsync($, 'button.btn-primary.btn--large')
confirmSigSignButton[0].click() confirmSigSignButton[0].click()
const txView = await queryAsync($, '.tx-view') const txView = await queryAsync($, '.tx-view')

@ -129,7 +129,7 @@ async function runSendFlowTest(assert, done) {
await customizeGas(assert, 0, 21000, '0', '$0.00 USD') await customizeGas(assert, 0, 21000, '0', '$0.00 USD')
await customizeGas(assert, 500, 60000, '0.003', '$3.60 USD') await customizeGas(assert, 500, 60000, '0.003', '$3.60 USD')
const sendButton = await queryAsync($, 'button.btn-primary--lg.page-container__footer-button') const sendButton = await queryAsync($, 'button.btn-primary.btn--large.page-container__footer-button')
assert.equal(sendButton[0].textContent, 'Next', 'next button rendered') assert.equal(sendButton[0].textContent, 'Next', 'next button rendered')
sendButton[0].click() sendButton[0].click()
await timeout() await timeout()
@ -169,13 +169,13 @@ async function runSendFlowTest(assert, done) {
sendAmountFieldInputInEdit.val('1.0') sendAmountFieldInputInEdit.val('1.0')
reactTriggerChange(sendAmountFieldInputInEdit[0]) reactTriggerChange(sendAmountFieldInputInEdit[0])
const sendButtonInEdit = await queryAsync($, '.btn-primary--lg.page-container__footer-button') const sendButtonInEdit = await queryAsync($, '.btn-primary.btn--large.page-container__footer-button')
assert.equal(sendButtonInEdit[0].textContent, 'Next', 'next button in edit rendered') assert.equal(sendButtonInEdit[0].textContent, 'Next', 'next button in edit rendered')
selectState.val('send new ui') selectState.val('send new ui')
reactTriggerChange(selectState[0]) reactTriggerChange(selectState[0])
const cancelButtonInEdit = await queryAsync($, '.btn-secondary--lg.page-container__footer-button') const cancelButtonInEdit = await queryAsync($, '.btn-default.btn--large.page-container__footer-button')
cancelButtonInEdit[0].click() cancelButtonInEdit[0].click()
// sendButtonInEdit[0].click() // sendButtonInEdit[0].click()

@ -11,9 +11,8 @@ const GIFEncoder = require('gifencoder')
const pngFileStream = require('png-file-stream') const pngFileStream = require('png-file-stream')
const sizeOfPng = require('image-size/lib/types/png') const sizeOfPng = require('image-size/lib/types/png')
const By = webdriver.By const By = webdriver.By
const { delay, buildWebDriver } = require('./func')
const localesIndex = require('../../app/_locales/index.json') const localesIndex = require('../../app/_locales/index.json')
// const localesIndex = [] const { delay, buildChromeWebDriver, buildFirefoxWebdriver, installWebExt, getExtensionIdChrome, getExtensionIdFirefox } = require('../e2e/func')
const eth = new Ethjs(new Ethjs.HttpProvider('http://localhost:8545')) const eth = new Ethjs(new Ethjs.HttpProvider('http://localhost:8545'))
@ -50,11 +49,10 @@ async function captureAllScreens() {
await cleanScreenShotDir() await cleanScreenShotDir()
// setup selenium and install extension
const extPath = path.resolve('dist/chrome') const extPath = path.resolve('dist/chrome')
driver = buildWebDriver(extPath) driver = buildChromeWebDriver(extPath)
await driver.get('chrome://extensions-frame') const extensionId = await getExtensionIdChrome(driver)
const extensionId = await driver.executeScript('return document.querySelector("extensions-manager").shadowRoot.querySelector("extensions-view-manager extensions-item-list").shadowRoot.querySelector("#container > div.items-container > extensions-item:nth-child(2)").getAttribute("id")')
await driver.get(`chrome-extension://${extensionId}/home.html`) await driver.get(`chrome-extension://${extensionId}/home.html`)
await delay(500) await delay(500)
tabs = await driver.getAllWindowHandles() tabs = await driver.getAllWindowHandles()
@ -165,7 +163,7 @@ async function captureAllScreens() {
await delay(300) await delay(300)
await captureLanguageScreenShots('metamask account detail export private key screen - password entered') await captureLanguageScreenShots('metamask account detail export private key screen - password entered')
await driver.findElement(By.css('.btn-primary--lg.export-private-key__button')).click() await driver.findElement(By.css('.btn-primary.btn--large.export-private-key__button')).click()
await delay(300) await delay(300)
await captureLanguageScreenShots('metamask account detail export private key screen - reveal key') await captureLanguageScreenShots('metamask account detail export private key screen - reveal key')

@ -47,7 +47,7 @@ describe('MetaMaskController', function () {
encryptor: { encryptor: {
encrypt: function (password, object) { encrypt: function (password, object) {
this.object = object this.object = object
return Promise.resolve() return Promise.resolve('mock-encrypted')
}, },
decrypt: function () { decrypt: function () {
return Promise.resolve(this.object) return Promise.resolve(this.object)
@ -64,6 +64,31 @@ describe('MetaMaskController', function () {
sandbox.restore() 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 () { describe('#getGasPrice', function () {
it('gives the 50th percentile lowest accepted gas price from recentBlocksController', async function () { it('gives the 50th percentile lowest accepted gas price from recentBlocksController', async function () {

@ -503,15 +503,21 @@ function requestRevealSeedWords (password) {
} }
function resetAccount () { function resetAccount () {
return (dispatch) => { return dispatch => {
dispatch(actions.showLoadingIndication())
return new Promise((resolve, reject) => {
background.resetAccount((err, account) => { background.resetAccount((err, account) => {
dispatch(actions.hideLoadingIndication()) dispatch(actions.hideLoadingIndication())
if (err) { if (err) {
dispatch(actions.displayWarning(err.message)) dispatch(actions.displayWarning(err.message))
return reject(err)
} }
log.info('Transaction history reset for ' + account) log.info('Transaction history reset for ' + account)
dispatch(actions.showAccountsPage()) dispatch(actions.showAccountsPage())
resolve(account)
})
}) })
} }
} }
@ -1397,16 +1403,24 @@ function markAccountsFound () {
function retryTransaction (txId) { function retryTransaction (txId) {
log.debug(`background.retryTransaction`) log.debug(`background.retryTransaction`)
let newTxId
return (dispatch) => { return (dispatch) => {
return new Promise((resolve, reject) => {
background.retryTransaction(txId, (err, newState) => { background.retryTransaction(txId, (err, newState) => {
if (err) { if (err) {
return dispatch(actions.displayWarning(err.message)) dispatch(actions.displayWarning(err.message))
reject(err)
} }
const { selectedAddressTxList } = newState const { selectedAddressTxList } = newState
const { id: newTxId } = selectedAddressTxList[selectedAddressTxList.length - 1] const { id } = selectedAddressTxList[selectedAddressTxList.length - 1]
dispatch(actions.updateMetamaskState(newState)) newTxId = id
dispatch(actions.viewPendingTx(newTxId)) resolve(newState)
})
}) })
.then(newState => dispatch(actions.updateMetamaskState(newState)))
.then(() => newTxId)
} }
} }

@ -76,7 +76,7 @@ class App extends Component {
h(Authenticated, { path: REVEAL_SEED_ROUTE, exact, component: RevealSeedConfirmation }), h(Authenticated, { path: REVEAL_SEED_ROUTE, exact, component: RevealSeedConfirmation }),
h(Authenticated, { path: SETTINGS_ROUTE, component: Settings }), h(Authenticated, { path: SETTINGS_ROUTE, component: Settings }),
h(Authenticated, { path: NOTICE_ROUTE, exact, component: NoticeScreen }), h(Authenticated, { path: NOTICE_ROUTE, exact, component: NoticeScreen }),
h(Authenticated, { path: CONFIRM_TRANSACTION_ROUTE, component: ConfirmTxScreen }), h(Authenticated, { path: `${CONFIRM_TRANSACTION_ROUTE}/:id?`, component: ConfirmTxScreen }),
h(Authenticated, { path: SEND_ROUTE, exact, component: SendTransactionScreen2 }), h(Authenticated, { path: SEND_ROUTE, exact, component: SendTransactionScreen2 }),
h(Authenticated, { path: ADD_TOKEN_ROUTE, exact, component: AddTokenPage }), h(Authenticated, { path: ADD_TOKEN_ROUTE, exact, component: AddTokenPage }),
h(Authenticated, { path: CONFIRM_ADD_TOKEN_ROUTE, exact, component: ConfirmAddTokenPage }), h(Authenticated, { path: CONFIRM_ADD_TOKEN_ROUTE, exact, component: ConfirmAddTokenPage }),
@ -137,7 +137,6 @@ class App extends Component {
(isLoading || isLoadingNetwork) && h(Loading, { (isLoading || isLoadingNetwork) && h(Loading, {
loadingMessage: loadMessage, loadingMessage: loadMessage,
fullScreen: true,
}), }),
// content // content

@ -2,20 +2,15 @@ import React, { Component } from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import classnames from 'classnames' import classnames from 'classnames'
const SECONDARY = 'secondary' const CLASSNAME_DEFAULT = 'btn-default'
const CLASSNAME_PRIMARY = 'btn-primary' const CLASSNAME_PRIMARY = 'btn-primary'
const CLASSNAME_PRIMARY_LARGE = 'btn-primary--lg'
const CLASSNAME_SECONDARY = 'btn-secondary' const CLASSNAME_SECONDARY = 'btn-secondary'
const CLASSNAME_SECONDARY_LARGE = 'btn-secondary--lg' const CLASSNAME_LARGE = 'btn--large'
const getClassName = (type, large = false) => { const typeHash = {
let output = type === SECONDARY ? CLASSNAME_SECONDARY : CLASSNAME_PRIMARY default: CLASSNAME_DEFAULT,
primary: CLASSNAME_PRIMARY,
if (large) { secondary: CLASSNAME_SECONDARY,
output += ` ${type === SECONDARY ? CLASSNAME_SECONDARY_LARGE : CLASSNAME_PRIMARY_LARGE}`
}
return output
} }
class Button extends Component { class Button extends Component {
@ -24,7 +19,11 @@ class Button extends Component {
return ( return (
<button <button
className={classnames(getClassName(type, large), className)} className={classnames(
typeHash[type],
large && CLASSNAME_LARGE,
className
)}
{ ...buttonProps } { ...buttonProps }
> >
{ this.props.children } { this.props.children }

@ -13,13 +13,21 @@ storiesOf('Button', module)
{text('text', 'Click me')} {text('text', 'Click me')}
</Button> </Button>
) )
.add('secondary', () => ( .add('secondary', () =>
<Button <Button
onClick={action('clicked')} onClick={action('clicked')}
type="secondary" type="secondary"
> >
{text('text', 'Click me')} {text('text', 'Click me')}
</Button> </Button>
)
.add('default', () => (
<Button
onClick={action('clicked')}
type="default"
>
{text('text', 'Click me')}
</Button>
)) ))
.add('large primary', () => ( .add('large primary', () => (
<Button <Button
@ -39,3 +47,12 @@ storiesOf('Button', module)
{text('text', 'Click me')} {text('text', 'Click me')}
</Button> </Button>
)) ))
.add('large default', () => (
<Button
onClick={action('clicked')}
type="default"
large
>
{text('text', 'Click me')}
</Button>
))

@ -308,7 +308,7 @@ CustomizeGasModal.prototype.render = function () {
}, [this.context.t('revert')]), }, [this.context.t('revert')]),
h('div.send-v2__customize-gas__buttons', [ h('div.send-v2__customize-gas__buttons', [
h('button.btn-secondary.send-v2__customize-gas__cancel', { h('button.btn-default.send-v2__customize-gas__cancel', {
onClick: this.props.hideModal, onClick: this.props.hideModal,
style: { style: {
marginRight: '10px', marginRight: '10px',

@ -1,5 +1,9 @@
@import './export-text-container/index'; @import './export-text-container/index';
@import './selected-account/index';
@import './info-box/index'; @import './info-box/index';
@import './pages/index'; @import './pages/index';
@import './modals/index';

@ -1,7 +1,6 @@
const { Component } = require('react') const { Component } = require('react')
const h = require('react-hyperscript') const h = require('react-hyperscript')
const PropTypes = require('prop-types') const PropTypes = require('prop-types')
const classnames = require('classnames')
const Spinner = require('../spinner') const Spinner = require('../spinner')
class LoadingScreen extends Component { class LoadingScreen extends Component {
@ -12,9 +11,7 @@ class LoadingScreen extends Component {
render () { render () {
return ( return (
h('.loading-overlay', { h('.loading-overlay', [
className: classnames({ 'loading-overlay--full-screen': this.props.fullScreen }),
}, [
h('.loading-overlay__container', [ h('.loading-overlay__container', [
h(Spinner, { h(Spinner, {
color: '#F7C06C', color: '#F7C06C',
@ -29,7 +26,6 @@ class LoadingScreen extends Component {
LoadingScreen.propTypes = { LoadingScreen.propTypes = {
loadingMessage: PropTypes.string, loadingMessage: PropTypes.string,
fullScreen: PropTypes.bool,
} }
module.exports = LoadingScreen module.exports = LoadingScreen

@ -0,0 +1,54 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import Button from '../../button'
class ConfirmResetAccount extends Component {
static propTypes = {
hideModal: PropTypes.func.isRequired,
resetAccount: PropTypes.func.isRequired,
}
static contextTypes = {
t: PropTypes.func,
}
handleReset () {
this.props.resetAccount()
.then(() => this.props.hideModal())
}
render () {
const { t } = this.context
return (
<div className="modal-container">
<div className="modal-container__content">
<div className="modal-container__title">
{ `${t('resetAccount')}?` }
</div>
<div className="modal-container__description">
{ t('resetAccountDescription') }
</div>
</div>
<div className="modal-container__footer">
<Button
type="default"
className="modal-container__footer-button"
onClick={() => this.props.hideModal()}
>
{ t('nevermind') }
</Button>
<Button
type="secondary"
className="modal-container__footer-button"
onClick={() => this.handleReset()}
>
{ t('reset') }
</Button>
</div>
</div>
)
}
}
export default ConfirmResetAccount

@ -0,0 +1,13 @@
import { connect } from 'react-redux'
import ConfirmResetAccount from './confirm-reset-account.component'
const { hideModal, resetAccount } = require('../../../actions')
const mapDispatchToProps = dispatch => {
return {
hideModal: () => dispatch(hideModal()),
resetAccount: () => dispatch(resetAccount()),
}
}
export default connect(null, mapDispatchToProps)(ConfirmResetAccount)

@ -0,0 +1,2 @@
import ConfirmResetAccount from './confirm-reset-account.container'
module.exports = ConfirmResetAccount

@ -109,7 +109,7 @@ DepositEtherModal.prototype.renderRow = function ({
]), ]),
!hideButton && h('div.deposit-ether-modal__buy-row__button', [ !hideButton && h('div.deposit-ether-modal__buy-row__button', [
h('button.btn-primary--lg.deposit-ether-modal__deposit-button', { h('button.btn-primary.btn--large.deposit-ether-modal__deposit-button', {
onClick: onButtonClick, onClick: onButtonClick,
}, [buttonLabel]), }, [buttonLabel]),
]), ]),

@ -9,7 +9,7 @@ const { getSelectedAccount } = require('../../selectors')
function mapStateToProps (state) { function mapStateToProps (state) {
return { return {
selectedAccount: getSelectedAccount(state), selectedAccount: getSelectedAccount(state),
identity: state.appState.modal.modalState.identity, identity: state.appState.modal.modalState.props.identity,
} }
} }

@ -87,14 +87,14 @@ ExportPrivateKeyModal.prototype.renderButton = function (className, onClick, lab
ExportPrivateKeyModal.prototype.renderButtons = function (privateKey, password, address, hideModal) { ExportPrivateKeyModal.prototype.renderButtons = function (privateKey, password, address, hideModal) {
return h('div.export-private-key-buttons', {}, [ return h('div.export-private-key-buttons', {}, [
!privateKey && this.renderButton( !privateKey && this.renderButton(
'btn-secondary--lg export-private-key__button export-private-key__button--cancel', 'btn-default btn--large export-private-key__button export-private-key__button--cancel',
() => hideModal(), () => hideModal(),
'Cancel' 'Cancel'
), ),
(privateKey (privateKey
? this.renderButton('btn-primary--lg export-private-key__button', () => hideModal(), this.context.t('done')) ? this.renderButton('btn-primary btn--large export-private-key__button', () => hideModal(), this.context.t('done'))
: this.renderButton('btn-primary--lg export-private-key__button', () => this.exportAccountAndGetPrivateKey(this.state.password, address), this.context.t('confirm')) : this.renderButton('btn-primary btn--large export-private-key__button', () => this.exportAccountAndGetPrivateKey(this.state.password, address), this.context.t('confirm'))
), ),
]) ])

@ -9,7 +9,7 @@ const Identicon = require('../identicon')
function mapStateToProps (state) { function mapStateToProps (state) {
return { return {
network: state.metamask.network, network: state.metamask.network,
token: state.appState.modal.modalState.token, token: state.appState.modal.modalState.props.token,
} }
} }

@ -0,0 +1,52 @@
.modal-container {
width: 100%;
height: 100%;
background-color: #fff;
display: flex;
flex-flow: column;
border-radius: 8px;
&__title {
font-size: 1.5rem;
font-weight: 500;
padding: 16px 0;
text-align: center;
}
&__description {
text-align: center;
font-size: .875rem;
}
&__content {
overflow-y: auto;
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
padding: 32px;
@media screen and (max-width: 575px) {
justify-content: center;
padding: 28px 20px;
}
}
&__footer {
display: flex;
flex-flow: row;
justify-content: center;
border-top: 1px solid #d2d8dd;
padding: 16px;
flex: 0 0 auto;
&-button {
min-width: 0;
margin-right: 16px;
&:last-of-type {
margin-right: 0;
}
}
}
}

@ -19,7 +19,30 @@ const ShapeshiftDepositTxModal = require('./shapeshift-deposit-tx-modal.js')
const HideTokenConfirmationModal = require('./hide-token-confirmation-modal') const HideTokenConfirmationModal = require('./hide-token-confirmation-modal')
const CustomizeGasModal = require('../customize-gas-modal') const CustomizeGasModal = require('../customize-gas-modal')
const NotifcationModal = require('./notification-modal') const NotifcationModal = require('./notification-modal')
const ConfirmResetAccount = require('./notification-modals/confirm-reset-account') const ConfirmResetAccount = require('./confirm-reset-account')
const TransactionConfirmed = require('./transaction-confirmed')
const WelcomeBeta = require('./welcome-beta')
const Notification = require('./notification')
const modalContainerBaseStyle = {
transform: 'translate3d(-50%, 0, 0px)',
border: '1px solid #CCCFD1',
borderRadius: '8px',
backgroundColor: '#FFFFFF',
boxShadow: '0 2px 22px 0 rgba(0,0,0,0.2)',
}
const modalContainerLaptopStyle = {
...modalContainerBaseStyle,
width: '344px',
top: '15%',
}
const modalContainerMobileStyle = {
...modalContainerBaseStyle,
width: '309px',
top: '12.5%',
}
const accountModalStyle = { const accountModalStyle = {
mobileModalStyle: { mobileModalStyle: {
@ -173,18 +196,18 @@ const MODALS = {
BETA_UI_NOTIFICATION_MODAL: { BETA_UI_NOTIFICATION_MODAL: {
contents: [ contents: [
h(NotifcationModal, { h(Notification, [
header: 'uiWelcome', h(WelcomeBeta),
message: 'uiWelcomeMessage', ]),
}),
], ],
mobileModalStyle: { mobileModalStyle: {
width: '95%', ...modalContainerMobileStyle,
top: getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_POPUP ? '52vh' : '36.5vh',
}, },
laptopModalStyle: { laptopModalStyle: {
width: '449px', ...modalContainerLaptopStyle,
top: 'calc(33% + 45px)', },
contentStyle: {
borderRadius: '8px',
}, },
}, },
@ -208,12 +231,13 @@ const MODALS = {
CONFIRM_RESET_ACCOUNT: { CONFIRM_RESET_ACCOUNT: {
contents: h(ConfirmResetAccount), contents: h(ConfirmResetAccount),
mobileModalStyle: { mobileModalStyle: {
width: '95%', ...modalContainerMobileStyle,
top: getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_POPUP ? '52vh' : '36.5vh',
}, },
laptopModalStyle: { laptopModalStyle: {
width: '473px', ...modalContainerLaptopStyle,
top: 'calc(33% + 45px)', },
contentStyle: {
borderRadius: '8px',
}, },
}, },
@ -265,6 +289,24 @@ const MODALS = {
}, },
}, },
TRANSACTION_CONFIRMED: {
disableBackdropClick: true,
contents: [
h(Notification, [
h(TransactionConfirmed),
]),
],
mobileModalStyle: {
...modalContainerMobileStyle,
},
laptopModalStyle: {
...modalContainerLaptopStyle,
},
contentStyle: {
borderRadius: '8px',
},
},
DEFAULT: { DEFAULT: {
contents: [], contents: [],
mobileModalStyle: {}, mobileModalStyle: {},
@ -306,7 +348,7 @@ module.exports = connect(mapStateToProps, mapDispatchToProps)(Modal)
Modal.prototype.render = function () { Modal.prototype.render = function () {
const modal = MODALS[this.props.modalState.name || 'DEFAULT'] const modal = MODALS[this.props.modalState.name || 'DEFAULT']
const children = modal.contents const { contents: children, disableBackdropClick = false } = modal
const modalStyle = modal[isMobileView() ? 'mobileModalStyle' : 'laptopModalStyle'] const modalStyle = modal[isMobileView() ? 'mobileModalStyle' : 'laptopModalStyle']
const contentStyle = modal.contentStyle || {} const contentStyle = modal.contentStyle || {}
@ -326,6 +368,7 @@ Modal.prototype.render = function () {
modalStyle, modalStyle,
contentStyle, contentStyle,
backdropStyle: BACKDROPSTYLE, backdropStyle: BACKDROPSTYLE,
closeOnClick: !disableBackdropClick,
}, },
children, children,
) )

@ -1,46 +0,0 @@
const { Component } = require('react')
const PropTypes = require('prop-types')
const h = require('react-hyperscript')
const connect = require('react-redux').connect
const actions = require('../../../actions')
const NotifcationModal = require('../notification-modal')
class ConfirmResetAccount extends Component {
render () {
const { resetAccount } = this.props
return h(NotifcationModal, {
header: 'Are you sure you want to reset account?',
message: h('div', [
h('span', `Resetting is for developer use only. This button wipes the current account's transaction history,
which is used to calculate the current account nonce. `),
h('a.notification-modal__link', {
href: 'http://metamask.helpscoutdocs.com/article/36-resetting-an-account',
target: '_blank',
onClick (event) { global.platform.openWindow({ url: event.target.href }) },
}, 'Read more.'),
]),
showCancelButton: true,
showConfirmButton: true,
onConfirm: resetAccount,
})
}
}
ConfirmResetAccount.propTypes = {
resetAccount: PropTypes.func,
}
const mapDispatchToProps = dispatch => {
return {
resetAccount: () => {
dispatch(actions.resetAccount())
},
}
}
module.exports = connect(null, mapDispatchToProps)(ConfirmResetAccount)

@ -0,0 +1,2 @@
import Notification from './notification.container'
module.exports = Notification

@ -0,0 +1,30 @@
import React from 'react'
import PropTypes from 'prop-types'
import Button from '../../button'
const Notification = (props, context) => {
return (
<div className="modal-container">
{ props.children }
<div className="modal-container__footer">
<Button
type="primary"
onClick={() => props.onHide()}
>
{ context.t('ok') }
</Button>
</div>
</div>
)
}
Notification.propTypes = {
onHide: PropTypes.func.isRequired,
children: PropTypes.element,
}
Notification.contextTypes = {
t: PropTypes.func,
}
export default Notification

@ -0,0 +1,38 @@
import { connect } from 'react-redux'
import Notification from './notification.component'
const { hideModal } = require('../../../actions')
const mapStateToProps = state => {
const { appState: { modal: { modalState: { props } } } } = state
const { onHide } = props
return {
onHide,
}
}
const mapDispatchToProps = dispatch => {
return {
hideModal: () => dispatch(hideModal()),
}
}
const mergeProps = (stateProps, dispatchProps, ownProps) => {
const { onHide, ...otherStateProps } = stateProps
const { hideModal, ...otherDispatchProps } = dispatchProps
return {
...otherStateProps,
...otherDispatchProps,
...ownProps,
onHide: () => {
hideModal()
if (onHide && typeof onHide === 'function') {
onHide()
}
},
}
}
export default connect(mapStateToProps, mapDispatchToProps, mergeProps)(Notification)

@ -8,7 +8,7 @@ const AccountModalContainer = require('./account-modal-container')
function mapStateToProps (state) { function mapStateToProps (state) {
return { return {
Qr: state.appState.modal.modalState.Qr, Qr: state.appState.modal.modalState.props.Qr,
} }
} }

@ -0,0 +1,2 @@
import TransactionConfirmed from './transaction-confirmed.component'
module.exports = TransactionConfirmed

@ -0,0 +1,24 @@
import React from 'react'
import PropTypes from 'prop-types'
const TransactionConfirmed = (props, context) => {
const { t } = context
return (
<div className="modal-container__content">
<img src="images/check-icon.svg" />
<div className="modal-container__title">
{ `${t('confirmed')}!` }
</div>
<div className="modal-container__description">
{ t('initialTransactionConfirmed') }
</div>
</div>
)
}
TransactionConfirmed.contextTypes = {
t: PropTypes.func,
}
export default TransactionConfirmed

@ -0,0 +1,2 @@
import WelcomeBeta from './welcome-beta.component'
module.exports = WelcomeBeta

@ -0,0 +1,23 @@
import React from 'react'
import PropTypes from 'prop-types'
const TransactionConfirmed = (props, context) => {
const { t } = context
return (
<div className="modal-container__content">
<div className="modal-container__title">
{ `${t('uiWelcome')}` }
</div>
<div className="modal-container__description">
{ t('uiWelcomeMessage') }
</div>
</div>
)
}
TransactionConfirmed.contextTypes = {
t: PropTypes.func,
}
export default TransactionConfirmed

@ -323,7 +323,7 @@ class AddToken extends Component {
</div> </div>
<div className="page-container__footer"> <div className="page-container__footer">
<Button <Button
type="secondary" type="default"
large large
className="page-container__footer-button" className="page-container__footer-button"
onClick={() => { onClick={() => {

@ -11,6 +11,10 @@
width: 50%; width: 50%;
text-align: center; text-align: center;
margin-top: 8px; margin-top: 8px;
@media screen and (max-width: 575px) {
width: 60%;
}
} }
&__link { &__link {

@ -15,7 +15,7 @@ export default class TokenListPlaceholder extends Component {
</div> </div>
<a <a
className="token-list-placeholder__link" className="token-list-placeholder__link"
href="http://metamask.helpscoutdocs.com/article/16-managing-erc20-tokens" href="https://consensys.zendesk.com/hc/en-us/articles/360004135092"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
> >

@ -87,7 +87,7 @@ export default class ConfirmAddToken extends Component {
</div> </div>
<div className="page-container__footer"> <div className="page-container__footer">
<Button <Button
type="secondary" type="default"
large large
className="page-container__footer-button" className="page-container__footer-button"
onClick={() => history.push(ADD_TOKEN_ROUTE)} onClick={() => history.push(ADD_TOKEN_ROUTE)}

@ -46,7 +46,7 @@ AccountImportSubview.prototype.render = function () {
}, },
onClick: () => { onClick: () => {
global.platform.openWindow({ global.platform.openWindow({
url: 'https://metamask.helpscoutdocs.com/article/17-what-are-loose-accounts', url: 'https://consensys.zendesk.com/hc/en-us/articles/360004180111-What-are-imported-accounts-New-UI',
}) })
}, },
}, this.context.t('here')), }, this.context.t('here')),

@ -51,7 +51,7 @@ class JsonImportSubview extends Component {
h('div.new-account-create-form__buttons', {}, [ h('div.new-account-create-form__buttons', {}, [
h('button.btn-secondary.new-account-create-form__button', { h('button.btn-default.new-account-create-form__button', {
onClick: () => this.props.history.push(DEFAULT_ROUTE), onClick: () => this.props.history.push(DEFAULT_ROUTE),
}, [ }, [
this.context.t('cancel'), this.context.t('cancel'),

@ -59,13 +59,13 @@ PrivateKeyImportView.prototype.render = function () {
h('div.new-account-import-form__buttons', {}, [ h('div.new-account-import-form__buttons', {}, [
h('button.btn-secondary--lg.new-account-create-form__button', { h('button.btn-default.btn--large.new-account-create-form__button', {
onClick: () => this.props.history.push(DEFAULT_ROUTE), onClick: () => this.props.history.push(DEFAULT_ROUTE),
}, [ }, [
this.context.t('cancel'), this.context.t('cancel'),
]), ]),
h('button.btn-primary--lg.new-account-create-form__button', { h('button.btn-primary.btn--large.new-account-create-form__button', {
onClick: () => this.createNewKeychain(), onClick: () => this.createNewKeychain(),
}, [ }, [
this.context.t('import'), this.context.t('import'),

@ -38,13 +38,13 @@ class NewAccountCreateForm extends Component {
h('div.new-account-create-form__buttons', {}, [ h('div.new-account-create-form__buttons', {}, [
h('button.btn-secondary--lg.new-account-create-form__button', { h('button.btn-default.btn--large.new-account-create-form__button', {
onClick: () => history.push(DEFAULT_ROUTE), onClick: () => history.push(DEFAULT_ROUTE),
}, [ }, [
this.context.t('cancel'), this.context.t('cancel'),
]), ]),
h('button.btn-primary--lg.new-account-create-form__button', { h('button.btn-primary.btn--large.new-account-create-form__button', {
onClick: () => { onClick: () => {
createAccount(newAccountName || defaultAccountName) createAccount(newAccountName || defaultAccountName)
.then(() => history.push(DEFAULT_ROUTE)) .then(() => history.push(DEFAULT_ROUTE))

@ -106,10 +106,10 @@ class RevealSeedPage extends Component {
renderPasswordPromptFooter () { renderPasswordPromptFooter () {
return ( return (
h('.page-container__footer', [ h('.page-container__footer', [
h('button.btn-secondary--lg.page-container__footer-button', { h('button.btn-default.btn--large.page-container__footer-button', {
onClick: () => this.props.history.push(DEFAULT_ROUTE), onClick: () => this.props.history.push(DEFAULT_ROUTE),
}, this.context.t('cancel')), }, this.context.t('cancel')),
h('button.btn-primary--lg.page-container__footer-button', { h('button.btn-primary.btn--large.page-container__footer-button', {
onClick: event => this.handleSubmit(event), onClick: event => this.handleSubmit(event),
disabled: this.state.password === '', disabled: this.state.password === '',
}, this.context.t('next')), }, this.context.t('next')),
@ -120,7 +120,7 @@ class RevealSeedPage extends Component {
renderRevealSeedFooter () { renderRevealSeedFooter () {
return ( return (
h('.page-container__footer', [ h('.page-container__footer', [
h('button.btn-secondary--lg.page-container__footer-button', { h('button.btn-default.btn--large.page-container__footer-button', {
onClick: () => this.props.history.push(DEFAULT_ROUTE), onClick: () => this.props.history.push(DEFAULT_ROUTE),
}, this.context.t('close')), }, this.context.t('close')),
]) ])

@ -217,7 +217,7 @@ class Settings extends Component {
]), ]),
h('div.settings__content-item', [ h('div.settings__content-item', [
h('div.settings__content-item-col', [ h('div.settings__content-item-col', [
h('button.btn-primary--lg.settings__button', { h('button.btn-primary.btn--large.settings__button', {
onClick (event) { onClick (event) {
window.logStateString((err, result) => { window.logStateString((err, result) => {
if (err) { if (err) {
@ -242,7 +242,7 @@ class Settings extends Component {
h('div.settings__content-item', this.context.t('revealSeedWords')), h('div.settings__content-item', this.context.t('revealSeedWords')),
h('div.settings__content-item', [ h('div.settings__content-item', [
h('div.settings__content-item-col', [ h('div.settings__content-item-col', [
h('button.btn-primary--lg.settings__button--red', { h('button.btn-primary.btn--large.settings__button--red', {
onClick: event => { onClick: event => {
event.preventDefault() event.preventDefault()
history.push(REVEAL_SEED_ROUTE) history.push(REVEAL_SEED_ROUTE)
@ -262,7 +262,7 @@ class Settings extends Component {
h('div.settings__content-item', this.context.t('useOldUI')), h('div.settings__content-item', this.context.t('useOldUI')),
h('div.settings__content-item', [ h('div.settings__content-item', [
h('div.settings__content-item-col', [ h('div.settings__content-item-col', [
h('button.btn-primary--lg.settings__button--orange', { h('button.btn-primary.btn--large.settings__button--orange', {
onClick (event) { onClick (event) {
event.preventDefault() event.preventDefault()
setFeatureFlagToBeta() setFeatureFlagToBeta()
@ -281,7 +281,7 @@ class Settings extends Component {
h('div.settings__content-item', this.context.t('resetAccount')), h('div.settings__content-item', this.context.t('resetAccount')),
h('div.settings__content-item', [ h('div.settings__content-item', [
h('div.settings__content-item-col', [ h('div.settings__content-item-col', [
h('button.btn-primary--lg.settings__button--orange', { h('button.btn-primary.btn--large.settings__button--orange', {
onClick (event) { onClick (event) {
event.preventDefault() event.preventDefault()
showResetAccountConfirmationModal() showResetAccountConfirmationModal()

@ -34,14 +34,7 @@ class UnlockPage extends Component {
} }
} }
tryUnlockMetamask (password) { async handleSubmit (event) {
const { tryUnlockMetamask, history } = this.props
tryUnlockMetamask(password)
.catch(({ message }) => this.setState({ error: message }))
.then(() => history.push(DEFAULT_ROUTE))
}
handleSubmit (event) {
event.preventDefault() event.preventDefault()
event.stopPropagation() event.stopPropagation()
@ -54,9 +47,14 @@ class UnlockPage extends Component {
this.setState({ error: null }) this.setState({ error: null })
tryUnlockMetamask(password) try {
.catch(({ message }) => this.setState({ error: message })) await tryUnlockMetamask(password)
.then(() => history.push(DEFAULT_ROUTE)) } catch ({ message }) {
this.setState({ error: message })
return
}
history.push(DEFAULT_ROUTE)
} }
handleInputChange ({ target }) { handleInputChange ({ target }) {

@ -291,18 +291,48 @@ ConfirmSendEther.prototype.convertToRenderableCurrency = function (value, curren
: value : value
} }
ConfirmSendEther.prototype.editTransaction = function (txMeta) { ConfirmSendEther.prototype.editTransaction = function () {
const { editTransaction, history } = this.props const { editTransaction, history } = this.props
const txMeta = this.gatherTxMeta()
editTransaction(txMeta) editTransaction(txMeta)
history.push(SEND_ROUTE) history.push(SEND_ROUTE)
} }
ConfirmSendEther.prototype.renderNetworkDisplay = function () { ConfirmSendEther.prototype.renderHeaderRow = function (isTxReprice) {
const windowType = window.METAMASK_UI_TYPE const windowType = window.METAMASK_UI_TYPE
const isFullScreen = windowType !== ENVIRONMENT_TYPE_NOTIFICATION &&
windowType !== ENVIRONMENT_TYPE_POPUP
return (windowType === ENVIRONMENT_TYPE_NOTIFICATION || windowType === ENVIRONMENT_TYPE_POPUP) if (isTxReprice && isFullScreen) {
? h(NetworkDisplay) return null
: null }
return (
h('.page-container__header-row', [
h('span.page-container__back-button', {
onClick: () => this.editTransaction(),
style: {
visibility: isTxReprice ? 'hidden' : 'initial',
},
}, 'Edit'),
!isFullScreen && h(NetworkDisplay),
])
)
}
ConfirmSendEther.prototype.renderHeader = function (isTxReprice) {
const title = isTxReprice ? this.context.t('speedUpTitle') : this.context.t('confirm')
const subtitle = isTxReprice
? this.context.t('speedUpSubtitle')
: this.context.t('pleaseReviewTransaction')
return (
h('.page-container__header', [
this.renderHeaderRow(isTxReprice),
h('.page-container__title', title),
h('.page-container__subtitle', subtitle),
])
)
} }
ConfirmSendEther.prototype.render = function () { ConfirmSendEther.prototype.render = function () {
@ -320,6 +350,7 @@ ConfirmSendEther.prototype.render = function () {
}, },
} = this.props } = this.props
const txMeta = this.gatherTxMeta() const txMeta = this.gatherTxMeta()
const isTxReprice = Boolean(txMeta.lastGasPrice)
const txParams = txMeta.txParams || {} const txParams = txMeta.txParams || {}
const { const {
@ -338,11 +369,6 @@ ConfirmSendEther.prototype.render = function () {
totalInETH, totalInETH,
} = this.getData() } = this.getData()
const title = txMeta.lastGasPrice ? 'Reprice Transaction' : 'Confirm'
const subtitle = txMeta.lastGasPrice
? 'Increase your gas fee to attempt to overwrite and speed up your transaction'
: 'Please review your transaction.'
const convertedAmountInFiat = this.convertToRenderableCurrency(amountInFIAT, currentCurrency) const convertedAmountInFiat = this.convertToRenderableCurrency(amountInFIAT, currentCurrency)
const convertedTotalInFiat = this.convertToRenderableCurrency(totalInFIAT, currentCurrency) const convertedTotalInFiat = this.convertToRenderableCurrency(totalInFIAT, currentCurrency)
@ -362,19 +388,7 @@ ConfirmSendEther.prototype.render = function () {
return ( return (
// Main Send token Card // Main Send token Card
h('.page-container', [ h('.page-container', [
h('.page-container__header', [ this.renderHeader(isTxReprice),
h('.page-container__header-row', [
h('span.page-container__back-button', {
onClick: () => this.editTransaction(txMeta),
style: {
visibility: !txMeta.lastGasPrice ? 'initial' : 'hidden',
},
}, 'Edit'),
this.renderNetworkDisplay(),
]),
h('.page-container__title', title),
h('.page-container__subtitle', subtitle),
]),
h('.page-container__content', [ h('.page-container__content', [
h(SenderToRecipient, { h(SenderToRecipient, {
senderName: fromName, senderName: fromName,

@ -12,6 +12,7 @@ const actions = require('../../actions')
const clone = require('clone') const clone = require('clone')
const Identicon = require('../identicon') const Identicon = require('../identicon')
const GasFeeDisplay = require('../send/gas-fee-display-v2.js') const GasFeeDisplay = require('../send/gas-fee-display-v2.js')
const NetworkDisplay = require('../network-display')
const ethUtil = require('ethereumjs-util') const ethUtil = require('ethereumjs-util')
const BN = ethUtil.BN const BN = ethUtil.BN
const { const {
@ -39,6 +40,11 @@ const {
} = require('../../selectors') } = require('../../selectors')
const { SEND_ROUTE, DEFAULT_ROUTE } = require('../../routes') const { SEND_ROUTE, DEFAULT_ROUTE } = require('../../routes')
const {
ENVIRONMENT_TYPE_POPUP,
ENVIRONMENT_TYPE_NOTIFICATION,
} = require('../../../../app/scripts/lib/enums')
ConfirmSendToken.contextTypes = { ConfirmSendToken.contextTypes = {
t: PropTypes.func, t: PropTypes.func,
} }
@ -430,6 +436,43 @@ ConfirmSendToken.prototype.convertToRenderableCurrency = function (value, curren
: value : value
} }
ConfirmSendToken.prototype.renderHeaderRow = function (isTxReprice) {
const windowType = window.METAMASK_UI_TYPE
const isFullScreen = windowType !== ENVIRONMENT_TYPE_NOTIFICATION &&
windowType !== ENVIRONMENT_TYPE_POPUP
if (isTxReprice && isFullScreen) {
return null
}
return (
h('.page-container__header-row', [
h('span.page-container__back-button', {
onClick: () => this.editTransaction(),
style: {
visibility: isTxReprice ? 'hidden' : 'initial',
},
}, 'Edit'),
!isFullScreen && h(NetworkDisplay),
])
)
}
ConfirmSendToken.prototype.renderHeader = function (isTxReprice) {
const title = isTxReprice ? this.context.t('speedUpTitle') : this.context.t('confirm')
const subtitle = isTxReprice
? this.context.t('speedUpSubtitle')
: this.context.t('pleaseReviewTransaction')
return (
h('.page-container__header', [
this.renderHeaderRow(isTxReprice),
h('.page-container__title', title),
h('.page-container__subtitle', subtitle),
])
)
}
ConfirmSendToken.prototype.render = function () { ConfirmSendToken.prototype.render = function () {
const txMeta = this.gatherTxMeta() const txMeta = this.gatherTxMeta()
const { const {
@ -443,25 +486,13 @@ ConfirmSendToken.prototype.render = function () {
}, },
} = this.getData() } = this.getData()
this.inputs = []
const isTxReprice = Boolean(txMeta.lastGasPrice) const isTxReprice = Boolean(txMeta.lastGasPrice)
const title = isTxReprice ? this.context.t('reprice_title') : this.context.t('confirm')
const subtitle = isTxReprice
? this.context.t('reprice_subtitle')
: this.context.t('pleaseReviewTransaction')
return ( return (
h('div.confirm-screen-container.confirm-send-token', [ h('div.confirm-screen-container.confirm-send-token', [
// Main Send token Card // Main Send token Card
h('div.page-container', [ h('div.page-container', [
h('div.page-container__header', [ this.renderHeader(isTxReprice),
!txMeta.lastGasPrice && h('button.confirm-screen-back-button', {
onClick: () => this.editTransaction(txMeta),
}, this.context.t('edit')),
h('div.page-container__title', title),
h('div.page-container__subtitle', subtitle),
]),
h('.page-container__content', [ h('.page-container__content', [
h('div.flex-row.flex-center.confirm-screen-identicons', [ h('div.flex-row.flex-center.confirm-screen-identicons', [
h('div.confirm-screen-account-wrapper', [ h('div.confirm-screen-account-wrapper', [

@ -130,7 +130,6 @@ PendingTx.prototype.render = function () {
if (isFetching) { if (isFetching) {
return h(Loading, { return h(Loading, {
fullScreen: true,
loadingMessage: this.context.t('generatingTransaction'), loadingMessage: this.context.t('generatingTransaction'),
}) })
} }
@ -157,9 +156,7 @@ PendingTx.prototype.render = function () {
sendTransaction, sendTransaction,
}) })
default: default:
return h(Loading, { return h(Loading)
fullScreen: true,
})
} }
} }

@ -0,0 +1,2 @@
import SelectedAccount from './selected-account.container'
module.exports = SelectedAccount

@ -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;
}
}
}

@ -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

@ -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)

@ -242,7 +242,7 @@ ShapeshiftForm.prototype.render = function () {
]), ]),
!depositAddress && h('button.btn-primary--lg.shapeshift-form__shapeshift-buy-btn', { !depositAddress && h('button.btn-primary.btn--large.shapeshift-form__shapeshift-buy-btn', {
className: btnClass, className: btnClass,
disabled: !token, disabled: !token,
onClick: () => this.onBuyWithShapeShift(), onClick: () => this.onBuyWithShapeShift(),

@ -235,12 +235,12 @@ SignatureRequest.prototype.renderFooter = function () {
} }
return h('div.request-signature__footer', [ return h('div.request-signature__footer', [
h('button.btn-secondary--lg.request-signature__footer__cancel-button', { h('button.btn-default.btn--large.request-signature__footer__cancel-button', {
onClick: event => { onClick: event => {
cancel(event).then(() => this.props.history.push(DEFAULT_ROUTE)) cancel(event).then(() => this.props.history.push(DEFAULT_ROUTE))
}, },
}, this.context.t('cancel')), }, this.context.t('cancel')),
h('button.btn-primary--lg', { h('button.btn-primary.btn--large', {
onClick: event => { onClick: event => {
sign(event).then(() => this.props.history.push(DEFAULT_ROUTE)) sign(event).then(() => this.props.history.push(DEFAULT_ROUTE))
}, },

@ -1,8 +1,15 @@
import React, { Component } from 'react' import React from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import { withStyles } from '@material-ui/core/styles' import { withStyles } from '@material-ui/core/styles'
import { default as MaterialTextField } from '@material-ui/core/TextField' import { default as MaterialTextField } from '@material-ui/core/TextField'
const inputLabelBase = {
transform: 'none',
transition: 'none',
position: 'initial',
color: '#5b5b5b',
}
const styles = { const styles = {
materialLabel: { materialLabel: {
'&$materialFocused': { '&$materialFocused': {
@ -46,29 +53,18 @@ const styles = {
border: '1px solid #2f9ae0', border: '1px solid #2f9ae0',
}, },
}, },
largeInputLabel: {
...inputLabelBase,
fontSize: '1rem',
},
inputLabel: { inputLabel: {
...inputLabelBase,
fontSize: '.75rem', fontSize: '.75rem',
transform: 'none',
transition: 'none',
position: 'initial',
color: '#5b5b5b',
}, },
} }
class TextField extends Component { const TextField = props => {
static defaultProps = { const { error, classes, material, startAdornment, largeLabel, ...textFieldProps } = props
error: null,
}
static propTypes = {
error: PropTypes.string,
classes: PropTypes.object,
material: PropTypes.bool,
startAdornment: PropTypes.element,
}
render () {
const { error, classes, material, startAdornment, ...textFieldProps } = this.props
return ( return (
<MaterialTextField <MaterialTextField
@ -76,7 +72,7 @@ class TextField extends Component {
helperText={error} helperText={error}
InputLabelProps={{ InputLabelProps={{
shrink: material ? undefined : true, shrink: material ? undefined : true,
className: material ? '' : classes.inputLabel, className: material ? '' : (largeLabel ? classes.largeInputLabel : classes.inputLabel),
FormLabelClasses: { FormLabelClasses: {
root: material ? classes.materialLabel : classes.formLabel, root: material ? classes.materialLabel : classes.formLabel,
focused: material ? classes.materialFocused : classes.formLabelFocused, focused: material ? classes.materialFocused : classes.formLabelFocused,
@ -97,6 +93,17 @@ class TextField extends Component {
/> />
) )
} }
TextField.defaultProps = {
error: null,
}
TextField.propTypes = {
error: PropTypes.string,
classes: PropTypes.object,
material: PropTypes.bool,
startAdornment: PropTypes.element,
largeLabel: PropTypes.bool,
} }
export default withStyles(styles)(TextField) export default withStyles(styles)(TextField)

@ -22,3 +22,32 @@ storiesOf('TextField', module)
error="Invalid value" error="Invalid value"
/> />
) )
.add('Mascara text', () =>
<TextField
label="Text"
type="text"
largeLabel
/>
)
.add('Material text', () =>
<TextField
label="Text"
type="text"
material
/>
)
.add('Material password', () =>
<TextField
label="Password"
type="password"
material
/>
)
.add('Material error', () =>
<TextField
type="text"
label="Name"
error="Invalid value"
material
/>
)

@ -101,8 +101,8 @@ TokenCell.prototype.render = function () {
h('div.token-list-item__balance-ellipsis', null, [ h('div.token-list-item__balance-ellipsis', null, [
h('div.token-list-item__balance-wrapper', 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', { showFiat && h('div.token-list-item__fiat-amount', {
style: {}, style: {},
}, formattedFiat), }, formattedFiat),

@ -1,5 +1,7 @@
const Component = require('react').Component const Component = require('react').Component
const PropTypes = require('prop-types') const PropTypes = require('prop-types')
const { compose } = require('recompose')
const { withRouter } = require('react-router-dom')
const h = require('react-hyperscript') const h = require('react-hyperscript')
const connect = require('react-redux').connect const connect = require('react-redux').connect
const inherits = require('util').inherits const inherits = require('util').inherits
@ -16,13 +18,16 @@ const { conversionUtil, multiplyCurrencies } = require('../conversion-util')
const { calcTokenAmount } = require('../token-util') const { calcTokenAmount } = require('../token-util')
const { getCurrentCurrency } = require('../selectors') const { getCurrentCurrency } = require('../selectors')
const { CONFIRM_TRANSACTION_ROUTE } = require('../routes')
TxListItem.contextTypes = { TxListItem.contextTypes = {
t: PropTypes.func, t: PropTypes.func,
} }
module.exports = connect(mapStateToProps, mapDispatchToProps)(TxListItem) module.exports = compose(
withRouter,
connect(mapStateToProps, mapDispatchToProps)
)(TxListItem)
function mapStateToProps (state) { function mapStateToProps (state) {
return { return {
@ -216,6 +221,7 @@ TxListItem.prototype.setSelectedToken = function (tokenAddress) {
TxListItem.prototype.resubmit = function () { TxListItem.prototype.resubmit = function () {
const { transactionId } = this.props const { transactionId } = this.props
this.props.retryTransaction(transactionId) this.props.retryTransaction(transactionId)
.then(id => this.props.history.push(`${CONFIRM_TRANSACTION_ROUTE}/${id}`))
} }
TxListItem.prototype.render = function () { TxListItem.prototype.render = function () {

@ -12,7 +12,7 @@ const { checksumAddress: toChecksumAddress } = require('../util')
const BalanceComponent = require('./balance-component') const BalanceComponent = require('./balance-component')
const TxList = require('./tx-list') const TxList = require('./tx-list')
const Identicon = require('./identicon') const SelectedAccount = require('./selected-account')
module.exports = compose( module.exports = compose(
withRouter, withRouter,
@ -103,7 +103,7 @@ TxView.prototype.renderButtons = function () {
} }
TxView.prototype.render = function () { TxView.prototype.render = function () {
const { selectedAddress, identity, network, isMascara } = this.props const { isMascara } = this.props
return h('div.tx-view.flex-column', { return h('div.tx-view.flex-column', {
style: {}, style: {},
@ -111,10 +111,12 @@ TxView.prototype.render = function () {
h('div.flex-row.phone-visible', { h('div.flex-row.phone-visible', {
style: { style: {
justifyContent: 'space-between', justifyContent: 'center',
alignItems: 'center', alignItems: 'center',
flex: '0 0 auto', 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(), onClick: () => this.props.sidebarOpen ? this.props.hideSidebar() : this.props.showSidebar(),
}), }),
h('.identicon-wrapper.select-none', { h(SelectedAccount),
style: {
marginLeft: '0.9em',
},
}, [
h(Identicon, {
diameter: 24,
address: selectedAddress,
network,
}),
]),
h('span.account-name', {
style: {},
}, [
identity.name,
]),
!isMascara && h('div.open-in-browser', { !isMascara && h('div.open-in-browser', {
onClick: () => global.platform.openExtensionInBrowser(), onClick: () => global.platform.openExtensionInBrowser(),

@ -36,7 +36,6 @@ function mapStateToProps (state) {
tokens: state.metamask.tokens, tokens: state.metamask.tokens,
keyrings: state.metamask.keyrings, keyrings: state.metamask.keyrings,
selectedAddress: selectors.getSelectedAddress(state), selectedAddress: selectors.getSelectedAddress(state),
selectedIdentity: selectors.getSelectedIdentity(state),
selectedAccount: selectors.getSelectedAccount(state), selectedAccount: selectors.getSelectedAccount(state),
selectedTokenAddress: state.metamask.selectedTokenAddress, selectedTokenAddress: state.metamask.selectedTokenAddress,
} }
@ -99,21 +98,24 @@ WalletView.prototype.render = function () {
const { const {
responsiveDisplayClassname, responsiveDisplayClassname,
selectedAddress, selectedAddress,
selectedIdentity,
keyrings, keyrings,
showAccountDetailModal, showAccountDetailModal,
sidebarOpen, sidebarOpen,
hideSidebar, hideSidebar,
history, history,
identities,
} = this.props } = this.props
// temporary logs + fake extra wallets // temporary logs + fake extra wallets
// console.log('walletview, selectedAccount:', selectedAccount) // console.log('walletview, selectedAccount:', selectedAccount)
const checksummedAddress = checksumAddress(selectedAddress) const checksummedAddress = checksumAddress(selectedAddress)
if (!selectedAddress) {
throw new Error('selectedAddress should not be ' + String(selectedAddress))
}
const keyring = keyrings.find((kr) => { const keyring = keyrings.find((kr) => {
return kr.accounts.includes(selectedAddress) || return kr.accounts.includes(selectedAddress)
kr.accounts.includes(selectedIdentity.address)
}) })
const type = keyring.type const type = keyring.type
@ -145,7 +147,7 @@ WalletView.prototype.render = function () {
h('span.account-name', { h('span.account-name', {
style: {}, style: {},
}, [ }, [
selectedIdentity.name, identities[selectedAddress].name,
]), ]),
h('button.btn-clear.wallet-view__details-button.allcaps', this.context.t('details')), h('button.btn-clear.wallet-view__details-button.allcaps', this.context.t('details')),

@ -7,6 +7,7 @@ const { compose } = require('recompose')
const actions = require('./actions') const actions = require('./actions')
const txHelper = require('../lib/tx-helper') const txHelper = require('../lib/tx-helper')
const log = require('loglevel') const log = require('loglevel')
const R = require('ramda')
const PendingTx = require('./components/pending-tx') const PendingTx = require('./components/pending-tx')
const SignatureRequest = require('./components/signature-request') const SignatureRequest = require('./components/signature-request')
@ -87,37 +88,74 @@ ConfirmTxScreen.prototype.componentDidUpdate = function (prevProps) {
network, network,
selectedAddressTxList, selectedAddressTxList,
send, send,
history,
match: { params: { id: transactionId } = {} },
} = this.props } = this.props
let prevTx
if (transactionId) {
prevTx = R.find(({ id }) => id + '' === transactionId)(selectedAddressTxList)
} else {
const { index: prevIndex, unapprovedTxs: prevUnapprovedTxs } = prevProps const { index: prevIndex, unapprovedTxs: prevUnapprovedTxs } = prevProps
const prevUnconfTxList = txHelper(prevUnapprovedTxs, {}, {}, {}, network) const prevUnconfTxList = txHelper(prevUnapprovedTxs, {}, {}, {}, network)
const prevTxData = prevUnconfTxList[prevIndex] || {} const prevTxData = prevUnconfTxList[prevIndex] || {}
const prevTx = selectedAddressTxList.find(({ id }) => id === prevTxData.id) || {} prevTx = selectedAddressTxList.find(({ id }) => id === prevTxData.id) || {}
}
const unconfTxList = txHelper(unapprovedTxs, {}, {}, {}, network) const unconfTxList = txHelper(unapprovedTxs, {}, {}, {}, network)
if (unconfTxList.length === 0 && if (prevTx.status === 'dropped') {
(prevTx.status === 'dropped' || !send.to && this.getUnapprovedMessagesTotal() === 0)) { this.props.dispatch(actions.showModal({
name: 'TRANSACTION_CONFIRMED',
onHide: () => history.push(DEFAULT_ROUTE),
}))
return
}
if (unconfTxList.length === 0 && !send.to && this.getUnapprovedMessagesTotal() === 0) {
this.props.history.push(DEFAULT_ROUTE) this.props.history.push(DEFAULT_ROUTE)
} }
} }
ConfirmTxScreen.prototype.render = function () { ConfirmTxScreen.prototype.getTxData = function () {
const props = this.props
const { const {
network, network,
index,
unapprovedTxs,
unapprovedMsgs,
unapprovedPersonalMsgs,
unapprovedTypedMessages,
match: { params: { id: transactionId } = {} },
} = this.props
const unconfTxList = txHelper(
unapprovedTxs, unapprovedTxs,
currentCurrency,
unapprovedMsgs, unapprovedMsgs,
unapprovedPersonalMsgs, unapprovedPersonalMsgs,
unapprovedTypedMessages, unapprovedTypedMessages,
network
)
log.info(`rendering a combined ${unconfTxList.length} unconf msgs & txs`)
return transactionId
? R.find(({ id }) => id + '' === transactionId)(unconfTxList)
: unconfTxList[index]
}
ConfirmTxScreen.prototype.render = function () {
const props = this.props
const {
currentCurrency,
conversionRate, conversionRate,
blockGasLimit, blockGasLimit,
// provider, // provider,
// computedBalances, // computedBalances,
} = props } = props
var unconfTxList = txHelper(unapprovedTxs, unapprovedMsgs, unapprovedPersonalMsgs, unapprovedTypedMessages, network) var txData = this.getTxData() || {}
var txData = unconfTxList[props.index] || {}
var txParams = txData.params || {} var txParams = txData.params || {}
// var isNotification = isPopupOrNotification() === 'notification' // var isNotification = isPopupOrNotification() === 'notification'
@ -136,7 +174,6 @@ ConfirmTxScreen.prototype.render = function () {
]), ]),
*/ */
log.info(`rendering a combined ${unconfTxList.length} unconf msg & txs`)
return currentTxView({ return currentTxView({
// Properties // Properties

@ -116,6 +116,10 @@
&__name { &__name {
color: $white; color: $white;
font-size: 18px; font-size: 18px;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
max-width: 200px;
} }
&__balance { &__balance {

@ -2,10 +2,10 @@
Buttons Buttons
*/ */
.btn-default,
.btn-primary, .btn-primary,
.btn-primary--lg, .btn-secondary {
.btn-secondary, height: 44px;
.btn-secondary--lg {
background: $white; background: $white;
display: flex; display: flex;
justify-content: center; justify-content: center;
@ -20,10 +20,16 @@
width: 100%; width: 100%;
text-transform: uppercase; text-transform: uppercase;
outline: none; outline: none;
&--disabled,
&[disabled] {
cursor: auto;
opacity: .5;
pointer-events: none;
}
} }
.btn-primary, .btn-primary {
.btn-primary--lg {
color: $curious-blue; color: $curious-blue;
border: 2px solid $spindle; border: 2px solid $spindle;
@ -35,17 +41,23 @@
&:hover { &:hover {
border-color: $curious-blue; border-color: $curious-blue;
} }
}
&--disabled, .btn-secondary {
&[disabled] { color: $monzo;
cursor: auto; border: 2px solid lighten($monzo, 40%);
opacity: .5;
pointer-events: none; &:active {
background: lighten($monzo, 55%);
border-color: $monzo;
}
&:hover {
border-color: $monzo;
} }
} }
.btn-secondary, .btn-default {
.btn-secondary--lg {
color: $scorpion; color: $scorpion;
border: 2px solid $dusty-gray; border: 2px solid $dusty-gray;
@ -57,20 +69,9 @@
&:hover { &:hover {
border-color: $scorpion; border-color: $scorpion;
} }
&--disabled,
&[disabled] {
cursor: auto;
opacity: .5;
pointer-events: none;
}
}
.btn-primary, .btn-secondary {
height: 44px;
} }
.btn-primary--lg, .btn-secondary--lg { .btn--large {
height: 54px; height: 54px;
} }

@ -6,6 +6,7 @@
justify-content: flex-start; justify-content: flex-start;
align-items: center; align-items: center;
flex: 0 0 auto; flex: 0 0 auto;
padding-top: 16px;
} }
@media screen and (min-width: $break-large) { @media screen and (min-width: $break-large) {

@ -11,8 +11,8 @@
background: rgba(255, 255, 255, .8); background: rgba(255, 255, 255, .8);
@media screen and (max-width: 575px) { @media screen and (max-width: 575px) {
margin-top: 56px; margin-top: 66px;
height: calc(100% - 56px); height: calc(100% - 66px);
} }
@media screen and (min-width: 576px) { @media screen and (min-width: 576px) {

@ -14,10 +14,17 @@ $wallet-balance-breakpoint-range: "screen and (min-width: #{$break-large}) and (
min-width: 0; min-width: 0;
&__token-balance { &__token-balance {
font-size: 1.5rem; margin-right: 4px;
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; 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} { @media #{$wallet-balance-breakpoint-range} {
font-size: 95%; font-size: 95%;
@ -66,7 +73,9 @@ $wallet-balance-breakpoint-range: "screen and (min-width: #{$break-large}) and (
} }
&__balance-wrapper { &__balance-wrapper {
flex: 1 1 auto; flex: 1;
flex-flow: row wrap;
display: flex;
min-width: 0; min-width: 0;
} }
} }

@ -42,6 +42,7 @@ function reduceApp (state, action) {
open: false, open: false,
modalState: { modalState: {
name: null, name: null,
props: {},
}, },
previousModalState: { previousModalState: {
name: null, name: null,
@ -88,13 +89,17 @@ function reduceApp (state, action) {
// modal methods: // modal methods:
case actions.MODAL_OPEN: case actions.MODAL_OPEN:
const { name, ...modalProps } = action.payload
return extend(appState, { return extend(appState, {
modal: Object.assign( modal: {
state.appState.modal, open: true,
{ open: true }, modalState: {
{ modalState: action.payload }, name: name,
{ previousModalState: appState.modal.modalState}, props: { ...modalProps },
), },
previousModalState: { ...appState.modal.modalState },
},
}) })
case actions.MODAL_CLOSE: case actions.MODAL_CLOSE:
@ -102,7 +107,7 @@ function reduceApp (state, action) {
modal: Object.assign( modal: Object.assign(
state.appState.modal, state.appState.modal,
{ open: false }, { open: false },
{ modalState: { name: null } }, { modalState: { name: null, props: {} } },
{ previousModalState: appState.modal.modalState}, { previousModalState: appState.modal.modalState},
), ),
}) })

@ -499,7 +499,7 @@ SendTransactionScreen.prototype.renderFooter = function () {
return h('div.page-container__footer', [ return h('div.page-container__footer', [
h(Button, { h(Button, {
type: 'secondary', type: 'default',
large: true, large: true,
className: 'page-container__footer-button', className: 'page-container__footer-button',
onClick: () => { onClick: () => {

Loading…
Cancel
Save