Merge pull request #337 from poanetwork/vb-eip-1193-final

Refactoring before EIP-1193
This commit is contained in:
Victor Baranov 2020-03-27 14:30:39 +03:00 committed by GitHub
commit 9ed5e6d6a3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
95 changed files with 11018 additions and 2129 deletions

View File

@ -92,7 +92,7 @@ workflows:
jobs:
prep-deps-npm:
docker:
- image: circleci/node:12.15.0-browsers
- image: circleci/node:10.19.0-browsers
steps:
- checkout
- restore_cache:
@ -108,7 +108,7 @@ jobs:
prep-build:
docker:
- image: circleci/node:12.15.0-browsers
- image: circleci/node:10.19.0-browsers
steps:
- checkout
- restore_cache:
@ -127,7 +127,7 @@ jobs:
prep-docs:
docker:
- image: circleci/node:12.15.0-browsers
- image: circleci/node:10.19.0-browsers
steps:
- checkout
- restore_cache:
@ -142,7 +142,7 @@ jobs:
prep-scss:
docker:
- image: circleci/node:12.15.0-browsers
- image: circleci/node:10.19.0-browsers
steps:
- checkout
- restore_cache:
@ -161,7 +161,7 @@ jobs:
test-lint:
docker:
- image: circleci/node:12.15.0-browsers
- image: circleci/node:10.19.0-browsers
steps:
- checkout
- restore_cache:
@ -172,7 +172,7 @@ jobs:
# test-deps:
# docker:
# - image: circleci/node:12.15.0-browsers
# - image: circleci/node:10.19.0-browsers
# steps:
# - checkout
# - restore_cache:
@ -183,7 +183,7 @@ jobs:
test-e2e-chrome:
docker:
- image: circleci/node:12.15.0-browsers
- image: circleci/node:10.19.0-browsers
steps:
- checkout
- restore_cache:
@ -208,7 +208,7 @@ jobs:
test-e2e-firefox:
docker:
- image: circleci/node:12.15.0-browsers
- image: circleci/node:10.19.0-browsers
steps:
- checkout
- restore_cache:
@ -229,7 +229,7 @@ jobs:
test-e2e-beta-chrome:
docker:
- image: circleci/node:12.15.0-browsers
- image: circleci/node:10.19.0-browsers
steps:
- checkout
- restore_cache:
@ -245,7 +245,7 @@ jobs:
test-e2e-beta-firefox:
docker:
- image: circleci/node:12.15.0-browsers
- image: circleci/node:10.19.0-browsers
steps:
- checkout
- restore_cache:
@ -266,7 +266,7 @@ jobs:
job-screens:
docker:
- image: circleci/node:12.15.0-browsers
- image: circleci/node:10.19.0-browsers
steps:
- checkout
- restore_cache:
@ -283,7 +283,7 @@ jobs:
job-publish-prerelease:
docker:
- image: circleci/node:12.15.0-browsers
- image: circleci/node:10.19.0-browsers
steps:
- checkout
- restore_cache:
@ -310,7 +310,7 @@ jobs:
job-publish-release:
docker:
- image: circleci/node:12.15.0-browsers
- image: circleci/node:10.19.0-browsers
steps:
- checkout
- restore_cache:
@ -323,7 +323,7 @@ jobs:
job-publish-postrelease:
docker:
- image: circleci/node:12.15.0-browsers
- image: circleci/node:10.19.0-browsers
steps:
- checkout
- restore_cache:
@ -340,7 +340,7 @@ jobs:
test-unit:
docker:
- image: circleci/node:12.15.0-browsers
- image: circleci/node:10.19.0-browsers
steps:
- checkout
- restore_cache:
@ -353,7 +353,7 @@ jobs:
environment:
browsers: '["Firefox"]'
docker:
- image: circleci/node:12.15.0-browsers
- image: circleci/node:10.19.0-browsers
steps:
- checkout
- restore_cache:
@ -371,7 +371,7 @@ jobs:
environment:
browsers: '["Chrome"]'
docker:
- image: circleci/node:12.15.0-browsers
- image: circleci/node:10.19.0-browsers
steps:
- checkout
- restore_cache:
@ -384,7 +384,7 @@ jobs:
environment:
browsers: '["Firefox"]'
docker:
- image: circleci/node:12.15.0-browsers
- image: circleci/node:10.19.0-browsers
steps:
- checkout
- restore_cache:
@ -402,7 +402,7 @@ jobs:
environment:
browsers: '["Chrome"]'
docker:
- image: circleci/node:12.15.0-browsers
- image: circleci/node:10.19.0-browsers
steps:
- checkout
- restore_cache:
@ -413,7 +413,7 @@ jobs:
all-tests-pass:
docker:
- image: circleci/node:12.15.0-browsers
- image: circleci/node:10.19.0-browsers
steps:
- run:
name: All Tests Passed

View File

