mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 01:39:44 +01:00
Merge branch 'develop' of github.com:MetaMask/metamask-extension into network-remove-provider-engine
This commit is contained in:
commit
9d77b0a196
35
CHANGELOG.md
35
CHANGELOG.md
@ -2,6 +2,41 @@
|
||||
|
||||
## 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
|
||||
|
||||
- Correctly format currency conversion for locally selected preferred currency.
|
||||
|
14
MISSION.md
Normal file
14
MISSION.md
Normal file
@ -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.
|
||||
|
12
README.md
12
README.md
@ -1,12 +1,16 @@
|
||||
# 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)
|
||||
|
||||
[Internal documentation](./docs/jsdocs)
|
||||
|
||||
## Support
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
- 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`.
|
||||
- Build the project to the `./dist/` folder with `gulp build`.
|
||||
- Optionally, to rebuild on file changes, run `gulp dev`.
|
||||
|
@ -402,6 +402,9 @@
|
||||
"infoHelp": {
|
||||
"message": "Info & Help"
|
||||
},
|
||||
"initialTransactionConfirmed": {
|
||||
"message": "Your initial transaction was confirmed by the network. Click OK to go back."
|
||||
},
|
||||
"insufficientFunds": {
|
||||
"message": "Insufficient funds."
|
||||
},
|
||||
@ -520,6 +523,9 @@
|
||||
"networks": {
|
||||
"message": "Networks"
|
||||
},
|
||||
"nevermind": {
|
||||
"message": "Nevermind"
|
||||
},
|
||||
"newAccount": {
|
||||
"message": "New Account"
|
||||
},
|
||||
@ -634,9 +640,15 @@
|
||||
"rejected": {
|
||||
"message": "Rejected"
|
||||
},
|
||||
"reset": {
|
||||
"message": "Reset"
|
||||
},
|
||||
"resetAccount": {
|
||||
"message": "Reset Account"
|
||||
},
|
||||
"resetAccountDescription": {
|
||||
"message": "Resetting your account will clear your transaction history."
|
||||
},
|
||||
"restoreFromSeed": {
|
||||
"message": "Restore account?"
|
||||
},
|
||||
@ -676,6 +688,9 @@
|
||||
"ropsten": {
|
||||
"message": "Ropsten Test Network"
|
||||
},
|
||||
"rpc": {
|
||||
"message": "Custom RPC"
|
||||
},
|
||||
"currentRpc": {
|
||||
"message": "Current RPC"
|
||||
},
|
||||
@ -701,10 +716,10 @@
|
||||
"save": {
|
||||
"message": "Save"
|
||||
},
|
||||
"reprice_title": {
|
||||
"message": "Reprice Transaction"
|
||||
"speedUpTitle": {
|
||||
"message": "Speed Up Transaction"
|
||||
},
|
||||
"reprice_subtitle": {
|
||||
"speedUpSubtitle": {
|
||||
"message": "Increase your gas price to attempt to overwrite and speed up your transaction"
|
||||
},
|
||||
"saveAsCsvFile": {
|
||||
@ -891,7 +906,7 @@
|
||||
"message": "Welcome to the New UI (Beta)"
|
||||
},
|
||||
"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": {
|
||||
"message": "Unapproved"
|
||||
|
17
app/images/check-icon.svg
Normal file
17
app/images/check-icon.svg
Normal file
@ -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__",
|
||||
"short_name": "__MSG_appName__",
|
||||
"version": "4.6.0",
|
||||
"version": "4.7.2",
|
||||
"manifest_version": 2,
|
||||
"author": "https://metamask.io",
|
||||
"description": "__MSG_appDescription__",
|
||||
@ -67,6 +67,9 @@
|
||||
"externally_connectable": {
|
||||
"matches": [
|
||||
"https://metamask.io/*"
|
||||
],
|
||||
"ids": [
|
||||
"*"
|
||||
]
|
||||
}
|
||||
}
|
@ -309,6 +309,7 @@ function setupController (initState, initLangCode) {
|
||||
// connect to other contexts
|
||||
//
|
||||
extension.runtime.onConnect.addListener(connectRemote)
|
||||
extension.runtime.onConnectExternal.addListener(connectExternal)
|
||||
|
||||
const metamaskInternalProcessHash = {
|
||||
[ENVIRONMENT_TYPE_POPUP]: true,
|
||||
@ -335,9 +336,9 @@ function setupController (initState, initLangCode) {
|
||||
function connectRemote (remotePort) {
|
||||
const processName = remotePort.name
|
||||
const isMetaMaskInternalProcess = metamaskInternalProcessHash[processName]
|
||||
const portStream = new PortStream(remotePort)
|
||||
|
||||
if (isMetaMaskInternalProcess) {
|
||||
const portStream = new PortStream(remotePort)
|
||||
// communication with popup
|
||||
controller.isClientOpen = true
|
||||
controller.setupTrustedCommunication(portStream, 'MetaMask')
|
||||
@ -370,12 +371,17 @@ function setupController (initState, initLangCode) {
|
||||
})
|
||||
}
|
||||
} else {
|
||||
// communication with page
|
||||
const originDomain = urlUtil.parse(remotePort.sender.url).hostname
|
||||
controller.setupUntrustedCommunication(portStream, originDomain)
|
||||
connectExternal(remotePort)
|
||||
}
|
||||
}
|
||||
|
||||
// communication with page or other extension
|
||||
function connectExternal(remotePort) {
|
||||
const originDomain = urlUtil.parse(remotePort.sender.url).hostname
|
||||
const portStream = new PortStream(remotePort)
|
||||
controller.setupUntrustedCommunication(portStream, originDomain)
|
||||
}
|
||||
|
||||
//
|
||||
// User Interface setup
|
||||
//
|
||||
|
@ -2,6 +2,7 @@ const ObservableStore = require('obs-store')
|
||||
const normalizeAddress = require('eth-sig-util').normalize
|
||||
const extend = require('xtend')
|
||||
|
||||
|
||||
class PreferencesController {
|
||||
|
||||
/**
|
||||
@ -28,7 +29,11 @@ class PreferencesController {
|
||||
featureFlags: {},
|
||||
currentLocale: opts.initLangCode,
|
||||
identities: {},
|
||||
lostIdentities: {},
|
||||
}, opts.initState)
|
||||
|
||||
this.diagnostics = opts.diagnostics
|
||||
|
||||
this.store = new ObservableStore(initState)
|
||||
}
|
||||
// PUBLIC METHODS
|
||||
@ -63,6 +68,13 @@ class PreferencesController {
|
||||
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) {
|
||||
const oldIdentities = this.store.getState().identities
|
||||
const identities = addresses.reduce((ids, address, index) => {
|
||||
@ -73,6 +85,68 @@ class PreferencesController {
|
||||
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
|
||||
*
|
||||
@ -111,7 +185,7 @@ class PreferencesController {
|
||||
/**
|
||||
* Adds a new token to the token array, or updates the token if passed an address that already exists.
|
||||
* Modifies the existing tokens array from the store. All objects in the tokens array array AddedToken objects.
|
||||
* @see AddedToken {@link AddedToken}
|
||||
* @see AddedToken {@link AddedToken}
|
||||
*
|
||||
* @param {string} rawAddress Hex address of the token contract. May or may not be a checksum address.
|
||||
* @param {string} symbol The symbol of the token
|
||||
@ -197,7 +271,7 @@ class PreferencesController {
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for the `currentAccountTab` property
|
||||
* Setter for the `currentAccountTab` property
|
||||
*
|
||||
* @param {string} currentAccountTab Specifies the new tab to be marked as current
|
||||
* @returns {Promise<void>} Promise resolves with undefined
|
||||
@ -215,7 +289,7 @@ class PreferencesController {
|
||||
* The returned list will have a max length of 2. If the _url currently exists it the list, it will be moved to the
|
||||
* end of the list. The current list is modified and returned as a promise.
|
||||
*
|
||||
* @param {string} _url The rpc url to add to the frequentRpcList.
|
||||
* @param {string} _url The rpc url to add to the frequentRpcList.
|
||||
* @returns {Promise<array>} The updated frequentRpcList.
|
||||
*
|
||||
*/
|
||||
|
@ -8,6 +8,7 @@ const TxGasUtil = require('./tx-gas-utils')
|
||||
const PendingTransactionTracker = require('./pending-tx-tracker')
|
||||
const NonceTracker = require('./nonce-tracker')
|
||||
const txUtils = require('./lib/util')
|
||||
const cleanErrorStack = require('../../lib/cleanErrorStack')
|
||||
const log = require('loglevel')
|
||||
|
||||
/**
|
||||
@ -123,6 +124,7 @@ class TransactionController extends EventEmitter {
|
||||
@param txParams {object} - txParams for the transaction
|
||||
@param opts {object} - with the key origin to put the origin on the txMeta
|
||||
*/
|
||||
|
||||
async newUnapprovedTransaction (txParams, opts = {}) {
|
||||
log.debug(`MetaMaskController newUnapprovedTransaction ${JSON.stringify(txParams)}`)
|
||||
const initialTxMeta = await this.addUnapprovedTransaction(txParams)
|
||||
@ -135,11 +137,11 @@ class TransactionController extends EventEmitter {
|
||||
case 'submitted':
|
||||
return resolve(finishedTxMeta.hash)
|
||||
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':
|
||||
return reject(new Error(finishedTxMeta.err.message))
|
||||
return reject(cleanErrorStack(new Error(finishedTxMeta.err.message)))
|
||||
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 ObservableStore = require('obs-store')
|
||||
const ethUtil = require('ethereumjs-util')
|
||||
const log = require('loglevel')
|
||||
const txStateHistoryHelper = require('./lib/tx-state-history-helper')
|
||||
const createId = require('../../lib/random-id')
|
||||
const { getFinalStates } = require('./lib/util')
|
||||
@ -398,13 +399,19 @@ class TransactionStateManager extends EventEmitter {
|
||||
_setTxStatus (txId, status) {
|
||||
const txMeta = this.getTx(txId)
|
||||
txMeta.status = status
|
||||
this.emit(`${txMeta.id}:${status}`, txId)
|
||||
this.emit(`tx:status-update`, txId, status)
|
||||
if (['submitted', 'rejected', 'failed'].includes(status)) {
|
||||
this.emit(`${txMeta.id}:finished`, txMeta)
|
||||
}
|
||||
this.updateTx(txMeta, `txStateManager: setting status to ${status}`)
|
||||
this.emit('update:badge')
|
||||
setTimeout(() => {
|
||||
try {
|
||||
this.updateTx(txMeta, `txStateManager: setting status to ${status}`)
|
||||
this.emit(`${txMeta.id}:${status}`, txId)
|
||||
this.emit(`tx:status-update`, txId, status)
|
||||
if (['submitted', 'rejected', 'failed'].includes(status)) {
|
||||
this.emit(`${txMeta.id}:finished`, txMeta)
|
||||
}
|
||||
this.emit('update:badge')
|
||||
} catch (error) {
|
||||
log.error(error)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
|
17
app/scripts/controllers/user-actions.js
Normal file
17
app/scripts/controllers/user-actions.js
Normal file
@ -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
|
24
app/scripts/lib/cleanErrorStack.js
Normal file
24
app/scripts/lib/cleanErrorStack.js
Normal file
@ -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() }
|
||||
sanitizeRPCError(error)
|
||||
log.error(`MetaMask - RPC Error: ${error.message}`, error)
|
||||
done()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
71
app/scripts/lib/diagnostics-reporter.js
Normal file
71
app/scripts/lib/diagnostics-reporter.js
Normal file
@ -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 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('_', '-'))
|
||||
|
||||
/**
|
||||
@ -12,10 +18,7 @@ const existingLocaleCodes = allLocales.map(locale => locale.code.toLowerCase().r
|
||||
*
|
||||
*/
|
||||
async function getFirstPreferredLangCode () {
|
||||
const userPreferredLocaleCodes = await promisify(
|
||||
extension.i18n.getAcceptLanguages,
|
||||
{ errorFirst: false }
|
||||
)()
|
||||
const userPreferredLocaleCodes = await getPreferredLocales()
|
||||
const firstPreferredLangCode = userPreferredLocaleCodes
|
||||
.map(code => code.toLowerCase())
|
||||
.find(code => existingLocaleCodes.includes(code))
|
||||
|
@ -45,6 +45,8 @@ const BN = require('ethereumjs-util').BN
|
||||
const GWEI_BN = new BN('1000000000')
|
||||
const percentile = require('percentile')
|
||||
const seedPhraseVerifier = require('./lib/seed-phrase-verifier')
|
||||
const cleanErrorStack = require('./lib/cleanErrorStack')
|
||||
const DiagnosticsReporter = require('./lib/diagnostics-reporter')
|
||||
const log = require('loglevel')
|
||||
|
||||
module.exports = class MetamaskController extends EventEmitter {
|
||||
@ -63,6 +65,12 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
const initState = opts.initState || {}
|
||||
this.recordFirstTimeInfo(initState)
|
||||
|
||||
// metamask diagnostics reporter
|
||||
this.diagnostics = opts.diagnostics || new DiagnosticsReporter({
|
||||
firstTimeInfo: initState.firstTimeInfo,
|
||||
version,
|
||||
})
|
||||
|
||||
// platform-specific api
|
||||
this.platform = opts.platform
|
||||
|
||||
@ -84,6 +92,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
this.preferencesController = new PreferencesController({
|
||||
initState: initState.PreferencesController,
|
||||
initLangCode: opts.initLangCode,
|
||||
diagnostics: this.diagnostics,
|
||||
})
|
||||
|
||||
// currency controller
|
||||
@ -139,6 +148,8 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
const address = addresses[0]
|
||||
this.preferencesController.setSelectedAddress(address)
|
||||
}
|
||||
// ensure preferences + identities controller know about all addresses
|
||||
this.preferencesController.addAddresses(addresses)
|
||||
this.accountTracker.syncWithAddresses(addresses)
|
||||
})
|
||||
|
||||
@ -348,7 +359,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
importAccountWithStrategy: nodeify(this.importAccountWithStrategy, this),
|
||||
|
||||
// vault management
|
||||
submitPassword: nodeify(keyringController.submitPassword, keyringController),
|
||||
submitPassword: nodeify(this.submitPassword, this),
|
||||
|
||||
// network management
|
||||
setProviderType: nodeify(networkController.setProviderType, networkController),
|
||||
@ -450,7 +461,11 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
async createNewVaultAndRestore (password, seed) {
|
||||
const release = await this.createVaultMutex.acquire()
|
||||
try {
|
||||
// clear known identities
|
||||
this.preferencesController.setAddresses([])
|
||||
// create new vault
|
||||
const vault = await this.keyringController.createNewVaultAndRestore(password, seed)
|
||||
// set new identities
|
||||
const accounts = await this.keyringController.getAccounts()
|
||||
this.preferencesController.setAddresses(accounts)
|
||||
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
|
||||
* @property {string} name - The account nickname.
|
||||
|
@ -27,7 +27,7 @@ module.exports = {
|
||||
|
||||
function transformState (state) {
|
||||
if (!state.KeyringController || !state.PreferencesController) {
|
||||
return
|
||||
return state
|
||||
}
|
||||
|
||||
if (!state.KeyringController.walletNicknames) {
|
||||
|
@ -2,18 +2,32 @@
|
||||
|
||||
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
|
||||
|
||||
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`.
|
||||
|
||||
## Building
|
||||
|
||||
While we develop on the main `develop` branch, our production version is maintained on the `master` branch.
|
||||
|
||||
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.
|
||||
|
||||
## Publishing
|
||||
|
||||
1. `npm run dist` to generate the latest build.
|
||||
2. Publish to chrome store.
|
||||
- Visit [the chrome developer dashboard](https://chrome.google.com/webstore/developer/dashboard?authuser=2).
|
||||
3. Publish to firefox addon marketplace.
|
||||
4. Post on Github releases page.
|
||||
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"
|
||||
margin="normal"
|
||||
fullWidth
|
||||
largeLabel
|
||||
/>
|
||||
<TextField
|
||||
id="confirm-password"
|
||||
@ -155,6 +156,7 @@ class CreatePasswordScreen extends Component {
|
||||
autoComplete="confirm-password"
|
||||
margin="normal"
|
||||
fullWidth
|
||||
largeLabel
|
||||
/>
|
||||
<button
|
||||
className="first-time-flow__button"
|
||||
|
@ -146,6 +146,7 @@ class ImportSeedPhraseScreen extends Component {
|
||||
error={passwordError}
|
||||
autoComplete="new-password"
|
||||
margin="normal"
|
||||
largeLabel
|
||||
/>
|
||||
<TextField
|
||||
id="confirm-password"
|
||||
@ -157,6 +158,7 @@ class ImportSeedPhraseScreen extends Component {
|
||||
error={confirmPasswordError}
|
||||
autoComplete="confirm-password"
|
||||
margin="normal"
|
||||
largeLabel
|
||||
/>
|
||||
<button
|
||||
className="first-time-flow__button"
|
||||
|
@ -123,10 +123,6 @@
|
||||
width: calc(100vw - 80px);
|
||||
}
|
||||
|
||||
.unique-image {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.create-password__title,
|
||||
.unique-image__title,
|
||||
.tou__title,
|
||||
@ -148,7 +144,7 @@
|
||||
height: 100%;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: space-evenly;
|
||||
justify-content: flex-start;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
@ -181,7 +177,6 @@
|
||||
margin: 0 !important;
|
||||
padding: 16px 20px !important;
|
||||
height: 30vh !important;
|
||||
width: calc(100% - 48px) !important;
|
||||
}
|
||||
|
||||
.backup-phrase__content-wrapper {
|
||||
@ -280,6 +275,12 @@
|
||||
width: 335px;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 575px) {
|
||||
.unique-image__body-text {
|
||||
width: initial;
|
||||
}
|
||||
}
|
||||
|
||||
.unique-image__body-text +
|
||||
.unique-image__body-text,
|
||||
.backup-phrase__body-text +
|
||||
@ -294,7 +295,7 @@
|
||||
border-radius: 8px;
|
||||
background-color: #FFFFFF;
|
||||
margin: 0 142px 0 0;
|
||||
height: 334px;
|
||||
height: 200px;
|
||||
overflow-y: auto;
|
||||
color: #757575;
|
||||
font-family: Roboto;
|
||||
@ -679,7 +680,7 @@ button.backup-phrase__confirm-seed-option:hover {
|
||||
}
|
||||
|
||||
.first-time-flow__input {
|
||||
width: 350px;
|
||||
max-width: 350px;
|
||||
}
|
||||
|
||||
.first-time-flow__button {
|
||||
|
111
package-lock.json
generated
111
package-lock.json
generated
@ -1500,7 +1500,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"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": {
|
||||
"version": "3.5.0",
|
||||
@ -7149,6 +7150,12 @@
|
||||
"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": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-5.0.1.tgz",
|
||||
@ -8248,20 +8255,20 @@
|
||||
}
|
||||
},
|
||||
"eth-keyring-controller": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/eth-keyring-controller/-/eth-keyring-controller-3.1.1.tgz",
|
||||
"integrity": "sha512-Z9HTzrop/V4Ld8Wq7uwetKecfWIyx25/uL8aFoZxV3kegZGoXaWoRmNy+4oW0WNLp4BcJ1lk6QfsGEdlymGjmA==",
|
||||
"version": "3.1.4",
|
||||
"resolved": "https://registry.npmjs.org/eth-keyring-controller/-/eth-keyring-controller-3.1.4.tgz",
|
||||
"integrity": "sha512-NNlVB/TBc8p9CblwECjPlUR+7MNQKiBa7tEFxIzZ9MjjNCEYPWDXTm0vJZzuDtVmFxYwIA53UD0QEn0QNxWNEQ==",
|
||||
"requires": {
|
||||
"bip39": "2.4.0",
|
||||
"bluebird": "3.5.1",
|
||||
"browser-passworder": "2.0.3",
|
||||
"eth-hd-keyring": "1.2.2",
|
||||
"eth-sig-util": "1.4.2",
|
||||
"eth-simple-keyring": "1.2.1",
|
||||
"ethereumjs-util": "5.2.0",
|
||||
"loglevel": "1.6.0",
|
||||
"obs-store": "2.4.1",
|
||||
"promise-filter": "1.1.0"
|
||||
"bip39": "^2.4.0",
|
||||
"bluebird": "^3.5.0",
|
||||
"browser-passworder": "^2.0.3",
|
||||
"eth-hd-keyring": "^1.2.2",
|
||||
"eth-sig-util": "^1.4.0",
|
||||
"eth-simple-keyring": "^1.2.2",
|
||||
"ethereumjs-util": "^5.1.2",
|
||||
"loglevel": "^1.5.0",
|
||||
"obs-store": "^2.4.1",
|
||||
"promise-filter": "^1.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"babelify": {
|
||||
@ -8269,8 +8276,8 @@
|
||||
"resolved": "https://registry.npmjs.org/babelify/-/babelify-7.3.0.tgz",
|
||||
"integrity": "sha1-qlau3nBn/XvVSWZu4W3ChQh+iOU=",
|
||||
"requires": {
|
||||
"babel-core": "6.26.0",
|
||||
"object-assign": "4.1.1"
|
||||
"babel-core": "^6.0.14",
|
||||
"object-assign": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"ethereumjs-util": {
|
||||
@ -8278,13 +8285,13 @@
|
||||
"resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.2.0.tgz",
|
||||
"integrity": "sha512-CJAKdI0wgMbQFLlLRtZKGcy/L6pzVRgelIZqRqNbuVFM3K9VEnyfbcvz0ncWMRNCe4kaHWjwRYQcYMucmwsnWA==",
|
||||
"requires": {
|
||||
"bn.js": "4.11.8",
|
||||
"create-hash": "1.1.3",
|
||||
"ethjs-util": "0.1.4",
|
||||
"keccak": "1.4.0",
|
||||
"rlp": "2.0.0",
|
||||
"safe-buffer": "5.1.1",
|
||||
"secp256k1": "3.4.0"
|
||||
"bn.js": "^4.11.0",
|
||||
"create-hash": "^1.1.2",
|
||||
"ethjs-util": "^0.1.3",
|
||||
"keccak": "^1.0.2",
|
||||
"rlp": "^2.0.0",
|
||||
"safe-buffer": "^5.1.1",
|
||||
"secp256k1": "^3.0.1"
|
||||
}
|
||||
},
|
||||
"obs-store": {
|
||||
@ -8292,11 +8299,11 @@
|
||||
"resolved": "https://registry.npmjs.org/obs-store/-/obs-store-2.4.1.tgz",
|
||||
"integrity": "sha512-wpA8G4uSn8cnCKZ0pFTvqsamvy0Sm1hR2ot0Qonbfj5yBMwdAp/eD4vDI+U/ZCbV1hb2V5GapL8YKUdGCvahgg==",
|
||||
"requires": {
|
||||
"babel-preset-es2015": "6.24.1",
|
||||
"babelify": "7.3.0",
|
||||
"readable-stream": "2.3.3",
|
||||
"through2": "2.0.3",
|
||||
"xtend": "4.0.1"
|
||||
"babel-preset-es2015": "^6.22.0",
|
||||
"babelify": "^7.3.0",
|
||||
"readable-stream": "^2.2.2",
|
||||
"through2": "^2.0.3",
|
||||
"xtend": "^4.0.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -8343,6 +8350,7 @@
|
||||
"dependencies": {
|
||||
"ethereumjs-abi": {
|
||||
"version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#4ea2fdfed09e8f99117d9362d17c6b01b64a2bcf",
|
||||
"from": "git+https://github.com/ethereumjs/ethereumjs-abi.git",
|
||||
"requires": {
|
||||
"bn.js": "4.11.8",
|
||||
"ethereumjs-util": "5.1.3"
|
||||
@ -8365,15 +8373,15 @@
|
||||
}
|
||||
},
|
||||
"eth-simple-keyring": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/eth-simple-keyring/-/eth-simple-keyring-1.2.1.tgz",
|
||||
"integrity": "sha1-bXs1LcWppQINYfafryHvsvY2P0U=",
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/eth-simple-keyring/-/eth-simple-keyring-1.2.2.tgz",
|
||||
"integrity": "sha512-uQVBYshHUOaXVoat1BpLA/QNMCr4hgdFBgwIB7rRmQ+m3vQQAseUsOM+biPDYzq6end+6LjcccElLpQaIZe6dg==",
|
||||
"requires": {
|
||||
"eth-sig-util": "1.4.2",
|
||||
"ethereumjs-util": "5.2.0",
|
||||
"ethereumjs-wallet": "0.6.0",
|
||||
"events": "1.1.1",
|
||||
"xtend": "4.0.1"
|
||||
"eth-sig-util": "^1.4.2",
|
||||
"ethereumjs-util": "^5.1.1",
|
||||
"ethereumjs-wallet": "^0.6.0",
|
||||
"events": "^1.1.1",
|
||||
"xtend": "^4.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"ethereumjs-util": {
|
||||
@ -8381,13 +8389,13 @@
|
||||
"resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.2.0.tgz",
|
||||
"integrity": "sha512-CJAKdI0wgMbQFLlLRtZKGcy/L6pzVRgelIZqRqNbuVFM3K9VEnyfbcvz0ncWMRNCe4kaHWjwRYQcYMucmwsnWA==",
|
||||
"requires": {
|
||||
"bn.js": "4.11.8",
|
||||
"create-hash": "1.1.3",
|
||||
"ethjs-util": "0.1.4",
|
||||
"keccak": "1.4.0",
|
||||
"rlp": "2.0.0",
|
||||
"safe-buffer": "5.1.1",
|
||||
"secp256k1": "3.4.0"
|
||||
"bn.js": "^4.11.0",
|
||||
"create-hash": "^1.1.2",
|
||||
"ethjs-util": "^0.1.3",
|
||||
"keccak": "^1.0.2",
|
||||
"rlp": "^2.0.0",
|
||||
"safe-buffer": "^5.1.1",
|
||||
"secp256k1": "^3.0.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -8574,7 +8582,7 @@
|
||||
"eth-query": "2.1.2",
|
||||
"ethereumjs-block": "1.7.0",
|
||||
"ethereumjs-tx": "1.3.3",
|
||||
"ethereumjs-util": "github:ethereumjs/ethereumjs-util#ac5d0908536b447083ea422b435da27f26615de9",
|
||||
"ethereumjs-util": "^5.0.1",
|
||||
"ethereumjs-vm": "2.3.5",
|
||||
"through2": "2.0.3",
|
||||
"treeify": "1.1.0",
|
||||
@ -8681,7 +8689,7 @@
|
||||
"async": "2.6.0",
|
||||
"ethereum-common": "0.2.0",
|
||||
"ethereumjs-tx": "1.3.3",
|
||||
"ethereumjs-util": "github:ethereumjs/ethereumjs-util#ac5d0908536b447083ea422b435da27f26615de9",
|
||||
"ethereumjs-util": "^5.0.0",
|
||||
"merkle-patricia-tree": "2.3.0"
|
||||
}
|
||||
},
|
||||
@ -8691,7 +8699,7 @@
|
||||
"integrity": "sha1-7OBR0+/b53GtKlGNYWMsoqt17Ls=",
|
||||
"requires": {
|
||||
"ethereum-common": "0.0.18",
|
||||
"ethereumjs-util": "github:ethereumjs/ethereumjs-util#ac5d0908536b447083ea422b435da27f26615de9"
|
||||
"ethereumjs-util": "^5.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"ethereum-common": {
|
||||
@ -8703,6 +8711,7 @@
|
||||
},
|
||||
"ethereumjs-util": {
|
||||
"version": "github:ethereumjs/ethereumjs-util#ac5d0908536b447083ea422b435da27f26615de9",
|
||||
"from": "github:ethereumjs/ethereumjs-util#ac5d0908536b447083ea422b435da27f26615de9",
|
||||
"requires": {
|
||||
"bn.js": "4.11.8",
|
||||
"create-hash": "1.1.3",
|
||||
@ -11192,6 +11201,7 @@
|
||||
"dependencies": {
|
||||
"async-eventemitter": {
|
||||
"version": "github:ahultgren/async-eventemitter#fa06e39e56786ba541c180061dbf2c0a5bbf951c",
|
||||
"from": "async-eventemitter@github:ahultgren/async-eventemitter#fa06e39e56786ba541c180061dbf2c0a5bbf951c",
|
||||
"requires": {
|
||||
"async": "2.6.0"
|
||||
}
|
||||
@ -11884,6 +11894,7 @@
|
||||
},
|
||||
"gulp": {
|
||||
"version": "github:gulpjs/gulp#71c094a51c7972d26f557899ddecab0210ef3776",
|
||||
"from": "github:gulpjs/gulp#4.0",
|
||||
"requires": {
|
||||
"glob-watcher": "4.0.0",
|
||||
"gulp-cli": "2.0.1",
|
||||
@ -15533,6 +15544,7 @@
|
||||
"dependencies": {
|
||||
"async-eventemitter": {
|
||||
"version": "github:ahultgren/async-eventemitter#fa06e39e56786ba541c180061dbf2c0a5bbf951c",
|
||||
"from": "async-eventemitter@github:ahultgren/async-eventemitter#fa06e39e56786ba541c180061dbf2c0a5bbf951c",
|
||||
"requires": {
|
||||
"async": "2.6.0"
|
||||
}
|
||||
@ -18307,7 +18319,7 @@
|
||||
"integrity": "sha512-LKd2OoIT9Re/OG38zXbd5pyHIk2IfcOUczCwkYXl5iJIbufg9nqpweh66VfPwMkUlrEvc7YVvtQdmSrB9V9TkQ==",
|
||||
"requires": {
|
||||
"async": "1.5.2",
|
||||
"ethereumjs-util": "github:ethereumjs/ethereumjs-util#ac5d0908536b447083ea422b435da27f26615de9",
|
||||
"ethereumjs-util": "^5.0.0",
|
||||
"level-ws": "0.0.0",
|
||||
"levelup": "1.3.9",
|
||||
"memdown": "1.4.1",
|
||||
@ -31329,7 +31341,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"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": {
|
||||
"async-eventemitter": {
|
||||
"version": "github:ahultgren/async-eventemitter#fa06e39e56786ba541c180061dbf2c0a5bbf951c",
|
||||
"from": "async-eventemitter@github:ahultgren/async-eventemitter#fa06e39e56786ba541c180061dbf2c0a5bbf951c",
|
||||
"requires": {
|
||||
"async": "2.6.0"
|
||||
}
|
||||
@ -31777,6 +31791,7 @@
|
||||
},
|
||||
"websocket": {
|
||||
"version": "git://github.com/frozeman/WebSocket-Node.git#7004c39c42ac98875ab61126e5b4a925430f592c",
|
||||
"from": "websocket@git://github.com/frozeman/WebSocket-Node.git#7004c39c42ac98875ab61126e5b4a925430f592c",
|
||||
"requires": {
|
||||
"debug": "2.6.9",
|
||||
"nan": "2.8.0",
|
||||
|
@ -9,7 +9,7 @@
|
||||
"dist": "gulp dist",
|
||||
"doc": "jsdoc -c development/tools/.jsdoc.json",
|
||||
"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:integration": "npm run test:integration:build && npm run test:flat && npm run test:mascara",
|
||||
"test:integration:build": "gulp build:scss",
|
||||
@ -100,7 +100,7 @@
|
||||
"eth-json-rpc-filters": "^2.1.1",
|
||||
"eth-json-rpc-infura": "^3.0.0",
|
||||
"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-query": "^2.1.2",
|
||||
"eth-sig-util": "^1.4.2",
|
||||
@ -223,6 +223,7 @@
|
||||
"css-loader": "^0.28.11",
|
||||
"deep-freeze-strict": "^1.1.1",
|
||||
"del": "^3.0.0",
|
||||
"dot-only-hunter": "^1.0.3",
|
||||
"envify": "^4.0.0",
|
||||
"enzyme": "^3.3.0",
|
||||
"enzyme-adapter-react-15": "^1.0.5",
|
||||
|
@ -121,7 +121,14 @@ describe('Metamask popup page', function () {
|
||||
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 () {
|
||||
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()
|
||||
})
|
||||
|
||||
|
@ -43,7 +43,7 @@ async function runAddTokenFlowTest (assert, done) {
|
||||
assert.equal(addTokenTitle[0].textContent, 'Add Tokens', 'add token title is correct')
|
||||
|
||||
// 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')
|
||||
cancelAddTokenButton.click()
|
||||
|
||||
@ -75,15 +75,15 @@ async function runAddTokenFlowTest (assert, done) {
|
||||
tokenWrapper[0].click()
|
||||
|
||||
// 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')
|
||||
nextButton[0].click()
|
||||
|
||||
// Confirm Add token
|
||||
const confirmAddToken = await queryAsync($, '.confirm-add-token')
|
||||
assert.ok(confirmAddToken[0], 'confirm add token rendered')
|
||||
assert.ok($('button.btn-primary--lg')[0], 'confirm add token button found')
|
||||
$('button.btn-primary--lg')[0].click()
|
||||
assert.ok($('button.btn-primary.btn--large')[0], 'confirm add token button found')
|
||||
$('button.btn-primary.btn--large')[0].click()
|
||||
|
||||
// Verify added token image
|
||||
let heroBalance = await queryAsync($, '.hero-balance')
|
||||
@ -120,7 +120,7 @@ async function runAddTokenFlowTest (assert, done) {
|
||||
const errorMessage = await queryAsync($, '#custom-symbol-helper-text')
|
||||
assert.ok(errorMessage[0], 'error rendered')
|
||||
|
||||
$('button.btn-secondary--lg')[0].click()
|
||||
$('button.btn-default.btn--large')[0].click()
|
||||
|
||||
// await timeout(100000)
|
||||
|
||||
|
@ -38,7 +38,7 @@ async function runConfirmSigRequestsTest(assert, done) {
|
||||
let confirmSigRowValue = await queryAsync($, '.request-signature__row-value')
|
||||
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()
|
||||
|
||||
confirmSigHeadline = await queryAsync($, '.request-signature__headline')
|
||||
@ -47,7 +47,7 @@ async function runConfirmSigRequestsTest(assert, done) {
|
||||
confirmSigRowValue = await queryAsync($, '.request-signature__row-value')
|
||||
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()
|
||||
|
||||
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[1].textContent, '1337')
|
||||
|
||||
confirmSigSignButton = await queryAsync($, 'button.btn-primary--lg')
|
||||
confirmSigSignButton = await queryAsync($, 'button.btn-primary.btn--large')
|
||||
confirmSigSignButton[0].click()
|
||||
|
||||
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, 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')
|
||||
sendButton[0].click()
|
||||
await timeout()
|
||||
@ -169,13 +169,13 @@ async function runSendFlowTest(assert, done) {
|
||||
sendAmountFieldInputInEdit.val('1.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')
|
||||
|
||||
selectState.val('send new ui')
|
||||
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()
|
||||
// sendButtonInEdit[0].click()
|
||||
|
||||
|
@ -11,9 +11,8 @@ const GIFEncoder = require('gifencoder')
|
||||
const pngFileStream = require('png-file-stream')
|
||||
const sizeOfPng = require('image-size/lib/types/png')
|
||||
const By = webdriver.By
|
||||
const { delay, buildWebDriver } = require('./func')
|
||||
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'))
|
||||
|
||||
@ -50,11 +49,10 @@ async function captureAllScreens() {
|
||||
|
||||
await cleanScreenShotDir()
|
||||
|
||||
// setup selenium and install extension
|
||||
const extPath = path.resolve('dist/chrome')
|
||||
driver = buildWebDriver(extPath)
|
||||
await driver.get('chrome://extensions-frame')
|
||||
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")')
|
||||
driver = buildChromeWebDriver(extPath)
|
||||
const extensionId = await getExtensionIdChrome(driver)
|
||||
|
||||
await driver.get(`chrome-extension://${extensionId}/home.html`)
|
||||
await delay(500)
|
||||
tabs = await driver.getAllWindowHandles()
|
||||
@ -165,7 +163,7 @@ async function captureAllScreens() {
|
||||
await delay(300)
|
||||
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 captureLanguageScreenShots('metamask account detail export private key screen - reveal key')
|
||||
|
||||
|
@ -47,7 +47,7 @@ describe('MetaMaskController', function () {
|
||||
encryptor: {
|
||||
encrypt: function (password, object) {
|
||||
this.object = object
|
||||
return Promise.resolve()
|
||||
return Promise.resolve('mock-encrypted')
|
||||
},
|
||||
decrypt: function () {
|
||||
return Promise.resolve(this.object)
|
||||
@ -64,6 +64,31 @@ describe('MetaMaskController', function () {
|
||||
sandbox.restore()
|
||||
})
|
||||
|
||||
describe('submitPassword', function () {
|
||||
const password = 'password'
|
||||
|
||||
beforeEach(async function () {
|
||||
await metamaskController.createNewVaultAndKeychain(password)
|
||||
})
|
||||
|
||||
it('removes any identities that do not correspond to known accounts.', async function () {
|
||||
const fakeAddress = '0xbad0'
|
||||
metamaskController.preferencesController.addAddresses([fakeAddress])
|
||||
await metamaskController.submitPassword(password)
|
||||
|
||||
const identities = Object.keys(metamaskController.preferencesController.store.getState().identities)
|
||||
const addresses = await metamaskController.keyringController.getAccounts()
|
||||
|
||||
identities.forEach((identity) => {
|
||||
assert.ok(addresses.includes(identity), `addresses should include all IDs: ${identity}`)
|
||||
})
|
||||
|
||||
addresses.forEach((address) => {
|
||||
assert.ok(identities.includes(address), `identities should include all Addresses: ${address}`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getGasPrice', function () {
|
||||
|
||||
it('gives the 50th percentile lowest accepted gas price from recentBlocksController', async function () {
|
||||
|
@ -503,17 +503,23 @@ function requestRevealSeedWords (password) {
|
||||
}
|
||||
|
||||
function resetAccount () {
|
||||
return (dispatch) => {
|
||||
background.resetAccount((err, account) => {
|
||||
dispatch(actions.hideLoadingIndication())
|
||||
if (err) {
|
||||
dispatch(actions.displayWarning(err.message))
|
||||
}
|
||||
return dispatch => {
|
||||
dispatch(actions.showLoadingIndication())
|
||||
|
||||
log.info('Transaction history reset for ' + account)
|
||||
dispatch(actions.showAccountsPage())
|
||||
})
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
background.resetAccount((err, account) => {
|
||||
dispatch(actions.hideLoadingIndication())
|
||||
if (err) {
|
||||
dispatch(actions.displayWarning(err.message))
|
||||
return reject(err)
|
||||
}
|
||||
|
||||
log.info('Transaction history reset for ' + account)
|
||||
dispatch(actions.showAccountsPage())
|
||||
resolve(account)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function addNewKeyring (type, opts) {
|
||||
@ -1397,16 +1403,24 @@ function markAccountsFound () {
|
||||
|
||||
function retryTransaction (txId) {
|
||||
log.debug(`background.retryTransaction`)
|
||||
let newTxId
|
||||
|
||||
return (dispatch) => {
|
||||
background.retryTransaction(txId, (err, newState) => {
|
||||
if (err) {
|
||||
return dispatch(actions.displayWarning(err.message))
|
||||
}
|
||||
const { selectedAddressTxList } = newState
|
||||
const { id: newTxId } = selectedAddressTxList[selectedAddressTxList.length - 1]
|
||||
dispatch(actions.updateMetamaskState(newState))
|
||||
dispatch(actions.viewPendingTx(newTxId))
|
||||
return new Promise((resolve, reject) => {
|
||||
background.retryTransaction(txId, (err, newState) => {
|
||||
if (err) {
|
||||
dispatch(actions.displayWarning(err.message))
|
||||
reject(err)
|
||||
}
|
||||
|
||||
const { selectedAddressTxList } = newState
|
||||
const { id } = selectedAddressTxList[selectedAddressTxList.length - 1]
|
||||
newTxId = id
|
||||
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: SETTINGS_ROUTE, component: Settings }),
|
||||
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: ADD_TOKEN_ROUTE, exact, component: AddTokenPage }),
|
||||
h(Authenticated, { path: CONFIRM_ADD_TOKEN_ROUTE, exact, component: ConfirmAddTokenPage }),
|
||||
@ -137,7 +137,6 @@ class App extends Component {
|
||||
|
||||
(isLoading || isLoadingNetwork) && h(Loading, {
|
||||
loadingMessage: loadMessage,
|
||||
fullScreen: true,
|
||||
}),
|
||||
|
||||
// content
|
||||
|
@ -2,20 +2,15 @@ import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import classnames from 'classnames'
|
||||
|
||||
const SECONDARY = 'secondary'
|
||||
const CLASSNAME_DEFAULT = 'btn-default'
|
||||
const CLASSNAME_PRIMARY = 'btn-primary'
|
||||
const CLASSNAME_PRIMARY_LARGE = 'btn-primary--lg'
|
||||
const CLASSNAME_SECONDARY = 'btn-secondary'
|
||||
const CLASSNAME_SECONDARY_LARGE = 'btn-secondary--lg'
|
||||
const CLASSNAME_LARGE = 'btn--large'
|
||||
|
||||
const getClassName = (type, large = false) => {
|
||||
let output = type === SECONDARY ? CLASSNAME_SECONDARY : CLASSNAME_PRIMARY
|
||||
|
||||
if (large) {
|
||||
output += ` ${type === SECONDARY ? CLASSNAME_SECONDARY_LARGE : CLASSNAME_PRIMARY_LARGE}`
|
||||
}
|
||||
|
||||
return output
|
||||
const typeHash = {
|
||||
default: CLASSNAME_DEFAULT,
|
||||
primary: CLASSNAME_PRIMARY,
|
||||
secondary: CLASSNAME_SECONDARY,
|
||||
}
|
||||
|
||||
class Button extends Component {
|
||||
@ -24,7 +19,11 @@ class Button extends Component {
|
||||
|
||||
return (
|
||||
<button
|
||||
className={classnames(getClassName(type, large), className)}
|
||||
className={classnames(
|
||||
typeHash[type],
|
||||
large && CLASSNAME_LARGE,
|
||||
className
|
||||
)}
|
||||
{ ...buttonProps }
|
||||
>
|
||||
{ this.props.children }
|
||||
|
@ -13,13 +13,21 @@ storiesOf('Button', module)
|
||||
{text('text', 'Click me')}
|
||||
</Button>
|
||||
)
|
||||
.add('secondary', () => (
|
||||
.add('secondary', () =>
|
||||
<Button
|
||||
onClick={action('clicked')}
|
||||
type="secondary"
|
||||
>
|
||||
{text('text', 'Click me')}
|
||||
</Button>
|
||||
)
|
||||
.add('default', () => (
|
||||
<Button
|
||||
onClick={action('clicked')}
|
||||
type="default"
|
||||
>
|
||||
{text('text', 'Click me')}
|
||||
</Button>
|
||||
))
|
||||
.add('large primary', () => (
|
||||
<Button
|
||||
@ -39,3 +47,12 @@ storiesOf('Button', module)
|
||||
{text('text', 'Click me')}
|
||||
</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')]),
|
||||
|
||||
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,
|
||||
style: {
|
||||
marginRight: '10px',
|
||||
|
@ -1,5 +1,9 @@
|
||||
@import './export-text-container/index';
|
||||
|
||||
@import './selected-account/index';
|
||||
|
||||
@import './info-box/index';
|
||||
|
||||
@import './pages/index';
|
||||
|
||||
@import './modals/index';
|
||||
|
@ -1,7 +1,6 @@
|
||||
const { Component } = require('react')
|
||||
const h = require('react-hyperscript')
|
||||
const PropTypes = require('prop-types')
|
||||
const classnames = require('classnames')
|
||||
const Spinner = require('../spinner')
|
||||
|
||||
class LoadingScreen extends Component {
|
||||
@ -12,9 +11,7 @@ class LoadingScreen extends Component {
|
||||
|
||||
render () {
|
||||
return (
|
||||
h('.loading-overlay', {
|
||||
className: classnames({ 'loading-overlay--full-screen': this.props.fullScreen }),
|
||||
}, [
|
||||
h('.loading-overlay', [
|
||||
h('.loading-overlay__container', [
|
||||
h(Spinner, {
|
||||
color: '#F7C06C',
|
||||
@ -29,7 +26,6 @@ class LoadingScreen extends Component {
|
||||
|
||||
LoadingScreen.propTypes = {
|
||||
loadingMessage: PropTypes.string,
|
||||
fullScreen: PropTypes.bool,
|
||||
}
|
||||
|
||||
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)
|
2
ui/app/components/modals/confirm-reset-account/index.js
Normal file
2
ui/app/components/modals/confirm-reset-account/index.js
Normal file
@ -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', [
|
||||
h('button.btn-primary--lg.deposit-ether-modal__deposit-button', {
|
||||
h('button.btn-primary.btn--large.deposit-ether-modal__deposit-button', {
|
||||
onClick: onButtonClick,
|
||||
}, [buttonLabel]),
|
||||
]),
|
||||
|
@ -9,7 +9,7 @@ const { getSelectedAccount } = require('../../selectors')
|
||||
function mapStateToProps (state) {
|
||||
return {
|
||||
selectedAccount: getSelectedAccount(state),
|
||||
identity: state.appState.modal.modalState.identity,
|
||||
identity: state.appState.modal.modalState.props.identity,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -87,14 +87,14 @@ ExportPrivateKeyModal.prototype.renderButton = function (className, onClick, lab
|
||||
ExportPrivateKeyModal.prototype.renderButtons = function (privateKey, password, address, hideModal) {
|
||||
return h('div.export-private-key-buttons', {}, [
|
||||
!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(),
|
||||
'Cancel'
|
||||
),
|
||||
|
||||
(privateKey
|
||||
? this.renderButton('btn-primary--lg 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', () => hideModal(), this.context.t('done'))
|
||||
: 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) {
|
||||
return {
|
||||
network: state.metamask.network,
|
||||
token: state.appState.modal.modalState.token,
|
||||
token: state.appState.modal.modalState.props.token,
|
||||
}
|
||||
}
|
||||
|
||||
|
52
ui/app/components/modals/index.scss
Normal file
52
ui/app/components/modals/index.scss
Normal file
@ -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 CustomizeGasModal = require('../customize-gas-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 = {
|
||||
mobileModalStyle: {
|
||||
@ -173,18 +196,18 @@ const MODALS = {
|
||||
|
||||
BETA_UI_NOTIFICATION_MODAL: {
|
||||
contents: [
|
||||
h(NotifcationModal, {
|
||||
header: 'uiWelcome',
|
||||
message: 'uiWelcomeMessage',
|
||||
}),
|
||||
h(Notification, [
|
||||
h(WelcomeBeta),
|
||||
]),
|
||||
],
|
||||
mobileModalStyle: {
|
||||
width: '95%',
|
||||
top: getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_POPUP ? '52vh' : '36.5vh',
|
||||
...modalContainerMobileStyle,
|
||||
},
|
||||
laptopModalStyle: {
|
||||
width: '449px',
|
||||
top: 'calc(33% + 45px)',
|
||||
...modalContainerLaptopStyle,
|
||||
},
|
||||
contentStyle: {
|
||||
borderRadius: '8px',
|
||||
},
|
||||
},
|
||||
|
||||
@ -208,12 +231,13 @@ const MODALS = {
|
||||
CONFIRM_RESET_ACCOUNT: {
|
||||
contents: h(ConfirmResetAccount),
|
||||
mobileModalStyle: {
|
||||
width: '95%',
|
||||
top: getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_POPUP ? '52vh' : '36.5vh',
|
||||
...modalContainerMobileStyle,
|
||||
},
|
||||
laptopModalStyle: {
|
||||
width: '473px',
|
||||
top: 'calc(33% + 45px)',
|
||||
...modalContainerLaptopStyle,
|
||||
},
|
||||
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: {
|
||||
contents: [],
|
||||
mobileModalStyle: {},
|
||||
@ -306,7 +348,7 @@ module.exports = connect(mapStateToProps, mapDispatchToProps)(Modal)
|
||||
Modal.prototype.render = function () {
|
||||
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 contentStyle = modal.contentStyle || {}
|
||||
|
||||
@ -326,6 +368,7 @@ Modal.prototype.render = function () {
|
||||
modalStyle,
|
||||
contentStyle,
|
||||
backdropStyle: BACKDROPSTYLE,
|
||||
closeOnClick: !disableBackdropClick,
|
||||
},
|
||||
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)
|
2
ui/app/components/modals/notification/index.js
Normal file
2
ui/app/components/modals/notification/index.js
Normal file
@ -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) {
|
||||
return {
|
||||
Qr: state.appState.modal.modalState.Qr,
|
||||
Qr: state.appState.modal.modalState.props.Qr,
|
||||
}
|
||||
}
|
||||
|
||||
|
2
ui/app/components/modals/transaction-confirmed/index.js
Normal file
2
ui/app/components/modals/transaction-confirmed/index.js
Normal file
@ -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
|
2
ui/app/components/modals/welcome-beta/index.js
Normal file
2
ui/app/components/modals/welcome-beta/index.js
Normal file
@ -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 className="page-container__footer">
|
||||
<Button
|
||||
type="secondary"
|
||||
type="default"
|
||||
large
|
||||
className="page-container__footer-button"
|
||||
onClick={() => {
|
||||
|
@ -11,6 +11,10 @@
|
||||
width: 50%;
|
||||
text-align: center;
|
||||
margin-top: 8px;
|
||||
|
||||
@media screen and (max-width: 575px) {
|
||||
width: 60%;
|
||||
}
|
||||
}
|
||||
|
||||
&__link {
|
||||
|
@ -15,7 +15,7 @@ export default class TokenListPlaceholder extends Component {
|
||||
</div>
|
||||
<a
|
||||
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"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
|
@ -87,7 +87,7 @@ export default class ConfirmAddToken extends Component {
|
||||
</div>
|
||||
<div className="page-container__footer">
|
||||
<Button
|
||||
type="secondary"
|
||||
type="default"
|
||||
large
|
||||
className="page-container__footer-button"
|
||||
onClick={() => history.push(ADD_TOKEN_ROUTE)}
|
||||
|
@ -46,7 +46,7 @@ AccountImportSubview.prototype.render = function () {
|
||||
},
|
||||
onClick: () => {
|
||||
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')),
|
||||
|
@ -51,7 +51,7 @@ class JsonImportSubview extends Component {
|
||||
|
||||
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),
|
||||
}, [
|
||||
this.context.t('cancel'),
|
||||
@ -105,7 +105,7 @@ class JsonImportSubview extends Component {
|
||||
}
|
||||
|
||||
this.props.importNewJsonAccount([ fileContents, password ])
|
||||
// JS runtime requires caught rejections but failures are handled by Redux
|
||||
// JS runtime requires caught rejections but failures are handled by Redux
|
||||
.catch()
|
||||
}
|
||||
}
|
||||
|
@ -59,13 +59,13 @@ PrivateKeyImportView.prototype.render = function () {
|
||||
|
||||
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),
|
||||
}, [
|
||||
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(),
|
||||
}, [
|
||||
this.context.t('import'),
|
||||
@ -91,7 +91,7 @@ PrivateKeyImportView.prototype.createNewKeychain = function () {
|
||||
const { importNewAccount, history } = this.props
|
||||
|
||||
importNewAccount('Private Key', [ privateKey ])
|
||||
// JS runtime requires caught rejections but failures are handled by Redux
|
||||
// JS runtime requires caught rejections but failures are handled by Redux
|
||||
.catch()
|
||||
.then(() => history.push(DEFAULT_ROUTE))
|
||||
}
|
||||
|
@ -38,13 +38,13 @@ class NewAccountCreateForm extends Component {
|
||||
|
||||
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),
|
||||
}, [
|
||||
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: () => {
|
||||
createAccount(newAccountName || defaultAccountName)
|
||||
.then(() => history.push(DEFAULT_ROUTE))
|
||||
|
@ -106,10 +106,10 @@ class RevealSeedPage extends Component {
|
||||
renderPasswordPromptFooter () {
|
||||
return (
|
||||
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),
|
||||
}, 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),
|
||||
disabled: this.state.password === '',
|
||||
}, this.context.t('next')),
|
||||
@ -120,7 +120,7 @@ class RevealSeedPage extends Component {
|
||||
renderRevealSeedFooter () {
|
||||
return (
|
||||
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),
|
||||
}, this.context.t('close')),
|
||||
])
|
||||
|
@ -217,7 +217,7 @@ class Settings extends Component {
|
||||
]),
|
||||
h('div.settings__content-item', [
|
||||
h('div.settings__content-item-col', [
|
||||
h('button.btn-primary--lg.settings__button', {
|
||||
h('button.btn-primary.btn--large.settings__button', {
|
||||
onClick (event) {
|
||||
window.logStateString((err, result) => {
|
||||
if (err) {
|
||||
@ -242,7 +242,7 @@ class Settings extends Component {
|
||||
h('div.settings__content-item', this.context.t('revealSeedWords')),
|
||||
h('div.settings__content-item', [
|
||||
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 => {
|
||||
event.preventDefault()
|
||||
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', [
|
||||
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) {
|
||||
event.preventDefault()
|
||||
setFeatureFlagToBeta()
|
||||
@ -281,7 +281,7 @@ class Settings extends Component {
|
||||
h('div.settings__content-item', this.context.t('resetAccount')),
|
||||
h('div.settings__content-item', [
|
||||
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) {
|
||||
event.preventDefault()
|
||||
showResetAccountConfirmationModal()
|
||||
|
@ -34,14 +34,7 @@ class UnlockPage extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
tryUnlockMetamask (password) {
|
||||
const { tryUnlockMetamask, history } = this.props
|
||||
tryUnlockMetamask(password)
|
||||
.catch(({ message }) => this.setState({ error: message }))
|
||||
.then(() => history.push(DEFAULT_ROUTE))
|
||||
}
|
||||
|
||||
handleSubmit (event) {
|
||||
async handleSubmit (event) {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
|
||||
@ -54,9 +47,14 @@ class UnlockPage extends Component {
|
||||
|
||||
this.setState({ error: null })
|
||||
|
||||
tryUnlockMetamask(password)
|
||||
.catch(({ message }) => this.setState({ error: message }))
|
||||
.then(() => history.push(DEFAULT_ROUTE))
|
||||
try {
|
||||
await tryUnlockMetamask(password)
|
||||
} catch ({ message }) {
|
||||
this.setState({ error: message })
|
||||
return
|
||||
}
|
||||
|
||||
history.push(DEFAULT_ROUTE)
|
||||
}
|
||||
|
||||
handleInputChange ({ target }) {
|
||||
|
@ -291,18 +291,48 @@ ConfirmSendEther.prototype.convertToRenderableCurrency = function (value, curren
|
||||
: value
|
||||
}
|
||||
|
||||
ConfirmSendEther.prototype.editTransaction = function (txMeta) {
|
||||
ConfirmSendEther.prototype.editTransaction = function () {
|
||||
const { editTransaction, history } = this.props
|
||||
const txMeta = this.gatherTxMeta()
|
||||
editTransaction(txMeta)
|
||||
history.push(SEND_ROUTE)
|
||||
}
|
||||
|
||||
ConfirmSendEther.prototype.renderNetworkDisplay = function () {
|
||||
ConfirmSendEther.prototype.renderHeaderRow = function (isTxReprice) {
|
||||
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)
|
||||
? h(NetworkDisplay)
|
||||
: null
|
||||
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),
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
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 () {
|
||||
@ -320,6 +350,7 @@ ConfirmSendEther.prototype.render = function () {
|
||||
},
|
||||
} = this.props
|
||||
const txMeta = this.gatherTxMeta()
|
||||
const isTxReprice = Boolean(txMeta.lastGasPrice)
|
||||
const txParams = txMeta.txParams || {}
|
||||
|
||||
const {
|
||||
@ -338,11 +369,6 @@ ConfirmSendEther.prototype.render = function () {
|
||||
totalInETH,
|
||||
} = 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 convertedTotalInFiat = this.convertToRenderableCurrency(totalInFIAT, currentCurrency)
|
||||
|
||||
@ -362,19 +388,7 @@ ConfirmSendEther.prototype.render = function () {
|
||||
return (
|
||||
// Main Send token Card
|
||||
h('.page-container', [
|
||||
h('.page-container__header', [
|
||||
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),
|
||||
]),
|
||||
this.renderHeader(isTxReprice),
|
||||
h('.page-container__content', [
|
||||
h(SenderToRecipient, {
|
||||
senderName: fromName,
|
||||
|
@ -12,6 +12,7 @@ const actions = require('../../actions')
|
||||
const clone = require('clone')
|
||||
const Identicon = require('../identicon')
|
||||
const GasFeeDisplay = require('../send/gas-fee-display-v2.js')
|
||||
const NetworkDisplay = require('../network-display')
|
||||
const ethUtil = require('ethereumjs-util')
|
||||
const BN = ethUtil.BN
|
||||
const {
|
||||
@ -39,6 +40,11 @@ const {
|
||||
} = require('../../selectors')
|
||||
const { SEND_ROUTE, DEFAULT_ROUTE } = require('../../routes')
|
||||
|
||||
const {
|
||||
ENVIRONMENT_TYPE_POPUP,
|
||||
ENVIRONMENT_TYPE_NOTIFICATION,
|
||||
} = require('../../../../app/scripts/lib/enums')
|
||||
|
||||
ConfirmSendToken.contextTypes = {
|
||||
t: PropTypes.func,
|
||||
}
|
||||
@ -430,6 +436,43 @@ ConfirmSendToken.prototype.convertToRenderableCurrency = function (value, curren
|
||||
: 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 () {
|
||||
const txMeta = this.gatherTxMeta()
|
||||
const {
|
||||
@ -443,25 +486,13 @@ ConfirmSendToken.prototype.render = function () {
|
||||
},
|
||||
} = this.getData()
|
||||
|
||||
this.inputs = []
|
||||
|
||||
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 (
|
||||
h('div.confirm-screen-container.confirm-send-token', [
|
||||
// Main Send token Card
|
||||
h('div.page-container', [
|
||||
h('div.page-container__header', [
|
||||
!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),
|
||||
]),
|
||||
this.renderHeader(isTxReprice),
|
||||
h('.page-container__content', [
|
||||
h('div.flex-row.flex-center.confirm-screen-identicons', [
|
||||
h('div.confirm-screen-account-wrapper', [
|
||||
|
@ -130,7 +130,6 @@ PendingTx.prototype.render = function () {
|
||||
|
||||
if (isFetching) {
|
||||
return h(Loading, {
|
||||
fullScreen: true,
|
||||
loadingMessage: this.context.t('generatingTransaction'),
|
||||
})
|
||||
}
|
||||
@ -157,9 +156,7 @@ PendingTx.prototype.render = function () {
|
||||
sendTransaction,
|
||||
})
|
||||
default:
|
||||
return h(Loading, {
|
||||
fullScreen: true,
|
||||
})
|
||||
return h(Loading)
|
||||
}
|
||||
}
|
||||
|
||||
|
2
ui/app/components/selected-account/index.js
Normal file
2
ui/app/components/selected-account/index.js
Normal file
@ -0,0 +1,2 @@
|
||||
import SelectedAccount from './selected-account.container'
|
||||
module.exports = SelectedAccount
|
38
ui/app/components/selected-account/index.scss
Normal file
38
ui/app/components/selected-account/index.scss
Normal file
@ -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,
|
||||
disabled: !token,
|
||||
onClick: () => this.onBuyWithShapeShift(),
|
||||
|
@ -235,12 +235,12 @@ SignatureRequest.prototype.renderFooter = function () {
|
||||
}
|
||||
|
||||
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 => {
|
||||
cancel(event).then(() => this.props.history.push(DEFAULT_ROUTE))
|
||||
},
|
||||
}, this.context.t('cancel')),
|
||||
h('button.btn-primary--lg', {
|
||||
h('button.btn-primary.btn--large', {
|
||||
onClick: event => {
|
||||
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 { withStyles } from '@material-ui/core/styles'
|
||||
import { default as MaterialTextField } from '@material-ui/core/TextField'
|
||||
|
||||
const inputLabelBase = {
|
||||
transform: 'none',
|
||||
transition: 'none',
|
||||
position: 'initial',
|
||||
color: '#5b5b5b',
|
||||
}
|
||||
|
||||
const styles = {
|
||||
materialLabel: {
|
||||
'&$materialFocused': {
|
||||
@ -46,57 +53,57 @@ const styles = {
|
||||
border: '1px solid #2f9ae0',
|
||||
},
|
||||
},
|
||||
largeInputLabel: {
|
||||
...inputLabelBase,
|
||||
fontSize: '1rem',
|
||||
},
|
||||
inputLabel: {
|
||||
...inputLabelBase,
|
||||
fontSize: '.75rem',
|
||||
transform: 'none',
|
||||
transition: 'none',
|
||||
position: 'initial',
|
||||
color: '#5b5b5b',
|
||||
},
|
||||
}
|
||||
|
||||
class TextField extends Component {
|
||||
static defaultProps = {
|
||||
error: null,
|
||||
}
|
||||
const TextField = props => {
|
||||
const { error, classes, material, startAdornment, largeLabel, ...textFieldProps } = props
|
||||
|
||||
static propTypes = {
|
||||
error: PropTypes.string,
|
||||
classes: PropTypes.object,
|
||||
material: PropTypes.bool,
|
||||
startAdornment: PropTypes.element,
|
||||
}
|
||||
return (
|
||||
<MaterialTextField
|
||||
error={Boolean(error)}
|
||||
helperText={error}
|
||||
InputLabelProps={{
|
||||
shrink: material ? undefined : true,
|
||||
className: material ? '' : (largeLabel ? classes.largeInputLabel : classes.inputLabel),
|
||||
FormLabelClasses: {
|
||||
root: material ? classes.materialLabel : classes.formLabel,
|
||||
focused: material ? classes.materialFocused : classes.formLabelFocused,
|
||||
error: classes.materialError,
|
||||
},
|
||||
}}
|
||||
InputProps={{
|
||||
startAdornment: startAdornment || undefined,
|
||||
disableUnderline: !material,
|
||||
classes: {
|
||||
root: material ? '' : classes.inputRoot,
|
||||
input: material ? '' : classes.input,
|
||||
underline: material ? classes.materialUnderline : '',
|
||||
focused: material ? '' : classes.inputFocused,
|
||||
},
|
||||
}}
|
||||
{...textFieldProps}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
render () {
|
||||
const { error, classes, material, startAdornment, ...textFieldProps } = this.props
|
||||
TextField.defaultProps = {
|
||||
error: null,
|
||||
}
|
||||
|
||||
return (
|
||||
<MaterialTextField
|
||||
error={Boolean(error)}
|
||||
helperText={error}
|
||||
InputLabelProps={{
|
||||
shrink: material ? undefined : true,
|
||||
className: material ? '' : classes.inputLabel,
|
||||
FormLabelClasses: {
|
||||
root: material ? classes.materialLabel : classes.formLabel,
|
||||
focused: material ? classes.materialFocused : classes.formLabelFocused,
|
||||
error: classes.materialError,
|
||||
},
|
||||
}}
|
||||
InputProps={{
|
||||
startAdornment: startAdornment || undefined,
|
||||
disableUnderline: !material,
|
||||
classes: {
|
||||
root: material ? '' : classes.inputRoot,
|
||||
input: material ? '' : classes.input,
|
||||
underline: material ? classes.materialUnderline : '',
|
||||
focused: material ? '' : classes.inputFocused,
|
||||
},
|
||||
}}
|
||||
{...textFieldProps}
|
||||
/>
|
||||
)
|
||||
}
|
||||
TextField.propTypes = {
|
||||
error: PropTypes.string,
|
||||
classes: PropTypes.object,
|
||||
material: PropTypes.bool,
|
||||
startAdornment: PropTypes.element,
|
||||
largeLabel: PropTypes.bool,
|
||||
}
|
||||
|
||||
export default withStyles(styles)(TextField)
|
||||
|
@ -22,3 +22,32 @@ storiesOf('TextField', module)
|
||||
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-wrapper', null, [
|
||||
h('h3.token-list-item__token-balance', `${string || 0} ${symbol}`),
|
||||
|
||||
h('div.token-list-item__token-balance', `${string || 0}`),
|
||||
h('div.token-list-item__token-symbol', symbol),
|
||||
showFiat && h('div.token-list-item__fiat-amount', {
|
||||
style: {},
|
||||
}, formattedFiat),
|
||||
|
@ -1,5 +1,7 @@
|
||||
const Component = require('react').Component
|
||||
const PropTypes = require('prop-types')
|
||||
const { compose } = require('recompose')
|
||||
const { withRouter } = require('react-router-dom')
|
||||
const h = require('react-hyperscript')
|
||||
const connect = require('react-redux').connect
|
||||
const inherits = require('util').inherits
|
||||
@ -16,13 +18,16 @@ const { conversionUtil, multiplyCurrencies } = require('../conversion-util')
|
||||
const { calcTokenAmount } = require('../token-util')
|
||||
|
||||
const { getCurrentCurrency } = require('../selectors')
|
||||
const { CONFIRM_TRANSACTION_ROUTE } = require('../routes')
|
||||
|
||||
TxListItem.contextTypes = {
|
||||
t: PropTypes.func,
|
||||
}
|
||||
|
||||
module.exports = connect(mapStateToProps, mapDispatchToProps)(TxListItem)
|
||||
|
||||
module.exports = compose(
|
||||
withRouter,
|
||||
connect(mapStateToProps, mapDispatchToProps)
|
||||
)(TxListItem)
|
||||
|
||||
function mapStateToProps (state) {
|
||||
return {
|
||||
@ -216,6 +221,7 @@ TxListItem.prototype.setSelectedToken = function (tokenAddress) {
|
||||
TxListItem.prototype.resubmit = function () {
|
||||
const { transactionId } = this.props
|
||||
this.props.retryTransaction(transactionId)
|
||||
.then(id => this.props.history.push(`${CONFIRM_TRANSACTION_ROUTE}/${id}`))
|
||||
}
|
||||
|
||||
TxListItem.prototype.render = function () {
|
||||
|
@ -12,7 +12,7 @@ const { checksumAddress: toChecksumAddress } = require('../util')
|
||||
|
||||
const BalanceComponent = require('./balance-component')
|
||||
const TxList = require('./tx-list')
|
||||
const Identicon = require('./identicon')
|
||||
const SelectedAccount = require('./selected-account')
|
||||
|
||||
module.exports = compose(
|
||||
withRouter,
|
||||
@ -103,7 +103,7 @@ TxView.prototype.renderButtons = function () {
|
||||
}
|
||||
|
||||
TxView.prototype.render = function () {
|
||||
const { selectedAddress, identity, network, isMascara } = this.props
|
||||
const { isMascara } = this.props
|
||||
|
||||
return h('div.tx-view.flex-column', {
|
||||
style: {},
|
||||
@ -111,10 +111,12 @@ TxView.prototype.render = function () {
|
||||
|
||||
h('div.flex-row.phone-visible', {
|
||||
style: {
|
||||
justifyContent: 'space-between',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
flex: '0 0 auto',
|
||||
margin: '10px',
|
||||
marginBottom: '16px',
|
||||
padding: '5px',
|
||||
borderBottom: '1px solid #e5e5e5',
|
||||
},
|
||||
}, [
|
||||
|
||||
@ -127,23 +129,7 @@ TxView.prototype.render = function () {
|
||||
onClick: () => this.props.sidebarOpen ? this.props.hideSidebar() : this.props.showSidebar(),
|
||||
}),
|
||||
|
||||
h('.identicon-wrapper.select-none', {
|
||||
style: {
|
||||
marginLeft: '0.9em',
|
||||
},
|
||||
}, [
|
||||
h(Identicon, {
|
||||
diameter: 24,
|
||||
address: selectedAddress,
|
||||
network,
|
||||
}),
|
||||
]),
|
||||
|
||||
h('span.account-name', {
|
||||
style: {},
|
||||
}, [
|
||||
identity.name,
|
||||
]),
|
||||
h(SelectedAccount),
|
||||
|
||||
!isMascara && h('div.open-in-browser', {
|
||||
onClick: () => global.platform.openExtensionInBrowser(),
|
||||
|
@ -36,7 +36,6 @@ function mapStateToProps (state) {
|
||||
tokens: state.metamask.tokens,
|
||||
keyrings: state.metamask.keyrings,
|
||||
selectedAddress: selectors.getSelectedAddress(state),
|
||||
selectedIdentity: selectors.getSelectedIdentity(state),
|
||||
selectedAccount: selectors.getSelectedAccount(state),
|
||||
selectedTokenAddress: state.metamask.selectedTokenAddress,
|
||||
}
|
||||
@ -99,21 +98,24 @@ WalletView.prototype.render = function () {
|
||||
const {
|
||||
responsiveDisplayClassname,
|
||||
selectedAddress,
|
||||
selectedIdentity,
|
||||
keyrings,
|
||||
showAccountDetailModal,
|
||||
sidebarOpen,
|
||||
hideSidebar,
|
||||
history,
|
||||
identities,
|
||||
} = this.props
|
||||
// temporary logs + fake extra wallets
|
||||
// console.log('walletview, selectedAccount:', selectedAccount)
|
||||
|
||||
const checksummedAddress = checksumAddress(selectedAddress)
|
||||
|
||||
if (!selectedAddress) {
|
||||
throw new Error('selectedAddress should not be ' + String(selectedAddress))
|
||||
}
|
||||
|
||||
const keyring = keyrings.find((kr) => {
|
||||
return kr.accounts.includes(selectedAddress) ||
|
||||
kr.accounts.includes(selectedIdentity.address)
|
||||
return kr.accounts.includes(selectedAddress)
|
||||
})
|
||||
|
||||
const type = keyring.type
|
||||
@ -145,7 +147,7 @@ WalletView.prototype.render = function () {
|
||||
h('span.account-name', {
|
||||
style: {},
|
||||
}, [
|
||||
selectedIdentity.name,
|
||||
identities[selectedAddress].name,
|
||||
]),
|
||||
|
||||
h('button.btn-clear.wallet-view__details-button.allcaps', this.context.t('details')),
|
||||
|
@ -7,6 +7,7 @@ const { compose } = require('recompose')
|
||||
const actions = require('./actions')
|
||||
const txHelper = require('../lib/tx-helper')
|
||||
const log = require('loglevel')
|
||||
const R = require('ramda')
|
||||
|
||||
const PendingTx = require('./components/pending-tx')
|
||||
const SignatureRequest = require('./components/signature-request')
|
||||
@ -87,37 +88,74 @@ ConfirmTxScreen.prototype.componentDidUpdate = function (prevProps) {
|
||||
network,
|
||||
selectedAddressTxList,
|
||||
send,
|
||||
history,
|
||||
match: { params: { id: transactionId } = {} },
|
||||
} = this.props
|
||||
const { index: prevIndex, unapprovedTxs: prevUnapprovedTxs } = prevProps
|
||||
const prevUnconfTxList = txHelper(prevUnapprovedTxs, {}, {}, {}, network)
|
||||
const prevTxData = prevUnconfTxList[prevIndex] || {}
|
||||
const prevTx = selectedAddressTxList.find(({ id }) => id === prevTxData.id) || {}
|
||||
|
||||
let prevTx
|
||||
|
||||
if (transactionId) {
|
||||
prevTx = R.find(({ id }) => id + '' === transactionId)(selectedAddressTxList)
|
||||
} else {
|
||||
const { index: prevIndex, unapprovedTxs: prevUnapprovedTxs } = prevProps
|
||||
const prevUnconfTxList = txHelper(prevUnapprovedTxs, {}, {}, {}, network)
|
||||
const prevTxData = prevUnconfTxList[prevIndex] || {}
|
||||
prevTx = selectedAddressTxList.find(({ id }) => id === prevTxData.id) || {}
|
||||
}
|
||||
|
||||
const unconfTxList = txHelper(unapprovedTxs, {}, {}, {}, network)
|
||||
|
||||
if (unconfTxList.length === 0 &&
|
||||
(prevTx.status === 'dropped' || !send.to && this.getUnapprovedMessagesTotal() === 0)) {
|
||||
if (prevTx.status === 'dropped') {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
ConfirmTxScreen.prototype.getTxData = function () {
|
||||
const {
|
||||
network,
|
||||
index,
|
||||
unapprovedTxs,
|
||||
unapprovedMsgs,
|
||||
unapprovedPersonalMsgs,
|
||||
unapprovedTypedMessages,
|
||||
match: { params: { id: transactionId } = {} },
|
||||
} = this.props
|
||||
|
||||
const unconfTxList = txHelper(
|
||||
unapprovedTxs,
|
||||
unapprovedMsgs,
|
||||
unapprovedPersonalMsgs,
|
||||
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 {
|
||||
network,
|
||||
unapprovedTxs,
|
||||
currentCurrency,
|
||||
unapprovedMsgs,
|
||||
unapprovedPersonalMsgs,
|
||||
unapprovedTypedMessages,
|
||||
conversionRate,
|
||||
blockGasLimit,
|
||||
// provider,
|
||||
// computedBalances,
|
||||
} = props
|
||||
|
||||
var unconfTxList = txHelper(unapprovedTxs, unapprovedMsgs, unapprovedPersonalMsgs, unapprovedTypedMessages, network)
|
||||
|
||||
var txData = unconfTxList[props.index] || {}
|
||||
var txData = this.getTxData() || {}
|
||||
var txParams = txData.params || {}
|
||||
|
||||
// var isNotification = isPopupOrNotification() === 'notification'
|
||||
@ -136,7 +174,6 @@ ConfirmTxScreen.prototype.render = function () {
|
||||
]),
|
||||
*/
|
||||
|
||||
log.info(`rendering a combined ${unconfTxList.length} unconf msg & txs`)
|
||||
|
||||
return currentTxView({
|
||||
// Properties
|
||||
|
@ -116,6 +116,10 @@
|
||||
&__name {
|
||||
color: $white;
|
||||
font-size: 18px;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
max-width: 200px;
|
||||
}
|
||||
|
||||
&__balance {
|
||||
|
@ -2,10 +2,10 @@
|
||||
Buttons
|
||||
*/
|
||||
|
||||
.btn-default,
|
||||
.btn-primary,
|
||||
.btn-primary--lg,
|
||||
.btn-secondary,
|
||||
.btn-secondary--lg {
|
||||
.btn-secondary {
|
||||
height: 44px;
|
||||
background: $white;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
@ -20,10 +20,16 @@
|
||||
width: 100%;
|
||||
text-transform: uppercase;
|
||||
outline: none;
|
||||
|
||||
&--disabled,
|
||||
&[disabled] {
|
||||
cursor: auto;
|
||||
opacity: .5;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-primary,
|
||||
.btn-primary--lg {
|
||||
.btn-primary {
|
||||
color: $curious-blue;
|
||||
border: 2px solid $spindle;
|
||||
|
||||
@ -35,17 +41,23 @@
|
||||
&:hover {
|
||||
border-color: $curious-blue;
|
||||
}
|
||||
}
|
||||
|
||||
&--disabled,
|
||||
&[disabled] {
|
||||
cursor: auto;
|
||||
opacity: .5;
|
||||
pointer-events: none;
|
||||
.btn-secondary {
|
||||
color: $monzo;
|
||||
border: 2px solid lighten($monzo, 40%);
|
||||
|
||||
&:active {
|
||||
background: lighten($monzo, 55%);
|
||||
border-color: $monzo;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
border-color: $monzo;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-secondary,
|
||||
.btn-secondary--lg {
|
||||
.btn-default {
|
||||
color: $scorpion;
|
||||
border: 2px solid $dusty-gray;
|
||||
|
||||
@ -57,20 +69,9 @@
|
||||
&:hover {
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -6,6 +6,7 @@
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
flex: 0 0 auto;
|
||||
padding-top: 16px;
|
||||
}
|
||||
|
||||
@media screen and (min-width: $break-large) {
|
||||
|
@ -11,8 +11,8 @@
|
||||
background: rgba(255, 255, 255, .8);
|
||||
|
||||
@media screen and (max-width: 575px) {
|
||||
margin-top: 56px;
|
||||
height: calc(100% - 56px);
|
||||
margin-top: 66px;
|
||||
height: calc(100% - 66px);
|
||||
}
|
||||
|
||||
@media screen and (min-width: 576px) {
|
||||
|
@ -14,10 +14,17 @@ $wallet-balance-breakpoint-range: "screen and (min-width: #{$break-large}) and (
|
||||
min-width: 0;
|
||||
|
||||
&__token-balance {
|
||||
font-size: 1.5rem;
|
||||
margin-right: 4px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
&__token-balance, &__token-symbol {
|
||||
font-size: 1.5rem;
|
||||
flex: 0 0 auto;
|
||||
|
||||
@media #{$wallet-balance-breakpoint-range} {
|
||||
font-size: 95%;
|
||||
@ -66,7 +73,9 @@ $wallet-balance-breakpoint-range: "screen and (min-width: #{$break-large}) and (
|
||||
}
|
||||
|
||||
&__balance-wrapper {
|
||||
flex: 1 1 auto;
|
||||
flex: 1;
|
||||
flex-flow: row wrap;
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
}
|
||||
}
|
||||
|
@ -42,6 +42,7 @@ function reduceApp (state, action) {
|
||||
open: false,
|
||||
modalState: {
|
||||
name: null,
|
||||
props: {},
|
||||
},
|
||||
previousModalState: {
|
||||
name: null,
|
||||
@ -88,13 +89,17 @@ function reduceApp (state, action) {
|
||||
|
||||
// modal methods:
|
||||
case actions.MODAL_OPEN:
|
||||
const { name, ...modalProps } = action.payload
|
||||
|
||||
return extend(appState, {
|
||||
modal: Object.assign(
|
||||
state.appState.modal,
|
||||
{ open: true },
|
||||
{ modalState: action.payload },
|
||||
{ previousModalState: appState.modal.modalState},
|
||||
),
|
||||
modal: {
|
||||
open: true,
|
||||
modalState: {
|
||||
name: name,
|
||||
props: { ...modalProps },
|
||||
},
|
||||
previousModalState: { ...appState.modal.modalState },
|
||||
},
|
||||
})
|
||||
|
||||
case actions.MODAL_CLOSE:
|
||||
@ -102,7 +107,7 @@ function reduceApp (state, action) {
|
||||
modal: Object.assign(
|
||||
state.appState.modal,
|
||||
{ open: false },
|
||||
{ modalState: { name: null } },
|
||||
{ modalState: { name: null, props: {} } },
|
||||
{ previousModalState: appState.modal.modalState},
|
||||
),
|
||||
})
|
||||
|
@ -499,7 +499,7 @@ SendTransactionScreen.prototype.renderFooter = function () {
|
||||
|
||||
return h('div.page-container__footer', [
|
||||
h(Button, {
|
||||
type: 'secondary',
|
||||
type: 'default',
|
||||
large: true,
|
||||
className: 'page-container__footer-button',
|
||||
onClick: () => {
|
||||
|
Loading…
Reference in New Issue
Block a user