@ -7,7 +7,7 @@
## Building locally
- Install [Node.js](https://nodejs.org/en/) version 12.x.x and npm version 6.14.2
- Install [Node.js](https://nodejs.org/en/) version 10.x.x and npm version 6.13.4
- If you are using [nvm](https://github.com/creationix/nvm#installation) (recommended) running `nvm use` will automatically choose the right node version for you.
- Select npm 6.9.0: ```npm install -g npm@6.9.0```
- Install dependencies: ```npm install```

View File

@ -5,7 +5,6 @@
// this needs to run before anything else
require('./lib/setupFetchDebugging')()
const urlUtil = require('url')
const endOfStream = require('end-of-stream')
const pump = require('pump')
const debounce = require('debounce-stream')
@ -29,7 +28,6 @@ const setupMetamaskMeshMetrics = require('./lib/setupMetamaskMeshMetrics')
const EdgeEncryptor = require('./edge-encryptor')
const getFirstPreferredLangCode = require('./lib/get-first-preferred-lang-code')
const getObjStructure = require('./lib/getObjStructure')
const ipfsContent = require('./lib/ipfsContent.js')
const {
ENVIRONMENT_TYPE_POPUP,
@ -59,7 +57,6 @@ const isIE = !!document.documentMode
// Edge 20+
const isEdge = !isIE && !!window.StyleMedia
let ipfsHandle
let popupIsOpen = false
let notificationIsOpen = false
const openMetamaskTabsIDs = {}
@ -166,7 +163,6 @@ async function initialize () {
const initLangCode = await getFirstPreferredLangCode()
await setupController(initState, initLangCode)
log.debug('Nifty Wallet initialization complete.')
ipfsHandle = ipfsContent(initState.NetworkController.provider)
}
//
@ -271,11 +267,6 @@ function setupController (initState, initLangCode) {
})
global.metamaskController = controller
controller.networkController.on('networkDidChange', () => {
ipfsHandle && ipfsHandle.remove()
ipfsHandle = ipfsContent(controller.networkController.providerStore.getState())
})
// report failed transactions to Sentry
controller.txController.on(`tx:status-update`, (txId, status) => {
if (status !== 'failed') return
@ -295,7 +286,7 @@ function setupController (initState, initLangCode) {
createStreamSink(persistData),
(error) => {
log.error('Nifty Wallet - Persistence pipeline failed', error)
}
},
)
/**
@ -397,9 +388,8 @@ function setupController (initState, initLangCode) {
// 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)
controller.setupUntrustedCommunication(portStream, remotePort.sender)
}
//
@ -410,6 +400,8 @@ function setupController (initState, initLangCode) {
controller.txController.on('update:badge', updateBadge)
controller.messageManager.on('updateBadge', updateBadge)
controller.personalMessageManager.on('updateBadge', updateBadge)
controller.decryptMessageManager.on('updateBadge', updateBadge)
controller.encryptionPublicKeyManager.on('updateBadge', updateBadge)
controller.typedMessageManager.on('updateBadge', updateBadge)
/**
@ -417,12 +409,15 @@ function setupController (initState, initLangCode) {
* The number reflects the current number of pending transactions or message signatures needing user approval.
*/
function updateBadge () {
var label = ''
var unapprovedTxCount = controller.txController.getUnapprovedTxCount()
var unapprovedMsgCount = controller.messageManager.unapprovedMsgCount
var unapprovedPersonalMsgs = controller.personalMessageManager.unapprovedPersonalMsgCount
var unapprovedTypedMsgs = controller.typedMessageManager.unapprovedTypedMessagesCount
var count = unapprovedTxCount + unapprovedMsgCount + unapprovedPersonalMsgs + unapprovedTypedMsgs
let label = ''
const unapprovedTxCount = controller.txController.getUnapprovedTxCount()
const unapprovedMsgCount = controller.messageManager.unapprovedMsgCount
const unapprovedPersonalMsgCount = controller.personalMessageManager.unapprovedPersonalMsgCount
const unapprovedDecryptMsgCount = controller.decryptMessageManager.unapprovedDecryptMsgCount
const unapprovedEncryptionPublicKeyMsgCount = controller.encryptionPublicKeyManager.unapprovedEncryptionPublicKeyMsgCount
const unapprovedTypedMessagesCount = controller.typedMessageManager.unapprovedTypedMessagesCount
const count = unapprovedTxCount + unapprovedMsgCount + unapprovedPersonalMsgCount + unapprovedDecryptMsgCount + unapprovedEncryptionPublicKeyMsgCount +
unapprovedTypedMessagesCount
if (count) {
label = String(count)
}
@ -463,12 +458,12 @@ function showWatchAssetUi () {
triggerUi()
return new Promise(
(resolve) => {
var interval = setInterval(() => {
const interval = setInterval(() => {
if (!notificationIsOpen) {
clearInterval(interval)
resolve()
}
}, 1000)
}
},
)
}

View File

@ -59,7 +59,7 @@ function setupStreams () {
pageStream,
pluginStream,
pageStream,
(err) => logStreamDisconnectWarning('Nifty Wallet Contentscript Forwarding', err)
(err) => logStreamDisconnectWarning('Nifty Wallet Contentscript Forwarding', err),
)
// setup local multistream channels
@ -70,13 +70,13 @@ function setupStreams () {
mux,
pageStream,
mux,
(err) => logStreamDisconnectWarning('Nifty Wallet Inpage', err)
(err) => logStreamDisconnectWarning('Nifty Wallet Inpage', err),
)
pump(
mux,
pluginStream,
mux,
(err) => logStreamDisconnectWarning('Nifty Wallet Background', err)
(err) => logStreamDisconnectWarning('Nifty Wallet Background', err),
)
// connect ping stream
@ -85,7 +85,7 @@ function setupStreams () {
mux,
pongStream,
mux,
(err) => logStreamDisconnectWarning('Nifty Wallet PingPongStream', err)
(err) => logStreamDisconnectWarning('Nifty Wallet PingPongStream', err),
)
// connect phishing warning stream

View File

@ -32,6 +32,7 @@ class PreferencesController {
tokens: [],
suggestedTokens: {},
useBlockie: false,
usePhishDetect: true,
featureFlags: {},
currentLocale: opts.initLangCode,
identities: {},
@ -77,6 +78,16 @@ class PreferencesController {
this.store.updateState({ useBlockie: val })
}
/**
* Setter for the `usePhishDetect` property
*
* @param {boolean} val - Whether or not the user prefers phishing domain protection
*
*/
setUsePhishDetect (val) {
this.store.updateState({ usePhishDetect: val })
}
getSuggestedTokens () {
return this.store.getState().suggestedTokens
}

View File

@ -1,17 +0,0 @@
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

View File

@ -1,10 +1,45 @@
/*global Web3*/
// need to make sure we aren't affected by overlapping namespaces
// and that we dont affect the app with our namespace
// mostly a fix for web3's BigNumber if AMD's "define" is defined...
let __define
/**
* Caches reference to global define object and deletes it to
* avoid conflicts with other global define objects, such as
* AMD's define function
*/
const cleanContextForImports = () => {
__define = global.define
try {
global.define = undefined
} catch (_) {
console.warn('Nifty Wallet - global.define could not be deleted.')
}
}
/**
* Restores global define object from cached reference
*/
const restoreContextAfterImports = () => {
try {
global.define = __define
} catch (_) {
console.warn('Nifty Wallet - global.define could not be overwritten.')
}
}
cleanContextForImports()
require('web3/dist/web3.min.js')
const log = require('loglevel')
const LocalMessageDuplexStream = require('post-message-stream')
const setupDappAutoReload = require('./lib/auto-reload.js')
const MetamaskInpageProvider = require('nifty-wallet-inpage-provider')
import log from 'loglevel'
import LocalMessageDuplexStream from 'post-message-stream'
import MetamaskInpageProvider from 'nifty-wallet-inpage-provider'
// TODO:deprecate:Q1-2020
import 'web3/dist/web3.min.js'
import setupDappAutoReload from './lib/auto-reload.js'
restoreContextAfterImports()
@ -15,13 +50,14 @@ log.setDefaultLevel(process.env.METAMASK_DEBUG ? 'debug' : 'warn')
//
// setup background connection
var metamaskStream = new LocalMessageDuplexStream({
const metamaskStream = new LocalMessageDuplexStream({
name: 'nifty-inpage',
target: 'nifty-contentscript',
})
// compose the inpage provider
var inpageProvider = new MetamaskInpageProvider(metamaskStream)
const inpageProvider = new MetamaskInpageProvider(metamaskStream)
// set a high max listener count to avoid unnecesary warnings
inpageProvider.setMaxListeners(100)
@ -43,79 +79,35 @@ inpageProvider.enable = function (options = {}) {
}
// Work around for web3@1.0 deleting the bound `sendAsync` but not the unbound
// `sendAsync` method on the prototype, causing `this` reference issues with drizzle
// `sendAsync` method on the prototype, causing `this` reference issues
const proxiedInpageProvider = new Proxy(inpageProvider, {
// straight up lie that we deleted the property so that it doesnt
// throw an error in strict mode
deleteProperty: () => true,
})
window.ethereum = proxiedInpageProvider
//
// TODO:deprecate:Q1-2020
//
// setup web3
//
var web3 = new Web3(inpageProvider)
const web3 = new Web3(proxiedInpageProvider)
web3.setProvider = function () {
log.debug('Nifty Wallet - overrode web3.setProvider')
}
log.debug('Nifty Wallet - injected web3')
proxiedInpageProvider._web3Ref = web3.eth
setupDappAutoReload(web3, inpageProvider.publicConfigStore)
// export global web3, with usage-detection and deprecation warning
/* TODO: Uncomment this area once auto-reload.js has been deprecated:
let hasBeenWarned = false
global.web3 = new Proxy(web3, {
get: (_web3, key) => {
// show warning once on web3 access
if (!hasBeenWarned && key !== 'currentProvider') {
console.warn('Nifty Wallet: web3 will be deprecated in the near future in favor of the ethereumProvider \nhttps://github.com/MetaMask/faq/blob/master/detecting_metamask.md#web3-deprecation')
hasBeenWarned = true
}
// return value normally
return _web3[key]
},
set: (_web3, key, value) => {
// set value normally
_web3[key] = value
},
})
*/
// set web3 defaultAccount
inpageProvider.publicConfigStore.subscribe(function (state) {
web3.eth.defaultAccount = state.selectedAddress
})
//
// end deprecate:Q1-2020
//
// need to make sure we aren't affected by overlapping namespaces
// and that we dont affect the app with our namespace
// mostly a fix for web3's BigNumber if AMD's "define" is defined...
var __define
/**
* Caches reference to global define object and deletes it to
* avoid conflicts with other global define objects, such as
* AMD's define function
*/
function cleanContextForImports () {
__define = global.define
try {
global.define = undefined
} catch (_) {
console.warn('Nifty Wallet - global.define could not be deleted.')
}
}
/**
* Restores global define object from cached reference
*/
function restoreContextAfterImports () {
try {
global.define = __define
} catch (_) {
console.warn('Nifty Wallet - global.define could not be overwritten.')
}
}
window.ethereum = proxiedInpageProvider

View File

@ -40,7 +40,9 @@ class ComposableObservableStore extends ObservableStore {
getFlatState () {
let flatState = {}
for (const key in this.config) {
flatState = { ...flatState, ...this.config[key].getState() }
const controller = this.config[key]
const state = controller.getState ? controller.getState() : controller.state
flatState = { ...flatState, ...state }
}
return flatState
}

View File

@ -1,3 +1,6 @@
// TODO:deprecate:Q1-2020
module.exports = setupDappAutoReload
function setupDappAutoReload (web3, observable) {
@ -5,11 +8,17 @@ function setupDappAutoReload (web3, observable) {
let reloadInProgress = false
let lastTimeUsed
let lastSeenNetwork
let hasBeenWarned = false
global.web3 = new Proxy(web3, {
get: (_web3, key) => {
// get the time of use
lastTimeUsed = Date.now()
// show warning once on web3 access
if (!hasBeenWarned && key !== 'currentProvider') {
console.warn(`MetaMask: In Q1 2020, MetaMask will no longer inject web3. For more information, see: https://medium.com/metamask/no-longer-injecting-web3-js-4a899ad6e59e`)
hasBeenWarned = true
}
// return value normally
return _web3[key]
},
@ -20,8 +29,16 @@ function setupDappAutoReload (web3, observable) {
})
observable.subscribe(function (state) {
// if the auto refresh on network change is false do not
// do anything
if (!window.ethereum.autoRefreshOnNetworkChange) {
return
}
// if reload in progress, no need to check reload logic
if (reloadInProgress) return
if (reloadInProgress) {
return
}
const currentNetwork = state.networkVersion
@ -32,10 +49,14 @@ function setupDappAutoReload (web3, observable) {
}
// skip reload logic if web3 not used
if (!lastTimeUsed) return
if (!lastTimeUsed) {
return
}
// if network did not change, exit
if (currentNetwork === lastSeenNetwork) return
if (currentNetwork === lastSeenNetwork) {
return
}
// initiate page reload
reloadInProgress = true

View File

@ -0,0 +1,14 @@
module.exports = createTabIdMiddleware
/**
* Returns a middleware that appends the DApp TabId to the request
* @param {{ tabId: number }} opts - The middleware options
* @returns {Function}
*/
function createTabIdMiddleware (opts) {
return function tabIdMiddleware (/** @type {any} */ req, /** @type {any} */ _, /** @type {Function} */ next) {
req.tabId = opts.tabId
next()
}
}

View File

@ -0,0 +1,311 @@
import EventEmitter from 'events'
import ObservableStore from 'obs-store'
import ethUtil from 'ethereumjs-util'
import { ethErrors } from 'eth-json-rpc-errors'
import createId from './random-id'
const hexRe = /^[0-9A-Fa-f]+$/g
import log from 'loglevel'
/**
* Represents, and contains data about, an 'eth_decrypt' type decryption request. These are created when a
* decryption for an eth_decrypt call is requested.
*
* @typedef {Object} DecryptMessage
* @property {number} id An id to track and identify the message object
* @property {Object} msgParams The parameters to pass to the decryptMessage method once the decryption request is
* approved.
* @property {Object} msgParams.metamaskId Added to msgParams for tracking and identification within MetaMask.
* @property {string} msgParams.data A hex string conversion of the raw buffer data of the decryption request
* @property {number} time The epoch time at which the this message was created
* @property {string} status Indicates whether the decryption request is 'unapproved', 'approved', 'decrypted' or 'rejected'
* @property {string} type The json-prc decryption method for which a decryption request has been made. A 'Message' will
* always have a 'eth_decrypt' type.
*
*/
export default class DecryptMessageManager extends EventEmitter {
/**
* Controller in charge of managing - storing, adding, removing, updating - DecryptMessage.
*
* @typedef {Object} DecryptMessageManager
* @property {Object} memStore The observable store where DecryptMessage are saved with persistance.
* @property {Object} memStore.unapprovedDecryptMsgs A collection of all DecryptMessages in the 'unapproved' state
* @property {number} memStore.unapprovedDecryptMsgCount The count of all DecryptMessages in this.memStore.unapprobedMsgs
* @property {array} messages Holds all messages that have been created by this DecryptMessageManager
*
*/
constructor () {
super()
this.memStore = new ObservableStore({
unapprovedDecryptMsgs: {},
unapprovedDecryptMsgCount: 0,
})
this.messages = []
}
/**
* A getter for the number of 'unapproved' DecryptMessages in this.messages
*
* @returns {number} The number of 'unapproved' DecryptMessages in this.messages
*
*/
get unapprovedDecryptMsgCount () {
return Object.keys(this.getUnapprovedMsgs()).length
}
/**
* A getter for the 'unapproved' DecryptMessages in this.messages
*
* @returns {Object} An index of DecryptMessage ids to DecryptMessages, for all 'unapproved' DecryptMessages in
* this.messages
*
*/
getUnapprovedMsgs () {
return this.messages.filter((msg) => msg.status === 'unapproved')
.reduce((result, msg) => {
result[msg.id] = msg; return result
}, {})
}
/**
* Creates a new DecryptMessage with an 'unapproved' status using the passed msgParams. this.addMsg is called to add
* the new DecryptMessage to this.messages, and to save the unapproved DecryptMessages from that list to
* this.memStore.
*
* @param {Object} msgParams The params for the eth_decrypt call to be made after the message is approved.
* @param {Object} req (optional) The original request object possibly containing the origin
* @returns {Promise<Buffer>} The raw decrypted message contents
*
*/
addUnapprovedMessageAsync (msgParams, req) {
return new Promise((resolve, reject) => {
if (!msgParams.from) {
reject(new Error('MetaMask Message for Decryption: from field is required.'))
}
const msgId = this.addUnapprovedMessage(msgParams, req)
this.once(`${msgId}:finished`, (data) => {
switch (data.status) {
case 'decrypted':
return resolve(data.rawData)
case 'rejected':
return reject(ethErrors.provider.userRejectedRequest('MetaMask Message for Decryption: User denied message decryption.'))
case 'errored':
return reject(new Error('This message cannot be decrypted'))
default:
return reject(new Error(`MetaMask Message for Decryption: Unknown problem: ${JSON.stringify(msgParams)}`))
}
})
})
}
/**
* Creates a new DecryptMessage with an 'unapproved' status using the passed msgParams. this.addMsg is called to add
* the new DecryptMessage to this.messages, and to save the unapproved DecryptMessages from that list to
* this.memStore.
*
* @param {Object} msgParams The params for the eth_decryptMsg call to be made after the message is approved.
* @param {Object} req (optional) The original request object possibly containing the origin
* @returns {number} The id of the newly created DecryptMessage.
*
*/
addUnapprovedMessage (msgParams, req) {
log.debug(`DecryptMessageManager addUnapprovedMessage: ${JSON.stringify(msgParams)}`)
// add origin from request
if (req) {
msgParams.origin = req.origin
}
msgParams.data = this.normalizeMsgData(msgParams.data)
// create txData obj with parameters and meta data
const time = (new Date()).getTime()
const msgId = createId()
const msgData = {
id: msgId,
msgParams: msgParams,
time: time,
status: 'unapproved',
type: 'eth_decrypt',
}
this.addMsg(msgData)
// signal update
this.emit('update')
return msgId
}
/**
* Adds a passed DecryptMessage to this.messages, and calls this._saveMsgList() to save the unapproved DecryptMessages from that
* list to this.memStore.
*
* @param {Message} msg The DecryptMessage to add to this.messages
*
*/
addMsg (msg) {
this.messages.push(msg)
this._saveMsgList()
}
/**
* Returns a specified DecryptMessage.
*
* @param {number} msgId The id of the DecryptMessage to get
* @returns {DecryptMessage|undefined} The DecryptMessage with the id that matches the passed msgId, or undefined
* if no DecryptMessage has that id.
*
*/
getMsg (msgId) {
return this.messages.find((msg) => msg.id === msgId)
}
/**
* Approves a DecryptMessage. Sets the message status via a call to this.setMsgStatusApproved, and returns a promise
* with the message params modified for proper decryption.
*
* @param {Object} msgParams The msgParams to be used when eth_decryptMsg is called, plus data added by MetaMask.
* @param {Object} msgParams.metamaskId Added to msgParams for tracking and identification within MetaMask.
* @returns {Promise<object>} Promises the msgParams object with metamaskId removed.
*
*/
approveMessage (msgParams) {
this.setMsgStatusApproved(msgParams.metamaskId)
return this.prepMsgForDecryption(msgParams)
}
/**
* Sets a DecryptMessage status to 'approved' via a call to this._setMsgStatus.
*
* @param {number} msgId The id of the DecryptMessage to approve.
*
*/
setMsgStatusApproved (msgId) {
this._setMsgStatus(msgId, 'approved')
}
/**
* Sets a DecryptMessage status to 'decrypted' via a call to this._setMsgStatus and updates that DecryptMessage in
* this.messages by adding the raw decryption data of the decryption request to the DecryptMessage
*
* @param {number} msgId The id of the DecryptMessage to decrypt.
* @param {buffer} rawData The raw data of the message request
*
*/
setMsgStatusDecrypted (msgId, rawData) {
const msg = this.getMsg(msgId)
msg.rawData = rawData
this._updateMsg(msg)
this._setMsgStatus(msgId, 'decrypted')
}
/**
* Removes the metamaskId property from passed msgParams and returns a promise which resolves the updated msgParams
*
* @param {Object} msgParams The msgParams to modify
* @returns {Promise<object>} Promises the msgParams with the metamaskId property removed
*
*/
prepMsgForDecryption (msgParams) {
delete msgParams.metamaskId
return Promise.resolve(msgParams)
}
/**
* Sets a DecryptMessage status to 'rejected' via a call to this._setMsgStatus.
*
* @param {number} msgId The id of the DecryptMessage to reject.
*
*/
rejectMsg (msgId) {
this._setMsgStatus(msgId, 'rejected')
}
/**
* Sets a TypedMessage status to 'errored' via a call to this._setMsgStatus.
*
* @param {number} msgId The id of the TypedMessage to error
*
*/
errorMessage (msgId, error) {
const msg = this.getMsg(msgId)
msg.error = error
this._updateMsg(msg)
this._setMsgStatus(msgId, 'errored')
}
/**
* Updates the status of a DecryptMessage in this.messages via a call to this._updateMsg
*
* @private
* @param {number} msgId The id of the DecryptMessage to update.
* @param {string} status The new status of the DecryptMessage.
* @throws A 'DecryptMessageManager - DecryptMessage not found for id: "${msgId}".' if there is no DecryptMessage
* in this.messages with an id equal to the passed msgId
* @fires An event with a name equal to `${msgId}:${status}`. The DecryptMessage is also fired.
* @fires If status is 'rejected' or 'decrypted', an event with a name equal to `${msgId}:finished` is fired along
* with the DecryptMessage
*
*/
_setMsgStatus (msgId, status) {
const msg = this.getMsg(msgId)
if (!msg) {
throw new Error('DecryptMessageManager - Message not found for id: "${msgId}".')
}
msg.status = status
this._updateMsg(msg)
this.emit(`${msgId}:${status}`, msg)
if (status === 'rejected' || status === 'decrypted' || status === 'errored') {
this.emit(`${msgId}:finished`, msg)
}
}
/**
* Sets a DecryptMessage in this.messages to the passed DecryptMessage if the ids are equal. Then saves the
* unapprovedDecryptMsgs index to storage via this._saveMsgList
*
* @private
* @param {msg} DecryptMessage A DecryptMessage that will replace an existing DecryptMessage (with the same
* id) in this.messages
*
*/
_updateMsg (msg) {
const index = this.messages.findIndex((message) => message.id === msg.id)
if (index !== -1) {
this.messages[index] = msg
}
this._saveMsgList()
}
/**
* Saves the unapproved DecryptMessages, and their count, to this.memStore
*
* @private
* @fires 'updateBadge'
*
*/
_saveMsgList () {
const unapprovedDecryptMsgs = this.getUnapprovedMsgs()
const unapprovedDecryptMsgCount = Object.keys(unapprovedDecryptMsgs).length
this.memStore.updateState({ unapprovedDecryptMsgs, unapprovedDecryptMsgCount })
this.emit('updateBadge')
}
/**
* A helper function that converts raw buffer data to a hex, or just returns the data if it is already formatted as a hex.
*
* @param {any} data The buffer data to convert to a hex
* @returns {string} A hex string conversion of the buffer data
*
*/
normalizeMsgData (data) {
try {
const stripped = ethUtil.stripHexPrefix(data)
if (stripped.match(hexRe)) {
return ethUtil.addHexPrefix(stripped)
}
} catch (e) {
log.debug(`Message was not hex encoded, interpreting as utf8.`)
}
return ethUtil.bufferToHex(Buffer.from(data, 'utf8'))
}
}

View File

@ -0,0 +1,286 @@
import EventEmitter from 'events'
import ObservableStore from 'obs-store'
import { ethErrors } from 'eth-json-rpc-errors'
import createId from './random-id'
import log from 'loglevel'
/**
* Represents, and contains data about, an 'eth_getEncryptionPublicKey' type request. These are created when
* an eth_getEncryptionPublicKey call is requested.
*
* @typedef {Object} EncryptionPublicKey
* @property {number} id An id to track and identify the message object
* @property {Object} msgParams The parameters to pass to the encryptionPublicKey method once the request is
* approved.
* @property {Object} msgParams.metamaskId Added to msgParams for tracking and identification within MetaMask.
* @property {string} msgParams.data A hex string conversion of the raw buffer data of the request
* @property {number} time The epoch time at which the this message was created
* @property {string} status Indicates whether the request is 'unapproved', 'approved', 'received' or 'rejected'
* @property {string} type The json-prc method for which a request has been made. A 'Message' will
* always have a 'eth_getEncryptionPublicKey' type.
*
*/
export default class EncryptionPublicKeyManager extends EventEmitter {
/**
* Controller in charge of managing - storing, adding, removing, updating - EncryptionPublicKey.
*
* @typedef {Object} EncryptionPublicKeyManager
* @property {Object} memStore The observable store where EncryptionPublicKey are saved with persistance.
* @property {Object} memStore.unapprovedEncryptionPublicKeyMsgs A collection of all EncryptionPublicKeys in the 'unapproved' state
* @property {number} memStore.unapprovedEncryptionPublicKeyMsgCount The count of all EncryptionPublicKeys in this.memStore.unapprobedMsgs
* @property {array} messages Holds all messages that have been created by this EncryptionPublicKeyManager
*
*/
constructor () {
super()
this.memStore = new ObservableStore({
unapprovedEncryptionPublicKeyMsgs: {},
unapprovedEncryptionPublicKeyMsgCount: 0,
})
this.messages = []
}
/**
* A getter for the number of 'unapproved' EncryptionPublicKeys in this.messages
*
* @returns {number} The number of 'unapproved' EncryptionPublicKeys in this.messages
*
*/
get unapprovedEncryptionPublicKeyMsgCount () {
return Object.keys(this.getUnapprovedMsgs()).length
}
/**
* A getter for the 'unapproved' EncryptionPublicKeys in this.messages
*
* @returns {Object} An index of EncryptionPublicKey ids to EncryptionPublicKeys, for all 'unapproved' EncryptionPublicKeys in
* this.messages
*
*/
getUnapprovedMsgs () {
return this.messages.filter((msg) => msg.status === 'unapproved')
.reduce((result, msg) => {
result[msg.id] = msg; return result
}, {})
}
/**
* Creates a new EncryptionPublicKey with an 'unapproved' status using the passed msgParams. this.addMsg is called to add
* the new EncryptionPublicKey to this.messages, and to save the unapproved EncryptionPublicKeys from that list to
* this.memStore.
*
* @param {Object} address The param for the eth_getEncryptionPublicKey call to be made after the message is approved.
* @param {Object} req (optional) The original request object possibly containing the origin
* @returns {Promise<Buffer>} The raw public key contents
*
*/
addUnapprovedMessageAsync (address, req) {
return new Promise((resolve, reject) => {
if (!address) {
reject(new Error('MetaMask Message for EncryptionPublicKey: address field is required.'))
}
const msgId = this.addUnapprovedMessage(address, req)
this.once(`${msgId}:finished`, (data) => {
switch (data.status) {
case 'received':
return resolve(data.rawData)
case 'rejected':
return reject(ethErrors.provider.userRejectedRequest('MetaMask Message for EncryptionPublicKey: User denied message EncryptionPublicKey.'))
default:
return reject(new Error(`MetaMask Message for EncryptionPublicKey: Unknown problem: ${JSON.stringify(address)}`))
}
})
})
}
/**
* Creates a new EncryptionPublicKey with an 'unapproved' status using the passed msgParams. this.addMsg is called to add
* the new EncryptionPublicKey to this.messages, and to save the unapproved EncryptionPublicKeys from that list to
* this.memStore.
*
* @param {Object} address The param for the eth_getEncryptionPublicKey call to be made after the message is approved.
* @param {Object} _req (optional) The original request object possibly containing the origin
* @returns {number} The id of the newly created EncryptionPublicKey.
*
*/
addUnapprovedMessage (address, req) {
log.debug(`EncryptionPublicKeyManager addUnapprovedMessage: address`)
// create txData obj with parameters and meta data
const time = (new Date()).getTime()
const msgId = createId()
const msgData = {
id: msgId,
msgParams: address,
time: time,
status: 'unapproved',
type: 'eth_getEncryptionPublicKey',
}
if (req) {
msgData.origin = req.origin
}
this.addMsg(msgData)
// signal update
this.emit('update')
return msgId
}
/**
* Adds a passed EncryptionPublicKey to this.messages, and calls this._saveMsgList() to save the unapproved EncryptionPublicKeys from that
* list to this.memStore.
*
* @param {Message} msg The EncryptionPublicKey to add to this.messages
*
*/
addMsg (msg) {
this.messages.push(msg)
this._saveMsgList()
}
/**
* Returns a specified EncryptionPublicKey.
*
* @param {number} msgId The id of the EncryptionPublicKey to get
* @returns {EncryptionPublicKey|undefined} The EncryptionPublicKey with the id that matches the passed msgId, or undefined
* if no EncryptionPublicKey has that id.
*
*/
getMsg (msgId) {
return this.messages.find((msg) => msg.id === msgId)
}
/**
* Approves a EncryptionPublicKey. Sets the message status via a call to this.setMsgStatusApproved, and returns a promise
* with any the message params modified for proper providing.
*
* @param {Object} msgParams The msgParams to be used when eth_getEncryptionPublicKey is called, plus data added by MetaMask.
* @param {Object} msgParams.metamaskId Added to msgParams for tracking and identification within MetaMask.
* @returns {Promise<object>} Promises the msgParams object with metamaskId removed.
*
*/
approveMessage (msgParams) {
this.setMsgStatusApproved(msgParams.metamaskId)
return this.prepMsgForEncryptionPublicKey(msgParams)
}
/**
* Sets a EncryptionPublicKey status to 'approved' via a call to this._setMsgStatus.
*
* @param {number} msgId The id of the EncryptionPublicKey to approve.
*
*/
setMsgStatusApproved (msgId) {
this._setMsgStatus(msgId, 'approved')
}
/**
* Sets a EncryptionPublicKey status to 'received' via a call to this._setMsgStatus and updates that EncryptionPublicKey in
* this.messages by adding the raw data of request to the EncryptionPublicKey
*
* @param {number} msgId The id of the EncryptionPublicKey.
* @param {buffer} rawData The raw data of the message request
*
*/
setMsgStatusReceived (msgId, rawData) {
const msg = this.getMsg(msgId)
msg.rawData = rawData
this._updateMsg(msg)
this._setMsgStatus(msgId, 'received')
}
/**
* Removes the metamaskId property from passed msgParams and returns a promise which resolves the updated msgParams
*
* @param {Object} msgParams The msgParams to modify
* @returns {Promise<object>} Promises the msgParams with the metamaskId property removed
*
*/
prepMsgForEncryptionPublicKey (msgParams) {
delete msgParams.metamaskId
return Promise.resolve(msgParams)
}
/**
* Sets a EncryptionPublicKey status to 'rejected' via a call to this._setMsgStatus.
*
* @param {number} msgId The id of the EncryptionPublicKey to reject.
*
*/
rejectMsg (msgId) {
this._setMsgStatus(msgId, 'rejected')
}
/**
* Sets a TypedMessage status to 'errored' via a call to this._setMsgStatus.
*
* @param {number} msgId The id of the TypedMessage to error
*
*/
errorMessage (msgId, error) {
const msg = this.getMsg(msgId)
msg.error = error
this._updateMsg(msg)
this._setMsgStatus(msgId, 'errored')
}
/**
* Updates the status of a EncryptionPublicKey in this.messages via a call to this._updateMsg
*
* @private
* @param {number} msgId The id of the EncryptionPublicKey to update.
* @param {string} status The new status of the EncryptionPublicKey.
* @throws A 'EncryptionPublicKeyManager - EncryptionPublicKey not found for id: "${msgId}".' if there is no EncryptionPublicKey
* in this.messages with an id equal to the passed msgId
* @fires An event with a name equal to `${msgId}:${status}`. The EncryptionPublicKey is also fired.
* @fires If status is 'rejected' or 'received', an event with a name equal to `${msgId}:finished` is fired along
* with the EncryptionPublicKey
*
*/
_setMsgStatus (msgId, status) {
const msg = this.getMsg(msgId)
if (!msg) {
throw new Error('EncryptionPublicKeyManager - Message not found for id: "${msgId}".')
}
msg.status = status
this._updateMsg(msg)
this.emit(`${msgId}:${status}`, msg)
if (status === 'rejected' || status === 'received') {
this.emit(`${msgId}:finished`, msg)
}
}
/**
* Sets a EncryptionPublicKey in this.messages to the passed EncryptionPublicKey if the ids are equal. Then saves the
* unapprovedEncryptionPublicKeyMsgs index to storage via this._saveMsgList
*
* @private
* @param {msg} EncryptionPublicKey A EncryptionPublicKey that will replace an existing EncryptionPublicKey (with the same
* id) in this.messages
*
*/
_updateMsg (msg) {
const index = this.messages.findIndex((message) => message.id === msg.id)
if (index !== -1) {
this.messages[index] = msg
}
this._saveMsgList()
}
/**
* Saves the unapproved EncryptionPublicKeys, and their count, to this.memStore
*
* @private
* @fires 'updateBadge'
*
*/
_saveMsgList () {
const unapprovedEncryptionPublicKeyMsgs = this.getUnapprovedMsgs()
const unapprovedEncryptionPublicKeyMsgCount = Object.keys(unapprovedEncryptionPublicKeyMsgs).length
this.memStore.updateState({ unapprovedEncryptionPublicKeyMsgs, unapprovedEncryptionPublicKeyMsgCount })
this.emit('updateBadge')
}
}

View File

@ -1,6 +1,7 @@
const ENVIRONMENT_TYPE_POPUP = 'popup'
const ENVIRONMENT_TYPE_NOTIFICATION = 'notification'
const ENVIRONMENT_TYPE_FULLSCREEN = 'fullscreen'
const ENVIRONMENT_TYPE_BACKGROUND = 'background'
const PLATFORM_BRAVE = 'Brave'
const PLATFORM_CHROME = 'Chrome'
@ -8,10 +9,11 @@ const PLATFORM_EDGE = 'Edge'
const PLATFORM_FIREFOX = 'Firefox'
const PLATFORM_OPERA = 'Opera'
module.exports = {
export {
ENVIRONMENT_TYPE_POPUP,
ENVIRONMENT_TYPE_NOTIFICATION,
ENVIRONMENT_TYPE_FULLSCREEN,
ENVIRONMENT_TYPE_BACKGROUND,
PLATFORM_BRAVE,
PLATFORM_CHROME,
PLATFORM_EDGE,

View File

@ -4,7 +4,7 @@ const allLocales = require('../../_locales/index.json')
const getPreferredLocales = extension.i18n ? promisify(
extension.i18n.getAcceptLanguages,
{ errorFirst: false }
{ errorFirst: false },
) : async () => []
const existingLocaleCodes = allLocales.map(locale => locale.code.toLowerCase().replace('_', '-'))

View File

@ -1,7 +1,8 @@
const EventEmitter = require('events')
const ObservableStore = require('obs-store')
const ethUtil = require('ethereumjs-util')
const createId = require('./random-id')
import EventEmitter from 'events'
import ObservableStore from 'obs-store'
import ethUtil from 'ethereumjs-util'
import { ethErrors } from 'eth-json-rpc-errors'
import createId from './random-id'
/**
* Represents, and contains data about, an 'eth_sign' type signature request. These are created when a signature for
@ -21,7 +22,7 @@ const createId = require('./random-id')
*
*/
module.exports = class MessageManager extends EventEmitter {
export default class MessageManager extends EventEmitter {
/**
* Controller in charge of managing - storing, adding, removing, updating - Messages.
@ -34,7 +35,7 @@ module.exports = class MessageManager extends EventEmitter {
* @property {array} messages Holds all messages that have been created by this MessageManager
*
*/
constructor (opts) {
constructor () {
super()
this.memStore = new ObservableStore({
unapprovedMsgs: {},
@ -46,7 +47,7 @@ module.exports = class MessageManager extends EventEmitter {
/**
* A getter for the number of 'unapproved' Messages in this.messages
*
* @returns {number} The number of 'unapproved' Messages in this.messages
* @returns {number} - The number of 'unapproved' Messages in this.messages
*
*/
get unapprovedMsgCount () {
@ -56,21 +57,23 @@ module.exports = class MessageManager extends EventEmitter {
/**
* A getter for the 'unapproved' Messages in this.messages
*
* @returns {Object} An index of Message ids to Messages, for all 'unapproved' Messages in this.messages
* @returns {Object} - An index of Message ids to Messages, for all 'unapproved' Messages in this.messages
*
*/
getUnapprovedMsgs () {
return this.messages.filter(msg => msg.status === 'unapproved')
.reduce((result, msg) => { result[msg.id] = msg; return result }, {})
return this.messages.filter((msg) => msg.status === 'unapproved')
.reduce((result, msg) => {
result[msg.id] = msg; return result
}, {})
}
/**
* Creates a new Message with an 'unapproved' status using the passed msgParams. this.addMsg is called to add the
* new Message to this.messages, and to save the unapproved Messages from that list to this.memStore.
*
* @param {Object} msgParams The params for the eth_sign call to be made after the message is approved.
* @param {Object} msgParams - The params for the eth_sign call to be made after the message is approved.
* @param {Object} req (optional) The original request object possibly containing the origin
* @returns {promise} after signature has been
* @returns {promise} - after signature has been
*
*/
addUnapprovedMessageAsync (msgParams, req) {
@ -82,7 +85,7 @@ module.exports = class MessageManager extends EventEmitter {
case 'signed':
return resolve(data.rawSig)
case 'rejected':
return reject(new Error('Nifty Wallet Message Signature: User denied message signature.'))
return reject(ethErrors.provider.userRejectedRequest('Nifty Wallet Message Signature: User denied message signature.'))
default:
return reject(new Error(`Nifty Wallet Message Signature: Unknown problem: ${JSON.stringify(msgParams)}`))
}
@ -94,19 +97,21 @@ module.exports = class MessageManager extends EventEmitter {
* Creates a new Message with an 'unapproved' status using the passed msgParams. this.addMsg is called to add the
* new Message to this.messages, and to save the unapproved Messages from that list to this.memStore.
*
* @param {Object} msgParams The params for the eth_sign call to be made after the message is approved.
* @param {Object} msgParams - The params for the eth_sign call to be made after the message is approved.
* @param {Object} req (optional) The original request object where the origin may be specificied
* @returns {number} The id of the newly created message.
* @returns {number} - The id of the newly created message.
*
*/
addUnapprovedMessage (msgParams, req) {
// add origin from request
if (req) msgParams.origin = req.origin
if (req) {
msgParams.origin = req.origin
}
msgParams.data = normalizeMsgData(msgParams.data)
// create txData obj with parameters and meta data
var time = (new Date()).getTime()
var msgId = createId()
var msgData = {
const time = (new Date()).getTime()
const msgId = createId()
const msgData = {
id: msgId,
msgParams: msgParams,
time: time,
@ -124,7 +129,7 @@ module.exports = class MessageManager extends EventEmitter {
* Adds a passed Message to this.messages, and calls this._saveMsgList() to save the unapproved Messages from that
* list to this.memStore.
*
* @param {Message} msg The Message to add to this.messages
* @param {Message} msg - The Message to add to this.messages
*
*/
addMsg (msg) {
@ -135,21 +140,21 @@ module.exports = class MessageManager extends EventEmitter {
/**
* Returns a specified Message.
*
* @param {number} msgId The id of the Message to get
* @returns {Message|undefined} The Message with the id that matches the passed msgId, or undefined if no Message has that id.
* @param {number} msgId - The id of the Message to get
* @returns {Message|undefined} - The Message with the id that matches the passed msgId, or undefined if no Message has that id.
*
*/
getMsg (msgId) {
return this.messages.find(msg => msg.id === msgId)
return this.messages.find((msg) => msg.id === msgId)
}
/**
* Approves a Message. Sets the message status via a call to this.setMsgStatusApproved, and returns a promise with
* any the message params modified for proper signing.
*
* @param {Object} msgParams The msgParams to be used when eth_sign is called, plus data added by MetaMask.
* @param {Object} msgParams - The msgParams to be used when eth_sign is called, plus data added by MetaMask.
* @param {Object} msgParams.metamaskId Added to msgParams for tracking and identification within MetaMask.
* @returns {Promise<object>} Promises the msgParams object with metamaskId removed.
* @returns {Promise<object>} - Promises the msgParams object with metamaskId removed.
*
*/
approveMessage (msgParams) {
@ -160,7 +165,7 @@ module.exports = class MessageManager extends EventEmitter {
/**
* Sets a Message status to 'approved' via a call to this._setMsgStatus.
*
* @param {number} msgId The id of the Message to approve.
* @param {number} msgId - The id of the Message to approve.
*
*/
setMsgStatusApproved (msgId) {
@ -171,8 +176,8 @@ module.exports = class MessageManager extends EventEmitter {
* Sets a Message status to 'signed' via a call to this._setMsgStatus and updates that Message in this.messages by
* adding the raw signature data of the signature request to the Message
*
* @param {number} msgId The id of the Message to sign.
* @param {buffer} rawSig The raw data of the signature request
* @param {number} msgId - The id of the Message to sign.
* @param {buffer} rawSig - The raw data of the signature request
*
*/
setMsgStatusSigned (msgId, rawSig) {
@ -185,8 +190,8 @@ module.exports = class MessageManager extends EventEmitter {
/**
* Removes the metamaskId property from passed msgParams and returns a promise which resolves the updated msgParams
*
* @param {Object} msgParams The msgParams to modify
* @returns {Promise<object>} Promises the msgParams with the metamaskId property removed
* @param {Object} msgParams - The msgParams to modify
* @returns {Promise<object>} - Promises the msgParams with the metamaskId property removed
*
*/
prepMsgForSigning (msgParams) {
@ -197,7 +202,7 @@ module.exports = class MessageManager extends EventEmitter {
/**
* Sets a Message status to 'rejected' via a call to this._setMsgStatus.
*
* @param {number} msgId The id of the Message to reject.
* @param {number} msgId - The id of the Message to reject.
*
*/
rejectMsg (msgId) {
@ -208,8 +213,8 @@ module.exports = class MessageManager extends EventEmitter {
* Updates the status of a Message in this.messages via a call to this._updateMsg
*
* @private
* @param {number} msgId The id of the Message to update.
* @param {string} status The new status of the Message.
* @param {number} msgId - The id of the Message to update.
* @param {string} status - The new status of the Message.
* @throws A 'MessageManager - Message not found for id: "${msgId}".' if there is no Message in this.messages with an
* id equal to the passed msgId
* @fires An event with a name equal to `${msgId}:${status}`. The Message is also fired.
@ -218,7 +223,9 @@ module.exports = class MessageManager extends EventEmitter {
*/
_setMsgStatus (msgId, status) {
const msg = this.getMsg(msgId)
if (!msg) throw new Error('MessageManager - Message not found for id: "${msgId}".')
if (!msg) {
throw new Error('MessageManager - Message not found for id: "${msgId}".')
}
msg.status = status
this._updateMsg(msg)
this.emit(`${msgId}:${status}`, msg)
@ -232,7 +239,7 @@ module.exports = class MessageManager extends EventEmitter {
* storage via this._saveMsgList
*
* @private
* @param {msg} Message A Message that will replace an existing Message (with the same id) in this.messages
* @param {msg} Message - A Message that will replace an existing Message (with the same id) in this.messages
*
*/
_updateMsg (msg) {
@ -262,8 +269,8 @@ module.exports = class MessageManager extends EventEmitter {
/**
* A helper function that converts raw buffer data to a hex, or just returns the data if it is already formatted as a hex.
*
* @param {any} data The buffer data to convert to a hex
* @returns {string} A hex string conversion of the buffer data
* @param {any} data - The buffer data to convert to a hex
* @returns {string} - A hex string conversion of the buffer data
*
*/
function normalizeMsgData (data) {

View File

@ -1,9 +1,11 @@
const EventEmitter = require('events')
const ObservableStore = require('obs-store')
const ethUtil = require('ethereumjs-util')
const createId = require('./random-id')
import EventEmitter from 'events'
import ObservableStore from 'obs-store'
import ethUtil from 'ethereumjs-util'
import { ethErrors } from 'eth-json-rpc-errors'
import createId from './random-id'
const hexRe = /^[0-9A-Fa-f]+$/g
const log = require('loglevel')
import log from 'loglevel'
/**
* Represents, and contains data about, an 'personal_sign' type signature request. These are created when a
@ -24,7 +26,7 @@ const log = require('loglevel')
*
*/
module.exports = class PersonalMessageManager extends EventEmitter {
export default class PersonalMessageManager extends EventEmitter {
/**
* Controller in charge of managing - storing, adding, removing, updating - PersonalMessage.
*
@ -36,7 +38,7 @@ module.exports = class PersonalMessageManager extends EventEmitter {
* @property {array} messages Holds all messages that have been created by this PersonalMessageManager
*
*/
constructor (opts) {
constructor () {
super()
this.memStore = new ObservableStore({
unapprovedPersonalMsgs: {},
@ -48,7 +50,7 @@ module.exports = class PersonalMessageManager extends EventEmitter {
/**
* A getter for the number of 'unapproved' PersonalMessages in this.messages
*
* @returns {number} The number of 'unapproved' PersonalMessages in this.messages
* @returns {number} - The number of 'unapproved' PersonalMessages in this.messages
*
*/
get unapprovedPersonalMsgCount () {
@ -58,13 +60,15 @@ module.exports = class PersonalMessageManager extends EventEmitter {
/**
* A getter for the 'unapproved' PersonalMessages in this.messages
*
* @returns {Object} An index of PersonalMessage ids to PersonalMessages, for all 'unapproved' PersonalMessages in
* @returns {Object} - An index of PersonalMessage ids to PersonalMessages, for all 'unapproved' PersonalMessages in
* this.messages
*
*/
getUnapprovedMsgs () {
return this.messages.filter(msg => msg.status === 'unapproved')
.reduce((result, msg) => { result[msg.id] = msg; return result }, {})
return this.messages.filter((msg) => msg.status === 'unapproved')
.reduce((result, msg) => {
result[msg.id] = msg; return result
}, {})
}
/**
@ -72,9 +76,9 @@ module.exports = class PersonalMessageManager extends EventEmitter {
* the new PersonalMessage to this.messages, and to save the unapproved PersonalMessages from that list to
* this.memStore.
*
* @param {Object} msgParams The params for the eth_sign call to be made after the message is approved.
* @param {Object} msgParams - The params for the eth_sign call to be made after the message is approved.
* @param {Object} req (optional) The original request object possibly containing the origin
* @returns {promise} When the message has been signed or rejected
* @returns {promise} - When the message has been signed or rejected
*
*/
addUnapprovedMessageAsync (msgParams, req) {
@ -88,7 +92,7 @@ module.exports = class PersonalMessageManager extends EventEmitter {
case 'signed':
return resolve(data.rawSig)
case 'rejected':
return reject(new Error('Nifty Wallet Message Signature: User denied message signature.'))
return reject(ethErrors.provider.userRejectedRequest('Nifty Wallet Message Signature: User denied message signature.'))
default:
return reject(new Error(`Nifty Wallet Message Signature: Unknown problem: ${JSON.stringify(msgParams)}`))
}
@ -101,20 +105,22 @@ module.exports = class PersonalMessageManager extends EventEmitter {
* the new PersonalMessage to this.messages, and to save the unapproved PersonalMessages from that list to
* this.memStore.
*
* @param {Object} msgParams The params for the eth_sign call to be made after the message is approved.
* @param {Object} msgParams - The params for the eth_sign call to be made after the message is approved.
* @param {Object} req (optional) The original request object possibly containing the origin
* @returns {number} The id of the newly created PersonalMessage.
* @returns {number} - The id of the newly created PersonalMessage.
*
*/
addUnapprovedMessage (msgParams, req) {
log.debug(`PersonalMessageManager addUnapprovedMessage: ${JSON.stringify(msgParams)}`)
// add origin from request
if (req) msgParams.origin = req.origin
if (req) {
msgParams.origin = req.origin
}
msgParams.data = this.normalizeMsgData(msgParams.data)
// create txData obj with parameters and meta data
var time = (new Date()).getTime()
var msgId = createId()
var msgData = {
const time = (new Date()).getTime()
const msgId = createId()
const msgData = {
id: msgId,
msgParams: msgParams,
time: time,
@ -132,7 +138,7 @@ module.exports = class PersonalMessageManager extends EventEmitter {
* Adds a passed PersonalMessage to this.messages, and calls this._saveMsgList() to save the unapproved PersonalMessages from that
* list to this.memStore.
*
* @param {Message} msg The PersonalMessage to add to this.messages
* @param {Message} msg - The PersonalMessage to add to this.messages
*
*/
addMsg (msg) {
@ -143,22 +149,22 @@ module.exports = class PersonalMessageManager extends EventEmitter {
/**
* Returns a specified PersonalMessage.
*
* @param {number} msgId The id of the PersonalMessage to get
* @returns {PersonalMessage|undefined} The PersonalMessage with the id that matches the passed msgId, or undefined
* @param {number} msgId - The id of the PersonalMessage to get
* @returns {PersonalMessage|undefined} - The PersonalMessage with the id that matches the passed msgId, or undefined
* if no PersonalMessage has that id.
*
*/
getMsg (msgId) {
return this.messages.find(msg => msg.id === msgId)
return this.messages.find((msg) => msg.id === msgId)
}
/**
* Approves a PersonalMessage. Sets the message status via a call to this.setMsgStatusApproved, and returns a promise
* with any the message params modified for proper signing.
*
* @param {Object} msgParams The msgParams to be used when eth_sign is called, plus data added by MetaMask.
* @param {Object} msgParams - The msgParams to be used when eth_sign is called, plus data added by MetaMask.
* @param {Object} msgParams.metamaskId Added to msgParams for tracking and identification within MetaMask.
* @returns {Promise<object>} Promises the msgParams object with metamaskId removed.
* @returns {Promise<object>} - Promises the msgParams object with metamaskId removed.
*
*/
approveMessage (msgParams) {
@ -169,7 +175,7 @@ module.exports = class PersonalMessageManager extends EventEmitter {
/**
* Sets a PersonalMessage status to 'approved' via a call to this._setMsgStatus.
*
* @param {number} msgId The id of the PersonalMessage to approve.
* @param {number} msgId - The id of the PersonalMessage to approve.
*
*/
setMsgStatusApproved (msgId) {
@ -180,8 +186,8 @@ module.exports = class PersonalMessageManager extends EventEmitter {
* Sets a PersonalMessage status to 'signed' via a call to this._setMsgStatus and updates that PersonalMessage in
* this.messages by adding the raw signature data of the signature request to the PersonalMessage
*
* @param {number} msgId The id of the PersonalMessage to sign.
* @param {buffer} rawSig The raw data of the signature request
* @param {number} msgId - The id of the PersonalMessage to sign.
* @param {buffer} rawSig - The raw data of the signature request
*
*/
setMsgStatusSigned (msgId, rawSig) {
@ -194,8 +200,8 @@ module.exports = class PersonalMessageManager extends EventEmitter {
/**
* Removes the metamaskId property from passed msgParams and returns a promise which resolves the updated msgParams
*
* @param {Object} msgParams The msgParams to modify
* @returns {Promise<object>} Promises the msgParams with the metamaskId property removed
* @param {Object} msgParams - The msgParams to modify
* @returns {Promise<object>} - Promises the msgParams with the metamaskId property removed
*
*/
prepMsgForSigning (msgParams) {
@ -206,7 +212,7 @@ module.exports = class PersonalMessageManager extends EventEmitter {
/**
* Sets a PersonalMessage status to 'rejected' via a call to this._setMsgStatus.
*
* @param {number} msgId The id of the PersonalMessage to reject.
* @param {number} msgId - The id of the PersonalMessage to reject.
*
*/
rejectMsg (msgId) {
@ -217,8 +223,8 @@ module.exports = class PersonalMessageManager extends EventEmitter {
* Updates the status of a PersonalMessage in this.messages via a call to this._updateMsg
*
* @private
* @param {number} msgId The id of the PersonalMessage to update.
* @param {string} status The new status of the PersonalMessage.
* @param {number} msgId - The id of the PersonalMessage to update.
* @param {string} status - The new status of the PersonalMessage.
* @throws A 'PersonalMessageManager - PersonalMessage not found for id: "${msgId}".' if there is no PersonalMessage
* in this.messages with an id equal to the passed msgId
* @fires An event with a name equal to `${msgId}:${status}`. The PersonalMessage is also fired.
@ -228,7 +234,9 @@ module.exports = class PersonalMessageManager extends EventEmitter {
*/
_setMsgStatus (msgId, status) {
const msg = this.getMsg(msgId)
if (!msg) throw new Error('PersonalMessageManager - Message not found for id: "${msgId}".')
if (!msg) {
throw new Error(`PersonalMessageManager - Message not found for id: "${msgId}".`)
}
msg.status = status
this._updateMsg(msg)
this.emit(`${msgId}:${status}`, msg)
@ -242,7 +250,7 @@ module.exports = class PersonalMessageManager extends EventEmitter {
* unapprovedPersonalMsgs index to storage via this._saveMsgList
*
* @private
* @param {msg} PersonalMessage A PersonalMessage that will replace an existing PersonalMessage (with the same
* @param {msg} PersonalMessage - A PersonalMessage that will replace an existing PersonalMessage (with the same
* id) in this.messages
*
*/
@ -271,8 +279,8 @@ module.exports = class PersonalMessageManager extends EventEmitter {
/**
* A helper function that converts raw buffer data to a hex, or just returns the data if it is already formatted as a hex.
*
* @param {any} data The buffer data to convert to a hex
* @returns {string} A hex string conversion of the buffer data
* @param {any} data - The buffer data to convert to a hex
* @returns {string} - A hex string conversion of the buffer data
*
*/
normalizeMsgData (data) {

View File

@ -0,0 +1,22 @@
import {
MAINNET_CHAIN_ID,
ROPSTEN_CHAIN_ID,
RINKEBY_CHAIN_ID,
KOVAN_CHAIN_ID,
GOERLI_CHAIN_ID,
} from './enums'
const standardNetworkId = {
'1': MAINNET_CHAIN_ID,
'3': ROPSTEN_CHAIN_ID,
'4': RINKEBY_CHAIN_ID,
'42': KOVAN_CHAIN_ID,
'5': GOERLI_CHAIN_ID,
}
function selectChainId (metamaskState) {
const { network, provider: { chainId } } = metamaskState
return standardNetworkId[network] || `0x${parseInt(chainId, 10).toString(16)}`
}
export default selectChainId

View File

@ -1,36 +1,10 @@
const Through = require('through2')
const ObjectMultiplex = require('obj-multiplex')
const pump = require('pump')
import ObjectMultiplex from 'obj-multiplex'
import pump from 'pump'
module.exports = {
jsonParseStream: jsonParseStream,
jsonStringifyStream: jsonStringifyStream,
setupMultiplex: setupMultiplex,
}
/**
* Returns a stream transform that parses JSON strings passing through
* @return {stream.Transform}
*/
function jsonParseStream () {
return Through.obj(function (serialized, _, cb) {
this.push(JSON.parse(serialized))
cb()
})
}
/**
* Returns a stream transform that calls {@code JSON.stringify}
* on objects passing through
* @return {stream.Transform} the stream transform
*/
function jsonStringifyStream () {
return Through.obj(function (obj, _, cb) {
this.push(JSON.stringify(obj))
cb()
})
}
/**
* Sets up stream multiplexing for the given stream
* @param {any} connectionStream - the stream to mux
@ -43,8 +17,10 @@ function setupMultiplex (connectionStream) {
mux,
connectionStream,
(err) => {
if (err) console.error(err)
}
if (err) {
console.error(err)
}
},
)
return mux
}

View File

@ -1,10 +1,11 @@
const EventEmitter = require('events')
const ObservableStore = require('obs-store')
const createId = require('./random-id')
const assert = require('assert')
const sigUtil = require('eth-sig-util')
const log = require('loglevel')
const jsonschema = require('jsonschema')
import EventEmitter from 'events'
import ObservableStore from 'obs-store'
import createId from './random-id'
import assert from 'assert'
import { ethErrors } from 'eth-json-rpc-errors'
import sigUtil from 'eth-sig-util'
import log from 'loglevel'
import jsonschema from 'jsonschema'
/**
* Represents, and contains data about, an 'eth_signTypedData' type signature request. These are created when a
@ -24,7 +25,7 @@ const jsonschema = require('jsonschema')
*
*/
module.exports = class TypedMessageManager extends EventEmitter {
export default class TypedMessageManager extends EventEmitter {
/**
* Controller in charge of managing - storing, adding, removing, updating - TypedMessage.
*/
@ -41,7 +42,7 @@ module.exports = class TypedMessageManager extends EventEmitter {
/**
* A getter for the number of 'unapproved' TypedMessages in this.messages
*
* @returns {number} The number of 'unapproved' TypedMessages in this.messages
* @returns {number} - The number of 'unapproved' TypedMessages in this.messages
*
*/
get unapprovedTypedMessagesCount () {
@ -51,13 +52,15 @@ module.exports = class TypedMessageManager extends EventEmitter {
/**
* A getter for the 'unapproved' TypedMessages in this.messages
*
* @returns {Object} An index of TypedMessage ids to TypedMessages, for all 'unapproved' TypedMessages in
* @returns {Object} - An index of TypedMessage ids to TypedMessages, for all 'unapproved' TypedMessages in
* this.messages
*
*/
getUnapprovedMsgs () {
return this.messages.filter(msg => msg.status === 'unapproved')
.reduce((result, msg) => { result[msg.id] = msg; return result }, {})
return this.messages.filter((msg) => msg.status === 'unapproved')
.reduce((result, msg) => {
result[msg.id] = msg; return result
}, {})
}
/**
@ -65,9 +68,9 @@ module.exports = class TypedMessageManager extends EventEmitter {
* the new TypedMessage to this.messages, and to save the unapproved TypedMessages from that list to
* this.memStore. Before any of this is done, msgParams are validated
*
* @param {Object} msgParams The params for the eth_sign call to be made after the message is approved.
* @param {Object} msgParams - The params for the eth_sign call to be made after the message is approved.
* @param {Object} req (optional) The original request object possibly containing the origin
* @returns {promise} When the message has been signed or rejected
* @returns {promise} - When the message has been signed or rejected
*
*/
addUnapprovedMessageAsync (msgParams, req, version) {
@ -78,7 +81,7 @@ module.exports = class TypedMessageManager extends EventEmitter {
case 'signed':
return resolve(data.rawSig)
case 'rejected':
return reject(new Error('Nifty Wallet Message Signature: User denied message signature.'))
return reject(ethErrors.provider.userRejectedRequest('Nifty Wallet Message Signature: User denied message signature.'))
case 'errored':
return reject(new Error(`Nifty Wallet Message Signature: ${data.error}`))
default:
@ -93,22 +96,24 @@ module.exports = class TypedMessageManager extends EventEmitter {
* the new TypedMessage to this.messages, and to save the unapproved TypedMessages from that list to
* this.memStore. Before any of this is done, msgParams are validated
*
* @param {Object} msgParams The params for the eth_sign call to be made after the message is approved.
* @param {Object} msgParams - The params for the eth_sign call to be made after the message is approved.
* @param {Object} req (optional) The original request object possibly containing the origin
* @returns {number} The id of the newly created TypedMessage.
* @returns {number} - The id of the newly created TypedMessage.
*
*/
addUnapprovedMessage (msgParams, req, version) {
msgParams.version = version
this.validateParams(msgParams)
// add origin from request
if (req) msgParams.origin = req.origin
if (req) {
msgParams.origin = req.origin
}
log.debug(`TypedMessageManager addUnapprovedMessage: ${JSON.stringify(msgParams)}`)
// create txData obj with parameters and meta data
var time = (new Date()).getTime()
var msgId = createId()
var msgData = {
const time = (new Date()).getTime()
const msgId = createId()
const msgData = {
id: msgId,
msgParams: msgParams,
time: time,
@ -125,7 +130,7 @@ module.exports = class TypedMessageManager extends EventEmitter {
/**
* Helper method for this.addUnapprovedMessage. Validates that the passed params have the required properties.
*
* @param {Object} params The params to validate
* @param {Object} params - The params to validate
*
*/
validateParams (params) {
@ -141,13 +146,16 @@ module.exports = class TypedMessageManager extends EventEmitter {
}, 'Expected EIP712 typed data')
break
case 'V3':
case 'V4':
let data
assert.equal(typeof params, 'object', 'Params should be an object.')
assert.ok('data' in params, 'Params must include a data field.')
assert.ok('from' in params, 'Params must include a from field.')
assert.equal(typeof params.from, 'string', 'From field must be a string.')
assert.equal(typeof params.data, 'string', 'Data must be passed as a valid JSON string.')
assert.doesNotThrow(() => { data = JSON.parse(params.data) }, 'Data must be passed as a valid JSON string.')
assert.doesNotThrow(() => {
data = JSON.parse(params.data)
}, 'Data must be passed as a valid JSON string.')
const validation = jsonschema.validate(data, sigUtil.TYPED_MESSAGE_SCHEMA)
assert.ok(data.primaryType in data.types, `Primary type of "${data.primaryType}" has no type definition.`)
assert.equal(validation.errors.length, 0, 'Data must conform to EIP-712 schema. See https://git.io/fNtcx.')
@ -155,6 +163,8 @@ module.exports = class TypedMessageManager extends EventEmitter {
const activeChainId = parseInt(this.networkController.getNetworkState())
chainId && assert.equal(chainId, activeChainId, `Provided chainId (${chainId}) must match the active chainId (${activeChainId})`)
break
default:
assert.fail(`Unknown params.version ${params.version}`)
}
}
@ -162,7 +172,7 @@ module.exports = class TypedMessageManager extends EventEmitter {
* Adds a passed TypedMessage to this.messages, and calls this._saveMsgList() to save the unapproved TypedMessages from that
* list to this.memStore.
*
* @param {Message} msg The TypedMessage to add to this.messages
* @param {Message} msg - The TypedMessage to add to this.messages
*
*/
addMsg (msg) {
@ -173,22 +183,22 @@ module.exports = class TypedMessageManager extends EventEmitter {
/**
* Returns a specified TypedMessage.
*
* @param {number} msgId The id of the TypedMessage to get
* @returns {TypedMessage|undefined} The TypedMessage with the id that matches the passed msgId, or undefined
* @param {number} msgId - The id of the TypedMessage to get
* @returns {TypedMessage|undefined} - The TypedMessage with the id that matches the passed msgId, or undefined
* if no TypedMessage has that id.
*
*/
getMsg (msgId) {
return this.messages.find(msg => msg.id === msgId)
return this.messages.find((msg) => msg.id === msgId)
}
/**
* Approves a TypedMessage. Sets the message status via a call to this.setMsgStatusApproved, and returns a promise
* with any the message params modified for proper signing.
*
* @param {Object} msgParams The msgParams to be used when eth_sign is called, plus data added by MetaMask.
* @param {Object} msgParams - The msgParams to be used when eth_sign is called, plus data added by MetaMask.
* @param {Object} msgParams.metamaskId Added to msgParams for tracking and identification within MetaMask.
* @returns {Promise<object>} Promises the msgParams object with metamaskId removed.
* @returns {Promise<object>} - Promises the msgParams object with metamaskId removed.
*
*/
approveMessage (msgParams) {
@ -199,7 +209,7 @@ module.exports = class TypedMessageManager extends EventEmitter {
/**
* Sets a TypedMessage status to 'approved' via a call to this._setMsgStatus.
*
* @param {number} msgId The id of the TypedMessage to approve.
* @param {number} msgId - The id of the TypedMessage to approve.
*
*/
setMsgStatusApproved (msgId) {
@ -210,8 +220,8 @@ module.exports = class TypedMessageManager extends EventEmitter {
* Sets a TypedMessage status to 'signed' via a call to this._setMsgStatus and updates that TypedMessage in
* this.messages by adding the raw signature data of the signature request to the TypedMessage
*
* @param {number} msgId The id of the TypedMessage to sign.
* @param {buffer} rawSig The raw data of the signature request
* @param {number} msgId - The id of the TypedMessage to sign.
* @param {buffer} rawSig - The raw data of the signature request
*
*/
setMsgStatusSigned (msgId, rawSig) {
@ -224,8 +234,8 @@ module.exports = class TypedMessageManager extends EventEmitter {
/**
* Removes the metamaskId property from passed msgParams and returns a promise which resolves the updated msgParams
*
* @param {Object} msgParams The msgParams to modify
* @returns {Promise<object>} Promises the msgParams with the metamaskId property removed
* @param {Object} msgParams - The msgParams to modify
* @returns {Promise<object>} - Promises the msgParams with the metamaskId property removed
*
*/
prepMsgForSigning (msgParams) {
@ -237,7 +247,7 @@ module.exports = class TypedMessageManager extends EventEmitter {
/**
* Sets a TypedMessage status to 'rejected' via a call to this._setMsgStatus.
*
* @param {number} msgId The id of the TypedMessage to reject.
* @param {number} msgId - The id of the TypedMessage to reject.
*
*/
rejectMsg (msgId) {
@ -247,7 +257,7 @@ module.exports = class TypedMessageManager extends EventEmitter {
/**
* Sets a TypedMessage status to 'errored' via a call to this._setMsgStatus.
*
* @param {number} msgId The id of the TypedMessage to error
* @param {number} msgId - The id of the TypedMessage to error
*
*/
errorMessage (msgId, error) {
@ -265,8 +275,8 @@ module.exports = class TypedMessageManager extends EventEmitter {
* Updates the status of a TypedMessage in this.messages via a call to this._updateMsg
*
* @private
* @param {number} msgId The id of the TypedMessage to update.
* @param {string} status The new status of the TypedMessage.
* @param {number} msgId - The id of the TypedMessage to update.
* @param {string} status - The new status of the TypedMessage.
* @throws A 'TypedMessageManager - TypedMessage not found for id: "${msgId}".' if there is no TypedMessage
* in this.messages with an id equal to the passed msgId
* @fires An event with a name equal to `${msgId}:${status}`. The TypedMessage is also fired.
@ -276,7 +286,9 @@ module.exports = class TypedMessageManager extends EventEmitter {
*/
_setMsgStatus (msgId, status) {
const msg = this.getMsg(msgId)
if (!msg) throw new Error('TypedMessageManager - Message not found for id: "${msgId}".')
if (!msg) {
throw new Error('TypedMessageManager - Message not found for id: "${msgId}".')
}
msg.status = status
this._updateMsg(msg)
this.emit(`${msgId}:${status}`, msg)
@ -290,7 +302,7 @@ module.exports = class TypedMessageManager extends EventEmitter {
* unapprovedTypedMsgs index to storage via this._saveMsgList
*
* @private
* @param {msg} TypedMessage A TypedMessage that will replace an existing TypedMessage (with the same
* @param {msg} TypedMessage - A TypedMessage that will replace an existing TypedMessage (with the same
* id) in this.messages
*
*/

View File

@ -1,6 +1,7 @@
const ethUtil = require('ethereumjs-util')
const assert = require('assert')
const BN = require('bn.js')
import extension from 'extensionizer'
import ethUtil from 'ethereumjs-util'
import assert from 'assert'
import BN from 'bn.js'
const {
ENVIRONMENT_TYPE_POPUP,
ENVIRONMENT_TYPE_NOTIFICATION,
@ -152,6 +153,24 @@ function capitalizeFirstLetter (msg) {
return msg.charAt(0).toUpperCase() + msg.slice(1)
}
/**
* Returns an Error if extension.runtime.lastError is present
* this is a workaround for the non-standard error object thats used
* @returns {Error}
*/
function checkForError () {
const lastError = extension.runtime.lastError
if (!lastError) {
return
}
// if it quacks like an Error, its an Error
if (lastError.stack && lastError.message) {
return lastError
}
// repair incomplete error object (eg chromium v77)
return new Error(lastError.message)
}
module.exports = {
removeListeners,
applyListeners,
@ -163,4 +182,5 @@ module.exports = {
bnToHex,
BnMultiplyByFraction,
capitalizeFirstLetter,
checkForError,
}

View File

@ -4,21 +4,24 @@
* @license MIT
*/
const EventEmitter = require('events')
const pump = require('pump')
const Dnode = require('dnode')
const ObservableStore = require('obs-store')
import EventEmitter from 'events'
import pump from 'pump'
import Dnode from 'dnode'
import extension from 'extensionizer'
import ObservableStore from 'obs-store'
const ComposableObservableStore = require('./lib/ComposableObservableStore')
const asStream = require('obs-store/lib/asStream')
import asStream from 'obs-store/lib/asStream'
const AccountTracker = require('./lib/account-tracker')
const RpcEngine = require('json-rpc-engine')
const debounce = require('debounce')
import RpcEngine from 'json-rpc-engine'
import { debounce } from 'lodash'
const createEngineStream = require('json-rpc-middleware-stream/engineStream')
const createFilterMiddleware = require('eth-json-rpc-filters')
const createSubscriptionManager = require('eth-json-rpc-filters/subscriptionManager')
const createOriginMiddleware = require('./lib/createOriginMiddleware')
const createLoggerMiddleware = require('./lib/createLoggerMiddleware')
const createProviderMiddleware = require('./lib/createProviderMiddleware')
import createTabIdMiddleware from './lib/createTabIdMiddleware'
import providerAsMiddleware from 'eth-json-rpc-middleware/providerAsMiddleware'
const setupMultiplex = require('./lib/stream-utils.js').setupMultiplex
const KeyringController = require('eth-keychain-controller')
const NetworkController = require('./controllers/network')
@ -28,34 +31,40 @@ const NoticeController = require('./notice-controller')
const ShapeShiftController = require('./controllers/shapeshift')
const AddressBookController = require('./controllers/address-book')
const InfuraController = require('./controllers/infura')
const BlacklistController = require('./controllers/blacklist')
const CachedBalancesController = require('./controllers/cached-balances')
const RecentBlocksController = require('./controllers/recent-blocks')
const MessageManager = require('./lib/message-manager')
const PersonalMessageManager = require('./lib/personal-message-manager')
const TypedMessageManager = require('./lib/typed-message-manager')
import MessageManager from './lib/message-manager'
import DecryptMessageManager from './lib/decrypt-message-manager'
import EncryptionPublicKeyManager from './lib/encryption-public-key-manager'
import PersonalMessageManager from './lib/personal-message-manager'
import TypedMessageManager from './lib/typed-message-manager'
const TransactionController = require('./controllers/transactions')
const BalancesController = require('./controllers/computed-balances')
const TokenRatesController = require('./controllers/token-rates')
const DetectTokensController = require('./controllers/detect-tokens')
const nodeify = require('./lib/nodeify')
const accountImporter = require('./account-import-strategies')
const Mutex = require('await-semaphore').Mutex
import { Mutex } from 'await-semaphore'
import selectChainId from './lib/select-chain-id'
const version = require('../manifest.json').version
const BN = require('ethereumjs-util').BN
import ethUtil, { BN } from 'ethereumjs-util'
const GWEI_BN = new BN('1000000000')
const percentile = require('percentile')
import percentile from 'percentile'
const seedPhraseVerifier = require('./lib/seed-phrase-verifier')
const log = require('loglevel')
import log from 'loglevel'
const TrezorKeyring = require('eth-trezor-keyring')
const LedgerBridgeKeyring = require('eth-ledger-bridge-keyring')
const EthQuery = require('eth-query')
const ethUtil = require('ethereumjs-util')
import EthQuery from 'eth-query'
const sigUtil = require('eth-sig-util')
import nanoid from 'nanoid'
const { importTypes } = require('../../old-ui/app/accounts/import/enums')
const { LEDGER, TREZOR } = require('../../old-ui/app/components/connect-hardware/enum')
const { ifPOA, ifRSK, getNetworkID, getDPath, setDPath } = require('../../old-ui/app/util')
import {
PhishingController,
} from 'gaba'
const {
CLASSIC_CODE,
MAINNET_CODE } = require('./controllers/network/enums')
@ -87,6 +96,10 @@ module.exports = class MetamaskController extends EventEmitter {
// observable state store
this.store = new ComposableObservableStore(initState)
// external connections by origin
// Do not modify directly. Use the associated methods.
this.connections = {}
// lock to ensure only one vault created at once
this.createVaultMutex = new Mutex()
@ -114,8 +127,7 @@ module.exports = class MetamaskController extends EventEmitter {
})
this.infuraController.scheduleInfuraNetworkCheck()
this.blacklistController = new BlacklistController()
this.blacklistController.scheduleUpdates()
this.phishingController = new PhishingController()
// rpc provider
this.initializeProvider()
@ -137,6 +149,7 @@ module.exports = class MetamaskController extends EventEmitter {
this.accountTracker = new AccountTracker({
provider: this.provider,
blockTracker: this.blockTracker,
network: this.networkController,
})
// start and stop polling for balances based on activeControllerConnections
@ -191,6 +204,7 @@ module.exports = class MetamaskController extends EventEmitter {
})
this.keyringController.memStore.subscribe((s) => this._onKeyringControllerUpdate(s))
this.keyringController.on('unlock', () => this.emit('unlock'))
// detect tokens controller
this.detectTokensController = new DetectTokensController({
@ -251,8 +265,14 @@ module.exports = class MetamaskController extends EventEmitter {
this.networkController.lookupNetwork()
this.messageManager = new MessageManager()
this.personalMessageManager = new PersonalMessageManager()
this.decryptMessageManager = new DecryptMessageManager()
this.encryptionPublicKeyManager = new EncryptionPublicKeyManager()
this.typedMessageManager = new TypedMessageManager({ networkController: this.networkController })
this.publicConfigStore = this.initPublicConfigStore()
// ensure isClientOpenAndUnlocked is updated when memState updates
this.on('update', (memState) => {
this.isClientOpenAndUnlocked = memState.isUnlocked && this._isClientOpen
})
this.store.updateStructure({
TransactionController: this.txController.store,
@ -276,6 +296,8 @@ module.exports = class MetamaskController extends EventEmitter {
TokenRatesController: this.tokenRatesController.store,
MessageManager: this.messageManager.memStore,
PersonalMessageManager: this.personalMessageManager.memStore,
DecryptMessageManager: this.decryptMessageManager.memStore,
EncryptionPublicKeyManager: this.encryptionPublicKeyManager.memStore,
TypesMessageManager: this.typedMessageManager.memStore,
KeyringController: this.keyringController.memStore,
PreferencesController: this.preferencesController.store,
@ -301,20 +323,23 @@ module.exports = class MetamaskController extends EventEmitter {
version,
// account mgmt
getAccounts: async () => {
const isUnlocked = this.keyringController.memStore.getState().isUnlocked
const selectedAddress = this.preferencesController.getSelectedAddress()
// only show address if account is unlocked
if (isUnlocked && selectedAddress) {
if (this.isUnlocked && selectedAddress) {
return [selectedAddress]
} else {
return []
}
return [] // changing this is a breaking change
},
// tx signing
processTransaction: this.newUnapprovedTransaction.bind(this),
// msg signing
processEthSignMessage: this.newUnsignedMessage.bind(this),
processTypedMessage: this.newUnsignedTypedMessage.bind(this),
processTypedMessageV3: this.newUnsignedTypedMessage.bind(this),
processTypedMessageV4: this.newUnsignedTypedMessage.bind(this),
processPersonalMessage: this.newUnsignedPersonalMessage.bind(this),
processDecryptMessage: this.newRequestDecryptMessage.bind(this),
processEncryptionPublicKey: this.newRequestEncryptionPublicKey.bind(this),
getPendingNonce: this.getPendingNonce.bind(this),
}
const providerProxy = this.networkController.initializeProvider(providerOpts)
@ -325,25 +350,30 @@ module.exports = class MetamaskController extends EventEmitter {
* Constructor helper: initialize a public config store.
* This store is used to make some config info available to Dapps synchronously.
*/
initPublicConfigStore () {
// get init state
createPublicConfigStore () {
// subset of state for metamask inpage provider
const publicConfigStore = new ObservableStore()
// memStore -> transform -> publicConfigStore
this.on('update', (memState) => {
this.isClientOpenAndUnlocked = memState.isUnlocked && this._isClientOpen
const publicState = selectPublicState(memState)
publicConfigStore.putState(publicState)
})
// setup memStore subscription hooks
this.on('update', updatePublicConfigStore)
updatePublicConfigStore(this.getState())
function selectPublicState (memState) {
const result = {
selectedAddress: memState.isUnlocked ? memState.selectedAddress : undefined,
networkVersion: memState.network,
}
return result
publicConfigStore.destroy = () => {
this.removeEventListener && this.removeEventListener('update', updatePublicConfigStore)
}
function updatePublicConfigStore (memState) {
publicConfigStore.putState(selectPublicState(memState))
}
function selectPublicState ({ isUnlocked, network, provider, selectedAddress }) {
return {
isUnlocked,
selectedAddress: isUnlocked ? selectedAddress : undefined,
networkVersion: network,
chainId: selectChainId({ network, provider }),
}
}
return publicConfigStore
}
@ -354,7 +384,7 @@ module.exports = class MetamaskController extends EventEmitter {
/**
* The metamask-state of the various controllers, made available to the UI
*
* @returns {Object} status
* @returns {Object} - status
*/
getState () {
const vault = this.keyringController.store.getState().vault
@ -379,11 +409,11 @@ module.exports = class MetamaskController extends EventEmitter {
*/
getApi () {
const keyringController = this.keyringController
const networkController = this.networkController
const preferencesController = this.preferencesController
const txController = this.txController
const noticeController = this.noticeController
const addressBookController = this.addressBookController
const networkController = this.networkController
return {
// etc
@ -391,6 +421,7 @@ module.exports = class MetamaskController extends EventEmitter {
setCurrentCurrency: this.setCurrentCurrency.bind(this),
setCurrentCoin: this.setCurrentCoin.bind(this),
setUseBlockie: this.setUseBlockie.bind(this),
setUsePhishDetect: this.setUsePhishDetect.bind(this),
setCurrentLocale: this.setCurrentLocale.bind(this),
setDProvider: this.setDProvider.bind(this),
markAccountsFound: this.markAccountsFound.bind(this),
@ -471,10 +502,15 @@ module.exports = class MetamaskController extends EventEmitter {
signPersonalMessage: nodeify(this.signPersonalMessage, this),
cancelPersonalMessage: this.cancelPersonalMessage.bind(this),
// personalMessageManager
// typedMessageManager
signTypedMessage: nodeify(this.signTypedMessage, this),
cancelTypedMessage: this.cancelTypedMessage.bind(this),
// decryptMessageManager
decryptMessage: nodeify(this.decryptMessage, this),
decryptMessageInline: nodeify(this.decryptMessageInline, this),
cancelDecryptMessage: this.cancelDecryptMessage.bind(this),
// notices
checkNotices: noticeController.updateNoticesList.bind(noticeController),
markNoticeRead: noticeController.markNoticeRead.bind(noticeController),
@ -1169,6 +1205,147 @@ module.exports = class MetamaskController extends EventEmitter {
}
}
// eth_decrypt methods
/**
* Called when a dapp uses the eth_decrypt method.
*
* @param {Object} msgParams - The params of the message to sign & return to the Dapp.
* @param {Object} req - (optional) the original request, containing the origin
* Passed back to the requesting Dapp.
*/
async newRequestDecryptMessage (msgParams, req) {
const promise = this.decryptMessageManager.addUnapprovedMessageAsync(msgParams, req)
this.sendUpdate()
this.opts.showUnconfirmedMessage()
return promise
}
/**
* Only decypt message and don't touch transaction state
*
* @param {Object} msgParams - The params of the message to decrypt.
* @returns {Promise<Object>} - A full state update.
*/
async decryptMessageInline (msgParams) {
log.info('MetaMaskController - decryptMessageInline')
// decrypt the message inline
const msgId = msgParams.metamaskId
const msg = this.decryptMessageManager.getMsg(msgId)
try {
const stripped = ethUtil.stripHexPrefix(msgParams.data)
const buff = Buffer.from(stripped, 'hex')
msgParams.data = JSON.parse(buff.toString('utf8'))
msg.rawData = await this.keyringController.decryptMessage(msgParams)
} catch (e) {
msg.error = e.message
}
this.decryptMessageManager._updateMsg(msg)
return this.getState()
}
/**
* Signifies a user's approval to decrypt a message in queue.
* Triggers decrypt, and the callback function from newUnsignedDecryptMessage.
*
* @param {Object} msgParams - The params of the message to decrypt & return to the Dapp.
* @returns {Promise<Object>} - A full state update.
*/
async decryptMessage (msgParams) {
log.info('MetaMaskController - decryptMessage')
const msgId = msgParams.metamaskId
// sets the status op the message to 'approved'
// and removes the metamaskId for decryption
try {
const cleanMsgParams = await this.decryptMessageManager.approveMessage(msgParams)
const stripped = ethUtil.stripHexPrefix(cleanMsgParams.data)
const buff = Buffer.from(stripped, 'hex')
cleanMsgParams.data = JSON.parse(buff.toString('utf8'))
// decrypt the message
const rawMess = await this.keyringController.decryptMessage(cleanMsgParams)
// tells the listener that the message has been decrypted and can be returned to the dapp
this.decryptMessageManager.setMsgStatusDecrypted(msgId, rawMess)
} catch (error) {
log.info('MetaMaskController - eth_decrypt failed.', error)
this.decryptMessageManager.errorMessage(msgId, error)
}
return this.getState()
}
/**
* Used to cancel a eth_decrypt type message.
* @param {string} msgId - The ID of the message to cancel.
* @param {Function} cb - The callback function called with a full state update.
*/
cancelDecryptMessage (msgId, cb) {
const messageManager = this.decryptMessageManager
messageManager.rejectMsg(msgId)
if (cb && typeof cb === 'function') {
cb(null, this.getState())
}
}
// eth_getEncryptionPublicKey methods
/**
* Called when a dapp uses the eth_getEncryptionPublicKey method.
*
* @param {Object} msgParams - The params of the message to sign & return to the Dapp.
* @param {Object} req - (optional) the original request, containing the origin
* Passed back to the requesting Dapp.
*/
async newRequestEncryptionPublicKey (msgParams, req) {
const promise = this.encryptionPublicKeyManager.addUnapprovedMessageAsync(msgParams, req)
this.sendUpdate()
this.opts.showUnconfirmedMessage()
return promise
}
/**
* Signifies a user's approval to receiving encryption public key in queue.
* Triggers receiving, and the callback function from newUnsignedEncryptionPublicKey.
*
* @param {Object} msgParams - The params of the message to receive & return to the Dapp.
* @returns {Promise<Object>} - A full state update.
*/
async encryptionPublicKey (msgParams) {
log.info('MetaMaskController - encryptionPublicKey')
const msgId = msgParams.metamaskId
// sets the status op the message to 'approved'
// and removes the metamaskId for decryption
try {
const params = await this.encryptionPublicKeyManager.approveMessage(msgParams)
// EncryptionPublicKey message
const publicKey = await this.keyringController.getEncryptionPublicKey(params.data)
// tells the listener that the message has been processed
// and can be returned to the dapp
this.encryptionPublicKeyManager.setMsgStatusReceived(msgId, publicKey)
} catch (error) {
log.info('MetaMaskController - eth_getEncryptionPublicKey failed.', error)
this.encryptionPublicKeyManager.errorMessage(msgId, error)
}
return this.getState()
}
/**
* Used to cancel a eth_getEncryptionPublicKey type message.
* @param {string} msgId - The ID of the message to cancel.
* @param {Function} cb - The callback function called with a full state update.
*/
cancelEncryptionPublicKey (msgId, cb) {
const messageManager = this.encryptionPublicKeyManager
messageManager.rejectMsg(msgId)
if (cb && typeof cb === 'function') {
cb(null, this.getState())
}
}
// eth_signTypedData methods
/**
@ -1356,29 +1533,37 @@ module.exports = class MetamaskController extends EventEmitter {
cb()
}
//=============================================================================
// SETUP
//=============================================================================
//=============================================================================
// SETUP
//=============================================================================
/**
* A runtime.MessageSender object, as provided by the browser:
* @see https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/runtime/MessageSender
* @typedef {Object} MessageSender
*/
/**
* Used to create a multiplexed stream for connecting to an untrusted context
* like a Dapp or other extension.
* @param {*} connectionStream - The Duplex stream to connect to.
* @param {string} originDomain - The domain requesting the stream, which
* may trigger a blacklist reload.
* @param {MessageSender} sender - The sender of the messages on this stream
*/
setupUntrustedCommunication (connectionStream, originDomain) {
// Check if new connection is blacklisted
if (this.blacklistController.checkForPhishing(originDomain)) {
log.debug('Nifty Wallet - sending phishing warning for', originDomain)
this.sendPhishingWarning(connectionStream, originDomain)
setupUntrustedCommunication (connectionStream, sender) {
const { usePhishDetect } = this.preferencesController.store.getState()
const hostname = (new URL(sender.url)).hostname
// Check if new connection is blacklisted if phishing detection is on
if (usePhishDetect && this.phishingController.test(hostname)) {
log.debug('Nifty Wallet - sending phishing warning for', hostname)
this.sendPhishingWarning(connectionStream, hostname)
return
}
// setup multiplexing
const mux = setupMultiplex(connectionStream)
// connect features
this.setupProviderConnection(mux.createStream('provider'), originDomain)
// messages between inpage and background
this.setupProviderConnection(mux.createStream('provider'), sender)
this.setupPublicConfig(mux.createStream('publicConfig'))
}
@ -1389,15 +1574,14 @@ module.exports = class MetamaskController extends EventEmitter {
* functions, like the ability to approve transactions or sign messages.
*
* @param {*} connectionStream - The duplex stream to connect to.
* @param {string} originDomain - The domain requesting the connection,
* used in logging and error reporting.
* @param {MessageSender} sender - The sender of the messages on this stream
*/
setupTrustedCommunication (connectionStream, originDomain) {
setupTrustedCommunication (connectionStream, sender) {
// setup multiplexing
const mux = setupMultiplex(connectionStream)
// connect features
this.setupControllerConnection(mux.createStream('controller'))
this.setupProviderConnection(mux.createStream('provider'), originDomain)
this.setupProviderConnection(mux.createStream('provider'), sender, true)
}
/**
@ -1421,9 +1605,7 @@ module.exports = class MetamaskController extends EventEmitter {
*/
setupControllerConnection (outStream) {
const api = this.getApi()
const dnode = Dnode(api, {
weak: false,
})
const dnode = Dnode(api)
// report new active controller connection
this.activeControllerConnections++
this.emit('controllerConnectionChanged', this.activeControllerConnections)
@ -1437,8 +1619,10 @@ module.exports = class MetamaskController extends EventEmitter {
this.activeControllerConnections--
this.emit('controllerConnectionChanged', this.activeControllerConnections)
// report any error
if (err) log.error(err)
}
if (err) {
log.error(err)
}
},
)
dnode.on('remote', (remote) => {
// push updates to popup
@ -1452,9 +1636,57 @@ module.exports = class MetamaskController extends EventEmitter {
/**
* A method for serving our ethereum provider over a given stream.
* @param {*} outStream - The stream to provide over.
* @param {string} origin - The URI of the requesting resource.
* @param {MessageSender} sender - The sender of the messages on this stream
* @param {boolean} isInternal - True if this is a connection with an internal process
*/
setupProviderConnection (outStream, origin) {
setupProviderConnection (outStream, sender, isInternal) {
const origin = isInternal
? 'metamask'
: (new URL(sender.url)).hostname
let extensionId
if (sender.id !== extension.runtime.id) {
extensionId = sender.id
}
let tabId
if (sender.tab && sender.tab.id) {
tabId = sender.tab.id
}
const engine = this.setupProviderEngine({ origin, location: sender.url, extensionId, tabId })
// setup connection
const providerStream = createEngineStream({ engine })
const connectionId = this.addConnection(origin, { engine })
pump(
outStream,
providerStream,
outStream,
(err) => {
// handle any middleware cleanup
engine._middleware.forEach((mid) => {
if (mid.destroy && typeof mid.destroy === 'function') {
mid.destroy()
}
})
connectionId && this.removeConnection(origin, connectionId)
if (err) {
log.error(err)
}
},
)
}
/**
* A method for creating a provider that is safely restricted for the requesting domain.
* @param {Object} options - Provider engine options
* @param {string} options.origin - The hostname of the sender
* @param {string} options.location - The full URL of the sender
* @param {extensionId} [options.extensionId] - The extension ID of the sender, if the sender is an external extension
* @param {tabId} [options.tabId] - The tab ID of the sender - if the sender is within a tab
**/
setupProviderEngine ({ origin, location, extensionId, tabId }) {
// setup json rpc engine stack
const engine = new RpcEngine()
const provider = this.provider
@ -1462,12 +1694,18 @@ module.exports = class MetamaskController extends EventEmitter {
// create filter polyfill middleware
const filterMiddleware = createFilterMiddleware({ provider, blockTracker })
// create subscription polyfill middleware
const subscriptionManager = createSubscriptionManager({ provider, blockTracker })
subscriptionManager.events.on('notification', (message) => engine.emit('notification', message))
// metadata
// append origin to each request
engine.push(createOriginMiddleware({ origin }))
// append tabId to each request if it exists
if (tabId) {
engine.push(createTabIdMiddleware({ tabId }))
}
// logging
engine.push(createLoggerMiddleware({ origin }))
// filter and subscription polyfills
engine.push(filterMiddleware)
@ -1479,21 +1717,8 @@ module.exports = class MetamaskController extends EventEmitter {
engine.push(this.createTypedDataMiddleware('eth_signTypedData_v1', 'V1').bind(this))
engine.push(this.createTypedDataMiddleware('eth_signTypedData_v3', 'V3', true).bind(this))
// forward to metamask primary provider
engine.push(createProviderMiddleware({ provider }))
// setup connection
const providerStream = createEngineStream({ engine })
pump(
outStream,
providerStream,
outStream,
(err) => {
// cleanup filter polyfill middleware
filterMiddleware.destroy()
if (err) log.error(err)
}
)
engine.push(providerAsMiddleware(provider))
return engine
}
/**
@ -1507,17 +1732,113 @@ module.exports = class MetamaskController extends EventEmitter {
* @param {*} outStream - The stream to provide public config over.
*/
setupPublicConfig (outStream) {
const configStream = asStream(this.publicConfigStore)
const configStore = this.createPublicConfigStore()
const configStream = asStream(configStore)
pump(
configStream,
outStream,
(err) => {
configStore.destroy()
configStream.destroy()
if (err) log.error(err)
}
if (err) {
log.error(err)
}
},
)
}
/**
* Adds a reference to a connection by origin. Ignores the 'metamask' origin.
* Caller must ensure that the returned id is stored such that the reference
* can be deleted later.
*
* @param {string} origin - The connection's origin string.
* @param {Object} options - Data associated with the connection
* @param {Object} options.engine - The connection's JSON Rpc Engine
* @returns {string} - The connection's id (so that it can be deleted later)
*/
addConnection (origin, { engine }) {
if (origin === 'metamask') {
return null
}
if (!this.connections[origin]) {
this.connections[origin] = {}
}
const id = nanoid()
this.connections[origin][id] = {
engine,
}
return id
}
/**
* Deletes a reference to a connection, by origin and id.
* Ignores unknown origins.
*
* @param {string} origin - The connection's origin string.
* @param {string} id - The connection's id, as returned from addConnection.
*/
removeConnection (origin, id) {
const connections = this.connections[origin]
if (!connections) {
return
}
delete connections[id]
if (Object.keys(connections.length === 0)) {
delete this.connections[origin]
}
}
/**
* Causes the RPC engines associated with the connections to the given origin
* to emit a notification event with the given payload.
* Does nothing if the extension is locked or the origin is unknown.
*
* @param {string} origin - The connection's origin string.
* @param {any} payload - The event payload.
*/
notifyConnections (origin, payload) {
const { isUnlocked } = this.getState()
const connections = this.connections[origin]
if (!isUnlocked || !connections) {
return
}
Object.values(connections).forEach((conn) => {
conn.engine && conn.engine.emit('notification', payload)
})
}
/**
* Causes the RPC engines associated with all connections to emit a
* notification event with the given payload.
* Does nothing if the extension is locked.
*
* @param {any} payload - The event payload.
*/
notifyAllConnections (payload) {
const { isUnlocked } = this.getState()
if (!isUnlocked) {
return
}
Object.values(this.connections).forEach((origin) => {
Object.values(origin).forEach((conn) => {
conn.engine && conn.engine.emit('notification', payload)
})
})
}
/**
* Handle a KeyringController update
* @param {object} state the KC state
@ -1554,6 +1875,13 @@ module.exports = class MetamaskController extends EventEmitter {
this.emit('update', this.getState())
}
/**
* @returns {boolean} Whether the extension is unlocked.
*/
isUnlocked () {
return this.keyringController.memStore.getState().isUnlocked
}
/**
* A method for estimating a good gas price
* For ETH, ETC: from gas price oracles
@ -1787,6 +2115,20 @@ module.exports = class MetamaskController extends EventEmitter {
}
}
/**
* Sets whether or not to use phishing detection.
* @param {boolean} val
* @param {Function} cb
*/
setUsePhishDetect (val, cb) {
try {
this.preferencesController.setUsePhishDetect(val)
cb(null)
} catch (err) {
cb(err)
}
}
/**
* A method for setting a user's current locale, affecting the language rendered.
* @param {string} key - Locale identifier.
@ -1837,7 +2179,7 @@ module.exports = class MetamaskController extends EventEmitter {
*/
set isClientOpen (open) {
this._isClientOpen = open
this.isClientOpenAndUnlocked = this.getState().isUnlocked && open
this.isClientOpenAndUnlocked = this.isUnlocked() && open
this.detectTokensController.isOpen = open
}
@ -1881,11 +2223,11 @@ module.exports = class MetamaskController extends EventEmitter {
}
}
/**
* Adds a domain to the {@link BlacklistController} whitelist
* @param {string} hostname the domain to whitelist
/**
* Adds a domain to the PhishingController whitelist
* @param {string} hostname - the domain to whitelist
*/
whitelistPhishingDomain (hostname) {
return this.blacklistController.whitelistDomain(hostname)
return this.phishingController.bypass(hostname)
}
}

View File

@ -1,6 +1,7 @@
const extension = require('extensionizer')
import extension from 'extensionizer'
const explorerLinks = require('eth-net-props').explorerLinks
const { capitalizeFirstLetter } = require('../lib/util')
const { capitalizeFirstLetter, getEnvironmentType, checkForError } = require('../lib/util')
const { ENVIRONMENT_TYPE_BACKGROUND } = require('../lib/enums')
class ExtensionPlatform {
@ -57,6 +58,9 @@ class ExtensionPlatform {
extensionURL += `#${route}`
}
this.openWindow({ url: extensionURL })
if (getEnvironmentType() !== ENVIRONMENT_TYPE_BACKGROUND) {
window.close()
}
}
getPlatformInfo (cb) {
@ -70,15 +74,57 @@ class ExtensionPlatform {
}
showTransactionNotification (txMeta) {
const { status, txReceipt: { status: receiptStatus } = {} } = txMeta
const status = txMeta.status
if (status === 'confirmed') {
this._showConfirmedTransaction(txMeta)
// There was an on-chain failure
receiptStatus === '0x0'
? this._showFailedTransaction(txMeta, 'Transaction encountered an error.')
: this._showConfirmedTransaction(txMeta)
} else if (status === 'failed') {
this._showFailedTransaction(txMeta)
}
}
currentTab () {
return new Promise((resolve, reject) => {
extension.tabs.getCurrent((tab) => {
const err = checkForError()
if (err) {
reject(err)
} else {
resolve(tab)
}
})
})
}
switchToTab (tabId) {
return new Promise((resolve, reject) => {
extension.tabs.update(tabId, { highlighted: true }, (tab) => {
const err = checkForError()
if (err) {
reject(err)
} else {
resolve(tab)
}
})
})
}
closeTab (tabId) {
return new Promise((resolve, reject) => {
extension.tabs.remove(tabId, () => {
const err = checkForError()
if (err) {
reject(err)
} else {
resolve()
}
})
})
}
_showConfirmedTransaction (txMeta) {
this._subscribeToNotificationClicked()
@ -91,11 +137,11 @@ class ExtensionPlatform {
this._showNotification(title, message, url)
}
_showFailedTransaction (txMeta) {
_showFailedTransaction (txMeta, errorMessage) {
const nonce = parseInt(txMeta.txParams.nonce, 16)
const title = 'Failed transaction'
const message = `Transaction ${nonce} failed! ${capitalizeFirstLetter(txMeta.err.message)}`
const message = `Transaction ${nonce} failed! ${errorMessage || capitalizeFirstLetter(txMeta.err.message)}`
this._showNotification(title, message)
}
@ -103,10 +149,10 @@ class ExtensionPlatform {
extension.notifications.create(
url,
{
'type': 'basic',
'title': title,
'iconUrl': extension.extension.getURL('../../images/icon-64.png'),
'message': message,
'type': 'basic',
'title': title,
'iconUrl': extension.extension.getURL('../../images/icon-64.png'),
'message': message,
})
}
@ -131,7 +177,6 @@ class ExtensionPlatform {
url: explorerLinks.getExplorerTxLinkFor(hash, networkId),
}
}
}
module.exports = ExtensionPlatform

View File

@ -151,6 +151,6 @@ function startApp () {
}),
]),
]
],
), container)
}

View File

@ -90,7 +90,7 @@ function startApp () {
}),
]),
]
],
), container)
}

View File

@ -10,7 +10,7 @@ const loggerMiddleware = createLogger()
const createStoreWithMiddleware = applyMiddleware(
thunkMiddleware,
loggerMiddleware
loggerMiddleware,
)(createStore)
function configureStore (initialState) {

View File

@ -121,10 +121,10 @@ async function startContainer (fileRegEx, testGenerator) {
await promisify(fs.writeFile)(
`${__dirname}/${sRootPath}tests/${testFilePath}`,
containerTest,
'utf8'
'utf8',
)
}
}
},
)
}, (err) => {
console.log('123', err)

View File

@ -200,16 +200,16 @@ gulp.task('copy',
gulp.parallel(...copyTaskNames),
'manifest:production',
'manifest:chrome',
'manifest:opera'
)
'manifest:opera',
),
)
gulp.task('dev:copy',
gulp.series(
gulp.parallel(...copyDevTaskNames),
'manifest:chrome',
'manifest:opera'
)
'manifest:opera',
),
)
gulp.task('lint-scss', function () {
@ -328,9 +328,9 @@ gulp.task('dev',
'dev:extension:js',
'dev:mascara:js',
'dev:copy',
'dev:reload'
)
)
'dev:reload',
),
),
)
gulp.task('dev:extension',
@ -339,9 +339,9 @@ gulp.task('dev:extension',
gulp.parallel(
'dev:extension:js',
'dev:copy',
'dev:reload'
)
)
'dev:reload',
),
),
)
gulp.task('dev:mascara',
@ -350,9 +350,9 @@ gulp.task('dev:mascara',
gulp.parallel(
'dev:mascara:js',
'dev:copy',
'dev:reload'
)
)
'dev:reload',
),
),
)
gulp.task('build',
@ -361,9 +361,9 @@ gulp.task('build',
gulpParallel(
'build:extension:js',
'build:mascara:js',
'copy'
)
)
'copy',
),
),
)
gulp.task('build:extension',
@ -371,9 +371,9 @@ gulp.task('build:extension',
'clean',
gulp.parallel(
'build:extension:js',
'copy'
)
)
'copy',
),
),
)
gulp.task('build:mascara',
@ -381,16 +381,16 @@ gulp.task('build:mascara',
'clean',
gulp.parallel(
'build:mascara:js',
'copy'
)
)
'copy',
),
),
)
gulp.task('dist',
gulp.series(
'build',
'zip'
)
'zip',
),
)
// task generators

View File

@ -194,5 +194,5 @@ export default connect(
dispatch => ({
goToCoinbase: address => dispatch(buyEth({ network: '1', address, amount: 0 })),
showAccountDetail: address => dispatch(showAccountDetail(address)),
})
}),
)(BuyEtherWidget)

View File

@ -196,5 +196,5 @@ export default connect(
dispatch => ({
goToCoinbase: address => dispatch(buyEth({ network: '1', address, amount: 0 })),
showAccountDetail: address => dispatch(showAccountDetail(address)),
})
}),
)(BuyEtherScreen)

View File

@ -157,6 +157,6 @@ export default compose(
dispatch => ({
confirmSeedWords: () => dispatch(confirmSeedWords()),
openBuyEtherModal: () => dispatch(showModal({ name: 'DEPOSIT_ETHER'})),
})
)
}),
),
)(ConfirmSeedScreen)

View File

@ -210,6 +210,6 @@ export default compose(
mapStateToProps,
dispatch => ({
createAccount: password => dispatch(createNewVaultAndKeychain(password)),
})
)
}),
),
)(CreatePasswordScreen)

View File

@ -210,5 +210,5 @@ export default connect(
dispatch => ({
importNewAccount: (strategy, args) => dispatch(importNewAccount(strategy, args)),
hideWarning: () => dispatch(hideWarning()),
})
}),
)(ImportAccountScreen)

View File

@ -186,5 +186,5 @@ export default connect(
dispatch(unMarkPasswordForgotten())
},
createNewVaultAndRestore: (pw, seed) => dispatch(createNewVaultAndRestore(pw, seed)),
})
}),
)(ImportSeedPhraseScreen)

View File

@ -95,5 +95,5 @@ const mapStateToProps = ({ metamask }) => {
export default compose(
withRouter,
connect(mapStateToProps)
connect(mapStateToProps),
)(FirstTimeFlow)

View File

@ -130,6 +130,6 @@ export default compose(
mapStateToProps,
dispatch => ({
markNoticeRead: notice => dispatch(markNoticeRead(notice)),
})
)
}),
),
)(NoticeScreen)

View File

@ -171,6 +171,6 @@ export default compose(
seedWords,
isLoading,
address: selectedAddress,
})
)
}),
),
)(BackupPhraseScreen)

View File

@ -45,6 +45,6 @@ export default compose(
connect(
({ metamask: { selectedAddress } }) => ({
address: selectedAddress,
})
)
}),
),
)(UniqueImageScreen)

View File

@ -214,5 +214,5 @@ export default connect(
shapeShiftSubview: () => dispatch(shapeShiftSubview()),
pairUpdate: coin => dispatch(pairUpdate(coin)),
buyWithShapeShift: data => dispatch(buyWithShapeShift(data)),
})
}),
)(ShapeShiftForm)

View File

@ -144,7 +144,7 @@ AccountDetailScreen.prototype.render = function () {
}, [
identity && identity.name,
]),
]
],
),
h(
AccountDropdowns,
@ -161,7 +161,7 @@ AccountDetailScreen.prototype.render = function () {
enableAccountOptions: true,
},
),
]
],
),
]),
h('.flex-row', {

View File

@ -50,7 +50,7 @@ const { getNetworkID } = require('./util')
module.exports = compose(
withRouter,
connect(mapStateToProps)
connect(mapStateToProps),
)(App)
inherits(App, Component)

View File

@ -92,7 +92,7 @@ ExportAccountView.prototype.render = function () {
}},
[
h('div.error', this.props.warning.split('-')),
]
],
)
),
])
@ -134,7 +134,7 @@ ExportAccountView.prototype.render = function () {
},
}, h(CopyButton, {
value: accountDetail.privateKey,
})
}),
),
]),
h('div', {

View File

@ -12,5 +12,5 @@ const mapStateToProps = state => {
export default compose(
connect(mapStateToProps),
withTokenTracker
withTokenTracker,
)(TokenBalance)

View File

@ -96,7 +96,7 @@ class AccountList extends Component {
>{`${a.balance}`}</span>
</label>
</div>
</div>
</div>,
)
})

View File

@ -40,7 +40,7 @@ RadioList.prototype.render = function () {
props.onClick(event)
},
})
})
}),
),
h('.text', {},
labels.map((label) => {
@ -52,7 +52,7 @@ RadioList.prototype.render = function () {
} else {
return h('.radio-titles.font-pre-medium', label)
}
})
}),
),
])
)

View File

@ -218,7 +218,7 @@ PendingTx.prototype.render = function () {
},
onClick: () => props.dispatch(actions.nextTx()),
}),
])]
])],
),
h(MiniAccountPanel, {

View File

@ -267,7 +267,7 @@ class SendTransactionScreen extends PersistentForm {
const abi = require('ethereumjs-abi')
return TOKEN_TRANSFER_FUNCTION_SIGNATURE + Array.prototype.map.call(
abi.rawEncode(['address', 'uint256'], [toAddress, ethUtil.addHexPrefix(amount)]),
x => ('00' + x.toString(16)).slice(-2)
x => ('00' + x.toString(16)).slice(-2),
).join('')
}
@ -305,7 +305,7 @@ const mapDispatchToProps = dispatch => {
toAddress,
tokensValueWithDec,
txParams,
confTxScreenParams
confTxScreenParams,
) => dispatch(actions.signTokenTx(tokenAddress, toAddress, tokensValueWithDec, txParams, confTxScreenParams)),
}
}

View File

@ -66,7 +66,7 @@ TokenCell.prototype.render = function () {
})
},
},
this.renderTokenOptions(menuToTop, ind)
this.renderTokenOptions(menuToTop, ind),
),
/*
@ -147,7 +147,7 @@ TokenCell.prototype.renderTokenOptions = function (menuToTop, ind) {
},
'Remove',
),
]
],
)
}

View File

@ -318,7 +318,7 @@ class ConfigScreen extends Component {
defaultValue: currentCurrency,
}, infuraCurrencies.map((currency) => {
return h('option', {key: currency.quote.code, value: currency.quote.code}, `${currency.quote.code.toUpperCase()} - ${currency.quote.name}`)
})
}),
),
])
}

10690
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -111,15 +111,17 @@
"eth-block-tracker": "^4.4.2",
"eth-contract-metadata": "^1.12.1",
"eth-ens-namehash": "^2.0.8",
"eth-json-rpc-errors": "^2.0.2",
"eth-json-rpc-filters": "github:poanetwork/eth-json-rpc-filters#3.0.2",
"eth-json-rpc-infura": "^4.0.2",
"eth-json-rpc-middleware": "^4.4.1",
"eth-keychain-controller": "github:vbaranov/KeyringController#5.1.0",
"eth-ledger-bridge-keyring": "github:vbaranov/eth-ledger-bridge-keyring#0.1.0-clear-accounts-flag",
"eth-method-registry": "^1.0.0",
"eth-net-props": "^1.0.33",
"eth-phishing-detect": "^1.1.4",
"eth-query": "^2.1.2",
"eth-sig-util": "^2.2.0",
"eth-sig-util": "^2.3.0",
"eth-token-watcher": "^1.1.7",
"eth-trezor-keyring": "github:vbaranov/eth-trezor-keyring#0.4.0",
"ethereumjs-abi": "^0.6.7",
@ -137,6 +139,7 @@
"fast-json-patch": "^2.0.4",
"fast-levenshtein": "^2.0.6",
"fuse.js": "^3.2.0",
"gaba": "^1.9.3",
"human-standard-token-abi": "^2.0.0",
"idb-global": "^2.1.0",
"iframe-stream": "^3.0.0",
@ -152,7 +155,8 @@
"metamascara": "^2.0.0",
"mkdirp": "^0.5.1",
"multihashes": "^0.4.12",
"nifty-wallet-inpage-provider": "git+ssh://git@github.com/poanetwork/nifty-wallet-inpage-provider.git#1.2.3",
"nanoid": "^2.1.6",
"nifty-wallet-inpage-provider": "github:poanetwork/nifty-wallet-inpage-provider#1.2.3",
"number-to-bn": "^1.7.0",
"obj-multiplex": "^1.0.0",
"obs-store": "^4.0.3",
@ -192,6 +196,7 @@
"request-promise": "^4.2.1",
"reselect": "^3.0.1",
"rockicon": "^1.0.0",
"rpc-cap": "^2.0.0",
"sandwich-expando": "^1.1.3",
"semaphore": "^1.0.5",
"semver": "^5.4.1",
@ -244,7 +249,6 @@
"eslint-plugin-json": "^1.2.0",
"eslint-plugin-mocha": "^6.2.2",
"eslint-plugin-react": "^7.18.3",
"eth-json-rpc-middleware": "^3.1.3",
"expect": "^25.0.0",
"fetch-mock": "^6.5.2",
"file-loader": "^1.1.11",

View File

@ -61,7 +61,7 @@ class Driver {
this.driver.wait(until.elementIsEnabled(element), this.timeout),
)
return acc
}, [])
}, []),
)
return elements
}

View File

@ -22,5 +22,5 @@ pump(
if (err) throw err
console.log(`Integration test build completed: "${bundlePath}"`)
process.exit(0)
}
},
)

View File

@ -25,7 +25,7 @@ async function runAddTokenFlowTest (assert, done) {
// Used to set values on TextField input component
const nativeInputValueSetter = Object.getOwnPropertyDescriptor(
window.HTMLInputElement.prototype, 'value'
window.HTMLInputElement.prototype, 'value',
).set
// Check that no tokens have been added

View File

@ -12,7 +12,7 @@ async function runFirstTimeUsageTest (assert, done) {
// Used to set values on TextField input component
const nativeInputValueSetter = Object.getOwnPropertyDescriptor(
window.HTMLInputElement.prototype, 'value'
window.HTMLInputElement.prototype, 'value',
).set
// const loader = (await findAsync($, '.loading-overlay'))[0]

View File

@ -42,13 +42,13 @@ async function customizeGas (assert, price, limit, ethFee, usdFee) {
assert.equal(
(await findAsync(sendGasField, '.currency-display-component'))[0].textContent,
ethFee,
'send gas field should show customized gas total'
'send gas field should show customized gas total',
)
assert.equal(
(await findAsync(sendGasField, '.currency-display__converted-value'))[0].textContent,
usdFee,
'send gas field should show customized gas total converted to USD'
'send gas field should show customized gas total converted to USD',
)
}

View File

@ -10,7 +10,7 @@ const loggerMiddleware = createLogger()
const createStoreWithMiddleware = applyMiddleware(
thunkMiddleware,
loggerMiddleware
loggerMiddleware,
)(createStore)
function configureStore (initialState) {

View File

@ -1,13 +1,23 @@
const assert = require('assert')
const sinon = require('sinon')
const clone = require('clone')
const nock = require('nock')
const createThoughStream = require('through2').obj
import assert from 'assert'
import sinon from 'sinon'
import { cloneDeep } from 'lodash'
import nock from 'nock'
import { obj as createThoughStream } from 'through2'
const blacklistJSON = require('eth-phishing-detect/src/config')
const MetaMaskController = require('../../../../app/scripts/metamask-controller')
const firstTimeState = require('../../../unit/localhostState')
const createTxMeta = require('../../../lib/createTxMeta')
const EthQuery = require('eth-query')
import EthQuery from 'eth-query'
import proxyquire from 'proxyquire'
const ExtensionizerMock = {
runtime: {
id: 'fake-extension-id',
},
}
const MetaMaskController = proxyquire('../../../../app/scripts/metamask-controller', {
'extensionizer': ExtensionizerMock,
})
const currentNetworkId = 42
const DEFAULT_LABEL = 'Account 1'
@ -57,7 +67,7 @@ describe('MetaMaskController', function () {
return Promise.resolve(this.object)
},
},
initState: clone(firstTimeState),
initState: cloneDeep(firstTimeState),
})
// disable diagnostics
metamaskController.diagnostics = null
@ -372,7 +382,7 @@ describe('MetaMaskController', function () {
sinon.spy(metamaskController.keyringController, 'addNewKeyring')
await metamaskController.connectHardware('trezor', 0).catch((e) => null)
const keyrings = await metamaskController.keyringController.getKeyringsByType(
'Trezor Hardware'
'Trezor Hardware',
)
assert.equal(metamaskController.keyringController.addNewKeyring.getCall(0).args, 'Trezor Hardware')
assert.equal(keyrings.length, 1)
@ -382,7 +392,7 @@ describe('MetaMaskController', function () {
sinon.spy(metamaskController.keyringController, 'addNewKeyring')
await metamaskController.connectHardware('ledger', 0).catch((e) => null)
const keyrings = await metamaskController.keyringController.getKeyringsByType(
'Ledger Hardware'
'Ledger Hardware',
)
assert.equal(metamaskController.keyringController.addNewKeyring.getCall(0).args, 'Ledger Hardware')
assert.equal(keyrings.length, 1)
@ -419,7 +429,7 @@ describe('MetaMaskController', function () {
await metamaskController.connectHardware('trezor', 0).catch((e) => null)
await metamaskController.forgetDevice('trezor')
const keyrings = await metamaskController.keyringController.getKeyringsByType(
'Trezor Hardware'
'Trezor Hardware',
)
assert.deepEqual(keyrings[0].accounts, [])
@ -468,7 +478,7 @@ describe('MetaMaskController', function () {
it('should set unlockedAccount in the keyring', async function () {
const keyrings = await metamaskController.keyringController.getKeyringsByType(
'Trezor Hardware'
'Trezor Hardware',
)
assert.equal(keyrings[0].unlockedAccount, accountToUnlock)
})
@ -821,47 +831,104 @@ describe('MetaMaskController', function () {
})
describe('#setupUntrustedCommunication', function () {
let streamTest
it('sets up phishing stream for untrusted communication ', async function () {
const phishingMessageSender = {
url: 'http://myethereumwalletntw.com',
tab: {},
}
await metamaskController.phishingController.updatePhishingLists()
const phishingUrl = 'myethereumwalletntw.com'
afterEach(function () {
streamTest.end()
})
it('sets up phishing stream for untrusted communication ', async () => {
await metamaskController.blacklistController.updatePhishingList()
console.log(blacklistJSON.blacklist.includes(phishingUrl))
const { promise, resolve } = deferredPromise()
streamTest = createThoughStream((chunk, enc, cb) => {
if (chunk.name !== 'phishing') return cb()
assert.equal(chunk.data.hostname, phishingUrl)
const streamTest = createThoughStream((chunk, _, cb) => {
if (chunk.name !== 'phishing') {
return cb()
}
assert.equal(chunk.data.hostname, (new URL(phishingMessageSender.url)).hostname)
resolve()
cb()
})
metamaskController.setupUntrustedCommunication(streamTest, phishingUrl)
metamaskController.setupUntrustedCommunication(streamTest, phishingMessageSender)
await promise
streamTest.end()
})
it('adds a tabId and origin to requests', function (done) {
const messageSender = {
url: 'http://mycrypto.com',
tab: { id: 456 },
}
const streamTest = createThoughStream((chunk, _, cb) => {
if (chunk.data && chunk.data.method) {
cb(null, chunk)
} else {
cb()
}
})
metamaskController.setupUntrustedCommunication(streamTest, messageSender)
const message = {
id: 1999133338649204,
jsonrpc: '2.0',
params: ['mock tx params'],
method: 'eth_sendTransaction',
}
streamTest.write({
name: 'provider',
data: message,
}, null, () => {
done()
})
})
it('should add only origin to request if tabId not provided', function (done) {
const messageSender = {
url: 'http://mycrypto.com',
}
const streamTest = createThoughStream((chunk, _, cb) => {
if (chunk.data && chunk.data.method) {
cb(null, chunk)
} else {
cb()
}
})
metamaskController.setupUntrustedCommunication(streamTest, messageSender)
const message = {
id: 1999133338649204,
jsonrpc: '2.0',
params: ['mock tx params'],
method: 'eth_sendTransaction',
}
streamTest.write({
name: 'provider',
data: message,
}, null, () => {
done()
})
})
})
describe('#setupTrustedCommunication', function () {
let streamTest
afterEach(function () {
streamTest.end()
})
it('sets up controller dnode api for trusted communication', function (done) {
streamTest = createThoughStream((chunk, enc, cb) => {
it('sets up controller dnode api for trusted communication', async function () {
const messageSender = {
url: 'http://mycrypto.com',
tab: {},
}
const { promise, resolve } = deferredPromise()
const streamTest = createThoughStream((chunk, _, cb) => {
assert.equal(chunk.name, 'controller')
resolve()
cb()
done()
})
metamaskController.setupTrustedCommunication(streamTest, 'mycrypto.com')
metamaskController.setupTrustedCommunication(streamTest, messageSender)
await promise
streamTest.end()
})
})
@ -964,6 +1031,8 @@ describe('MetaMaskController', function () {
function deferredPromise () {
let resolve
const promise = new Promise(_resolve => { resolve = _resolve })
const promise = new Promise((_resolve) => {
resolve = _resolve
})
return { promise, resolve }
}

View File

@ -1,5 +1,5 @@
const assert = require('assert')
const MessageManager = require('../../../app/scripts/lib/message-manager')
import assert from 'assert'
import MessageManager from '../../../app/scripts/lib/message-manager'
describe('Message Manager', function () {
let messageManager
@ -10,7 +10,7 @@ describe('Message Manager', function () {
describe('#getMsgList', function () {
it('when new should return empty array', function () {
var result = messageManager.messages
const result = messageManager.messages
assert.ok(Array.isArray(result))
assert.equal(result.length, 0)
})
@ -21,9 +21,9 @@ describe('Message Manager', function () {
describe('#addMsg', function () {
it('adds a Msg returned in getMsgList', function () {
var Msg = { id: 1, status: 'approved', metamaskNetworkId: 'unit test' }
const Msg = { id: 1, status: 'approved', metamaskNetworkId: 'unit test' }
messageManager.addMsg(Msg)
var result = messageManager.messages
const result = messageManager.messages
assert.ok(Array.isArray(result))
assert.equal(result.length, 1)
assert.equal(result[0].id, 1)
@ -32,10 +32,10 @@ describe('Message Manager', function () {
describe('#setMsgStatusApproved', function () {
it('sets the Msg status to approved', function () {
var Msg = { id: 1, status: 'unapproved', metamaskNetworkId: 'unit test' }
const Msg = { id: 1, status: 'unapproved', metamaskNetworkId: 'unit test' }
messageManager.addMsg(Msg)
messageManager.setMsgStatusApproved(1)
var result = messageManager.messages
const result = messageManager.messages
assert.ok(Array.isArray(result))
assert.equal(result.length, 1)
assert.equal(result[0].status, 'approved')
@ -44,10 +44,10 @@ describe('Message Manager', function () {
describe('#rejectMsg', function () {
it('sets the Msg status to rejected', function () {
var Msg = { id: 1, status: 'unapproved', metamaskNetworkId: 'unit test' }
const Msg = { id: 1, status: 'unapproved', metamaskNetworkId: 'unit test' }
messageManager.addMsg(Msg)
messageManager.rejectMsg(1)
var result = messageManager.messages
const result = messageManager.messages
assert.ok(Array.isArray(result))
assert.equal(result.length, 1)
assert.equal(result[0].status, 'rejected')
@ -59,7 +59,7 @@ describe('Message Manager', function () {
messageManager.addMsg({ id: '1', status: 'unapproved', metamaskNetworkId: 'unit test' })
messageManager.addMsg({ id: '2', status: 'approved', metamaskNetworkId: 'unit test' })
messageManager._updateMsg({ id: '1', status: 'blah', hash: 'foo', metamaskNetworkId: 'unit test' })
var result = messageManager.getMsg('1')
const result = messageManager.getMsg('1')
assert.equal(result.hash, 'foo')
})
})

View File

@ -1,6 +1,5 @@
const assert = require('assert')
const PersonalMessageManager = require('../../../app/scripts/lib/personal-message-manager')
import assert from 'assert'
import PersonalMessageManager from '../../../app/scripts/lib/personal-message-manager'
describe('Personal Message Manager', function () {
let messageManager
@ -11,7 +10,7 @@ describe('Personal Message Manager', function () {
describe('#getMsgList', function () {
it('when new should return empty array', function () {
var result = messageManager.messages
const result = messageManager.messages
assert.ok(Array.isArray(result))
assert.equal(result.length, 0)
})
@ -22,9 +21,9 @@ describe('Personal Message Manager', function () {
describe('#addMsg', function () {
it('adds a Msg returned in getMsgList', function () {
var Msg = { id: 1, status: 'approved', metamaskNetworkId: 'unit test' }
const Msg = { id: 1, status: 'approved', metamaskNetworkId: 'unit test' }
messageManager.addMsg(Msg)
var result = messageManager.messages
const result = messageManager.messages
assert.ok(Array.isArray(result))
assert.equal(result.length, 1)
assert.equal(result[0].id, 1)
@ -33,10 +32,10 @@ describe('Personal Message Manager', function () {
describe('#setMsgStatusApproved', function () {
it('sets the Msg status to approved', function () {
var Msg = { id: 1, status: 'unapproved', metamaskNetworkId: 'unit test' }
const Msg = { id: 1, status: 'unapproved', metamaskNetworkId: 'unit test' }
messageManager.addMsg(Msg)
messageManager.setMsgStatusApproved(1)
var result = messageManager.messages
const result = messageManager.messages
assert.ok(Array.isArray(result))
assert.equal(result.length, 1)
assert.equal(result[0].status, 'approved')
@ -45,10 +44,10 @@ describe('Personal Message Manager', function () {
describe('#rejectMsg', function () {
it('sets the Msg status to rejected', function () {
var Msg = { id: 1, status: 'unapproved', metamaskNetworkId: 'unit test' }
const Msg = { id: 1, status: 'unapproved', metamaskNetworkId: 'unit test' }
messageManager.addMsg(Msg)
messageManager.rejectMsg(1)
var result = messageManager.messages
const result = messageManager.messages
assert.ok(Array.isArray(result))
assert.equal(result.length, 1)
assert.equal(result[0].status, 'rejected')
@ -60,7 +59,7 @@ describe('Personal Message Manager', function () {
messageManager.addMsg({ id: '1', status: 'unapproved', metamaskNetworkId: 'unit test' })
messageManager.addMsg({ id: '2', status: 'approved', metamaskNetworkId: 'unit test' })
messageManager._updateMsg({ id: '1', status: 'blah', hash: 'foo', metamaskNetworkId: 'unit test' })
var result = messageManager.getMsg('1')
const result = messageManager.getMsg('1')
assert.equal(result.hash, 'foo')
})
})
@ -87,20 +86,20 @@ describe('Personal Message Manager', function () {
describe('#normalizeMsgData', function () {
it('converts text to a utf8 hex string', function () {
var input = 'hello'
var output = messageManager.normalizeMsgData(input)
const input = 'hello'
const output = messageManager.normalizeMsgData(input)
assert.equal(output, '0x68656c6c6f', 'predictably hex encoded')
})
it('tolerates a hex prefix', function () {
var input = '0x12'
var output = messageManager.normalizeMsgData(input)
const input = '0x12'
const output = messageManager.normalizeMsgData(input)
assert.equal(output, '0x12', 'un modified')
})
it('tolerates normal hex', function () {
var input = '12'
var output = messageManager.normalizeMsgData(input)
const input = '12'
const output = messageManager.normalizeMsgData(input)
assert.equal(output, '0x12', 'adds prefix')
})
})

View File

@ -22,7 +22,7 @@ describe('ErrorComponent', () => {
wrapper = mount(
<Provider store={store}>
<ErrorComponent error="Error!"/>
</Provider>
</Provider>,
)
})
it('shows error', () => {
@ -35,7 +35,7 @@ describe('ErrorComponent', () => {
wrapper = mount(
<Provider store={store}>
<ErrorComponent/>
</Provider>
</Provider>,
)
})

View File

@ -49,7 +49,7 @@ describe('ChooseContractExecutor component', () => {
inputValues={[]}
txParams={{}}
/>
</Provider>
</Provider>,
)
})

View File

@ -31,7 +31,7 @@ describe('ExecutorCell component', () => {
}}
onClick={() => {}}
/>
</Provider>
</Provider>,
)
})

View File

@ -24,7 +24,7 @@ describe('SendHeader component', () => {
<SendHeader
title={'Execute Method'}
/>
</Provider>
</Provider>,
)
})
it('renders correct title', () => {

View File

@ -33,7 +33,7 @@ describe('SendProfile component', () => {
wrapper = mount(
<Provider store={store}>
<SendProfile/>
</Provider>
</Provider>,
)
})
it('shows identity name', () => {

View File

@ -55,7 +55,7 @@ describe('Dropdown components', function () {
closeMenu,
onClick,
}, 'Item 2'),
]
],
), store)
dropdownComponent = component
})

View File

@ -44,7 +44,7 @@ describe('Token Cell', () => {
currentCurrency={'usd'}
image={'./test-image'}
/>
</Provider>
</Provider>,
)
})

View File

@ -323,6 +323,8 @@ var actions = {
SET_DPROVIDER: 'SET_DPROVIDER',
setDProvider,
setUsePhishDetect,
// locale
SET_CURRENT_LOCALE: 'SET_CURRENT_LOCALE',
SET_LOCALE_MESSAGES: 'SET_LOCALE_MESSAGES',
@ -1863,7 +1865,7 @@ function addTokens (tokens) {
.entries(tokens)
.map(([_, { address, symbol, decimals, image, network }]) => (
dispatch(addToken(address, symbol, decimals, image, network))
))
)),
)
}
}
@ -2634,6 +2636,19 @@ function setUseBlockie (val) {
}
}
function setUsePhishDetect (val) {
return (dispatch) => {
dispatch(showLoadingIndication())
log.debug(`background.setUsePhishDetect`)
background.setUsePhishDetect(val, (err) => {
dispatch(hideLoadingIndication())
if (err) {
return dispatch(displayWarning(err.message))
}
})
}
}
function updateCurrentLocale (key) {
return (dispatch) => {
dispatch(actions.showLoadingIndication())

View File

@ -34,7 +34,7 @@ export default class Button extends Component {
'button',
typeHash[type],
large && CLASSNAME_LARGE,
className
className,
)}
{ ...buttonProps }
>

View File

@ -11,7 +11,7 @@ storiesOf('Button', module)
type="primary"
>
{text('text', 'Click me')}
</Button>
</Button>,
)
.add('secondary', () =>
<Button
@ -19,7 +19,7 @@ storiesOf('Button', module)
type="secondary"
>
{text('text', 'Click me')}
</Button>
</Button>,
)
.add('default', () => (
<Button

View File

@ -135,7 +135,7 @@ class AccountDropdowns extends Component {
]),
]),
]
],
)
})
}
@ -193,7 +193,7 @@ class AccountDropdowns extends Component {
style: {
marginLeft: '8px',
},
}
},
),
h('span', {
style: {
@ -226,7 +226,7 @@ class AccountDropdowns extends Component {
style: {
marginLeft: '8px',
},
}
},
),
h('span', {
style: {
@ -237,9 +237,9 @@ class AccountDropdowns extends Component {
lineHeight: '23px',
},
}, this.context.t('importAccount')),
]
],
),
]
],
)
}
@ -348,7 +348,7 @@ class AccountDropdowns extends Component {
this.context.t('addToken'),
),
]
],
)
}
@ -393,9 +393,9 @@ class AccountDropdowns extends Component {
})
},
},
this.renderAccountOptions()
this.renderAccountOptions(),
),
]
],
)
}
}

View File

@ -46,10 +46,10 @@ class Dropdown extends Component {
border-radius: 4px;
}
li.dropdown-menu-item { color: rgb(185, 185, 185); }
`
`,
),
...children,
]
],
)
}
}
@ -94,7 +94,7 @@ class DropdownMenuItem extends Component {
color: 'white',
}, style),
},
children
children,
)
}
}

View File

@ -30,7 +30,7 @@ Item.prototype.render = function () {
return children
? h('div', { className: itemClassName, onClick }, children)
: h('div.menu__item', { className: itemClassName, onClick }, [ iconComponent, textComponent ]
.filter(d => Boolean(d))
.filter(d => Boolean(d)),
)
}

View File

@ -26,6 +26,6 @@ NetworkDropdownIcon.prototype.render = function () {
height: `${diameter}px`,
width: `${diameter}px`,
},
})
}),
)
}

View File

@ -63,7 +63,7 @@ NetworkDropdown.contextTypes = {
module.exports = compose(
withRouter,
connect(mapStateToProps, mapDispatchToProps)
connect(mapStateToProps, mapDispatchToProps),
)(NetworkDropdown)
@ -110,7 +110,7 @@ NetworkDropdown.prototype.render = function () {
h('div.network-dropdown-content',
{},
this.context.t('defaultNetwork')
this.context.t('defaultNetwork'),
),
]),
@ -133,7 +133,7 @@ NetworkDropdown.prototype.render = function () {
color: providerType === 'mainnet' ? '#ffffff' : '#9b9b9b',
},
}, this.context.t('mainnet')),
]
],
),
h(
@ -155,7 +155,7 @@ NetworkDropdown.prototype.render = function () {
color: providerType === 'ropsten' ? '#ffffff' : '#9b9b9b',
},
}, this.context.t('ropsten')),
]
],
),
h(
@ -177,7 +177,7 @@ NetworkDropdown.prototype.render = function () {
color: providerType === 'kovan' ? '#ffffff' : '#9b9b9b',
},
}, this.context.t('kovan')),
]
],
),
h(
@ -199,7 +199,7 @@ NetworkDropdown.prototype.render = function () {
color: providerType === 'rinkeby' ? '#ffffff' : '#9b9b9b',
},
}, this.context.t('rinkeby')),
]
],
),
h(
@ -221,7 +221,7 @@ NetworkDropdown.prototype.render = function () {
color: providerType === 'poa' ? '#ffffff' : '#9b9b9b',
},
}, this.context.t('poa')),
]
],
),
h(
@ -243,7 +243,7 @@ NetworkDropdown.prototype.render = function () {
color: providerType === 'localhost' ? '#ffffff' : '#9b9b9b',
},
}, this.context.t('localhost')),
]
],
),
this.renderCustomOption(props.provider),
@ -267,7 +267,7 @@ NetworkDropdown.prototype.render = function () {
color: activeNetwork === 'custom' ? '#ffffff' : '#9b9b9b',
},
}, this.context.t('customRPC')),
]
],
),
])
@ -334,7 +334,7 @@ NetworkDropdown.prototype.renderCommonRpc = function (rpcList, provider) {
props.delRpcTarget(rpc)
},
}),
]
],
)
}
})
@ -372,7 +372,7 @@ NetworkDropdown.prototype.renderCustomOption = function (provider) {
color: '#ffffff',
},
}, rpcTarget),
]
],
)
}
}

View File

@ -77,7 +77,7 @@ class SimpleDropdown extends Component {
h('div.simple-dropdown__selected', this.getDisplayValue() || placeholder || 'Select'),
h('i.fa.fa-caret-down.fa-lg.simple-dropdown__caret'),
isOpen && this.renderOptions(),
]
],
)
}
}

View File

@ -16,7 +16,7 @@ describe('', () => {
style = {{test: 'style'}}
closeMenu = {closeMenuSpy}
>
</DropdownMenuItem>
</DropdownMenuItem>,
)
})

View File

@ -11,7 +11,7 @@ describe('Dropdown Menu Components', () => {
beforeEach(() => {
wrapper = shallow(
<Menu className = {'Test Class'} isShowing = {true}/>
<Menu className = {'Test Class'} isShowing = {true}/>,
)
})
@ -33,7 +33,7 @@ describe('Dropdown Menu Components', () => {
text = {'test text'}
className = {'test className'}
onClick = {onClickSpy}
/>
/>,
)
})

View File

@ -8,7 +8,7 @@ describe('ModalContent Component', () => {
const wrapper = shallow(
<ModalContent
title="Modal Title"
/>
/>,
)
assert.equal(wrapper.find('.modal-content__title').length, 1)
@ -20,7 +20,7 @@ describe('ModalContent Component', () => {
const wrapper = shallow(
<ModalContent
description="Modal Description"
/>
/>,
)
assert.equal(wrapper.find('.modal-content__title').length, 0)
@ -33,7 +33,7 @@ describe('ModalContent Component', () => {
<ModalContent
title="Modal Title"
description="Modal Description"
/>
/>,
)
assert.equal(wrapper.find('.modal-content__title').length, 1)

View File

@ -24,7 +24,7 @@ describe('Modal Component', () => {
cancelText="Cancel"
onSubmit={handleSubmit}
submitText="Submit"
/>
/>,
)
const buttons = wrapper.find(Button)
@ -54,7 +54,7 @@ describe('Modal Component', () => {
onSubmit={() => {}}
submitText="Submit"
submitType="confirm"
/>
/>,
)
const buttons = wrapper.find(Button)
@ -72,7 +72,7 @@ describe('Modal Component', () => {
submitText="Submit"
>
<div className="test-child" />
</Modal>
</Modal>,
)
assert.ok(wrapper.find('.test-class'))
@ -89,7 +89,7 @@ describe('Modal Component', () => {
submitText="Submit"
headerText="My Header"
onClose={handleCancel}
/>
/>,
)
assert.ok(wrapper.find('.modal-container__header'))

View File

@ -80,7 +80,7 @@ BuyOptions.prototype.render = function () {
this.renderModalContentOption(
this.context.t('directDeposit'),
this.context.t('depositFromAccount'),
() => this.goToAccountDetailsModal()
() => this.goToAccountDetailsModal(),
),
]),

View File

@ -78,7 +78,7 @@ ExportPrivateKeyModal.prototype.exportAccountAndGetPrivateKey = function (passwo
ExportPrivateKeyModal.prototype.renderPasswordLabel = function (privateKey) {
return h('span.private-key-password-label', privateKey
? this.context.t('copyPrivateKey')
: this.context.t('typePassword')
: this.context.t('typePassword'),
)
}

View File

@ -296,7 +296,7 @@ function generateTokenTransferData ({ toAddress = '0x0', amount = '0x0', selecte
if (!selectedToken) return
return TOKEN_TRANSFER_FUNCTION_SIGNATURE + Array.prototype.map.call(
abi.rawEncode(['address', 'uint256'], [toAddress, ethUtil.addHexPrefix(amount)]),
x => ('00' + x.toString(16)).slice(-2)
x => ('00' + x.toString(16)).slice(-2),
).join('')
}

View File

@ -7,41 +7,41 @@ storiesOf('TextField', module)
<TextField
label="Text"
type="text"
/>
/>,
)
.add('password', () =>
<TextField
label="Password"
type="password"
/>
/>,
)
.add('error', () =>
<TextField
type="text"
label="Name"
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
@ -49,5 +49,5 @@ storiesOf('TextField', module)
label="Name"
error="Invalid value"
material
/>
/>,
)

View File

@ -65,7 +65,7 @@ TokenCell.prototype.render = function () {
if (contractExchangeRates[address]) {
currentTokenToFiatRate = multiplyCurrencies(
contractExchangeRates[address],
conversionRate
conversionRate,
)
currentTokenInFiat = conversionUtil(string, {
fromNumericBase: 'dec',

View File

@ -70,7 +70,7 @@ const baseChange = {
const fromAndToCurrencyPropsNotEqual = R.compose(
R.not,
R.eqBy(R.__, 'fromCurrency', 'toCurrency'),
R.flip(R.prop)
R.flip(R.prop),
)
// Lens
@ -82,8 +82,8 @@ const whenPredSetCRWithPropAndSetter = (pred, prop, setter) => R.when(
pred,
R.converge(
conversionRateLens,
[R.pipe(R.prop(prop), setter), R.identity]
)
[R.pipe(R.prop(prop), setter), R.identity],
),
)
// conditional 'value' setting wrappers
@ -91,13 +91,13 @@ const whenPredSetWithPropAndSetter = (pred, prop, setter) => R.when(
pred,
R.converge(
valuePropertyLens,
[R.pipe(R.prop(prop), setter), R.identity]
)
[R.pipe(R.prop(prop), setter), R.identity],
),
)
const whenPropApplySetterMap = (prop, setterMap) => whenPredSetWithPropAndSetter(
R.prop(prop),
prop,
R.prop(R.__, setterMap)
R.prop(R.__, setterMap),
)
// Conversion utility function
@ -111,7 +111,7 @@ const converter = R.pipe(
whenPredSetWithPropAndSetter(R.prop('numberOfDecimals'), 'numberOfDecimals', round),
whenPredSetWithPropAndSetter(R.prop('roundDown'), 'roundDown', roundDown),
whenPropApplySetterMap('toNumericBase', baseChange),
R.view(R.lensProp('value'))
R.view(R.lensProp('value')),
)
const conversionUtil = (value, {
@ -209,7 +209,7 @@ const conversionMax = (
) => {
const firstIsGreater = conversionGreaterThan(
{ ...firstProps },
{ ...secondProps }
{ ...secondProps },
)
return firstIsGreater ? firstProps.value : secondProps.value

View File

@ -78,7 +78,7 @@ describe('Confirm Transaction Duck', () => {
it('should initialize state', () => {
assert.deepEqual(
ConfirmTransactionReducer({}),
initialState
initialState,
)
})
@ -106,7 +106,7 @@ describe('Confirm Transaction Duck', () => {
...mockState.confirmTransaction.txData,
id: 2,
},
}
},
)
})
@ -118,7 +118,7 @@ describe('Confirm Transaction Duck', () => {
{
...mockState.confirmTransaction,
txData: {},
}
},
)
})
@ -136,7 +136,7 @@ describe('Confirm Transaction Duck', () => {
...mockState.confirmTransaction.tokenData,
name: 'defToken',
},
}
},
)
})
@ -148,7 +148,7 @@ describe('Confirm Transaction Duck', () => {
{
...mockState.confirmTransaction,
tokenData: {},
}
},
)
})
@ -166,7 +166,7 @@ describe('Confirm Transaction Duck', () => {
...mockState.confirmTransaction.methodData,
name: 'transferFrom',
},
}
},
)
})
@ -178,7 +178,7 @@ describe('Confirm Transaction Duck', () => {
{
...mockState.confirmTransaction,
methodData: {},
}
},
)
})
@ -197,7 +197,7 @@ describe('Confirm Transaction Duck', () => {
fiatTransactionAmount: '123.45',
ethTransactionAmount: '.5',
hexTransactionAmount: '0x1',
}
},
)
})
@ -216,7 +216,7 @@ describe('Confirm Transaction Duck', () => {
fiatTransactionFee: '123.45',
ethTransactionFee: '.5',
hexTransactionFee: '0x1',
}
},
)
})
@ -235,7 +235,7 @@ describe('Confirm Transaction Duck', () => {
fiatTransactionTotal: '123.45',
ethTransactionTotal: '.5',
hexTransactionTotal: '0x1',
}
},
)
})
@ -254,7 +254,7 @@ describe('Confirm Transaction Duck', () => {
tokenSymbol: 'DEF',
tokenDecimals: '1',
},
}
},
)
})
@ -267,7 +267,7 @@ describe('Confirm Transaction Duck', () => {
{
...mockState.confirmTransaction,
nonce: '0x1',
}
},
)
})
@ -280,7 +280,7 @@ describe('Confirm Transaction Duck', () => {
{
...mockState.confirmTransaction,
toSmartContract: true,
}
},
)
})
@ -292,7 +292,7 @@ describe('Confirm Transaction Duck', () => {
{
...mockState.confirmTransaction,
fetchingData: true,
}
},
)
})
@ -303,7 +303,7 @@ describe('Confirm Transaction Duck', () => {
}),
{
fetchingData: false,
}
},
)
})
@ -314,7 +314,7 @@ describe('Confirm Transaction Duck', () => {
}),
{
...initialState,
}
},
)
})
})
@ -329,7 +329,7 @@ describe('Confirm Transaction Duck', () => {
assert.deepEqual(
actions.updateTxData(txData),
expectedAction
expectedAction,
)
})
@ -340,7 +340,7 @@ describe('Confirm Transaction Duck', () => {
assert.deepEqual(
actions.clearTxData(),
expectedAction
expectedAction,
)
})
@ -353,7 +353,7 @@ describe('Confirm Transaction Duck', () => {
assert.deepEqual(
actions.updateTokenData(tokenData),
expectedAction
expectedAction,
)
})
@ -364,7 +364,7 @@ describe('Confirm Transaction Duck', () => {
assert.deepEqual(
actions.clearTokenData(),
expectedAction
expectedAction,
)
})
@ -377,7 +377,7 @@ describe('Confirm Transaction Duck', () => {
assert.deepEqual(
actions.updateMethodData(methodData),
expectedAction
expectedAction,
)
})
@ -388,7 +388,7 @@ describe('Confirm Transaction Duck', () => {
assert.deepEqual(
actions.clearMethodData(),
expectedAction
expectedAction,
)
})
@ -401,7 +401,7 @@ describe('Confirm Transaction Duck', () => {
assert.deepEqual(
actions.updateTransactionAmounts(transactionAmounts),
expectedAction
expectedAction,
)
})
@ -414,7 +414,7 @@ describe('Confirm Transaction Duck', () => {
assert.deepEqual(
actions.updateTransactionFees(transactionFees),
expectedAction
expectedAction,
)
})
@ -427,7 +427,7 @@ describe('Confirm Transaction Duck', () => {
assert.deepEqual(
actions.updateTransactionTotals(transactionTotals),
expectedAction
expectedAction,
)
})
@ -443,7 +443,7 @@ describe('Confirm Transaction Duck', () => {
assert.deepEqual(
actions.updateTokenProps(tokenProps),
expectedAction
expectedAction,
)
})
@ -456,7 +456,7 @@ describe('Confirm Transaction Duck', () => {
assert.deepEqual(
actions.updateNonce(nonce),
expectedAction
expectedAction,
)
})
@ -467,7 +467,7 @@ describe('Confirm Transaction Duck', () => {
assert.deepEqual(
actions.setFetchingData(true),
expectedAction
expectedAction,
)
})
@ -478,7 +478,7 @@ describe('Confirm Transaction Duck', () => {
assert.deepEqual(
actions.setFetchingData(false),
expectedAction
expectedAction,
)
})
@ -489,7 +489,7 @@ describe('Confirm Transaction Duck', () => {
assert.deepEqual(
actions.clearConfirmTransaction(),
expectedAction
expectedAction,
)
})
})
@ -498,7 +498,7 @@ describe('Confirm Transaction Duck', () => {
beforeEach(() => {
global.eth = {
getCode: sinon.stub().callsFake(
address => Promise.resolve(address && address.match(/isContract/) ? 'not-0x' : '0x')
address => Promise.resolve(address && address.match(/isContract/) ? 'not-0x' : '0x'),
),
}
})

View File

@ -30,7 +30,7 @@ describe('Send Duck', () => {
it('should initialize state', () => {
assert.deepEqual(
SendReducer({}),
initState
initState,
)
})
@ -40,7 +40,7 @@ describe('Send Duck', () => {
type: 'someOtherAction',
value: 'someValue',
}),
Object.assign({}, mockState.send)
Object.assign({}, mockState.send),
)
})
@ -49,7 +49,7 @@ describe('Send Duck', () => {
SendReducer(mockState, {
type: OPEN_FROM_DROPDOWN,
}),
Object.assign({fromDropdownOpen: true}, mockState.send)
Object.assign({fromDropdownOpen: true}, mockState.send),
)
})
@ -63,7 +63,7 @@ describe('Send Duck', () => {
SendReducer(mockState, {
type: CLOSE_FROM_DROPDOWN,
}),
Object.assign({fromDropdownOpen: false}, mockState.send)
Object.assign({fromDropdownOpen: false}, mockState.send),
)
})
@ -72,7 +72,7 @@ describe('Send Duck', () => {
SendReducer(mockState, {
type: OPEN_TO_DROPDOWN,
}),
Object.assign({toDropdownOpen: true}, mockState.send)
Object.assign({toDropdownOpen: true}, mockState.send),
)
})
@ -81,7 +81,7 @@ describe('Send Duck', () => {
SendReducer(mockState, {
type: CLOSE_TO_DROPDOWN,
}),
Object.assign({toDropdownOpen: false}, mockState.send)
Object.assign({toDropdownOpen: false}, mockState.send),
)
})
@ -103,7 +103,7 @@ describe('Send Duck', () => {
someError: false,
someOtherError: true,
},
})
}),
)
})
@ -112,7 +112,7 @@ describe('Send Duck', () => {
SendReducer(mockState, {
type: RESET_SEND_STATE,
}),
Object.assign({}, initState)
Object.assign({}, initState),
)
})
})
@ -120,35 +120,35 @@ describe('Send Duck', () => {
describe('openFromDropdown', () => {
assert.deepEqual(
openFromDropdown(),
{ type: OPEN_FROM_DROPDOWN }
{ type: OPEN_FROM_DROPDOWN },
)
})
describe('closeFromDropdown', () => {
assert.deepEqual(
closeFromDropdown(),
{ type: CLOSE_FROM_DROPDOWN }
{ type: CLOSE_FROM_DROPDOWN },
)
})
describe('openToDropdown', () => {
assert.deepEqual(
openToDropdown(),
{ type: OPEN_TO_DROPDOWN }
{ type: OPEN_TO_DROPDOWN },
)
})
describe('closeToDropdown', () => {
assert.deepEqual(
closeToDropdown(),
{ type: CLOSE_TO_DROPDOWN }
{ type: CLOSE_TO_DROPDOWN },
)
})
describe('updateSendErrors', () => {
assert.deepEqual(
updateSendErrors('mockErrorObject'),
{ type: UPDATE_SEND_ERRORS, value: 'mockErrorObject' }
{ type: UPDATE_SEND_ERRORS, value: 'mockErrorObject' },
)
})

View File

@ -18,28 +18,28 @@ describe('Confirm Transaction utils', () => {
it('should return true if the first value is greater than the second value', () => {
assert.equal(
utils.hexGreaterThan('0xb', '0xa'),
true
true,
)
})
it('should return false if the first value is less than the second value', () => {
assert.equal(
utils.hexGreaterThan('0xa', '0xb'),
false
false,
)
})
it('should return false if the first value is equal to the second value', () => {
assert.equal(
utils.hexGreaterThan('0xa', '0xa'),
false
false,
)
})
it('should correctly compare prefixed and non-prefixed hex values', () => {
assert.equal(
utils.hexGreaterThan('0xb', 'a'),
true
true,
)
})
})
@ -48,14 +48,14 @@ describe('Confirm Transaction utils', () => {
it('should multiply the hex gasLimit and hex gasPrice values together', () => {
assert.equal(
utils.getHexGasTotal({ gasLimit: '0x5208', gasPrice: '0x3b9aca00' }),
'0x1319718a5000'
'0x1319718a5000',
)
})
it('should prefix the result with 0x', () => {
assert.equal(
utils.getHexGasTotal({ gasLimit: '5208', gasPrice: '3b9aca00' }),
'0x1319718a5000'
'0x1319718a5000',
)
})
})
@ -64,14 +64,14 @@ describe('Confirm Transaction utils', () => {
it('should add two values together rounding to 6 decimal places', () => {
assert.equal(
utils.addEth('0.12345678', '0'),
'0.123457'
'0.123457',
)
})
it('should add any number of values together rounding to 6 decimal places', () => {
assert.equal(
utils.addEth('0.1', '0.02', '0.003', '0.0004', '0.00005', '0.000006', '0.0000007'),
'0.123457'
'0.123457',
)
})
})
@ -80,14 +80,14 @@ describe('Confirm Transaction utils', () => {
it('should add two values together rounding to 2 decimal places', () => {
assert.equal(
utils.addFiat('0.12345678', '0'),
'0.12'
'0.12',
)
})
it('should add any number of values together rounding to 2 decimal places', () => {
assert.equal(
utils.addFiat('0.1', '0.02', '0.003', '0.0004', '0.00005', '0.000006', '0.0000007'),
'0.12'
'0.12',
)
})
})

View File

@ -48,6 +48,6 @@ const mapStateToProps = state => {
}
module.exports = compose(
connect(mapStateToProps)
connect(mapStateToProps),
)(I18nProvider)

View File

@ -33,6 +33,7 @@ const selectors = {
getTotalUnapprovedCount,
preferencesSelector,
getMetaMaskAccounts,
getUsePhishDetect,
}
module.exports = selectors
@ -69,6 +70,10 @@ function getMetaMaskAccounts (state) {
return selectedAccounts
}
function getUsePhishDetect (state) {
return Boolean(state.metamask.usePhishDetect)
}
function getSelectedAccount (state) {
const accounts = getMetaMaskAccounts(state)
const selectedAddress = getSelectedAddress(state)
@ -166,7 +171,7 @@ function getSelectedTokenToFiatRate (state) {
const tokenToFiatRate = multiplyCurrencies(
conversionRate,
selectedTokenExchangeRate,
{ toNumericBase: 'dec' }
{ toNumericBase: 'dec' },
)
return tokenToFiatRate

View File

@ -20,14 +20,14 @@ export const unconfirmedTransactionsListSelector = createSelector(
unapprovedMsgs = {},
unapprovedPersonalMsgs = {},
unapprovedTypedMessages = {},
network
network,
) => txHelper(
unapprovedTxs,
unapprovedMsgs,
unapprovedPersonalMsgs,
unapprovedTypedMessages,
network
) || []
network,
) || [],
)
export const unconfirmedTransactionsHashSelector = createSelector(
@ -41,7 +41,7 @@ export const unconfirmedTransactionsHashSelector = createSelector(
unapprovedMsgs = {},
unapprovedPersonalMsgs = {},
unapprovedTypedMessages = {},
network
network,
) => {
const filteredUnapprovedTxs = Object.keys(unapprovedTxs).reduce((acc, address) => {
const { metamaskNetworkId } = unapprovedTxs[address]
@ -60,7 +60,7 @@ export const unconfirmedTransactionsHashSelector = createSelector(
...unapprovedPersonalMsgs,
...unapprovedTypedMessages,
}
}
},
)
const unapprovedMsgCountSelector = state => state.metamask.unapprovedMsgCount
@ -78,7 +78,7 @@ export const unconfirmedTransactionsCountSelector = createSelector(
unapprovedMsgCount = 0,
unapprovedPersonalMsgCount = 0,
unapprovedTypedMessagesCount = 0,
network
network,
) => {
const filteredUnapprovedTxIds = Object.keys(unapprovedTxs).filter(txId => {
const { metamaskNetworkId } = unapprovedTxs[txId]
@ -87,7 +87,7 @@ export const unconfirmedTransactionsCountSelector = createSelector(
return filteredUnapprovedTxIds.length + unapprovedTypedMessagesCount + unapprovedMsgCount +
unapprovedPersonalMsgCount
}
},
)
@ -102,22 +102,22 @@ const contractExchangeRatesSelector = state => state.metamask.contractExchangeRa
const tokenDecimalsSelector = createSelector(
tokenPropsSelector,
tokenProps => tokenProps && tokenProps.tokenDecimals
tokenProps => tokenProps && tokenProps.tokenDecimals,
)
const tokenDataParamsSelector = createSelector(
tokenDataSelector,
tokenData => tokenData && tokenData.params || []
tokenData => tokenData && tokenData.params || [],
)
const txParamsSelector = createSelector(
txDataSelector,
txData => txData && txData.txParams || {}
txData => txData && txData.txParams || {},
)
export const tokenAddressSelector = createSelector(
txParamsSelector,
txParams => txParams && txParams.to
txParams => txParams && txParams.to,
)
const TOKEN_PARAM_SPENDER = '_spender'
@ -147,7 +147,7 @@ export const tokenAmountAndToAddressSelector = createSelector(
toAddress,
tokenAmount,
}
}
},
)
export const approveTokenAmountAndToAddressSelector = createSelector(
@ -172,7 +172,7 @@ export const approveTokenAmountAndToAddressSelector = createSelector(
toAddress,
tokenAmount,
}
}
},
)
export const sendTokenTokenAmountAndToAddressSelector = createSelector(
@ -197,11 +197,11 @@ export const sendTokenTokenAmountAndToAddressSelector = createSelector(
toAddress,
tokenAmount,
}
}
},
)
export const contractExchangeRateSelector = createSelector(
contractExchangeRatesSelector,
tokenAddressSelector,
(contractExchangeRates, tokenAddress) => contractExchangeRates[tokenAddress]
(contractExchangeRates, tokenAddress) => contractExchangeRates[tokenAddress],
)

View File

@ -7,5 +7,5 @@ export const selectedTokenSelector = createSelector(
selectedTokenAddressSelector,
(tokens = [], selectedTokenAddress = '') => {
return tokens.find(({ address }) => address === selectedTokenAddress)
}
},
)

View File

@ -33,26 +33,26 @@ export const transactionsSelector = createSelector(
.sort((a, b) => b.time - a.time)
: txsToRender
.sort((a, b) => b.time - a.time)
}
},
)
export const pendingTransactionsSelector = createSelector(
transactionsSelector,
(transactions = []) => (
transactions.filter(transaction => transaction.status in pendingStatusHash).reverse()
)
),
)
export const submittedPendingTransactionsSelector = createSelector(
transactionsSelector,
(transactions = []) => (
transactions.filter(transaction => transaction.status === SUBMITTED_STATUS)
)
),
)
export const completedTransactionsSelector = createSelector(
transactionsSelector,
(transactions = []) => (
transactions.filter(transaction => !(transaction.status in pendingStatusHash))
)
),
)

View File

@ -71,6 +71,6 @@ export default compose(
mapStateToProps,
dispatch => ({
closeWelcomeScreen: () => dispatch(closeWelcomeScreen()),
})
)
}),
),
)(WelcomeScreen)

View File

@ -79,7 +79,7 @@ async function startApp (metamaskState, accountManager, opts) {
h(Root, {
// inject initial state
store: store,
}
},
), opts.container)
return store