From 9f3445252c766053b7a773d75dceb1c1bbfdc6f5 Mon Sep 17 00:00:00 2001 From: frankiebee Date: Thu, 9 Mar 2017 18:24:41 -0800 Subject: [PATCH 001/372] put background in service worker --- library/controller.js | 22 +++ library/controllers/index-db-controller.js | 96 ++++++++++ library/index.js | 2 +- library/popup.js | 2 +- library/server.js | 6 + library/sw-controller.js | 49 +++++ library/sw-core.js | 203 +++++++++++++++++++++ 7 files changed, 378 insertions(+), 2 deletions(-) create mode 100644 library/controllers/index-db-controller.js create mode 100644 library/sw-controller.js create mode 100644 library/sw-core.js diff --git a/library/controller.js b/library/controller.js index 5823287cc..ba97da93c 100644 --- a/library/controller.js +++ b/library/controller.js @@ -1,3 +1,4 @@ +/* const urlUtil = require('url') const extend = require('xtend') const Dnode = require('dnode') @@ -157,3 +158,24 @@ function initializeZeroClient() { } } + +*/ +const SWcontroller = require('./sw-controller') +console.log('outside:open') +const background = new SWcontroller({ + fileName: 'sw-build.js', + registerOpts: { + scope: './', + } +}) + +background.startWorker() +.then(registerdWorker => { + return background.sendMessage('connect') +}) +.then((port) => { + debugger +}) +.catch(err => { + console.error(`SW Controller: ${err}`) +}) diff --git a/library/controllers/index-db-controller.js b/library/controllers/index-db-controller.js new file mode 100644 index 000000000..8b90db066 --- /dev/null +++ b/library/controllers/index-db-controller.js @@ -0,0 +1,96 @@ +const EventEmitter = require('events') +module.exports = class IndexDbController extends EventEmitter { + + constructor (opts) { + super() + this.migrations = opts.migrations + this.key = opts.key + this.dbObject = opts.global.indexedDB + this.IDBTransaction = opts.global.IDBTransaction || opts.global.webkitIDBTransaction || opts.global.msIDBTransaction || {READ_WRITE: "readwrite"}; // This line should only be needed if it is needed to support the object's constants for older browsers + this.IDBKeyRange = opts.global.IDBKeyRange || opts.global.webkitIDBKeyRange || opts.global.msIDBKeyRange; + this.version = opts.version + this.logging = opts.logging + this.initialState = opts.initialState + if (this.logging) this.on('log', logger) + } + + // Opens the database connection and returns a promise + open (version = this.version) { + return new Promise((resolve, reject) => { + const dbOpenRequest = this.dbObject.open(this.key, version) + dbOpenRequest.onerror = (event) => { + return reject(event) + } + dbOpenRequest.onsuccess = (event) => { + this.db = dbOpenRequest.result + if (!this.db.objectStoreNames.length) { + Object.keys(this.initialState).forEach((key) => { + this._add(key, this.initialState[key]) + }) + } + this.emit('success') + resolve(this.db) + } + dbOpenRequest.onupgradeneeded = (event) => { + // if (this.migrators) + this.db = event.target.result + this.migrate() + } + }) + } + + requestObjectStore (key, type = 'readonly') { + return new Promise((resolve, reject) => { + const dbReadWrite = this.db.transaction(key, type) + const dataStore = dbReadWrite.objectStore(key) + resolve(dataStore) + }) + } + + get (key) { + return this.requestObjectStore(key) + .then((dataObject)=> { + return new Promise((resolve, reject) => { + const getRequest = dataObject.get(key) + getRequest.onsuccess = (event) => resolve(event.currentTarget.result) + getRequest.onerror = (event) => reject(event) + }) + }) + } + + put (key, store) { + return this.requestObjectStore(key, 'readwrite') + .then((dataObject)=> { + const putRequest = dataObject.put(store) + putRequest.onsuccess = (event) => Promise.resolve(event.currentTarget.result) + putRequest.onerror = (event) => Promise.reject(event) + }) + } + + update (key, value) { + + } + + migrate () { + // this.migrations + + // Place holder for future migrations eg: + this.db.deleteObjectStore('meta') + this.db.deleteObjectStore('data') + this.db.createObjectStore('dataStore') + } + + _add (key, objStore, cb = logger) { + return this.requestObjectStore(key, 'readwrite') + .then((dataObject)=> { + const addRequest = dataObject.add(objStore, key) + addRequest.onsuccess = (event) => Promise.resolve(event.currentTarget.result) + addRequest.onerror = (event) => Promise.reject(event) + }) + } + +} + +function logger (err, ress) { + err ? console.error(`Logger says: ${err}`) : console.dir(`Logger says: ${ress}`) +} diff --git a/library/index.js b/library/index.js index b5f4f6637..af82c6546 100644 --- a/library/index.js +++ b/library/index.js @@ -26,7 +26,7 @@ var shouldPop = false window.addEventListener('click', function(){ if (!shouldPop) return shouldPop = false - window.open('http://127.0.0.1:9001/popup/popup.html', '', 'width=360 height=500') + window.open('http://localhost:9001/popup/popup.html', '', 'width=360 height=500') console.log('opening window...') }) diff --git a/library/popup.js b/library/popup.js index 667b13371..59b70edbb 100644 --- a/library/popup.js +++ b/library/popup.js @@ -11,7 +11,7 @@ var name = 'popup' window.METAMASK_UI_TYPE = name var iframeStream = setupIframe({ - zeroClientProvider: 'http://127.0.0.1:9001', + zeroClientProvider: 'http://localhost:9001', sandboxAttributes: ['allow-scripts', 'allow-popups', 'allow-same-origin'], container: document.body, }) diff --git a/library/server.js b/library/server.js index 797ad8a77..b9f59c2a1 100644 --- a/library/server.js +++ b/library/server.js @@ -7,6 +7,7 @@ const zeroBundle = createBundle('./index.js') const controllerBundle = createBundle('./controller.js') const popupBundle = createBundle('./popup.js') const appBundle = createBundle('./example/index.js') +const swBuild = createBundle('./sw-core.js') // // Iframe Server @@ -24,6 +25,11 @@ iframeServer.use('/popup', express.static('../dist/chrome')) iframeServer.get('/controller.js', function(req, res){ res.send(controllerBundle.latest) }) +iframeServer.get('/sw-build.js', function(req, res){ + console.log('/sw-build.js') + res.setHeader('Content-Type', 'application/javascript') + res.send(swBuild.latest) +}) // serve background controller iframeServer.use(express.static('./server')) diff --git a/library/sw-controller.js b/library/sw-controller.js new file mode 100644 index 000000000..d5065a933 --- /dev/null +++ b/library/sw-controller.js @@ -0,0 +1,49 @@ +const EventEmitter = require('events') + +module.exports = class serviceWorkerController extends EventEmitter{ + constructor (opts) { + super() + this.fileName = opts.fileName + this.registerOpts = opts.registerOpts + this.serviceWorker = navigator.serviceWorker + } + + + startWorker () { + // check to see if their is a preregistered service worker + if (!this.serviceWorker.controller) { + return Promise.resolve(this.registerWorker()) + } else { + return Promise.resolve(this.serviceWorker.ready) + } + } + + registerWorker () { + return this.serviceWorker.register(this.fileName, this.registerOpts) + .then(sw => { + return sw + }) + } + + syncSW (registeredSW) { + return registeredSW.sync.register('sync') + .then(() => { + console.log('sync') + }) + } + + sendMessage (message) { + const self = this + return new Promise((resolve, reject) => { + var messageChannel = new MessageChannel() + messageChannel.port1.onmessage = (event) => { + if (event.data.err) { + reject(event.data.error) + } else { + resolve(event.data.data) + } + } + self.serviceWorker.controller.postMessage(message, [messageChannel.port2]) + }) + } +} diff --git a/library/sw-core.js b/library/sw-core.js new file mode 100644 index 000000000..e018dff0f --- /dev/null +++ b/library/sw-core.js @@ -0,0 +1,203 @@ +global.window = global +const SWGlobal = self +const urlUtil = require('url') +const endOfStream = require('end-of-stream') +const asyncQ = require('async-q') +const pipe = require('pump') +// const ParentStream = require('iframe-stream').ParentStream +const setupMultiplex = require('../app/scripts/lib/stream-utils.js').setupMultiplex +const PortStream = require('../app/scripts/lib/port-stream.js') +// const notification = require('../app/scripts/lib/notifications.js') + +const DbController = require('./controllers/index-db-controller') + +// // all this will go in service worker +const MetamaskController = require('../app/scripts/metamask-controller') +// const extension = require('../app/scripts/lib/extension') +// const LocalStorageStore = require('obs-store/lib/localStorage') +const storeTransform = require('obs-store/lib/transform') +const Migrator = require('../app/scripts/lib/migrator/') +const migrations = require('../app/scripts/migrations/') +const firstTimeState = require('../app/scripts/first-time-state') + +const STORAGE_KEY = 'metamask-config' +const METAMASK_DEBUG = 'GULP_METAMASK_DEBUG' +let popupIsOpen = false + +self.addEventListener('install', function(event) { + event.waitUntil(self.skipWaiting()) +}) +self.addEventListener('activate', function(event) { + event.waitUntil(self.clients.claim()) +}) + +self.onsync = function (syncEvent) { +// What is done when a sync even is fired + console.log('inside:sync') + var focused + self.clients.matchAll() + .then(clients => { + clients.forEach(function(client) { + + }) + }) +} + + + +console.log('inside:open') + + +// // state persistence +let diskStore +const dbController = new DbController({ + key: STORAGE_KEY, + global: self, + version: 6, + initialState: { + dataStore: { + meta: 2, + data: firstTimeState, + }, + }, +}) +asyncQ.waterfall([ + () => loadStateFromPersistence(), + (initState) => setupController(initState), +]) +.then(() => console.log('MetaMask initialization complete.')) +.catch((err) => { + console.log('WHILE SETTING UP:') + console.error(err) +}) + +// initialization flow + +// +// State and Persistence +// +function loadStateFromPersistence() { + // migrations + let migrator = new Migrator({ migrations }) + const initialState = migrator.generateInitialState(firstTimeState) + dbController.initialState = initialState + return dbController.open() + .then((stuff) => { + return dbController.get('dataStore') + }) + .then((data) => { + if (!data) { + return dbController._add('dataStore', initialState) + .then(() => dbController.get('dataStore')) + .then((versionedData) => Promise.resolve(versionedData.data)) + } + + return Promise.resolve(data.data) + }) + .catch((err) => console.error(err)) + + // return asyncQ.waterfall([ + // // read from disk + // () => Promise.resolve(diskStore || initialState), + // // migrate data + // (versionedData) => migrator.migrateData(versionedData), + // // write to disk + // (versionedData) => { + // diskStore.put(versionedData) + // return Promise.resolve(versionedData) + // }, + // // resolve to just data + // (versionedData) => Promise.resolve(versionedData.data), + // ]) +} + +function setupController (initState, client) { + + // + // MetaMask Controller + // + + const controller = new MetamaskController({ + // User confirmation callbacks: + showUnconfirmedMessage: triggerUi, + unlockAccountMessage: triggerUi, + showUnapprovedTx: triggerUi, + // initial state + initState, + }) + global.metamaskController = controller + + // setup state persistence + pipe( + controller.store, + storeTransform(versionifyData), + diskStore + ) + + function versionifyData(state) { + let versionedData = diskStore.getState() + versionedData.data = state + return versionedData + } + + // + // connect to other contexts + // + var connectionStream //= new ParentStream() + SWGlobal.onmessage = (message) => { + + debugger + connectRemote(connectionStream, message.origin) + } + + connectRemote(connectionStream, client.origin) + + function connectRemote (connectionStream, originDomain) { + var isMetaMaskInternalProcess = (originDomain === 'http://localhost:9001') + if (isMetaMaskInternalProcess) { + // communication with popup + controller.setupTrustedCommunication(connectionStream, 'MetaMask') + } else { + // communication with page + setupUntrustedCommunication(connectionStream, originDomain) + } + } + + function setupUntrustedCommunication (connectionStream, originDomain) { + // setup multiplexing + var mx = setupMultiplex(connectionStream) + // connect features + controller.setupProviderConnection(mx.createStream('provider'), originDomain) + controller.setupPublicConfig(mx.createStream('publicConfig')) + } + + function setupTrustedCommunication (connectionStream, originDomain) { + // setup multiplexing + var mx = setupMultiplex(connectionStream) + // connect features + controller.setupProviderConnection(mx.createStream('provider'), originDomain) + } + // + // User Interface setup + // + return Promise.resolve() + +} + +// // // +// // // Etc... +// // // + +// // // popup trigger +function triggerUi () { + if (!popupIsOpen) notification.show() +} + +// function getParentHref(){ +// try { +// var parentLocation = window.parent.location +// return parentLocation.hostname + ':' + parentLocation.port +// } catch (err) { +// return 'unknown' +// } +// } From 60f00a950cbe90edda4d9de4a67c3e0c528f6638 Mon Sep 17 00:00:00 2001 From: frankiebee Date: Thu, 9 Mar 2017 21:53:49 -0800 Subject: [PATCH 002/372] notes and clean up --- library/controller.js | 162 --------------------- library/controllers/index-db-controller.js | 4 - library/sw-core.js | 20 +-- 3 files changed, 7 insertions(+), 179 deletions(-) diff --git a/library/controller.js b/library/controller.js index ba97da93c..bad8d33cd 100644 --- a/library/controller.js +++ b/library/controller.js @@ -1,165 +1,3 @@ -/* -const urlUtil = require('url') -const extend = require('xtend') -const Dnode = require('dnode') -const eos = require('end-of-stream') -const ParentStream = require('iframe-stream').ParentStream -const PortStream = require('../app/scripts/lib/port-stream.js') -const notification = require('../app/scripts/lib/notifications.js') -const messageManager = require('../app/scripts/lib/message-manager') -const setupMultiplex = require('../app/scripts/lib/stream-utils.js').setupMultiplex -const MetamaskController = require('../app/scripts/metamask-controller') -const extension = require('../app/scripts/lib/extension') - -const STORAGE_KEY = 'metamask-config' - - -initializeZeroClient() - -function initializeZeroClient() { - - const controller = new MetamaskController({ - // User confirmation callbacks: - showUnconfirmedMessage, - unlockAccountMessage, - showUnapprovedTx, - // Persistence Methods: - setData, - loadData, - }) - const idStore = controller.idStore - - function unlockAccountMessage () { - console.log('notif stub - unlockAccountMessage') - } - - function showUnconfirmedMessage (msgParams, msgId) { - console.log('notif stub - showUnconfirmedMessage') - } - - function showUnapprovedTx (txParams, txData, onTxDoneCb) { - console.log('notif stub - showUnapprovedTx') - } - - // - // connect to other contexts - // - - var connectionStream = new ParentStream() - - connectRemote(connectionStream, getParentHref()) - - function connectRemote (connectionStream, originDomain) { - var isMetaMaskInternalProcess = (originDomain === '127.0.0.1:9001') - if (isMetaMaskInternalProcess) { - // communication with popup - setupTrustedCommunication(connectionStream, 'MetaMask') - } else { - // communication with page - setupUntrustedCommunication(connectionStream, originDomain) - } - } - - function setupUntrustedCommunication (connectionStream, originDomain) { - // setup multiplexing - var mx = setupMultiplex(connectionStream) - // connect features - controller.setupProviderConnection(mx.createStream('provider'), originDomain) - controller.setupPublicConfig(mx.createStream('publicConfig')) - } - - function setupTrustedCommunication (connectionStream, originDomain) { - // setup multiplexing - var mx = setupMultiplex(connectionStream) - // connect features - setupControllerConnection(mx.createStream('controller')) - controller.setupProviderConnection(mx.createStream('provider'), originDomain) - } - - // - // remote features - // - - function setupControllerConnection (stream) { - controller.stream = stream - var api = controller.getApi() - var dnode = Dnode(api) - stream.pipe(dnode).pipe(stream) - dnode.on('remote', (remote) => { - // push updates to popup - controller.ethStore.on('update', controller.sendUpdate.bind(controller)) - controller.listeners.push(remote) - idStore.on('update', controller.sendUpdate.bind(controller)) - - // teardown on disconnect - eos(stream, () => { - controller.ethStore.removeListener('update', controller.sendUpdate.bind(controller)) - }) - }) - } - - function loadData () { - var oldData = getOldStyleData() - var newData - try { - newData = JSON.parse(window.localStorage[STORAGE_KEY]) - } catch (e) {} - - var data = extend({ - meta: { - version: 0, - }, - data: { - config: { - provider: { - type: 'testnet', - }, - }, - }, - }, oldData || null, newData || null) - return data - } - - function getOldStyleData () { - var config, wallet, seedWords - - var result = { - meta: { version: 0 }, - data: {}, - } - - try { - config = JSON.parse(window.localStorage['config']) - result.data.config = config - } catch (e) {} - try { - wallet = JSON.parse(window.localStorage['lightwallet']) - result.data.wallet = wallet - } catch (e) {} - try { - seedWords = window.localStorage['seedWords'] - result.data.seedWords = seedWords - } catch (e) {} - - return result - } - - function setData (data) { - window.localStorage[STORAGE_KEY] = JSON.stringify(data) - } - - function getParentHref(){ - try { - var parentLocation = window.parent.location - return parentLocation.hostname + ':' + parentLocation.port - } catch (err) { - return 'unknown' - } - } - -} - -*/ const SWcontroller = require('./sw-controller') console.log('outside:open') const background = new SWcontroller({ diff --git a/library/controllers/index-db-controller.js b/library/controllers/index-db-controller.js index 8b90db066..4c53e1094 100644 --- a/library/controllers/index-db-controller.js +++ b/library/controllers/index-db-controller.js @@ -72,11 +72,7 @@ module.exports = class IndexDbController extends EventEmitter { } migrate () { - // this.migrations - // Place holder for future migrations eg: - this.db.deleteObjectStore('meta') - this.db.deleteObjectStore('data') this.db.createObjectStore('dataStore') } diff --git a/library/sw-core.js b/library/sw-core.js index e018dff0f..1afcc3cff 100644 --- a/library/sw-core.js +++ b/library/sw-core.js @@ -11,7 +11,6 @@ const PortStream = require('../app/scripts/lib/port-stream.js') const DbController = require('./controllers/index-db-controller') -// // all this will go in service worker const MetamaskController = require('../app/scripts/metamask-controller') // const extension = require('../app/scripts/lib/extension') // const LocalStorageStore = require('obs-store/lib/localStorage') @@ -53,7 +52,7 @@ let diskStore const dbController = new DbController({ key: STORAGE_KEY, global: self, - version: 6, + version: 2, initialState: { dataStore: { meta: 2, @@ -95,6 +94,9 @@ function loadStateFromPersistence() { return Promise.resolve(data.data) }) .catch((err) => console.error(err)) + /* + need to get migrations working + */ // return asyncQ.waterfall([ // // read from disk @@ -143,10 +145,11 @@ function setupController (initState, client) { // // connect to other contexts // + /* + need to write a service worker stream for this + */ var connectionStream //= new ParentStream() SWGlobal.onmessage = (message) => { - - debugger connectRemote(connectionStream, message.origin) } @@ -192,12 +195,3 @@ function setupController (initState, client) { function triggerUi () { if (!popupIsOpen) notification.show() } - -// function getParentHref(){ -// try { -// var parentLocation = window.parent.location -// return parentLocation.hostname + ':' + parentLocation.port -// } catch (err) { -// return 'unknown' -// } -// } From b6e2eaf7b1d14fcca8ed614791937a5ccbfc00dd Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Thu, 16 Mar 2017 11:16:03 -0700 Subject: [PATCH 003/372] Remove eth-lightwallet --- CHANGELOG.md | 1 + app/scripts/lib/id-management.js | 90 ----- app/scripts/lib/idStore-migrator.js | 80 ---- app/scripts/lib/idStore.js | 343 ------------------ app/scripts/metamask-controller.js | 38 +- package.json | 1 - test/integration/lib/idStore-migrator-test.js | 92 ----- test/unit/id-management-test.js | 35 -- test/unit/idStore-migration-test.js | 83 ----- test/unit/idStore-test.js | 142 -------- 10 files changed, 2 insertions(+), 903 deletions(-) delete mode 100644 app/scripts/lib/id-management.js delete mode 100644 app/scripts/lib/idStore-migrator.js delete mode 100644 app/scripts/lib/idStore.js delete mode 100644 test/integration/lib/idStore-migrator-test.js delete mode 100644 test/unit/id-management-test.js delete mode 100644 test/unit/idStore-migration-test.js delete mode 100644 test/unit/idStore-test.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 77dcc53c6..3cd7b90ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - Allow sending to ENS names in send form on Ropsten. - Added an address book functionality that remembers the last 15 unique addresses sent to. - Can now change network to custom RPC URL from lock screen. +- Removed support for old, lightwallet based vault. Users who have not opened app in over a month will need to recover with their seed phrase. This will allow Firefox support sooner. ## 3.4.0 2017-3-8 diff --git a/app/scripts/lib/id-management.js b/app/scripts/lib/id-management.js deleted file mode 100644 index 90b3fdb13..000000000 --- a/app/scripts/lib/id-management.js +++ /dev/null @@ -1,90 +0,0 @@ -/* ID Management - * - * This module exists to hold the decrypted credentials for the current session. - * It therefore exposes sign methods, because it is able to perform these - * with noa dditional authentication, because its very instantiation - * means the vault is unlocked. - */ - -const ethUtil = require('ethereumjs-util') -const Transaction = require('ethereumjs-tx') - -module.exports = IdManagement - -function IdManagement (opts) { - if (!opts) opts = {} - - this.keyStore = opts.keyStore - this.derivedKey = opts.derivedKey - this.configManager = opts.configManager - this.hdPathString = "m/44'/60'/0'/0" - - this.getAddresses = function () { - return this.keyStore.getAddresses(this.hdPathString).map(function (address) { return '0x' + address }) - } - - this.signTx = function (txParams) { - - // normalize values - txParams.gasPrice = ethUtil.intToHex(txParams.gasPrice) - txParams.to = ethUtil.addHexPrefix(txParams.to) - txParams.from = ethUtil.addHexPrefix(txParams.from.toLowerCase()) - txParams.value = ethUtil.addHexPrefix(txParams.value) - txParams.data = ethUtil.addHexPrefix(txParams.data) - txParams.gasLimit = ethUtil.addHexPrefix(txParams.gasLimit || txParams.gas) - txParams.nonce = ethUtil.addHexPrefix(txParams.nonce) - var tx = new Transaction(txParams) - - // sign tx - var privKeyHex = this.exportPrivateKey(txParams.from) - var privKey = ethUtil.toBuffer(privKeyHex) - tx.sign(privKey) - - // Add the tx hash to the persisted meta-tx object - var txHash = ethUtil.bufferToHex(tx.hash()) - var metaTx = this.configManager.getTx(txParams.metamaskId) - metaTx.hash = txHash - this.configManager.updateTx(metaTx) - - // return raw serialized tx - var rawTx = ethUtil.bufferToHex(tx.serialize()) - return rawTx - } - - this.signMsg = function (address, message) { - // sign message - var privKeyHex = this.exportPrivateKey(address.toLowerCase()) - var privKey = ethUtil.toBuffer(privKeyHex) - var msgSig = ethUtil.ecsign(new Buffer(message.replace('0x', ''), 'hex'), privKey) - var rawMsgSig = ethUtil.bufferToHex(concatSig(msgSig.v, msgSig.r, msgSig.s)) - return rawMsgSig - } - - this.getSeed = function () { - return this.keyStore.getSeed(this.derivedKey) - } - - this.exportPrivateKey = function (address) { - var privKeyHex = ethUtil.addHexPrefix(this.keyStore.exportPrivateKey(address, this.derivedKey, this.hdPathString)) - return privKeyHex - } -} - -function padWithZeroes (number, length) { - var myString = '' + number - while (myString.length < length) { - myString = '0' + myString - } - return myString -} - -function concatSig (v, r, s) { - const rSig = ethUtil.fromSigned(r) - const sSig = ethUtil.fromSigned(s) - const vSig = ethUtil.bufferToInt(v) - const rStr = padWithZeroes(ethUtil.toUnsigned(rSig).toString('hex'), 64) - const sStr = padWithZeroes(ethUtil.toUnsigned(sSig).toString('hex'), 64) - const vStr = ethUtil.stripHexPrefix(ethUtil.intToHex(vSig)) - return ethUtil.addHexPrefix(rStr.concat(sStr, vStr)).toString('hex') -} - diff --git a/app/scripts/lib/idStore-migrator.js b/app/scripts/lib/idStore-migrator.js deleted file mode 100644 index 62d21eee7..000000000 --- a/app/scripts/lib/idStore-migrator.js +++ /dev/null @@ -1,80 +0,0 @@ -const IdentityStore = require('./idStore') -const HdKeyring = require('eth-hd-keyring') -const sigUtil = require('eth-sig-util') -const normalize = sigUtil.normalize -const denodeify = require('denodeify') - -module.exports = class IdentityStoreMigrator { - - constructor ({ configManager }) { - this.configManager = configManager - const hasOldVault = this.hasOldVault() - if (!hasOldVault) { - this.idStore = new IdentityStore({ configManager }) - } - } - - migratedVaultForPassword (password) { - const hasOldVault = this.hasOldVault() - const configManager = this.configManager - - if (!this.idStore) { - this.idStore = new IdentityStore({ configManager }) - } - - if (!hasOldVault) { - return Promise.resolve(null) - } - - const idStore = this.idStore - const submitPassword = denodeify(idStore.submitPassword.bind(idStore)) - - return submitPassword(password) - .then(() => { - const serialized = this.serializeVault() - return this.checkForLostAccounts(serialized) - }) - } - - serializeVault () { - const mnemonic = this.idStore._idmgmt.getSeed() - const numberOfAccounts = this.idStore._getAddresses().length - - return { - type: 'HD Key Tree', - data: { mnemonic, numberOfAccounts }, - } - } - - checkForLostAccounts (serialized) { - const hd = new HdKeyring() - return hd.deserialize(serialized.data) - .then((hexAccounts) => { - const newAccounts = hexAccounts.map(normalize) - const oldAccounts = this.idStore._getAddresses().map(normalize) - const lostAccounts = oldAccounts.reduce((result, account) => { - if (newAccounts.includes(account)) { - return result - } else { - result.push(account) - return result - } - }, []) - - return { - serialized, - lostAccounts: lostAccounts.map((address) => { - return { - address, - privateKey: this.idStore.exportAccount(address), - } - }), - } - }) - } - - hasOldVault () { - const wallet = this.configManager.getWallet() - return wallet - } -} diff --git a/app/scripts/lib/idStore.js b/app/scripts/lib/idStore.js deleted file mode 100644 index 01474035e..000000000 --- a/app/scripts/lib/idStore.js +++ /dev/null @@ -1,343 +0,0 @@ -const EventEmitter = require('events').EventEmitter -const inherits = require('util').inherits -const ethUtil = require('ethereumjs-util') -const KeyStore = require('eth-lightwallet').keystore -const clone = require('clone') -const extend = require('xtend') -const autoFaucet = require('./auto-faucet') -const DEFAULT_RPC = 'https://testrpc.metamask.io/' -const IdManagement = require('./id-management') - - -module.exports = IdentityStore - -inherits(IdentityStore, EventEmitter) -function IdentityStore (opts = {}) { - EventEmitter.call(this) - - // we just use the ethStore to auto-add accounts - this._ethStore = opts.ethStore - this.configManager = opts.configManager - // lightwallet key store - this._keyStore = null - // lightwallet wrapper - this._idmgmt = null - - this.hdPathString = "m/44'/60'/0'/0" - - this._currentState = { - selectedAddress: null, - identities: {}, - } - // not part of serilized metamask state - only kept in memory -} - -// -// public -// - -IdentityStore.prototype.createNewVault = function (password, cb) { - delete this._keyStore - var serializedKeystore = this.configManager.getWallet() - - if (serializedKeystore) { - this.configManager.setData({}) - } - - this.purgeCache() - this._createVault(password, null, (err) => { - if (err) return cb(err) - - this._autoFaucet() - - this.configManager.setShowSeedWords(true) - var seedWords = this._idmgmt.getSeed() - - this._loadIdentities() - - cb(null, seedWords) - }) -} - -IdentityStore.prototype.recoverSeed = function (cb) { - this.configManager.setShowSeedWords(true) - if (!this._idmgmt) return cb(new Error('Unauthenticated. Please sign in.')) - var seedWords = this._idmgmt.getSeed() - cb(null, seedWords) -} - -IdentityStore.prototype.recoverFromSeed = function (password, seed, cb) { - this.purgeCache() - - this._createVault(password, seed, (err) => { - if (err) return cb(err) - - this._loadIdentities() - cb(null, this.getState()) - }) -} - -IdentityStore.prototype.setStore = function (store) { - this._ethStore = store -} - -IdentityStore.prototype.clearSeedWordCache = function (cb) { - const configManager = this.configManager - configManager.setShowSeedWords(false) - cb(null, configManager.getSelectedAccount()) -} - -IdentityStore.prototype.getState = function () { - const configManager = this.configManager - var seedWords = this.getSeedIfUnlocked() - return clone(extend(this._currentState, { - isInitialized: !!configManager.getWallet() && !seedWords, - isUnlocked: this._isUnlocked(), - seedWords: seedWords, - selectedAddress: configManager.getSelectedAccount(), - })) -} - -IdentityStore.prototype.getSeedIfUnlocked = function () { - const configManager = this.configManager - var showSeed = configManager.getShouldShowSeedWords() - var idmgmt = this._idmgmt - var shouldShow = showSeed && !!idmgmt - var seedWords = shouldShow ? idmgmt.getSeed() : null - return seedWords -} - -IdentityStore.prototype.getSelectedAddress = function () { - const configManager = this.configManager - return configManager.getSelectedAccount() -} - -IdentityStore.prototype.setSelectedAddressSync = function (address) { - const configManager = this.configManager - if (!address) { - var addresses = this._getAddresses() - address = addresses[0] - } - - configManager.setSelectedAccount(address) - return address -} - -IdentityStore.prototype.setSelectedAddress = function (address, cb) { - const resultAddress = this.setSelectedAddressSync(address) - if (cb) return cb(null, resultAddress) -} - -IdentityStore.prototype.revealAccount = function (cb) { - const derivedKey = this._idmgmt.derivedKey - const keyStore = this._keyStore - const configManager = this.configManager - - keyStore.setDefaultHdDerivationPath(this.hdPathString) - keyStore.generateNewAddress(derivedKey, 1) - const addresses = keyStore.getAddresses() - const address = addresses[ addresses.length - 1 ] - - this._ethStore.addAccount(ethUtil.addHexPrefix(address)) - - configManager.setWallet(keyStore.serialize()) - - this._loadIdentities() - this._didUpdate() - cb(null) -} - -IdentityStore.prototype.getNetwork = function (err) { - if (err) { - this._currentState.network = 'loading' - this._didUpdate() - } - - this.web3.version.getNetwork((err, network) => { - if (err) { - this._currentState.network = 'loading' - return this._didUpdate() - } - if (global.METAMASK_DEBUG) { - console.log('web3.getNetwork returned ' + network) - } - this._currentState.network = network - this._didUpdate() - }) -} - -IdentityStore.prototype.setLocked = function (cb) { - delete this._keyStore - delete this._idmgmt - cb() -} - -IdentityStore.prototype.submitPassword = function (password, cb) { - const configManager = this.configManager - this.tryPassword(password, (err) => { - if (err) return cb(err) - // load identities before returning... - this._loadIdentities() - cb(null, configManager.getSelectedAccount()) - }) -} - -IdentityStore.prototype.exportAccount = function (address, cb) { - var privateKey = this._idmgmt.exportPrivateKey(address) - if (cb) cb(null, privateKey) - return privateKey -} - -// private -// - -IdentityStore.prototype._didUpdate = function () { - this.emit('update', this.getState()) -} - -IdentityStore.prototype._isUnlocked = function () { - var result = Boolean(this._keyStore) && Boolean(this._idmgmt) - return result -} - -// load identities from keyStoreet -IdentityStore.prototype._loadIdentities = function () { - const configManager = this.configManager - if (!this._isUnlocked()) throw new Error('not unlocked') - - var addresses = this._getAddresses() - addresses.forEach((address, i) => { - // // add to ethStore - if (this._ethStore) { - this._ethStore.addAccount(ethUtil.addHexPrefix(address)) - } - // add to identities - const defaultLabel = 'Account ' + (i + 1) - const nickname = configManager.nicknameForWallet(address) - var identity = { - name: nickname || defaultLabel, - address: address, - mayBeFauceting: this._mayBeFauceting(i), - } - this._currentState.identities[address] = identity - }) - this._didUpdate() -} - -IdentityStore.prototype.saveAccountLabel = function (account, label, cb) { - const configManager = this.configManager - configManager.setNicknameForWallet(account, label) - this._loadIdentities() - cb(null, label) -} - -// mayBeFauceting -// If on testnet, index 0 may be fauceting. -// The UI will have to check the balance to know. -// If there is no balance and it mayBeFauceting, -// then it is in fact fauceting. -IdentityStore.prototype._mayBeFauceting = function (i) { - const configManager = this.configManager - var config = configManager.getProvider() - if (i === 0 && - config.type === 'rpc' && - config.rpcTarget === DEFAULT_RPC) { - return true - } - return false -} - -// -// keyStore managment - unlocking + deserialization -// - -IdentityStore.prototype.tryPassword = function (password, cb) { - var serializedKeystore = this.configManager.getWallet() - var keyStore = KeyStore.deserialize(serializedKeystore) - - keyStore.keyFromPassword(password, (err, pwDerivedKey) => { - if (err) return cb(err) - - const isCorrect = keyStore.isDerivedKeyCorrect(pwDerivedKey) - if (!isCorrect) return cb(new Error('Lightwallet - password incorrect')) - - this._keyStore = keyStore - this._createIdMgmt(pwDerivedKey) - cb() - }) -} - -IdentityStore.prototype._createVault = function (password, seedPhrase, cb) { - const opts = { - password, - hdPathString: this.hdPathString, - } - - if (seedPhrase) { - opts.seedPhrase = seedPhrase - } - - KeyStore.createVault(opts, (err, keyStore) => { - if (err) return cb(err) - - this._keyStore = keyStore - - keyStore.keyFromPassword(password, (err, derivedKey) => { - if (err) return cb(err) - - this.purgeCache() - - keyStore.addHdDerivationPath(this.hdPathString, derivedKey, {curve: 'secp256k1', purpose: 'sign'}) - - this._createFirstWallet(derivedKey) - this._createIdMgmt(derivedKey) - this.setSelectedAddressSync() - - cb() - }) - }) -} - -IdentityStore.prototype._createIdMgmt = function (derivedKey) { - this._idmgmt = new IdManagement({ - keyStore: this._keyStore, - derivedKey: derivedKey, - configManager: this.configManager, - }) -} - -IdentityStore.prototype.purgeCache = function () { - this._currentState.identities = {} - let accounts - try { - accounts = Object.keys(this._ethStore._currentState.accounts) - } catch (e) { - accounts = [] - } - accounts.forEach((address) => { - this._ethStore.removeAccount(address) - }) -} - -IdentityStore.prototype._createFirstWallet = function (derivedKey) { - const keyStore = this._keyStore - keyStore.setDefaultHdDerivationPath(this.hdPathString) - keyStore.generateNewAddress(derivedKey, 1) - this.configManager.setWallet(keyStore.serialize()) - var addresses = keyStore.getAddresses() - this._ethStore.addAccount(ethUtil.addHexPrefix(addresses[0])) -} - -// get addresses and normalize address hexString -IdentityStore.prototype._getAddresses = function () { - return this._keyStore.getAddresses(this.hdPathString).map((address) => { - return ethUtil.addHexPrefix(address) - }) -} - -IdentityStore.prototype._autoFaucet = function () { - var addresses = this._getAddresses() - autoFaucet(addresses[0]) -} - -// util diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 25f9d9e5d..92533e022 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -23,7 +23,6 @@ const ConfigManager = require('./lib/config-manager') const extension = require('./lib/extension') const autoFaucet = require('./lib/auto-faucet') const nodeify = require('./lib/nodeify') -const IdStoreMigrator = require('./lib/idStore-migrator') const accountImporter = require('./account-import-strategies') const version = require('../manifest.json').version @@ -115,11 +114,6 @@ module.exports = class MetamaskController extends EventEmitter { this.personalMessageManager = new PersonalMessageManager() this.publicConfigStore = this.initPublicConfigStore() - // TEMPORARY UNTIL FULL DEPRECATION: - this.idStoreMigrator = new IdStoreMigrator({ - configManager: this.configManager, - }) - // manual disk state subscriptions this.txManager.store.subscribe((state) => { this.store.updateState({ TransactionManager: state }) @@ -366,8 +360,7 @@ module.exports = class MetamaskController extends EventEmitter { // submitPassword (password, cb) { - this.migrateOldVaultIfAny(password) - .then(this.keyringController.submitPassword.bind(this.keyringController, password)) + return this.keyringController.submitPassword(password) .then((newState) => { cb(null, newState) }) .catch((reason) => { cb(reason) }) } @@ -562,35 +555,6 @@ module.exports = class MetamaskController extends EventEmitter { cb(null, this.getState()) } - // Migrate Old Vault If Any - // @string password - // - // returns Promise() - // - // Temporary step used when logging in. - // Checks if old style (pre-3.0.0) Metamask Vault exists. - // If so, persists that vault in the new vault format - // with the provided password, so the other unlock steps - // may be completed without interruption. - migrateOldVaultIfAny (password) { - - if (!this.checkIfShouldMigrate()) { - return Promise.resolve(password) - } - - const keyringController = this.keyringController - - return this.idStoreMigrator.migratedVaultForPassword(password) - .then(this.restoreOldVaultAccounts.bind(this)) - .then(this.restoreOldLostAccounts.bind(this)) - .then(keyringController.persistAllKeyrings.bind(keyringController, password)) - .then(() => password) - } - - checkIfShouldMigrate() { - return !!this.configManager.getWallet() && !this.configManager.getVault() - } - restoreOldVaultAccounts(migratorOutput) { const { serialized } = migratorOutput return this.keyringController.restoreKeyring(serialized) diff --git a/package.json b/package.json index 96d58fa1b..72f3cbb3d 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,6 @@ "ensnare": "^1.0.0", "eth-bin-to-ops": "^1.0.1", "eth-hd-keyring": "^1.1.1", - "eth-lightwallet": "^2.3.3", "eth-query": "^1.0.3", "eth-sig-util": "^1.1.1", "eth-simple-keyring": "^1.1.1", diff --git a/test/integration/lib/idStore-migrator-test.js b/test/integration/lib/idStore-migrator-test.js deleted file mode 100644 index 290216ae8..000000000 --- a/test/integration/lib/idStore-migrator-test.js +++ /dev/null @@ -1,92 +0,0 @@ -const ObservableStore = require('obs-store') -const ConfigManager = require('../../../app/scripts/lib/config-manager') -const IdStoreMigrator = require('../../../app/scripts/lib/idStore-migrator') -const SimpleKeyring = require('eth-simple-keyring') -const normalize = require('eth-sig-util').normalize - -const oldStyleVault = require('../mocks/oldVault.json').data -const badStyleVault = require('../mocks/badVault.json').data - -const PASSWORD = '12345678' -const FIRST_ADDRESS = '0x4dd5d356c5A016A220bCD69e82e5AF680a430d00'.toLowerCase() -const BAD_STYLE_FIRST_ADDRESS = '0xac39b311dceb2a4b2f5d8461c1cdaf756f4f7ae9' -const SEED = 'fringe damage bounce extend tunnel afraid alert sound all soldier all dinner' - -QUnit.module('Old Style Vaults', { - beforeEach: function () { - let managers = managersFromInitState(oldStyleVault) - - this.configManager = managers.configManager - this.migrator = managers.migrator - } -}) - -QUnit.test('migrator:isInitialized', function (assert) { - assert.ok(this.migrator) -}) - -QUnit.test('migrator:migratedVaultForPassword', function (assert) { - var done = assert.async() - - this.migrator.migratedVaultForPassword(PASSWORD) - .then((result) => { - assert.ok(result, 'migratedVaultForPassword returned result') - const { serialized, lostAccounts } = result - assert.equal(serialized.data.mnemonic, SEED, 'seed phrase recovered') - assert.equal(lostAccounts.length, 0, 'no lost accounts') - done() - }) -}) - -QUnit.module('Old Style Vaults with bad HD seed', { - beforeEach: function () { - let managers = managersFromInitState(badStyleVault) - - this.configManager = managers.configManager - this.migrator = managers.migrator - } -}) - -QUnit.test('migrator:migratedVaultForPassword', function (assert) { - var done = assert.async() - - this.migrator.migratedVaultForPassword(PASSWORD) - .then((result) => { - assert.ok(result, 'migratedVaultForPassword returned result') - const { serialized, lostAccounts } = result - - assert.equal(lostAccounts.length, 1, 'one lost account') - assert.equal(lostAccounts[0].address, '0xe15D894BeCB0354c501AE69429B05143679F39e0'.toLowerCase()) - assert.ok(lostAccounts[0].privateKey, 'private key exported') - - var lostAccount = lostAccounts[0] - var privateKey = lostAccount.privateKey - - var simple = new SimpleKeyring() - simple.deserialize([privateKey]) - .then(() => { - return simple.getAccounts() - }) - .then((accounts) => { - assert.equal(normalize(accounts[0]), lostAccount.address, 'recovered address.') - done() - }) - .catch((reason) => { - assert.ifError(reason) - done(reason) - }) - }) -}) - -function managersFromInitState(initState){ - - let configManager = new ConfigManager({ - store: new ObservableStore(initState), - }) - - let migrator = new IdStoreMigrator({ - configManager: configManager, - }) - - return { configManager, migrator } -} diff --git a/test/unit/id-management-test.js b/test/unit/id-management-test.js deleted file mode 100644 index cbc6403bc..000000000 --- a/test/unit/id-management-test.js +++ /dev/null @@ -1,35 +0,0 @@ -var assert = require('assert') -var IdManagement = require('../../app/scripts/lib/id-management') -var sinon = require('sinon') - -describe('IdManagement', function() { - - beforeEach(function() { - // sinon allows stubbing methods that are easily verified - this.sinon = sinon.sandbox.create() - window.localStorage = {} // Hacking localStorage support into JSDom - }) - - afterEach(function() { - // sinon requires cleanup otherwise it will overwrite context - this.sinon.restore() - }) - - describe('#signMsg', function () { - it('passes the dennis test', function() { - const address = '0x9858e7d8b79fc3e6d989636721584498926da38a' - const message = '0x879a053d4800c6354e76c7985a865d2922c82fb5b3f4577b2fe08b998954f2e0' - const privateKey = '0x7dd98753d7b4394095de7d176c58128e2ed6ee600abe97c9f6d9fd65015d9b18' - const expectedResult = '0x28fcb6768e5110144a55b2e6ce9d1ea5a58103033632d272d2b5cf506906f7941a00b539383fd872109633d8c71c404e13dba87bc84166ee31b0e36061a69e161c' - - const idManagement = new IdManagement() - const exportKeyStub = sinon.stub(idManagement, 'exportPrivateKey', (addr) => { - assert.equal(addr, address) - return privateKey - }) - - const result = idManagement.signMsg(address, message) - assert.equal(result, expectedResult) - }) - }) -}) diff --git a/test/unit/idStore-migration-test.js b/test/unit/idStore-migration-test.js deleted file mode 100644 index 81a99ef63..000000000 --- a/test/unit/idStore-migration-test.js +++ /dev/null @@ -1,83 +0,0 @@ -const async = require('async') -const assert = require('assert') -const ObservableStore = require('obs-store') -const ethUtil = require('ethereumjs-util') -const BN = ethUtil.BN -const ConfigManager = require('../../app/scripts/lib/config-manager') -const firstTimeState = require('../../app/scripts/first-time-state') -const delegateCallCode = require('../lib/example-code.json').delegateCallCode -const clone = require('clone') - -// The old way: -const IdentityStore = require('../../app/scripts/lib/idStore') -const STORAGE_KEY = 'metamask-config' - -// The new ways: -var KeyringController = require('../../app/scripts/keyring-controller') -const mockEncryptor = require('../lib/mock-encryptor') -const MockSimpleKeychain = require('../lib/mock-simple-keychain') -const sinon = require('sinon') - -const mockVault = { - seed: 'picnic injury awful upper eagle junk alert toss flower renew silly vague', - account: '0x5d8de92c205279c10e5669f797b853ccef4f739a', -} - -const badVault = { - seed: 'radar blur cabbage chef fix engine embark joy scheme fiction master release', -} - -describe('IdentityStore to KeyringController migration', function() { - - // The stars of the show: - let idStore, keyringController, seedWords, configManager - - let password = 'password123' - let entropy = 'entripppppyy duuude' - let accounts = [] - let newAccounts = [] - let originalKeystore - - // This is a lot of setup, I know! - // We have to create an old style vault, populate it, - // and THEN create a new one, before we can run tests on it. - beforeEach(function(done) { - this.sinon = sinon.sandbox.create() - let store = new ObservableStore(clone(firstTimeState)) - configManager = new ConfigManager({ store }) - - idStore = new IdentityStore({ - configManager: configManager, - ethStore: { - addAccount(acct) { accounts.push(ethUtil.addHexPrefix(acct)) }, - del(acct) { delete accounts[acct] }, - }, - }) - - idStore._createVault(password, mockVault.seed, (err) => { - assert.ifError(err, 'createNewVault threw error') - originalKeystore = idStore._idmgmt.keyStore - - idStore.setLocked((err) => { - assert.ifError(err, 'createNewVault threw error') - keyringController = new KeyringController({ - configManager, - ethStore: { - addAccount(acct) { newAccounts.push(ethUtil.addHexPrefix(acct)) }, - del(acct) { delete newAccounts[acct] }, - }, - txManager: { - getTxList: () => [], - getUnapprovedTxList: () => [] - }, - }) - - // Stub out the browser crypto for a mock encryptor. - // Browser crypto is tested in the integration test suite. - keyringController.encryptor = mockEncryptor - done() - }) - }) - }) - -}) diff --git a/test/unit/idStore-test.js b/test/unit/idStore-test.js deleted file mode 100644 index 000c58a82..000000000 --- a/test/unit/idStore-test.js +++ /dev/null @@ -1,142 +0,0 @@ -const async = require('async') -const assert = require('assert') -const ethUtil = require('ethereumjs-util') -const BN = ethUtil.BN -const configManagerGen = require('../lib/mock-config-manager') -const delegateCallCode = require('../lib/example-code.json').delegateCallCode -const IdentityStore = require('../../app/scripts/lib/idStore') - -describe('IdentityStore', function() { - - describe('#createNewVault', function () { - let idStore - let password = 'password123' - let seedWords - let accounts = [] - let originalKeystore - - before(function(done) { - window.localStorage = {} // Hacking localStorage support into JSDom - - idStore = new IdentityStore({ - configManager: configManagerGen(), - ethStore: { - addAccount(acct) { accounts.push(ethUtil.addHexPrefix(acct)) }, - }, - }) - - idStore.createNewVault(password, (err, seeds) => { - assert.ifError(err, 'createNewVault threw error') - seedWords = seeds - originalKeystore = idStore._idmgmt.keyStore - done() - }) - }) - - describe('#recoverFromSeed', function() { - let newAccounts = [] - - before(function() { - window.localStorage = {} // Hacking localStorage support into JSDom - - idStore = new IdentityStore({ - configManager: configManagerGen(), - ethStore: { - addAccount(acct) { newAccounts.push(ethUtil.addHexPrefix(acct)) }, - }, - }) - }) - - it('should return the expected keystore', function (done) { - - idStore.recoverFromSeed(password, seedWords, (err) => { - assert.ifError(err) - - let newKeystore = idStore._idmgmt.keyStore - assert.equal(newAccounts[0], accounts[0]) - done() - }) - }) - }) - }) - - describe('#recoverFromSeed BIP44 compliance', function() { - const salt = 'lightwalletSalt' - - let password = 'secret!' - let accounts = {} - let idStore - - var assertions = [ - { - seed: 'picnic injury awful upper eagle junk alert toss flower renew silly vague', - account: '0x5d8de92c205279c10e5669f797b853ccef4f739a', - }, - { - seed: 'radar blur cabbage chef fix engine embark joy scheme fiction master release', - account: '0xe15d894becb0354c501ae69429b05143679f39e0', - }, - { - seed: 'phone coyote caught pattern found table wedding list tumble broccoli chief swing', - account: '0xb0e868f24bc7fec2bce2efc2b1c344d7569cd9d2', - }, - { - seed: 'recycle tag bird palace blue village anxiety census cook soldier example music', - account: '0xab34a45920afe4af212b96ec51232aaa6a33f663', - }, - { - seed: 'half glimpse tape cute harvest sweet bike voyage actual floor poet lazy', - account: '0x28e9044597b625ac4beda7250011670223de43b2', - }, - { - seed: 'flavor tiger carpet motor angry hungry document inquiry large critic usage liar', - account: '0xb571be96558940c4e9292e1999461aa7499fb6cd', - }, - ] - - before(function() { - window.localStorage = {} // Hacking localStorage support into JSDom - - idStore = new IdentityStore({ - configManager: configManagerGen(), - ethStore: { - addAccount(acct) { accounts[acct] = acct}, - del(acct) { delete accounts[acct] }, - }, - }) - }) - - it('should enforce seed compliance with TestRPC', function (done) { - this.timeout(10000) - const tests = assertions.map((assertion) => { - return function (cb) { - - idStore.recoverFromSeed(password, assertion.seed, (err) => { - assert.ifError(err) - - var expected = assertion.account.toLowerCase() - var received = accounts[expected].toLowerCase() - assert.equal(received, expected) - - idStore.tryPassword(password, function (err) { - - assert.ok(idStore._isUnlocked(), 'should unlock the id store') - - idStore.submitPassword(password, function(err, account) { - assert.ifError(err) - assert.equal(account, expected) - assert.equal(Object.keys(idStore._getAddresses()).length, 1, 'only one account on restore') - cb() - }) - }) - }) - } - }) - - async.series(tests, function(err, results) { - assert.ifError(err) - done() - }) - }) - }) -}) From 35c05607b091f6064be6802a2eb1023d837c9c85 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Thu, 16 Mar 2017 12:22:09 -0700 Subject: [PATCH 004/372] Improve personal_sign style textarea was not resizing the way I'd expected, so made it permanently larger, to accomodate larger messages. --- ui/app/components/binary-renderer.js | 21 +++++++++++-------- .../pending-personal-msg-details.js | 13 ++++++++++-- 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/ui/app/components/binary-renderer.js b/ui/app/components/binary-renderer.js index a9d49b128..0b6a1f5c2 100644 --- a/ui/app/components/binary-renderer.js +++ b/ui/app/components/binary-renderer.js @@ -2,6 +2,7 @@ const Component = require('react').Component const h = require('react-hyperscript') const inherits = require('util').inherits const ethUtil = require('ethereumjs-util') +const extend = require('xtend') module.exports = BinaryRenderer @@ -12,20 +13,22 @@ function BinaryRenderer () { BinaryRenderer.prototype.render = function () { const props = this.props - const { value } = props + const { value, style } = props const text = this.hexToText(value) + const defaultStyle = extend({ + width: '315px', + maxHeight: '210px', + resize: 'none', + border: 'none', + background: 'white', + padding: '3px', + }, style) + return ( h('textarea.font-small', { readOnly: true, - style: { - width: '315px', - maxHeight: '210px', - resize: 'none', - border: 'none', - background: 'white', - padding: '3px', - }, + style: defaultStyle, defaultValue: text, }) ) diff --git a/ui/app/components/pending-personal-msg-details.js b/ui/app/components/pending-personal-msg-details.js index fa2c6416c..1050513f2 100644 --- a/ui/app/components/pending-personal-msg-details.js +++ b/ui/app/components/pending-personal-msg-details.js @@ -40,9 +40,18 @@ PendingMsgDetails.prototype.render = function () { }), // message data - h('div', [ + h('div', { + style: { + height: '260px', + }, + }, [ h('label.font-small', { style: { display: 'block' } }, 'MESSAGE'), - h(BinaryRenderer, { value: data }), + h(BinaryRenderer, { + value: data, + style: { + height: '215px', + }, + }), ]), ]) From 889132b16cbafbb5a8e35dd2cf6acc9a7fe3d5fa Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Tue, 21 Mar 2017 06:57:49 -0700 Subject: [PATCH 005/372] Add action to hide loading indication on an incorrect pw. --- ui/app/actions.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/ui/app/actions.js b/ui/app/actions.js index b09021577..13a9cf547 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -269,7 +269,10 @@ function requestRevealSeed (password) { dispatch(actions.showLoadingIndication()) log.debug(`background.submitPassword`) background.submitPassword(password, (err) => { - if (err) return dispatch(actions.displayWarning(err.message)) + if (err) { + dispatch(actions.hideLoadingIndication()) + return dispatch(actions.displayWarning(err.message)) + } log.debug(`background.placeSeedWords`) background.placeSeedWords((err) => { if (err) return dispatch(actions.displayWarning(err.message)) @@ -698,7 +701,7 @@ function setRpcTarget (newRpc) { } } -// Calls the addressBookController to add a new address. +// Calls the addressBookController to add a new address. function addToAddressBook (recipient, nickname) { log.debug(`background.addToAddressBook`) return (dispatch) => { From 10040bee497e3c9b3775dc444b3e8a7b30fab536 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Tue, 21 Mar 2017 06:59:08 -0700 Subject: [PATCH 006/372] Changelog addition. --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3cd7b90ff..c081147b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - Added an address book functionality that remembers the last 15 unique addresses sent to. - Can now change network to custom RPC URL from lock screen. - Removed support for old, lightwallet based vault. Users who have not opened app in over a month will need to recover with their seed phrase. This will allow Firefox support sooner. +- Fixed bug where spinner wouldn't disappear on incorrect password submission on seed word reveal. ## 3.4.0 2017-3-8 From a7b7be8309b5b81514b9ef748e315f01d02bc178 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Tue, 21 Mar 2017 08:52:28 -0700 Subject: [PATCH 007/372] Add new development states for UI development. --- .../states/private-key-export-success.json | 70 +++++++++++++++++++ development/states/private-key-export.json | 69 ++++++++++++++++++ 2 files changed, 139 insertions(+) create mode 100644 development/states/private-key-export-success.json create mode 100644 development/states/private-key-export.json diff --git a/development/states/private-key-export-success.json b/development/states/private-key-export-success.json new file mode 100644 index 000000000..2ff3c4d17 --- /dev/null +++ b/development/states/private-key-export-success.json @@ -0,0 +1,70 @@ +{ + "metamask": { + "isInitialized": true, + "isUnlocked": true, + "rpcTarget": "https://rawtestrpc.metamask.io/", + "identities": { + "0x07284e146926a4facd0ea60598dc4f001ad620f1": { + "address": "0x07284e146926a4facd0ea60598dc4f001ad620f1", + "name": "Account 1" + } + }, + "unapprovedTxs": {}, + "noActiveNotices": true, + "frequentRpcList": [], + "addressBook": [], + "network": "3", + "accounts": { + "0x07284e146926a4facd0ea60598dc4f001ad620f1": { + "code": "0x", + "nonce": "0x0", + "balance": "0x0", + "address": "0x07284e146926a4facd0ea60598dc4f001ad620f1" + } + }, + "transactions": {}, + "selectedAddressTxList": [], + "unapprovedMsgs": {}, + "unapprovedMsgCount": 0, + "unapprovedPersonalMsgs": {}, + "unapprovedPersonalMsgCount": 0, + "keyringTypes": [ + "Simple Key Pair", + "HD Key Tree" + ], + "keyrings": [ + { + "type": "HD Key Tree", + "accounts": [ + "07284e146926a4facd0ea60598dc4f001ad620f1" + ] + } + ], + "selectedAddress": "0x07284e146926a4facd0ea60598dc4f001ad620f1", + "currentCurrency": "USD", + "conversionRate": 43.35903476, + "conversionDate": 1490105102, + "provider": { + "type": "testnet" + }, + "shapeShiftTxList": [], + "lostAccounts": [], + "seedWords": null + }, + "appState": { + "menuOpen": false, + "currentView": { + "name": "accountDetail", + "context": "0x07284e146926a4facd0ea60598dc4f001ad620f1" + }, + "accountDetail": { + "subview": "export", + "accountExport": "completed", + "privateKey": "549c9638ad06432568969accacad4a02f8548cc358085938071745138ec134b7" + }, + "transForward": true, + "isLoading": false, + "warning": null + }, + "identities": {} +} diff --git a/development/states/private-key-export.json b/development/states/private-key-export.json new file mode 100644 index 000000000..db7a53e22 --- /dev/null +++ b/development/states/private-key-export.json @@ -0,0 +1,69 @@ +{ + "metamask": { + "isInitialized": true, + "isUnlocked": true, + "rpcTarget": "https://rawtestrpc.metamask.io/", + "identities": { + "0x07284e146926a4facd0ea60598dc4f001ad620f1": { + "address": "0x07284e146926a4facd0ea60598dc4f001ad620f1", + "name": "Account 1" + } + }, + "unapprovedTxs": {}, + "noActiveNotices": true, + "frequentRpcList": [], + "addressBook": [], + "network": "3", + "accounts": { + "0x07284e146926a4facd0ea60598dc4f001ad620f1": { + "code": "0x", + "nonce": "0x0", + "balance": "0x0", + "address": "0x07284e146926a4facd0ea60598dc4f001ad620f1" + } + }, + "transactions": {}, + "selectedAddressTxList": [], + "unapprovedMsgs": {}, + "unapprovedMsgCount": 0, + "unapprovedPersonalMsgs": {}, + "unapprovedPersonalMsgCount": 0, + "keyringTypes": [ + "Simple Key Pair", + "HD Key Tree" + ], + "keyrings": [ + { + "type": "HD Key Tree", + "accounts": [ + "07284e146926a4facd0ea60598dc4f001ad620f1" + ] + } + ], + "selectedAddress": "0x07284e146926a4facd0ea60598dc4f001ad620f1", + "currentCurrency": "USD", + "conversionRate": 43.35903476, + "conversionDate": 1490105102, + "provider": { + "type": "testnet" + }, + "shapeShiftTxList": [], + "lostAccounts": [], + "seedWords": null + }, + "appState": { + "menuOpen": false, + "currentView": { + "name": "accountDetail", + "context": "0x07284e146926a4facd0ea60598dc4f001ad620f1" + }, + "accountDetail": { + "subview": "export", + "accountExport": "requested" + }, + "transForward": true, + "isLoading": false, + "warning": null + }, + "identities": {} +} From 0e43606b162db28c72ea0b76a14a92f88c8f2109 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Tue, 21 Mar 2017 08:53:34 -0700 Subject: [PATCH 008/372] Adjust private key confirmation style and logic. --- ui/app/actions.js | 26 +++++--- ui/app/components/account-export.js | 92 ++++++++++++++++++----------- ui/app/css/index.css | 3 +- 3 files changed, 75 insertions(+), 46 deletions(-) diff --git a/ui/app/actions.js b/ui/app/actions.js index b09021577..9d6676d01 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -698,7 +698,7 @@ function setRpcTarget (newRpc) { } } -// Calls the addressBookController to add a new address. +// Calls the addressBookController to add a new address. function addToAddressBook (recipient, nickname) { log.debug(`background.addToAddressBook`) return (dispatch) => { @@ -772,22 +772,30 @@ function requestExportAccount () { } } -function exportAccount (address) { +function exportAccount (password, address) { var self = this return function (dispatch) { dispatch(self.showLoadingIndication()) - log.debug(`background.exportAccount`) - background.exportAccount(address, function (err, result) { - dispatch(self.hideLoadingIndication()) - + log.debug(`background.submitPassword`) + background.submitPassword(password, function (err) { if (err) { - log.error(err) - return dispatch(self.displayWarning('Had a problem exporting the account.')) + log.error('Error in submiting password.') + dispatch(self.hideLoadingIndication()) + return dispatch(self.displayWarning('Incorrect Password.')) } + log.debug(`background.exportAccount`) + background.exportAccount(address, function (err, result) { + dispatch(self.hideLoadingIndication()) - dispatch(self.showPrivateKey(result)) + if (err) { + log.error(err) + return dispatch(self.displayWarning('Had a problem exporting the account.')) + } + + dispatch(self.showPrivateKey(result)) + }) }) } } diff --git a/ui/app/components/account-export.js b/ui/app/components/account-export.js index 6d8b099a5..38a1d28ef 100644 --- a/ui/app/components/account-export.js +++ b/ui/app/components/account-export.js @@ -4,14 +4,21 @@ const inherits = require('util').inherits const copyToClipboard = require('copy-to-clipboard') const actions = require('../actions') const ethUtil = require('ethereumjs-util') +const connect = require('react-redux').connect -module.exports = ExportAccountView +module.exports = connect(mapStateToProps)(ExportAccountView) inherits(ExportAccountView, Component) function ExportAccountView () { Component.call(this) } +function mapStateToProps (state) { + return { + warning: state.appState.warning, + } +} + ExportAccountView.prototype.render = function () { console.log('EXPORT VIEW') console.dir(this.props) @@ -28,35 +35,57 @@ ExportAccountView.prototype.render = function () { if (notExporting) return h('div') if (exportRequested) { - var warning = `Exporting your private key is very dangerous, - and you should only do it if you know what you're doing.` - var confirmation = `If you're absolutely sure, type "I understand" below and - submit.` + var warning = `Export private keys at your own risk.` return ( - h('div', { - key: 'exporting', style: { - margin: '0 20px', + display: 'inline-block', + textAlign: 'center', }, - }, [ - h('p.error', warning), - h('p', confirmation), - h('input#exportAccount.sizing-input', { - onKeyPress: this.onExportKeyPress.bind(this), - style: { - position: 'relative', - top: '1.5px', + }, + [ + h('div', { + key: 'exporting', + style: { + margin: '0 20px', + }, + }, [ + h('p.error', warning), + h('input#exportAccount.sizing-input', { + placeholder: 'confirm password', + onKeyPress: this.onExportKeyPress.bind(this), + style: { + position: 'relative', + top: '1.5px', + marginBottom: '7px', + }, + }), + ]), + h('div', { + key: 'buttons', + style: { + margin: '0 20px', + }, }, - }), - h('button', { - onClick: () => this.onExportKeyPress({ key: 'Enter', preventDefault: () => {} }), - }, 'Submit'), - h('button', { - onClick: () => this.props.dispatch(actions.backToAccountDetail(this.props.address)), - }, 'Cancel'), - ]) - + [ + h('button', { + onClick: () => this.onExportKeyPress({ key: 'Enter', preventDefault: () => {} }), + style: { + marginRight: '10px', + }, + }, 'Submit'), + h('button', { + onClick: () => this.props.dispatch(actions.backToAccountDetail(this.props.address)), + }, 'Cancel'), + ]), + (this.props.warning) && ( + h('span.error', { + style: { + margin: '20px', + }, + }, this.props.warning.split('-')) + ), + ]) ) } @@ -89,15 +118,6 @@ ExportAccountView.prototype.onExportKeyPress = function (event) { if (event.key !== 'Enter') return event.preventDefault() - var input = document.getElementById('exportAccount') - if (input.value === 'I understand') { - this.props.dispatch(actions.exportAccount(this.props.address)) - } else { - input.value = '' - input.placeholder = 'Please retype "I understand" exactly.' - } -} - -ExportAccountView.prototype.exportAccount = function (address) { - this.props.dispatch(actions.exportAccount(address)) + var input = document.getElementById('exportAccount').value + this.props.dispatch(actions.exportAccount(input, this.props.address)) } diff --git a/ui/app/css/index.css b/ui/app/css/index.css index 8c6ff29d3..3ec0ac5c5 100644 --- a/ui/app/css/index.css +++ b/ui/app/css/index.css @@ -266,8 +266,9 @@ app sections } .sizing-input{ - font-size: 1em; + font-size: 14px; height: 30px; + padding-left: 5px; } .editable-label{ display: flex; From 6e6fa703cc3512e3d998aeca32e08f477ae31971 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Tue, 21 Mar 2017 08:57:19 -0700 Subject: [PATCH 009/372] Add to changelog. --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3cd7b90ff..d7e1dc4fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - Added an address book functionality that remembers the last 15 unique addresses sent to. - Can now change network to custom RPC URL from lock screen. - Removed support for old, lightwallet based vault. Users who have not opened app in over a month will need to recover with their seed phrase. This will allow Firefox support sooner. +- Polish the private key UI. ## 3.4.0 2017-3-8 From 605c2a7404ba235aa4894a5b6f4b6afa6b41004f Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Tue, 21 Mar 2017 09:15:12 -0700 Subject: [PATCH 010/372] Remove redundant removal of loading indicator. Integrate loading indicator disappear with display warning. --- ui/app/actions.js | 7 ++----- ui/app/reducers/app.js | 1 + 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/ui/app/actions.js b/ui/app/actions.js index 13a9cf547..ac54158d0 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -215,7 +215,7 @@ function confirmSeedWords () { dispatch(actions.showLoadingIndication()) log.debug(`background.clearSeedWordCache`) background.clearSeedWordCache((err, account) => { - dispatch(actions.hideLoadingIndication()) + // dispatch(actions.hideLoadingIndication()) if (err) { return dispatch(actions.displayWarning(err.message)) } @@ -231,7 +231,7 @@ function createNewVaultAndRestore (password, seed) { dispatch(actions.showLoadingIndication()) log.debug(`background.createNewVaultAndRestore`) background.createNewVaultAndRestore(password, seed, (err) => { - dispatch(actions.hideLoadingIndication()) + // dispatch(actions.hideLoadingIndication()) if (err) return dispatch(actions.displayWarning(err.message)) dispatch(actions.showAccountsPage()) }) @@ -270,13 +270,11 @@ function requestRevealSeed (password) { log.debug(`background.submitPassword`) background.submitPassword(password, (err) => { if (err) { - dispatch(actions.hideLoadingIndication()) return dispatch(actions.displayWarning(err.message)) } log.debug(`background.placeSeedWords`) background.placeSeedWords((err) => { if (err) return dispatch(actions.displayWarning(err.message)) - dispatch(actions.hideLoadingIndication()) }) }) } @@ -299,7 +297,6 @@ function importNewAccount (strategy, args) { dispatch(actions.showLoadingIndication('This may take a while, be patient.')) log.debug(`background.importAccountWithStrategy`) background.importAccountWithStrategy(strategy, args, (err) => { - dispatch(actions.hideLoadingIndication()) if (err) return dispatch(actions.displayWarning(err.message)) log.debug(`background.getState`) background.getState((err, newState) => { diff --git a/ui/app/reducers/app.js b/ui/app/reducers/app.js index 7ea1e1d7c..b9e3f7b16 100644 --- a/ui/app/reducers/app.js +++ b/ui/app/reducers/app.js @@ -426,6 +426,7 @@ function reduceApp (state, action) { case actions.DISPLAY_WARNING: return extend(appState, { warning: action.value, + isLoading: false, }) case actions.HIDE_WARNING: From b0c0c30689773113ce592b0540de10696e54d372 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Tue, 21 Mar 2017 13:38:27 -0700 Subject: [PATCH 011/372] Uncomment lines relating to showing indicator. --- ui/app/actions.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/app/actions.js b/ui/app/actions.js index ac54158d0..2fefccf9f 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -215,7 +215,7 @@ function confirmSeedWords () { dispatch(actions.showLoadingIndication()) log.debug(`background.clearSeedWordCache`) background.clearSeedWordCache((err, account) => { - // dispatch(actions.hideLoadingIndication()) + dispatch(actions.hideLoadingIndication()) if (err) { return dispatch(actions.displayWarning(err.message)) } @@ -231,7 +231,7 @@ function createNewVaultAndRestore (password, seed) { dispatch(actions.showLoadingIndication()) log.debug(`background.createNewVaultAndRestore`) background.createNewVaultAndRestore(password, seed, (err) => { - // dispatch(actions.hideLoadingIndication()) + dispatch(actions.hideLoadingIndication()) if (err) return dispatch(actions.displayWarning(err.message)) dispatch(actions.showAccountsPage()) }) From d4e8ff188bc82a974fd0a767c2676d4a4b9747b4 Mon Sep 17 00:00:00 2001 From: frankiebee Date: Tue, 21 Mar 2017 15:35:01 -0700 Subject: [PATCH 012/372] WIP:stream --- library/controllers/index-db-controller.js | 9 ++++----- library/sw-core.js | 15 ++++++++------- package.json | 1 + 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/library/controllers/index-db-controller.js b/library/controllers/index-db-controller.js index 4c53e1094..3373de113 100644 --- a/library/controllers/index-db-controller.js +++ b/library/controllers/index-db-controller.js @@ -5,9 +5,9 @@ module.exports = class IndexDbController extends EventEmitter { super() this.migrations = opts.migrations this.key = opts.key - this.dbObject = opts.global.indexedDB - this.IDBTransaction = opts.global.IDBTransaction || opts.global.webkitIDBTransaction || opts.global.msIDBTransaction || {READ_WRITE: "readwrite"}; // This line should only be needed if it is needed to support the object's constants for older browsers - this.IDBKeyRange = opts.global.IDBKeyRange || opts.global.webkitIDBKeyRange || opts.global.msIDBKeyRange; + this.dbObject = global.indexedDB + this.IDBTransaction = global.IDBTransaction || global.webkitIDBTransaction || global.msIDBTransaction || {READ_WRITE: "readwrite"}; // This line should only be needed if it is needed to support the object's constants for older browsers + this.IDBKeyRange = global.IDBKeyRange || global.webkitIDBKeyRange || global.msIDBKeyRange; this.version = opts.version this.logging = opts.logging this.initialState = opts.initialState @@ -72,8 +72,7 @@ module.exports = class IndexDbController extends EventEmitter { } migrate () { - // Place holder for future migrations eg: - this.db.createObjectStore('dataStore') + this.db.createObjectStore(this.name) } _add (key, objStore, cb = logger) { diff --git a/library/sw-core.js b/library/sw-core.js index 1afcc3cff..41fce637f 100644 --- a/library/sw-core.js +++ b/library/sw-core.js @@ -4,7 +4,9 @@ const urlUtil = require('url') const endOfStream = require('end-of-stream') const asyncQ = require('async-q') const pipe = require('pump') -// const ParentStream = require('iframe-stream').ParentStream + +const SwGlobalListener = require('sw-stream/lib/sw-global-listener.js') +const connectionListener = new SwGlobalListener(self) const setupMultiplex = require('../app/scripts/lib/stream-utils.js').setupMultiplex const PortStream = require('../app/scripts/lib/port-stream.js') // const notification = require('../app/scripts/lib/notifications.js') @@ -148,12 +150,11 @@ function setupController (initState, client) { /* need to write a service worker stream for this */ - var connectionStream //= new ParentStream() - SWGlobal.onmessage = (message) => { - connectRemote(connectionStream, message.origin) - } - - connectRemote(connectionStream, client.origin) + // var connectionStream = new ParentStream() + connectionListener.on('remote', (portStream, messageEvent) => { + debugger + connectRemote(connectionStream, messageEvent.origin) + }) function connectRemote (connectionStream, originDomain) { var isMetaMaskInternalProcess = (originDomain === 'http://localhost:9001') diff --git a/package.json b/package.json index 96d58fa1b..30e89356f 100644 --- a/package.json +++ b/package.json @@ -104,6 +104,7 @@ "request-promise": "^4.1.1", "sandwich-expando": "^1.0.5", "semaphore": "^1.0.5", + "sw-stream": "^1.0.1", "textarea-caret": "^3.0.1", "three.js": "^0.73.2", "through2": "^2.0.1", From f2e40e85b7751e891ce04935ad85a984076495fd Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Wed, 22 Mar 2017 12:18:13 -0400 Subject: [PATCH 013/372] Add one more loading indication. --- ui/app/actions.js | 1 + 1 file changed, 1 insertion(+) diff --git a/ui/app/actions.js b/ui/app/actions.js index 2fefccf9f..3b1fc0435 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -300,6 +300,7 @@ function importNewAccount (strategy, args) { if (err) return dispatch(actions.displayWarning(err.message)) log.debug(`background.getState`) background.getState((err, newState) => { + dispatch(actions.hideLoadingIndication()) if (err) { return dispatch(actions.displayWarning(err.message)) } From 046774e76877f8f833445c76e2013d7f2c9bfff3 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Wed, 22 Mar 2017 12:38:04 -0400 Subject: [PATCH 014/372] Require redux-logger of 2.10.2 and up (#1235) * Require redux-logger of 2.8.1 and up * Bump to 2.10.2 and above, 2.10.1 had critical bug. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 72f3cbb3d..488e7e90d 100644 --- a/package.json +++ b/package.json @@ -98,7 +98,7 @@ "react-tooltip-component": "^0.3.0", "readable-stream": "^2.1.2", "redux": "^3.0.5", - "redux-logger": "^2.3.1", + "redux-logger": "^2.10.2", "redux-thunk": "^1.0.2", "request-promise": "^4.1.1", "sandwich-expando": "^1.0.5", From 8c7be4340349032655503b4137147e1978b0f0bd Mon Sep 17 00:00:00 2001 From: frankiebee Date: Wed, 22 Mar 2017 09:48:41 -0700 Subject: [PATCH 015/372] Have better error messages --- app/scripts/transaction-manager.js | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/app/scripts/transaction-manager.js b/app/scripts/transaction-manager.js index c6cfdf11d..31c1c8431 100644 --- a/app/scripts/transaction-manager.js +++ b/app/scripts/transaction-manager.js @@ -172,7 +172,10 @@ module.exports = class TransactionManager extends EventEmitter { ], (err) => { self.nonceLock.leave() if (err) { - this.setTxStatusFailed(txId) + this.setTxStatusFailed(txId, { + errCode: err.errCode || err, + message: err.message || 'Transaction failed during approval', + }) return cb(err) } cb() @@ -291,7 +294,10 @@ module.exports = class TransactionManager extends EventEmitter { this._setTxStatus(txId, 'confirmed') } - setTxStatusFailed (txId) { + setTxStatusFailed (txId, reason) { + let txMeta = this.getTx(txId) + txMeta.err = reason + this.updateTx(txMeta) this._setTxStatus(txId, 'failed') } @@ -312,12 +318,11 @@ module.exports = class TransactionManager extends EventEmitter { var txHash = txMeta.hash var txId = txMeta.id if (!txHash) { - txMeta.err = { + let errReason = { errCode: 'No hash was provided', message: 'We had an error while submitting this transaction, please try again.', } - this.updateTx(txMeta) - return this.setTxStatusFailed(txId) + return this.setTxStatusFailed(txId, errReason) } this.txProviderUtils.query.getTransactionByHash(txHash, (err, txParams) => { if (err || !txParams) { From 2ffd42f9f06e1b89b49c12f8ded1c12ecb249c57 Mon Sep 17 00:00:00 2001 From: frankiebee Date: Wed, 22 Mar 2017 09:49:44 -0700 Subject: [PATCH 016/372] Add to CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d7e1dc4fc..6621d89f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Current Master +- Add better error messages for when a transaction fails on approval - Allow sending to ENS names in send form on Ropsten. - Added an address book functionality that remembers the last 15 unique addresses sent to. - Can now change network to custom RPC URL from lock screen. From 9f1f0bff1ea3267702a2ab75af293e6265e255e4 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Wed, 22 Mar 2017 10:35:02 -0700 Subject: [PATCH 017/372] Some progress --- ui/app/components/hex-as-decimal-input.js | 5 +++-- ui/app/components/pending-tx-details.js | 18 +++++++++++++----- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/ui/app/components/hex-as-decimal-input.js b/ui/app/components/hex-as-decimal-input.js index c89ed0416..b2f1917f2 100644 --- a/ui/app/components/hex-as-decimal-input.js +++ b/ui/app/components/hex-as-decimal-input.js @@ -23,7 +23,7 @@ function HexAsDecimalInput () { HexAsDecimalInput.prototype.render = function () { const props = this.props - const { value, onChange } = props + const { value, onChange, min } = props const toEth = props.toEth const suffix = props.suffix const decimalValue = decimalize(value, toEth) @@ -38,8 +38,9 @@ HexAsDecimalInput.prototype.render = function () { textRendering: 'geometricPrecision', }, }, [ - h('input.ether-balance.ether-balance-amount', { + h('input.hex-input', { type: 'number', + min, style: extend({ display: 'block', textAlign: 'right', diff --git a/ui/app/components/pending-tx-details.js b/ui/app/components/pending-tx-details.js index e92ce575f..822e5d84b 100644 --- a/ui/app/components/pending-tx-details.js +++ b/ui/app/components/pending-tx-details.js @@ -12,6 +12,10 @@ const addressSummary = util.addressSummary const nameForAddress = require('../../lib/contract-namer') const HexInput = require('./hex-as-decimal-input') +const DEFAULT_GAS_PRICE = '0x4a817c800' +const DEFAULT_GAS_PRICE_BN = new BN(DEFAULT_GAS_PRICE.substr(2), 16) +const FOUR_BN = new BN('4', 10) + module.exports = PendingTxDetails inherits(PendingTxDetails, Component) @@ -78,7 +82,6 @@ PTXP.render = function () { labelColor: '#F7861C', }), ]), - ]), forwardCarrat(), @@ -122,6 +125,7 @@ PTXP.render = function () { }, [ h(HexInput, { value: gas, + min: 21000, // The hard lower limit for gas. suffix: 'UNITS', style: { position: 'relative', @@ -143,6 +147,7 @@ PTXP.render = function () { h(HexInput, { value: gasPrice, suffix: 'WEI', + min: DEFAULT_GAS_PRICE_BN.div(FOUR_BN).toString(10), style: { position: 'relative', top: '5px', @@ -176,7 +181,8 @@ PTXP.render = function () { }, }, [ h(EthBalance, { - value: maxCost.toString(16), + value: '0x' + txFee.add(new BN(txParams.value, 16)).toString(16), + maxCost.toString(16), inline: true, labelColor: 'black', fontSize: '16px', @@ -267,7 +273,7 @@ PTXP.calculateGas = function () { var txParams = txMeta.txParams var gasCost = new BN(ethUtil.stripHexPrefix(txParams.gas || txMeta.estimatedGas), 16) - var gasPrice = new BN(ethUtil.stripHexPrefix(txParams.gasPrice || '0x4a817c800'), 16) + var gasPrice = new BN(ethUtil.stripHexPrefix(txParams.gasPrice || DEFAULT_GAS_PRICE), 16) var txFee = gasCost.mul(gasPrice) var txValue = new BN(ethUtil.stripHexPrefix(txParams.value || '0x0'), 16) var maxCost = txValue.add(txFee) @@ -280,10 +286,12 @@ PTXP.calculateGas = function () { txMeta.maxCost = maxCostHex txMeta.txParams.gasPrice = gasPriceHex - this.setState({ + const newState = { txFee: '0x' + txFee.toString('hex'), maxCost: '0x' + maxCost.toString('hex'), - }) + } + log.info(`tx form updating local state with ${JSON.stringify(newState)}`) + this.setState(newState) if (this.props.onTxChange) { this.props.onTxChange(txMeta) From 9906da86a4669c1eaf7f693398de380b2973bfe8 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Wed, 22 Mar 2017 14:25:56 -0400 Subject: [PATCH 018/372] Modify logic for injection conditions. --- app/scripts/contentscript.js | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/app/scripts/contentscript.js b/app/scripts/contentscript.js index ab64dc9fa..020208ceb 100644 --- a/app/scripts/contentscript.js +++ b/app/scripts/contentscript.js @@ -69,14 +69,10 @@ function shouldInjectWeb3 () { } function isAllowedSuffix (testCase) { - var prohibitedTypes = ['xml', 'pdf'] - var currentUrl = window.location.href - var currentRegex - for (let i = 0; i < prohibitedTypes.length; i++) { - currentRegex = new RegExp(`\.${prohibitedTypes[i]}$`) - if (currentRegex.test(currentUrl)) { - return false - } + const doctype = window.document.doctype + if (doctype) { + return doctype.name === 'html' + } else { + return false } - return true } From c5f96be98d7fe36a369c48b775d0b1fe857844c6 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Wed, 22 Mar 2017 15:59:46 -0400 Subject: [PATCH 019/372] Add kovan to config file. --- app/scripts/config.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/scripts/config.js b/app/scripts/config.js index b4541a04a..ec421744d 100644 --- a/app/scripts/config.js +++ b/app/scripts/config.js @@ -1,5 +1,6 @@ const MAINET_RPC_URL = 'https://mainnet.infura.io/metamask' const TESTNET_RPC_URL = 'https://ropsten.infura.io/metamask' +const KOVAN_RPC_URL = 'https://kovan.infura.io/metamask' const DEFAULT_RPC_URL = TESTNET_RPC_URL global.METAMASK_DEBUG = 'GULP_METAMASK_DEBUG' @@ -10,5 +11,6 @@ module.exports = { mainnet: MAINET_RPC_URL, testnet: TESTNET_RPC_URL, morden: TESTNET_RPC_URL, + kovan: KOVAN_RPC_URL, }, } From 33dd7954a72797d23a0d8ed652a626addb1af55d Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Wed, 22 Mar 2017 16:00:50 -0400 Subject: [PATCH 020/372] Add kovan config settings to config manager. --- app/scripts/lib/config-manager.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/scripts/lib/config-manager.js b/app/scripts/lib/config-manager.js index 6868637e5..e31cb45ed 100644 --- a/app/scripts/lib/config-manager.js +++ b/app/scripts/lib/config-manager.js @@ -5,6 +5,7 @@ const normalize = require('eth-sig-util').normalize const TESTNET_RPC = MetamaskConfig.network.testnet const MAINNET_RPC = MetamaskConfig.network.mainnet const MORDEN_RPC = MetamaskConfig.network.morden +const KOVAN_RPC = MetamaskConfig.network.kovan /* The config-manager is a convenience object * wrapping a pojo-migrator. @@ -150,6 +151,9 @@ ConfigManager.prototype.getCurrentRpcAddress = function () { case 'morden': return MORDEN_RPC + case 'kovan': + return KOVAN_RPC + default: return provider && provider.rpcTarget ? provider.rpcTarget : TESTNET_RPC } From 4116b37d32e467bbd4cbb113851667115d28cf64 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Wed, 22 Mar 2017 16:01:38 -0400 Subject: [PATCH 021/372] Modify css rule for unused hollow diamond. --- ui/app/css/lib.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/app/css/lib.css b/ui/app/css/lib.css index 99c6f1b9d..670dc9fd0 100644 --- a/ui/app/css/lib.css +++ b/ui/app/css/lib.css @@ -188,7 +188,7 @@ hr.horizontal-line { .hollow-diamond { transform: rotate(45deg); - border: 1px solid #038789; + border: 3px solid #690496; } .pending-dot { From c00544de91caec4d76f34aeaf91f025292ae0384 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Wed, 22 Mar 2017 16:02:17 -0400 Subject: [PATCH 022/372] Add conditional kovan logic to etherscan link generators. --- ui/lib/account-link.js | 4 +++- ui/lib/explorer-link.js | 5 ++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/ui/lib/account-link.js b/ui/lib/account-link.js index 77db0851d..948f32da1 100644 --- a/ui/lib/account-link.js +++ b/ui/lib/account-link.js @@ -1,7 +1,6 @@ module.exports = function (address, network) { const net = parseInt(network) let link - switch (net) { case 1: // main net link = `http://etherscan.io/address/${address}` @@ -12,6 +11,9 @@ module.exports = function (address, network) { case 3: // ropsten test net link = `http://testnet.etherscan.io/address/${address}` break + case 42: // kovan test net + link = `http://kovan.etherscan.io/address/${address}` + break default: link = '' break diff --git a/ui/lib/explorer-link.js b/ui/lib/explorer-link.js index dc6be2984..7ae19cca0 100644 --- a/ui/lib/explorer-link.js +++ b/ui/lib/explorer-link.js @@ -5,9 +5,12 @@ module.exports = function (hash, network) { case 1: // main net prefix = '' break - case 3: // morden test net + case 3: // ropsten test net prefix = 'testnet.' break + case 42: // kovan test net + prefix = 'kovan.' + break default: prefix = '' } From b3dfc4e639bf4e73784d8a53227cfc0bc0a650fd Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Wed, 22 Mar 2017 16:03:02 -0400 Subject: [PATCH 023/372] Add kovan conditional to config screen. --- ui/app/config.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ui/app/config.js b/ui/app/config.js index 3f0507f48..444365de2 100644 --- a/ui/app/config.js +++ b/ui/app/config.js @@ -161,6 +161,11 @@ function currentProviderDisplay (metamaskState) { value = 'Ropsten Test Network' break + case 'kovan': + title = 'Current Network' + value = 'Kovan Test Network' + break + default: title = 'Current RPC' value = metamaskState.provider.rpcTarget From 4757858df0ea63952ec8e435749a0f2df0782612 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Wed, 22 Mar 2017 16:03:51 -0400 Subject: [PATCH 024/372] Add conditional kovan to current network component. --- ui/app/components/network.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/ui/app/components/network.js b/ui/app/components/network.js index 77805fd57..d9045167f 100644 --- a/ui/app/components/network.js +++ b/ui/app/components/network.js @@ -40,6 +40,9 @@ Network.prototype.render = function () { } else if (parseInt(networkNumber) === 3) { hoverText = 'Ropsten Test Network' iconName = 'ropsten-test-network' + } else if (providerName === 'kovan') { + hoverText = 'Kovan Test Network' + iconName = 'kovan-test-network' } else { hoverText = 'Unknown Private Network' iconName = 'unknown-private-network' @@ -70,6 +73,15 @@ Network.prototype.render = function () { }}, 'Ropsten Test Net'), ]) + case 'kovan-test-network': + return h('.network-indicator', [ + h('.menu-icon.hollow-diamond'), + h('.network-name', { + style: { + color: '#690496', + }}, + 'Kovan Test Net'), + ]) default: return h('.network-indicator', [ h('i.fa.fa-question-circle.fa-lg', { From 34f3889eb00d41920c67ec2d4845626b5b480eb5 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Wed, 22 Mar 2017 16:04:28 -0400 Subject: [PATCH 025/372] Add kovan to drop-menu-item --- ui/app/components/drop-menu-item.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ui/app/components/drop-menu-item.js b/ui/app/components/drop-menu-item.js index 9f002234e..3eb6ec876 100644 --- a/ui/app/components/drop-menu-item.js +++ b/ui/app/components/drop-menu-item.js @@ -42,7 +42,10 @@ DropMenuItem.prototype.activeNetworkRender = function () { if (providerType === 'mainnet') return h('.check', '✓') break case 'Ropsten Test Network': - if (provider.type === 'testnet') return h('.check', '✓') + if (providerType === 'testnet') return h('.check', '✓') + break + case 'Kovan Test Network': + if (providerType === 'kovan') return h('.check', '✓') break case 'Localhost 8545': if (activeNetwork === 'http://localhost:8545') return h('.check', '✓') From b26c53452e2817f4f00a4770eae8828731e97808 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Wed, 22 Mar 2017 16:05:04 -0400 Subject: [PATCH 026/372] Add Kovan test network to our application. --- ui/app/app.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/ui/app/app.js b/ui/app/app.js index 9c1ba8a3a..5a7596aca 100644 --- a/ui/app/app.js +++ b/ui/app/app.js @@ -255,6 +255,15 @@ App.prototype.renderNetworkDropdown = function () { provider: props.provider, }), + h(DropMenuItem, { + label: 'Kovan Test Network', + closeMenu: () => this.setState({ isNetworkMenuOpen: false}), + action: () => props.dispatch(actions.setProviderType('kovan')), + icon: h('.menu-icon.hollow-diamond'), + activeNetworkRender: props.network, + provider: props.provider, + }), + h(DropMenuItem, { label: 'Localhost 8545', closeMenu: () => this.setState({ isNetworkMenuOpen: false }), From 1c7e04db4a2997ec6a62558d331b7e23190cebdc Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Wed, 22 Mar 2017 16:06:48 -0400 Subject: [PATCH 027/372] Changelog bump. --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6621d89f4..34ab63ab4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - Can now change network to custom RPC URL from lock screen. - Removed support for old, lightwallet based vault. Users who have not opened app in over a month will need to recover with their seed phrase. This will allow Firefox support sooner. - Polish the private key UI. +- Add Kovan as an option on our network list. ## 3.4.0 2017-3-8 From 1b7326048db25afc36baea84ebcf9ece0847f3c3 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Wed, 22 Mar 2017 16:41:19 -0400 Subject: [PATCH 028/372] Add current block number and hash to the state. --- app/scripts/lib/eth-store.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/scripts/lib/eth-store.js b/app/scripts/lib/eth-store.js index 8812a507b..243253df2 100644 --- a/app/scripts/lib/eth-store.js +++ b/app/scripts/lib/eth-store.js @@ -19,6 +19,8 @@ class EthereumStore extends ObservableStore { super({ accounts: {}, transactions: {}, + currentBlockNumber: '0', + currentBlockHash: '', }) this._provider = opts.provider this._query = new EthQuery(this._provider) @@ -69,6 +71,8 @@ class EthereumStore extends ObservableStore { _updateForBlock (block) { const blockNumber = '0x' + block.number.toString('hex') this._currentBlockNumber = blockNumber + this.updateState({ currentBlockNumber: parseInt(blockNumber) }) + this.updateState({ currentBlockHash: `0x${block.hash.toString('hex')}`}) async.parallel([ this._updateAccounts.bind(this), this._updateTransactions.bind(this, blockNumber), @@ -129,4 +133,4 @@ class EthereumStore extends ObservableStore { } -module.exports = EthereumStore \ No newline at end of file +module.exports = EthereumStore From 5d149258427b25f34dca89e5dfaba57a4f1cb95b Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Wed, 22 Mar 2017 16:54:10 -0400 Subject: [PATCH 029/372] Fix styling of error message. --- ui/app/accounts/import/json.js | 3 +-- ui/app/accounts/import/private-key.js | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/ui/app/accounts/import/json.js b/ui/app/accounts/import/json.js index 1c2b331d4..5ed31ab0a 100644 --- a/ui/app/accounts/import/json.js +++ b/ui/app/accounts/import/json.js @@ -60,7 +60,7 @@ JsonImportSubview.prototype.render = function () { }, }, 'Import'), - error ? h('span.warning', error) : null, + error ? h('span.error', error) : null, ]) ) } @@ -95,4 +95,3 @@ JsonImportSubview.prototype.createNewKeychain = function () { this.props.dispatch(actions.importNewAccount('JSON File', [ fileContents, password ])) } - diff --git a/ui/app/accounts/import/private-key.js b/ui/app/accounts/import/private-key.js index b139a0374..68ccee58e 100644 --- a/ui/app/accounts/import/private-key.js +++ b/ui/app/accounts/import/private-key.js @@ -48,7 +48,7 @@ PrivateKeyImportView.prototype.render = function () { }, }, 'Import'), - error ? h('span.warning', error) : null, + error ? h('span.error', error) : null, ]) ) } @@ -65,4 +65,3 @@ PrivateKeyImportView.prototype.createNewKeychain = function () { const privateKey = input.value this.props.dispatch(actions.importNewAccount('Private Key', [ privateKey ])) } - From 41e276b0367281bb3e6481d0e7c6af89b7319f90 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Wed, 22 Mar 2017 17:46:51 -0400 Subject: [PATCH 030/372] Hide the password in the private key retrieval screen. --- ui/app/components/account-export.js | 1 + 1 file changed, 1 insertion(+) diff --git a/ui/app/components/account-export.js b/ui/app/components/account-export.js index 38a1d28ef..888196c5d 100644 --- a/ui/app/components/account-export.js +++ b/ui/app/components/account-export.js @@ -52,6 +52,7 @@ ExportAccountView.prototype.render = function () { }, [ h('p.error', warning), h('input#exportAccount.sizing-input', { + type: 'password', placeholder: 'confirm password', onKeyPress: this.onExportKeyPress.bind(this), style: { From 77907038ffe0edbe5e3472f98fd10d73b76464b8 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Wed, 22 Mar 2017 15:14:33 -0700 Subject: [PATCH 031/372] Got basic validations working --- ui/app/components/hex-as-decimal-input.js | 114 ++++++++++++++----- ui/app/components/pending-tx-details.js | 26 +++-- ui/app/components/pending-tx.js | 133 +++++++++++++--------- ui/app/conf-tx.js | 22 ++-- ui/app/css/index.css | 12 +- 5 files changed, 199 insertions(+), 108 deletions(-) diff --git a/ui/app/components/hex-as-decimal-input.js b/ui/app/components/hex-as-decimal-input.js index b2f1917f2..bbd86b254 100644 --- a/ui/app/components/hex-as-decimal-input.js +++ b/ui/app/components/hex-as-decimal-input.js @@ -9,6 +9,7 @@ module.exports = HexAsDecimalInput inherits(HexAsDecimalInput, Component) function HexAsDecimalInput () { + this.state = { invalid: null } Component.call(this) } @@ -23,50 +24,101 @@ function HexAsDecimalInput () { HexAsDecimalInput.prototype.render = function () { const props = this.props - const { value, onChange, min } = props + const state = this.state + + const { value, onChange, min, max } = props + const toEth = props.toEth const suffix = props.suffix const decimalValue = decimalize(value, toEth) const style = props.style return ( - h('.flex-row', { - style: { - alignItems: 'flex-end', - lineHeight: '13px', - fontFamily: 'Montserrat Light', - textRendering: 'geometricPrecision', - }, - }, [ - h('input.hex-input', { - type: 'number', - min, - style: extend({ - display: 'block', - textAlign: 'right', - backgroundColor: 'transparent', - border: '1px solid #bdbdbd', - - }, style), - value: decimalValue, - onChange: (event) => { - const hexString = (event.target.value === '') ? '' : hexify(event.target.value) - onChange(hexString) + h('.flex-column', [ + h('.flex-row', { + style: { + alignItems: 'flex-end', + lineHeight: '13px', + fontFamily: 'Montserrat Light', + textRendering: 'geometricPrecision', }, - }), - h('div', { + }, [ + h('input.hex-input', { + type: 'number', + required: true, + min: min, + max: max, + style: extend({ + display: 'block', + textAlign: 'right', + backgroundColor: 'transparent', + border: '1px solid #bdbdbd', + + }, style), + value: parseInt(decimalValue), + onChange: (event) => { + const target = event.target + const valid = target.checkValidity() + if (valid) { + this.setState({ invalid: null }) + } + const hexString = (event.target.value === '') ? '' : hexify(event.target.value) + onChange(hexString) + }, + onInvalid: (event) => { + const msg = this.constructWarning() + if (msg === state.invalid) { + return + } + this.setState({ invalid: msg }) + event.preventDefault() + }, + }), + h('div', { + style: { + color: ' #AEAEAE', + fontSize: '12px', + marginLeft: '5px', + marginRight: '6px', + width: '20px', + }, + }, suffix), + ]), + + state.invalid ? h('span.error', { style: { - color: ' #AEAEAE', - fontSize: '12px', - marginLeft: '5px', - marginRight: '6px', - width: '20px', + position: 'absolute', + right: '0px', + textAlign: 'right', + transform: 'translateY(26px)', + padding: '3px', + background: 'rgba(255,255,255,0.85)', + zIndex: '1', + textTransform: 'capitalize', + border: '2px solid #E20202', }, - }, suffix), + }, state.invalid) : null, ]) ) } +HexAsDecimalInput.prototype.constructWarning = function () { + const { name, min, max } = this.props + let message = name ? name + ' ' : '' + + if (min && max) { + message += `must be greater than or equal to ${min} and less than or equal to ${max}.` + } else if (min) { + message += `must be greater than or equal to ${min}.` + } else if (max) { + message += `must be less than or equal to ${max}.` + } else { + message += 'Invalid input.' + } + + return message +} + function hexify (decimalString) { const hexBN = new BN(decimalString, 10) return '0x' + hexBN.toString('hex') diff --git a/ui/app/components/pending-tx-details.js b/ui/app/components/pending-tx-details.js index 822e5d84b..375a50f22 100644 --- a/ui/app/components/pending-tx-details.js +++ b/ui/app/components/pending-tx-details.js @@ -12,15 +12,17 @@ const addressSummary = util.addressSummary const nameForAddress = require('../../lib/contract-namer') const HexInput = require('./hex-as-decimal-input') -const DEFAULT_GAS_PRICE = '0x4a817c800' -const DEFAULT_GAS_PRICE_BN = new BN(DEFAULT_GAS_PRICE.substr(2), 16) -const FOUR_BN = new BN('4', 10) +const DEFAULT_GAS_PRICE_BN = new BN(20000000000, '10') +const DEFAULT_GAS_PRICE = DEFAULT_GAS_PRICE_BN.toString(16) + +const MIN_GAS_PRICE_BN = new BN(20000000) module.exports = PendingTxDetails inherits(PendingTxDetails, Component) function PendingTxDetails () { Component.call(this) + this.state = { valid: true } } const PTXP = PendingTxDetails.prototype @@ -40,6 +42,7 @@ PTXP.render = function () { const gasPrice = (state.gasPrice === undefined) ? txData.gasPrice : state.gasPrice var txFee = state.txFee || txData.txFee || '' + var txFeeBn = new BN(txFee, 16) var maxCost = state.maxCost || txData.maxCost || '' var dataLength = txParams.data ? (txParams.data.length - 2) / 2 : 0 var imageify = props.imageifyIdenticons === undefined ? true : props.imageifyIdenticons @@ -124,6 +127,7 @@ PTXP.render = function () { h('.cell.value', { }, [ h(HexInput, { + name: 'Gas Limit', value: gas, min: 21000, // The hard lower limit for gas. suffix: 'UNITS', @@ -145,9 +149,10 @@ PTXP.render = function () { h('.cell.value', { }, [ h(HexInput, { + name: 'Gas Price', value: gasPrice, suffix: 'WEI', - min: DEFAULT_GAS_PRICE_BN.div(FOUR_BN).toString(10), + min: MIN_GAS_PRICE_BN.toString(10), style: { position: 'relative', top: '5px', @@ -163,7 +168,7 @@ PTXP.render = function () { // Max Transaction Fee (calculated) h('.cell.row', [ h('.cell.label', 'Max Transaction Fee'), - h(EthBalance, { value: txFee.toString(16) }), + h(EthBalance, { value: txFeeBn.toString(16) }), ]), h('.cell.row', { @@ -181,8 +186,7 @@ PTXP.render = function () { }, }, [ h(EthBalance, { - value: '0x' + txFee.add(new BN(txParams.value, 16)).toString(16), - maxCost.toString(16), + value: maxCost.toString(16), inline: true, labelColor: 'black', fontSize: '16px', @@ -267,6 +271,10 @@ PTXP.componentDidUpdate = function (prevProps, previousState) { } } +PTXP.isValid = function () { + return this.state.valid +} + PTXP.calculateGas = function () { const txMeta = this.gatherParams() log.debug(`pending-tx-details calculating gas for ${JSON.stringify(txMeta)}`) @@ -274,6 +282,10 @@ PTXP.calculateGas = function () { var txParams = txMeta.txParams var gasCost = new BN(ethUtil.stripHexPrefix(txParams.gas || txMeta.estimatedGas), 16) var gasPrice = new BN(ethUtil.stripHexPrefix(txParams.gasPrice || DEFAULT_GAS_PRICE), 16) + + const valid = !gasPrice.lt(MIN_GAS_PRICE_BN) + this.props.validChanged(valid) + var txFee = gasCost.mul(gasPrice) var txValue = new BN(ethUtil.stripHexPrefix(txParams.value || '0x0'), 16) var maxCost = txValue.add(txFee) diff --git a/ui/app/components/pending-tx.js b/ui/app/components/pending-tx.js index 2ab6f25a9..f6fb6f85e 100644 --- a/ui/app/components/pending-tx.js +++ b/ui/app/components/pending-tx.js @@ -17,11 +17,15 @@ function mapStateToProps (state) { inherits(PendingTx, Component) function PendingTx () { Component.call(this) + this.state = { valid: true } } PendingTx.prototype.render = function () { const props = this.props - const newProps = extend(props, {ref: 'details'}) + const newProps = extend(props, { + ref: 'details', + validChanged: this.validChanged.bind(this), + }) const txData = props.txData return ( @@ -30,70 +34,87 @@ PendingTx.prototype.render = function () { key: txData.id, }, [ - // tx info - h(PendingTxDetails, newProps), - - h('style', ` - .conf-buttons button { - margin-left: 10px; - text-transform: uppercase; - } - `), - - txData.simulationFails ? - h('.error', { - style: { - marginLeft: 50, - fontSize: '0.9em', - }, - }, 'Transaction Error. Exception thrown in contract code.') - : null, - - props.insufficientBalance ? - h('span.error', { - style: { - marginLeft: 50, - fontSize: '0.9em', - }, - }, 'Insufficient balance for transaction') - : null, - - // send + cancel - h('.flex-row.flex-space-around.conf-buttons', { - style: { - display: 'flex', - justifyContent: 'flex-end', - margin: '14px 25px', + h('form#pending-tx-form', { + onSubmit: (event) => { + event.preventDefault() + const form = document.querySelector('form#pending-tx-form') + const valid = form.checkValidity() + this.setState({ valid }) + + if (valid && this.refs.details.verifyGasParams()) { + props.sendTransaction(txData, event) + } else { + this.props.dispatch(actions.displayWarning('Invalid Gas Parameters')) + } }, }, [ + // tx info + h(PendingTxDetails, newProps), + + h('style', ` + .conf-buttons button { + margin-left: 10px; + text-transform: uppercase; + } + `), + + txData.simulationFails ? + h('.error', { + style: { + marginLeft: 50, + fontSize: '0.9em', + }, + }, 'Transaction Error. Exception thrown in contract code.') + : null, + props.insufficientBalance ? - h('button', { - onClick: props.buyEth, - }, 'Buy Ether') + h('span.error', { + style: { + marginLeft: 50, + fontSize: '0.9em', + }, + }, 'Insufficient balance for transaction') : null, - h('button', { - onClick: () => { - this.refs.details.resetGasFields() - }, - }, 'Reset'), - - h('button.confirm.btn-green', { - disabled: props.insufficientBalance, - onClick: (txData, event) => { - if (this.refs.details.verifyGasParams()) { - props.sendTransaction(txData, event) - } else { - this.props.dispatch(actions.displayWarning('Invalid Gas Parameters')) - } + // send + cancel + h('.flex-row.flex-space-around.conf-buttons', { + style: { + display: 'flex', + justifyContent: 'flex-end', + margin: '14px 25px', }, - }, 'Accept'), + }, [ + - h('button.cancel.btn-red', { - onClick: props.cancelTransaction, - }, 'Reject'), + props.insufficientBalance ? + h('button', { + onClick: props.buyEth, + }, 'Buy Ether') + : null, + + h('button', { + onClick: () => { + this.refs.details.resetGasFields() + }, + }, 'Reset'), + + h('input.confirm.btn-green', { + type: 'submit', + value: 'ACCEPT', + style: { marginLeft: '10px' }, + disabled: props.insufficientBalance || !this.state.valid, + }), + + h('button.cancel.btn-red', { + onClick: props.cancelTransaction, + }, 'Reject'), + ]), ]), ]) ) } + +PendingTx.prototype.validChanged = function (newValid) { + this.setState({ valid: newValid }) +} diff --git a/ui/app/conf-tx.js b/ui/app/conf-tx.js index 07985094c..ce7422f9c 100644 --- a/ui/app/conf-tx.js +++ b/ui/app/conf-tx.js @@ -158,7 +158,7 @@ ConfirmTxScreen.prototype.checkBalanceAgainstTx = function (txData) { } ConfirmTxScreen.prototype.buyEth = function (address, event) { - event.stopPropagation() + this.stopPropagation(event) this.props.dispatch(actions.buyEthView(address)) } @@ -172,14 +172,14 @@ ConfirmTxScreen.prototype.onTxChange = function (txData) { // Must default to any local state txData, // to allow manual override of gas calculations. ConfirmTxScreen.prototype.sendTransaction = function (txData, event) { - event.stopPropagation() + this.stopPropagation(event) const state = this.state || {} const txMeta = state.txData this.props.dispatch(actions.updateAndApproveTx(txMeta || txData)) } ConfirmTxScreen.prototype.cancelTransaction = function (txData, event) { - event.stopPropagation() + this.stopPropagation(event) this.props.dispatch(actions.cancelTx(txData)) } @@ -187,32 +187,38 @@ ConfirmTxScreen.prototype.signMessage = function (msgData, event) { log.info('conf-tx.js: signing message') var params = msgData.msgParams params.metamaskId = msgData.id - event.stopPropagation() + this.stopPropagation(event) this.props.dispatch(actions.signMsg(params)) } +ConfirmTxScreen.prototype.stopPropagation = function (event) { + if (event.stopPropagation) { + event.stopPropagation() + } +} + ConfirmTxScreen.prototype.signPersonalMessage = function (msgData, event) { log.info('conf-tx.js: signing personal message') var params = msgData.msgParams params.metamaskId = msgData.id - event.stopPropagation() + this.stopPropagation(event) this.props.dispatch(actions.signPersonalMsg(params)) } ConfirmTxScreen.prototype.cancelMessage = function (msgData, event) { log.info('canceling message') - event.stopPropagation() + this.stopPropagation(event) this.props.dispatch(actions.cancelMsg(msgData)) } ConfirmTxScreen.prototype.cancelPersonalMessage = function (msgData, event) { log.info('canceling personal message') - event.stopPropagation() + this.stopPropagation(event) this.props.dispatch(actions.cancelPersonalMsg(msgData)) } ConfirmTxScreen.prototype.goHome = function (event) { - event.stopPropagation() + this.stopPropagation(event) this.props.dispatch(actions.goHome()) } diff --git a/ui/app/css/index.css b/ui/app/css/index.css index 8c6ff29d3..7771ddd99 100644 --- a/ui/app/css/index.css +++ b/ui/app/css/index.css @@ -32,7 +32,7 @@ input:focus, textarea:focus { height: 500px; } -button { +button, input[type="submit"] { font-family: 'Montserrat Bold'; outline: none; cursor: pointer; @@ -46,17 +46,17 @@ button { box-shadow: 0px 3px 6px rgba(247, 134, 28, 0.36); } -button.btn-green { +.btn-green, input[type="submit"].btn-green { background: rgba(106, 195, 96, 1); box-shadow: 0px 3px 6px rgba(106, 195, 96, 0.36); } -button.btn-red { +.btn-red { background: rgba(254, 35, 17, 1); box-shadow: 0px 3px 6px rgba(254, 35, 17, 0.36); } -button[disabled] { +button[disabled], input[type="submit"][disabled] { cursor: not-allowed; background: rgba(197, 197, 197, 1); box-shadow: 0px 3px 6px rgba(197, 197, 197, 0.36); @@ -66,10 +66,10 @@ button.spaced { margin: 2px; } -button:not([disabled]):hover { +button:not([disabled]):hover, input[type="submit"]:not([disabled]):hover { transform: scale(1.1); } -button:not([disabled]):active { +button:not([disabled]):active, input[type="submit"]:not([disabled]):active { transform: scale(0.95); } From de0d9af066d57f98580d53359a53271bf8760515 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Wed, 22 Mar 2017 15:30:49 -0700 Subject: [PATCH 032/372] Bump changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6621d89f4..8ddfb85ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,9 @@ - Can now change network to custom RPC URL from lock screen. - Removed support for old, lightwallet based vault. Users who have not opened app in over a month will need to recover with their seed phrase. This will allow Firefox support sooner. - Polish the private key UI. +- Enforce minimum values for gas price and gas limit. +- Fix bug where total gas was sometimes not live-updated. +- Fix bug where editing gas value could have some abrupt behaviors (#1233) ## 3.4.0 2017-3-8 From 8726ece16a9357219524c59c6bb463971063a92b Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Wed, 22 Mar 2017 21:29:46 -0400 Subject: [PATCH 033/372] Create announcement for Kovan. --- notices/archive/notice_1.md | 1 + notices/notice-nonce.json | 2 +- notices/notices.json | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 notices/archive/notice_1.md diff --git a/notices/archive/notice_1.md b/notices/archive/notice_1.md new file mode 100644 index 000000000..488b60cc9 --- /dev/null +++ b/notices/archive/notice_1.md @@ -0,0 +1 @@ +MetaMask now lists a new network on our dropdown list: Kovan, a [Proof of Authority](https://github.com/paritytech/parity/wiki/Proof-of-Authority-Chains) testchain managed by several blockchain organizations such as Digix, Etherscan, and Parity. It is designed to be a more stable and reliable testnet alternative to Ropsten and was created in response to recent attacks that slowed down the Ropsten network. You can read more about Kovan [here](https://medium.com/@Digix/announcing-kovan-a-stable-ethereum-public-testnet-10ac7cb6c85f#.6o8sz8cct) and [here](https://medium.com/@Digix/letter-from-the-ceo-some-context-regarding-kovan-7b5121adb901#.kfv7zhw83). As with Ropsten, the default remote node to connect to Kovan is managed by Infura. diff --git a/notices/notice-nonce.json b/notices/notice-nonce.json index d00491fd7..d8263ee98 100644 --- a/notices/notice-nonce.json +++ b/notices/notice-nonce.json @@ -1 +1 @@ -1 +2 \ No newline at end of file diff --git a/notices/notices.json b/notices/notices.json index 9f28b32a6..5503c7855 100644 --- a/notices/notices.json +++ b/notices/notices.json @@ -1 +1 @@ -[{"read":false,"date":"Thu Feb 09 2017","title":"Terms of Use","body":"# Terms of Use #\n\n**THIS AGREEMENT IS SUBJECT TO BINDING ARBITRATION AND A WAIVER OF CLASS ACTION RIGHTS AS DETAILED IN SECTION 13. PLEASE READ THE AGREEMENT CAREFULLY.**\n\n_Our Terms of Use have been updated as of September 5, 2016_\n\n## 1. Acceptance of Terms ##\n\nMetaMask provides a platform for managing Ethereum (or \"ETH\") accounts, and allowing ordinary websites to interact with the Ethereum blockchain, while keeping the user in control over what transactions they approve, through our website located at[ ](http://metamask.io)[https://metamask.io/](https://metamask.io/) and browser plugin (the \"Site\") — which includes text, images, audio, code and other materials (collectively, the “Content”) and all of the features, and services provided. The Site, and any other features, tools, materials, or other services offered from time to time by MetaMask are referred to here as the “Service.” Please read these Terms of Use (the “Terms” or “Terms of Use”) carefully before using the Service. By using or otherwise accessing the Services, or clicking to accept or agree to these Terms where that option is made available, you (1) accept and agree to these Terms (2) consent to the collection, use, disclosure and other handling of information as described in our Privacy Policy and (3) any additional terms, rules and conditions of participation issued by MetaMask from time to time. If you do not agree to the Terms, then you may not access or use the Content or Services.\n\n## 2. Modification of Terms of Use ##\n\nExcept for Section 13, providing for binding arbitration and waiver of class action rights, MetaMask reserves the right, at its sole discretion, to modify or replace the Terms of Use at any time. The most current version of these Terms will be posted on our Site. You shall be responsible for reviewing and becoming familiar with any such modifications. Use of the Services by you after any modification to the Terms constitutes your acceptance of the Terms of Use as modified.\n\n\n\n## 3. Eligibility ##\n\nYou hereby represent and warrant that you are fully able and competent to enter into the terms, conditions, obligations, affirmations, representations and warranties set forth in these Terms and to abide by and comply with these Terms.\n\nMetaMask is a global platform and by accessing the Content or Services, you are representing and warranting that, you are of the legal age of majority in your jurisdiction as is required to access such Services and Content and enter into arrangements as provided by the Service. You further represent that you are otherwise legally permitted to use the service in your jurisdiction including owning cryptographic tokens of value, and interacting with the Services or Content in any way. You further represent you are responsible for ensuring compliance with the laws of your jurisdiction and acknowledge that MetaMask is not liable for your compliance with such laws.\n\n## 4 Account Password and Security ##\n\nWhen setting up an account within MetaMask, you will be responsible for keeping your own account secrets, which may be a twelve-word seed phrase, an account file, or other locally stored secret information. MetaMask encrypts this information locally with a password you provide, that we never send to our servers. You agree to (a) never use the same password for MetaMask that you have ever used outside of this service; (b) keep your secret information and password confidential and do not share them with anyone else; (c) immediately notify MetaMask of any unauthorized use of your account or breach of security. MetaMask cannot and will not be liable for any loss or damage arising from your failure to comply with this section.\n\n## 5. Representations, Warranties, and Risks ##\n\n### 5.1. Warranty Disclaimer ###\n\nYou expressly understand and agree that your use of the Service is at your sole risk. The Service (including the Service and the Content) are provided on an \"AS IS\" and \"as available\" basis, without warranties of any kind, either express or implied, including, without limitation, implied warranties of merchantability, fitness for a particular purpose or non-infringement. You acknowledge that MetaMask has no control over, and no duty to take any action regarding: which users gain access to or use the Service; what effects the Content may have on you; how you may interpret or use the Content; or what actions you may take as a result of having been exposed to the Content. You release MetaMask from all liability for you having acquired or not acquired Content through the Service. MetaMask makes no representations concerning any Content contained in or accessed through the Service, and MetaMask will not be responsible or liable for the accuracy, copyright compliance, legality or decency of material contained in or accessed through the Service.\n\n### 5.2 Sophistication and Risk of Cryptographic Systems ###\n\nBy utilizing the Service or interacting with the Content or platform in any way, you represent that you understand the inherent risks associated with cryptographic systems; and warrant that you have an understanding of the usage and intricacies of native cryptographic tokens, like Ether (ETH) and Bitcoin (BTC), smart contract based tokens such as those that follow the Ethereum Token Standard (https://github.com/ethereum/EIPs/issues/20), and blockchain-based software systems.\n\n### 5.3 Risk of Regulatory Actions in One or More Jurisdictions ###\n\nMetaMask and ETH could be impacted by one or more regulatory inquiries or regulatory action, which could impede or limit the ability of MetaMask to continue to develop, or which could impede or limit your ability to access or use the Service or Ethereum blockchain.\n\n### 5.4 Risk of Weaknesses or Exploits in the Field of Cryptography ###\n\nYou acknowledge and understand that Cryptography is a progressing field. Advances in code cracking or technical advances such as the development of quantum computers may present risks to cryptocurrencies and Services of Content, which could result in the theft or loss of your cryptographic tokens or property. To the extent possible, MetaMask intends to update the protocol underlying Services to account for any advances in cryptography and to incorporate additional security measures, but does not guarantee or otherwise represent full security of the system. By using the Service or accessing Content, you acknowledge these inherent risks.\n\n### 5.5 Volatility of Crypto Currencies ###\n\nYou understand that Ethereum and other blockchain technologies and associated currencies or tokens are highly volatile due to many factors including but not limited to adoption, speculation, technology and security risks. You also acknowledge that the cost of transacting on such technologies is variable and may increase at any time causing impact to any activities taking place on the Ethereum blockchain. You acknowledge these risks and represent that MetaMask cannot be held liable for such fluctuations or increased costs.\n\n### 5.6 Application Security ###\n\nYou acknowledge that Ethereum applications are code subject to flaws and acknowledge that you are solely responsible for evaluating any code provided by the Services or Content and the trustworthiness of any third-party websites, products, smart-contracts, or Content you access or use through the Service. You further expressly acknowledge and represent that Ethereum applications can be written maliciously or negligently, that MetaMask cannot be held liable for your interaction with such applications and that such applications may cause the loss of property or even identity. This warning and others later provided by MetaMask in no way evidence or represent an on-going duty to alert you to all of the potential risks of utilizing the Service or Content.\n\n## 6. Indemnity ##\n\nYou agree to release and to indemnify, defend and hold harmless MetaMask and its parents, subsidiaries, affiliates and agencies, as well as the officers, directors, employees, shareholders and representatives of any of the foregoing entities, from and against any and all losses, liabilities, expenses, damages, costs (including attorneys’ fees and court costs) claims or actions of any kind whatsoever arising or resulting from your use of the Service, your violation of these Terms of Use, and any of your acts or omissions that implicate publicity rights, defamation or invasion of privacy. MetaMask reserves the right, at its own expense, to assume exclusive defense and control of any matter otherwise subject to indemnification by you and, in such case, you agree to cooperate with MetaMask in the defense of such matter.\n\n## 7. Limitation on liability ##\n\nYOU ACKNOWLEDGE AND AGREE THAT YOU ASSUME FULL RESPONSIBILITY FOR YOUR USE OF THE SITE AND SERVICE. YOU ACKNOWLEDGE AND AGREE THAT ANY INFORMATION YOU SEND OR RECEIVE DURING YOUR USE OF THE SITE AND SERVICE MAY NOT BE SECURE AND MAY BE INTERCEPTED OR LATER ACQUIRED BY UNAUTHORIZED PARTIES. YOU ACKNOWLEDGE AND AGREE THAT YOUR USE OF THE SITE AND SERVICE IS AT YOUR OWN RISK. RECOGNIZING SUCH, YOU UNDERSTAND AND AGREE THAT, TO THE FULLEST EXTENT PERMITTED BY APPLICABLE LAW, NEITHER METAMASK NOR ITS SUPPLIERS OR LICENSORS WILL BE LIABLE TO YOU FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY OR OTHER DAMAGES OF ANY KIND, INCLUDING WITHOUT LIMITATION DAMAGES FOR LOSS OF PROFITS, GOODWILL, USE, DATA OR OTHER TANGIBLE OR INTANGIBLE LOSSES OR ANY OTHER DAMAGES BASED ON CONTRACT, TORT, STRICT LIABILITY OR ANY OTHER THEORY (EVEN IF METAMASK HAD BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES), RESULTING FROM THE SITE OR SERVICE; THE USE OR THE INABILITY TO USE THE SITE OR SERVICE; UNAUTHORIZED ACCESS TO OR ALTERATION OF YOUR TRANSMISSIONS OR DATA; STATEMENTS OR CONDUCT OF ANY THIRD PARTY ON THE SITE OR SERVICE; ANY ACTIONS WE TAKE OR FAIL TO TAKE AS A RESULT OF COMMUNICATIONS YOU SEND TO US; HUMAN ERRORS; TECHNICAL MALFUNCTIONS; FAILURES, INCLUDING PUBLIC UTILITY OR TELEPHONE OUTAGES; OMISSIONS, INTERRUPTIONS, LATENCY, DELETIONS OR DEFECTS OF ANY DEVICE OR NETWORK, PROVIDERS, OR SOFTWARE (INCLUDING, BUT NOT LIMITED TO, THOSE THAT DO NOT PERMIT PARTICIPATION IN THE SERVICE); ANY INJURY OR DAMAGE TO COMPUTER EQUIPMENT; INABILITY TO FULLY ACCESS THE SITE OR SERVICE OR ANY OTHER WEBSITE; THEFT, TAMPERING, DESTRUCTION, OR UNAUTHORIZED ACCESS TO, IMAGES OR OTHER CONTENT OF ANY KIND; DATA THAT IS PROCESSED LATE OR INCORRECTLY OR IS INCOMPLETE OR LOST; TYPOGRAPHICAL, PRINTING OR OTHER ERRORS, OR ANY COMBINATION THEREOF; OR ANY OTHER MATTER RELATING TO THE SITE OR SERVICE.\n\nSOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF CERTAIN WARRANTIES OR THE LIMITATION OR EXCLUSION OF LIABILITY FOR INCIDENTAL OR CONSEQUENTIAL DAMAGES. ACCORDINGLY, SOME OF THE ABOVE LIMITATIONS MAY NOT APPLY TO YOU.\n\n## 8. Our Proprietary Rights ##\n\nAll title, ownership and intellectual property rights in and to the Service are owned by MetaMask or its licensors. You acknowledge and agree that the Service contains proprietary and confidential information that is protected by applicable intellectual property and other laws. Except as expressly authorized by MetaMask, you agree not to copy, modify, rent, lease, loan, sell, distribute, perform, display or create derivative works based on the Service, in whole or in part. MetaMask issues a license for MetaMask, found [here](https://github.com/MetaMask/metamask-plugin/blob/master/LICENSE). For information on other licenses utilized in the development of MetaMask, please see our attribution page at: [https://metamask.io/attributions.html](https://metamask.io/attributions.html)\n\n## 9. Links ##\n\nThe Service provides, or third parties may provide, links to other World Wide Web or accessible sites, applications or resources. Because MetaMask has no control over such sites, applications and resources, you acknowledge and agree that MetaMask is not responsible for the availability of such external sites, applications or resources, and does not endorse and is not responsible or liable for any content, advertising, products or other materials on or available from such sites or resources. You further acknowledge and agree that MetaMask shall not be responsible or liable, directly or indirectly, for any damage or loss caused or alleged to be caused by or in connection with use of or reliance on any such content, goods or services available on or through any such site or resource.\n\n## 10. Termination and Suspension ##\n\nMetaMask may terminate or suspend all or part of the Service and your MetaMask access immediately, without prior notice or liability, if you breach any of the terms or conditions of the Terms. Upon termination of your access, your right to use the Service will immediately cease.\n\nThe following provisions of the Terms survive any termination of these Terms: INDEMNITY; WARRANTY DISCLAIMERS; LIMITATION ON LIABILITY; OUR PROPRIETARY RIGHTS; LINKS; TERMINATION; NO THIRD PARTY BENEFICIARIES; BINDING ARBITRATION AND CLASS ACTION WAIVER; GENERAL INFORMATION.\n\n## 11. No Third Party Beneficiaries ##\n\nYou agree that, except as otherwise expressly provided in these Terms, there shall be no third party beneficiaries to the Terms.\n\n## 12. Notice and Procedure For Making Claims of Copyright Infringement ##\n\nIf you believe that your copyright or the copyright of a person on whose behalf you are authorized to act has been infringed, please provide MetaMask’s Copyright Agent a written Notice containing the following information:\n\n· an electronic or physical signature of the person authorized to act on behalf of the owner of the copyright or other intellectual property interest;\n\n· a description of the copyrighted work or other intellectual property that you claim has been infringed;\n\n· a description of where the material that you claim is infringing is located on the Service;\n\n· your address, telephone number, and email address;\n\n· a statement by you that you have a good faith belief that the disputed use is not authorized by the copyright owner, its agent, or the law;\n\n· a statement by you, made under penalty of perjury, that the above information in your Notice is accurate and that you are the copyright or intellectual property owner or authorized to act on the copyright or intellectual property owner's behalf.\n\nMetaMask’s Copyright Agent can be reached at:\n\nEmail: copyright [at] metamask [dot] io\n\nMail:\n\nAttention:\n\nMetaMask Copyright ℅ ConsenSys\n\n49 Bogart Street\n\nBrooklyn, NY 11206\n\n## 13. Binding Arbitration and Class Action Waiver ##\n\nPLEASE READ THIS SECTION CAREFULLY – IT MAY SIGNIFICANTLY AFFECT YOUR LEGAL RIGHTS, INCLUDING YOUR RIGHT TO FILE A LAWSUIT IN COURT\n\n### 13.1 Initial Dispute Resolution ###\n\nThe parties shall use their best efforts to engage directly to settle any dispute, claim, question, or disagreement and engage in good faith negotiations which shall be a condition to either party initiating a lawsuit or arbitration.\n\n### 13.2 Binding Arbitration ###\n\nIf the parties do not reach an agreed upon solution within a period of 30 days from the time informal dispute resolution under the Initial Dispute Resolution provision begins, then either party may initiate binding arbitration as the sole means to resolve claims, subject to the terms set forth below. Specifically, all claims arising out of or relating to these Terms (including their formation, performance and breach), the parties’ relationship with each other and/or your use of the Service shall be finally settled by binding arbitration administered by the American Arbitration Association in accordance with the provisions of its Commercial Arbitration Rules and the supplementary procedures for consumer related disputes of the American Arbitration Association (the \"AAA\"), excluding any rules or procedures governing or permitting class actions.\n\nThe arbitrator, and not any federal, state or local court or agency, shall have exclusive authority to resolve all disputes arising out of or relating to the interpretation, applicability, enforceability or formation of these Terms, including, but not limited to any claim that all or any part of these Terms are void or voidable, or whether a claim is subject to arbitration. The arbitrator shall be empowered to grant whatever relief would be available in a court under law or in equity. The arbitrator’s award shall be written, and binding on the parties and may be entered as a judgment in any court of competent jurisdiction.\n\nThe parties understand that, absent this mandatory provision, they would have the right to sue in court and have a jury trial. They further understand that, in some instances, the costs of arbitration could exceed the costs of litigation and the right to discovery may be more limited in arbitration than in court.\n\n### 13.3 Location ###\n\nBinding arbitration shall take place in New York. You agree to submit to the personal jurisdiction of any federal or state court in New York County, New York, in order to compel arbitration, to stay proceedings pending arbitration, or to confirm, modify, vacate or enter judgment on the award entered by the arbitrator.\n\n### 13.4 Class Action Waiver ###\n\nThe parties further agree that any arbitration shall be conducted in their individual capacities only and not as a class action or other representative action, and the parties expressly waive their right to file a class action or seek relief on a class basis. YOU AND METAMASK AGREE THAT EACH MAY BRING CLAIMS AGAINST THE OTHER ONLY IN YOUR OR ITS INDIVIDUAL CAPACITY, AND NOT AS A PLAINTIFF OR CLASS MEMBER IN ANY PURPORTED CLASS OR REPRESENTATIVE PROCEEDING. If any court or arbitrator determines that the class action waiver set forth in this paragraph is void or unenforceable for any reason or that an arbitration can proceed on a class basis, then the arbitration provision set forth above shall be deemed null and void in its entirety and the parties shall be deemed to have not agreed to arbitrate disputes.\n\n### 13.5 Exception - Litigation of Intellectual Property and Small Claims Court Claims ###\n\nNotwithstanding the parties' decision to resolve all disputes through arbitration, either party may bring an action in state or federal court to protect its intellectual property rights (\"intellectual property rights\" means patents, copyrights, moral rights, trademarks, and trade secrets, but not privacy or publicity rights). Either party may also seek relief in a small claims court for disputes or claims within the scope of that court’s jurisdiction.\n\n### 13.6 30-Day Right to Opt Out ###\n\nYou have the right to opt-out and not be bound by the arbitration and class action waiver provisions set forth above by sending written notice of your decision to opt-out to the following address: MetaMask ℅ ConsenSys, 49 Bogart Street, Brooklyn NY 11206 and via email at legal-opt@metamask.io. The notice must be sent within 30 days of September 6, 2016 or your first use of the Service, whichever is later, otherwise you shall be bound to arbitrate disputes in accordance with the terms of those paragraphs. If you opt-out of these arbitration provisions, MetaMask also will not be bound by them.\n\n### 13.7 Changes to This Section ###\n\nMetaMask will provide 60-days’ notice of any changes to this section. Changes will become effective on the 60th day, and will apply prospectively only to any claims arising after the 60th day.\n\nFor any dispute not subject to arbitration you and MetaMask agree to submit to the personal and exclusive jurisdiction of and venue in the federal and state courts located in New York, New York. You further agree to accept service of process by mail, and hereby waive any and all jurisdictional and venue defenses otherwise available.\n\nThe Terms and the relationship between you and MetaMask shall be governed by the laws of the State of New York without regard to conflict of law provisions.\n\n## 14. General Information ##\n\n### 14.1 Entire Agreement ###\n\nThese Terms (and any additional terms, rules and conditions of participation that MetaMask may post on the Service) constitute the entire agreement between you and MetaMask with respect to the Service and supersedes any prior agreements, oral or written, between you and MetaMask. In the event of a conflict between these Terms and the additional terms, rules and conditions of participation, the latter will prevail over the Terms to the extent of the conflict.\n\n### 14.2 Waiver and Severability of Terms ###\n\nThe failure of MetaMask to exercise or enforce any right or provision of the Terms shall not constitute a waiver of such right or provision. If any provision of the Terms is found by an arbitrator or court of competent jurisdiction to be invalid, the parties nevertheless agree that the arbitrator or court should endeavor to give effect to the parties' intentions as reflected in the provision, and the other provisions of the Terms remain in full force and effect.\n\n### 14.3 Statute of Limitations ###\n\nYou agree that regardless of any statute or law to the contrary, any claim or cause of action arising out of or related to the use of the Service or the Terms must be filed within one (1) year after such claim or cause of action arose or be forever barred.\n\n### 14.4 Section Titles ###\n\nThe section titles in the Terms are for convenience only and have no legal or contractual effect.\n\n### 14.5 Communications ###\n\nUsers with questions, complaints or claims with respect to the Service may contact us using the relevant contact information set forth above and at communications@metamask.io.\n\n## 15 Related Links ##\n\n**[Terms of Use](https://metamask.io/terms.html)**\n\n**[Privacy](https://metamask.io/privacy.html)**\n\n**[Attributions](https://metamask.io/attributions.html)**\n\n","id":0}] \ No newline at end of file +[{"read":false,"date":"Thu Feb 09 2017","title":"Terms of Use","body":"# Terms of Use #\n\n**THIS AGREEMENT IS SUBJECT TO BINDING ARBITRATION AND A WAIVER OF CLASS ACTION RIGHTS AS DETAILED IN SECTION 13. PLEASE READ THE AGREEMENT CAREFULLY.**\n\n_Our Terms of Use have been updated as of September 5, 2016_\n\n## 1. Acceptance of Terms ##\n\nMetaMask provides a platform for managing Ethereum (or \"ETH\") accounts, and allowing ordinary websites to interact with the Ethereum blockchain, while keeping the user in control over what transactions they approve, through our website located at[ ](http://metamask.io)[https://metamask.io/](https://metamask.io/) and browser plugin (the \"Site\") — which includes text, images, audio, code and other materials (collectively, the “Content”) and all of the features, and services provided. The Site, and any other features, tools, materials, or other services offered from time to time by MetaMask are referred to here as the “Service.” Please read these Terms of Use (the “Terms” or “Terms of Use”) carefully before using the Service. By using or otherwise accessing the Services, or clicking to accept or agree to these Terms where that option is made available, you (1) accept and agree to these Terms (2) consent to the collection, use, disclosure and other handling of information as described in our Privacy Policy and (3) any additional terms, rules and conditions of participation issued by MetaMask from time to time. If you do not agree to the Terms, then you may not access or use the Content or Services.\n\n## 2. Modification of Terms of Use ##\n\nExcept for Section 13, providing for binding arbitration and waiver of class action rights, MetaMask reserves the right, at its sole discretion, to modify or replace the Terms of Use at any time. The most current version of these Terms will be posted on our Site. You shall be responsible for reviewing and becoming familiar with any such modifications. Use of the Services by you after any modification to the Terms constitutes your acceptance of the Terms of Use as modified.\n\n\n\n## 3. Eligibility ##\n\nYou hereby represent and warrant that you are fully able and competent to enter into the terms, conditions, obligations, affirmations, representations and warranties set forth in these Terms and to abide by and comply with these Terms.\n\nMetaMask is a global platform and by accessing the Content or Services, you are representing and warranting that, you are of the legal age of majority in your jurisdiction as is required to access such Services and Content and enter into arrangements as provided by the Service. You further represent that you are otherwise legally permitted to use the service in your jurisdiction including owning cryptographic tokens of value, and interacting with the Services or Content in any way. You further represent you are responsible for ensuring compliance with the laws of your jurisdiction and acknowledge that MetaMask is not liable for your compliance with such laws.\n\n## 4 Account Password and Security ##\n\nWhen setting up an account within MetaMask, you will be responsible for keeping your own account secrets, which may be a twelve-word seed phrase, an account file, or other locally stored secret information. MetaMask encrypts this information locally with a password you provide, that we never send to our servers. You agree to (a) never use the same password for MetaMask that you have ever used outside of this service; (b) keep your secret information and password confidential and do not share them with anyone else; (c) immediately notify MetaMask of any unauthorized use of your account or breach of security. MetaMask cannot and will not be liable for any loss or damage arising from your failure to comply with this section.\n\n## 5. Representations, Warranties, and Risks ##\n\n### 5.1. Warranty Disclaimer ###\n\nYou expressly understand and agree that your use of the Service is at your sole risk. The Service (including the Service and the Content) are provided on an \"AS IS\" and \"as available\" basis, without warranties of any kind, either express or implied, including, without limitation, implied warranties of merchantability, fitness for a particular purpose or non-infringement. You acknowledge that MetaMask has no control over, and no duty to take any action regarding: which users gain access to or use the Service; what effects the Content may have on you; how you may interpret or use the Content; or what actions you may take as a result of having been exposed to the Content. You release MetaMask from all liability for you having acquired or not acquired Content through the Service. MetaMask makes no representations concerning any Content contained in or accessed through the Service, and MetaMask will not be responsible or liable for the accuracy, copyright compliance, legality or decency of material contained in or accessed through the Service.\n\n### 5.2 Sophistication and Risk of Cryptographic Systems ###\n\nBy utilizing the Service or interacting with the Content or platform in any way, you represent that you understand the inherent risks associated with cryptographic systems; and warrant that you have an understanding of the usage and intricacies of native cryptographic tokens, like Ether (ETH) and Bitcoin (BTC), smart contract based tokens such as those that follow the Ethereum Token Standard (https://github.com/ethereum/EIPs/issues/20), and blockchain-based software systems.\n\n### 5.3 Risk of Regulatory Actions in One or More Jurisdictions ###\n\nMetaMask and ETH could be impacted by one or more regulatory inquiries or regulatory action, which could impede or limit the ability of MetaMask to continue to develop, or which could impede or limit your ability to access or use the Service or Ethereum blockchain.\n\n### 5.4 Risk of Weaknesses or Exploits in the Field of Cryptography ###\n\nYou acknowledge and understand that Cryptography is a progressing field. Advances in code cracking or technical advances such as the development of quantum computers may present risks to cryptocurrencies and Services of Content, which could result in the theft or loss of your cryptographic tokens or property. To the extent possible, MetaMask intends to update the protocol underlying Services to account for any advances in cryptography and to incorporate additional security measures, but does not guarantee or otherwise represent full security of the system. By using the Service or accessing Content, you acknowledge these inherent risks.\n\n### 5.5 Volatility of Crypto Currencies ###\n\nYou understand that Ethereum and other blockchain technologies and associated currencies or tokens are highly volatile due to many factors including but not limited to adoption, speculation, technology and security risks. You also acknowledge that the cost of transacting on such technologies is variable and may increase at any time causing impact to any activities taking place on the Ethereum blockchain. You acknowledge these risks and represent that MetaMask cannot be held liable for such fluctuations or increased costs.\n\n### 5.6 Application Security ###\n\nYou acknowledge that Ethereum applications are code subject to flaws and acknowledge that you are solely responsible for evaluating any code provided by the Services or Content and the trustworthiness of any third-party websites, products, smart-contracts, or Content you access or use through the Service. You further expressly acknowledge and represent that Ethereum applications can be written maliciously or negligently, that MetaMask cannot be held liable for your interaction with such applications and that such applications may cause the loss of property or even identity. This warning and others later provided by MetaMask in no way evidence or represent an on-going duty to alert you to all of the potential risks of utilizing the Service or Content.\n\n## 6. Indemnity ##\n\nYou agree to release and to indemnify, defend and hold harmless MetaMask and its parents, subsidiaries, affiliates and agencies, as well as the officers, directors, employees, shareholders and representatives of any of the foregoing entities, from and against any and all losses, liabilities, expenses, damages, costs (including attorneys’ fees and court costs) claims or actions of any kind whatsoever arising or resulting from your use of the Service, your violation of these Terms of Use, and any of your acts or omissions that implicate publicity rights, defamation or invasion of privacy. MetaMask reserves the right, at its own expense, to assume exclusive defense and control of any matter otherwise subject to indemnification by you and, in such case, you agree to cooperate with MetaMask in the defense of such matter.\n\n## 7. Limitation on liability ##\n\nYOU ACKNOWLEDGE AND AGREE THAT YOU ASSUME FULL RESPONSIBILITY FOR YOUR USE OF THE SITE AND SERVICE. YOU ACKNOWLEDGE AND AGREE THAT ANY INFORMATION YOU SEND OR RECEIVE DURING YOUR USE OF THE SITE AND SERVICE MAY NOT BE SECURE AND MAY BE INTERCEPTED OR LATER ACQUIRED BY UNAUTHORIZED PARTIES. YOU ACKNOWLEDGE AND AGREE THAT YOUR USE OF THE SITE AND SERVICE IS AT YOUR OWN RISK. RECOGNIZING SUCH, YOU UNDERSTAND AND AGREE THAT, TO THE FULLEST EXTENT PERMITTED BY APPLICABLE LAW, NEITHER METAMASK NOR ITS SUPPLIERS OR LICENSORS WILL BE LIABLE TO YOU FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY OR OTHER DAMAGES OF ANY KIND, INCLUDING WITHOUT LIMITATION DAMAGES FOR LOSS OF PROFITS, GOODWILL, USE, DATA OR OTHER TANGIBLE OR INTANGIBLE LOSSES OR ANY OTHER DAMAGES BASED ON CONTRACT, TORT, STRICT LIABILITY OR ANY OTHER THEORY (EVEN IF METAMASK HAD BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES), RESULTING FROM THE SITE OR SERVICE; THE USE OR THE INABILITY TO USE THE SITE OR SERVICE; UNAUTHORIZED ACCESS TO OR ALTERATION OF YOUR TRANSMISSIONS OR DATA; STATEMENTS OR CONDUCT OF ANY THIRD PARTY ON THE SITE OR SERVICE; ANY ACTIONS WE TAKE OR FAIL TO TAKE AS A RESULT OF COMMUNICATIONS YOU SEND TO US; HUMAN ERRORS; TECHNICAL MALFUNCTIONS; FAILURES, INCLUDING PUBLIC UTILITY OR TELEPHONE OUTAGES; OMISSIONS, INTERRUPTIONS, LATENCY, DELETIONS OR DEFECTS OF ANY DEVICE OR NETWORK, PROVIDERS, OR SOFTWARE (INCLUDING, BUT NOT LIMITED TO, THOSE THAT DO NOT PERMIT PARTICIPATION IN THE SERVICE); ANY INJURY OR DAMAGE TO COMPUTER EQUIPMENT; INABILITY TO FULLY ACCESS THE SITE OR SERVICE OR ANY OTHER WEBSITE; THEFT, TAMPERING, DESTRUCTION, OR UNAUTHORIZED ACCESS TO, IMAGES OR OTHER CONTENT OF ANY KIND; DATA THAT IS PROCESSED LATE OR INCORRECTLY OR IS INCOMPLETE OR LOST; TYPOGRAPHICAL, PRINTING OR OTHER ERRORS, OR ANY COMBINATION THEREOF; OR ANY OTHER MATTER RELATING TO THE SITE OR SERVICE.\n\nSOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF CERTAIN WARRANTIES OR THE LIMITATION OR EXCLUSION OF LIABILITY FOR INCIDENTAL OR CONSEQUENTIAL DAMAGES. ACCORDINGLY, SOME OF THE ABOVE LIMITATIONS MAY NOT APPLY TO YOU.\n\n## 8. Our Proprietary Rights ##\n\nAll title, ownership and intellectual property rights in and to the Service are owned by MetaMask or its licensors. You acknowledge and agree that the Service contains proprietary and confidential information that is protected by applicable intellectual property and other laws. Except as expressly authorized by MetaMask, you agree not to copy, modify, rent, lease, loan, sell, distribute, perform, display or create derivative works based on the Service, in whole or in part. MetaMask issues a license for MetaMask, found [here](https://github.com/MetaMask/metamask-plugin/blob/master/LICENSE). For information on other licenses utilized in the development of MetaMask, please see our attribution page at: [https://metamask.io/attributions.html](https://metamask.io/attributions.html)\n\n## 9. Links ##\n\nThe Service provides, or third parties may provide, links to other World Wide Web or accessible sites, applications or resources. Because MetaMask has no control over such sites, applications and resources, you acknowledge and agree that MetaMask is not responsible for the availability of such external sites, applications or resources, and does not endorse and is not responsible or liable for any content, advertising, products or other materials on or available from such sites or resources. You further acknowledge and agree that MetaMask shall not be responsible or liable, directly or indirectly, for any damage or loss caused or alleged to be caused by or in connection with use of or reliance on any such content, goods or services available on or through any such site or resource.\n\n## 10. Termination and Suspension ##\n\nMetaMask may terminate or suspend all or part of the Service and your MetaMask access immediately, without prior notice or liability, if you breach any of the terms or conditions of the Terms. Upon termination of your access, your right to use the Service will immediately cease.\n\nThe following provisions of the Terms survive any termination of these Terms: INDEMNITY; WARRANTY DISCLAIMERS; LIMITATION ON LIABILITY; OUR PROPRIETARY RIGHTS; LINKS; TERMINATION; NO THIRD PARTY BENEFICIARIES; BINDING ARBITRATION AND CLASS ACTION WAIVER; GENERAL INFORMATION.\n\n## 11. No Third Party Beneficiaries ##\n\nYou agree that, except as otherwise expressly provided in these Terms, there shall be no third party beneficiaries to the Terms.\n\n## 12. Notice and Procedure For Making Claims of Copyright Infringement ##\n\nIf you believe that your copyright or the copyright of a person on whose behalf you are authorized to act has been infringed, please provide MetaMask’s Copyright Agent a written Notice containing the following information:\n\n· an electronic or physical signature of the person authorized to act on behalf of the owner of the copyright or other intellectual property interest;\n\n· a description of the copyrighted work or other intellectual property that you claim has been infringed;\n\n· a description of where the material that you claim is infringing is located on the Service;\n\n· your address, telephone number, and email address;\n\n· a statement by you that you have a good faith belief that the disputed use is not authorized by the copyright owner, its agent, or the law;\n\n· a statement by you, made under penalty of perjury, that the above information in your Notice is accurate and that you are the copyright or intellectual property owner or authorized to act on the copyright or intellectual property owner's behalf.\n\nMetaMask’s Copyright Agent can be reached at:\n\nEmail: copyright [at] metamask [dot] io\n\nMail:\n\nAttention:\n\nMetaMask Copyright ℅ ConsenSys\n\n49 Bogart Street\n\nBrooklyn, NY 11206\n\n## 13. Binding Arbitration and Class Action Waiver ##\n\nPLEASE READ THIS SECTION CAREFULLY – IT MAY SIGNIFICANTLY AFFECT YOUR LEGAL RIGHTS, INCLUDING YOUR RIGHT TO FILE A LAWSUIT IN COURT\n\n### 13.1 Initial Dispute Resolution ###\n\nThe parties shall use their best efforts to engage directly to settle any dispute, claim, question, or disagreement and engage in good faith negotiations which shall be a condition to either party initiating a lawsuit or arbitration.\n\n### 13.2 Binding Arbitration ###\n\nIf the parties do not reach an agreed upon solution within a period of 30 days from the time informal dispute resolution under the Initial Dispute Resolution provision begins, then either party may initiate binding arbitration as the sole means to resolve claims, subject to the terms set forth below. Specifically, all claims arising out of or relating to these Terms (including their formation, performance and breach), the parties’ relationship with each other and/or your use of the Service shall be finally settled by binding arbitration administered by the American Arbitration Association in accordance with the provisions of its Commercial Arbitration Rules and the supplementary procedures for consumer related disputes of the American Arbitration Association (the \"AAA\"), excluding any rules or procedures governing or permitting class actions.\n\nThe arbitrator, and not any federal, state or local court or agency, shall have exclusive authority to resolve all disputes arising out of or relating to the interpretation, applicability, enforceability or formation of these Terms, including, but not limited to any claim that all or any part of these Terms are void or voidable, or whether a claim is subject to arbitration. The arbitrator shall be empowered to grant whatever relief would be available in a court under law or in equity. The arbitrator’s award shall be written, and binding on the parties and may be entered as a judgment in any court of competent jurisdiction.\n\nThe parties understand that, absent this mandatory provision, they would have the right to sue in court and have a jury trial. They further understand that, in some instances, the costs of arbitration could exceed the costs of litigation and the right to discovery may be more limited in arbitration than in court.\n\n### 13.3 Location ###\n\nBinding arbitration shall take place in New York. You agree to submit to the personal jurisdiction of any federal or state court in New York County, New York, in order to compel arbitration, to stay proceedings pending arbitration, or to confirm, modify, vacate or enter judgment on the award entered by the arbitrator.\n\n### 13.4 Class Action Waiver ###\n\nThe parties further agree that any arbitration shall be conducted in their individual capacities only and not as a class action or other representative action, and the parties expressly waive their right to file a class action or seek relief on a class basis. YOU AND METAMASK AGREE THAT EACH MAY BRING CLAIMS AGAINST THE OTHER ONLY IN YOUR OR ITS INDIVIDUAL CAPACITY, AND NOT AS A PLAINTIFF OR CLASS MEMBER IN ANY PURPORTED CLASS OR REPRESENTATIVE PROCEEDING. If any court or arbitrator determines that the class action waiver set forth in this paragraph is void or unenforceable for any reason or that an arbitration can proceed on a class basis, then the arbitration provision set forth above shall be deemed null and void in its entirety and the parties shall be deemed to have not agreed to arbitrate disputes.\n\n### 13.5 Exception - Litigation of Intellectual Property and Small Claims Court Claims ###\n\nNotwithstanding the parties' decision to resolve all disputes through arbitration, either party may bring an action in state or federal court to protect its intellectual property rights (\"intellectual property rights\" means patents, copyrights, moral rights, trademarks, and trade secrets, but not privacy or publicity rights). Either party may also seek relief in a small claims court for disputes or claims within the scope of that court’s jurisdiction.\n\n### 13.6 30-Day Right to Opt Out ###\n\nYou have the right to opt-out and not be bound by the arbitration and class action waiver provisions set forth above by sending written notice of your decision to opt-out to the following address: MetaMask ℅ ConsenSys, 49 Bogart Street, Brooklyn NY 11206 and via email at legal-opt@metamask.io. The notice must be sent within 30 days of September 6, 2016 or your first use of the Service, whichever is later, otherwise you shall be bound to arbitrate disputes in accordance with the terms of those paragraphs. If you opt-out of these arbitration provisions, MetaMask also will not be bound by them.\n\n### 13.7 Changes to This Section ###\n\nMetaMask will provide 60-days’ notice of any changes to this section. Changes will become effective on the 60th day, and will apply prospectively only to any claims arising after the 60th day.\n\nFor any dispute not subject to arbitration you and MetaMask agree to submit to the personal and exclusive jurisdiction of and venue in the federal and state courts located in New York, New York. You further agree to accept service of process by mail, and hereby waive any and all jurisdictional and venue defenses otherwise available.\n\nThe Terms and the relationship between you and MetaMask shall be governed by the laws of the State of New York without regard to conflict of law provisions.\n\n## 14. General Information ##\n\n### 14.1 Entire Agreement ###\n\nThese Terms (and any additional terms, rules and conditions of participation that MetaMask may post on the Service) constitute the entire agreement between you and MetaMask with respect to the Service and supersedes any prior agreements, oral or written, between you and MetaMask. In the event of a conflict between these Terms and the additional terms, rules and conditions of participation, the latter will prevail over the Terms to the extent of the conflict.\n\n### 14.2 Waiver and Severability of Terms ###\n\nThe failure of MetaMask to exercise or enforce any right or provision of the Terms shall not constitute a waiver of such right or provision. If any provision of the Terms is found by an arbitrator or court of competent jurisdiction to be invalid, the parties nevertheless agree that the arbitrator or court should endeavor to give effect to the parties' intentions as reflected in the provision, and the other provisions of the Terms remain in full force and effect.\n\n### 14.3 Statute of Limitations ###\n\nYou agree that regardless of any statute or law to the contrary, any claim or cause of action arising out of or related to the use of the Service or the Terms must be filed within one (1) year after such claim or cause of action arose or be forever barred.\n\n### 14.4 Section Titles ###\n\nThe section titles in the Terms are for convenience only and have no legal or contractual effect.\n\n### 14.5 Communications ###\n\nUsers with questions, complaints or claims with respect to the Service may contact us using the relevant contact information set forth above and at communications@metamask.io.\n\n## 15 Related Links ##\n\n**[Terms of Use](https://metamask.io/terms.html)**\n\n**[Privacy](https://metamask.io/privacy.html)**\n\n**[Attributions](https://metamask.io/attributions.html)**\n\n","id":0},{"read":false,"date":"Wed Mar 22 2017","title":"Announcing Kovan Support","body":"MetaMask now lists a new network on our dropdown list: Kovan, a [Proof of Authority](https://github.com/paritytech/parity/wiki/Proof-of-Authority-Chains) testchain managed by several blockchain organizations such as Digix, Etherscan, and Parity. It is designed to be a more stable and reliable testnet alternative to Ropsten and was created in response to recent attacks that slowed down the Ropsten network. You can read more about Kovan [here](https://medium.com/@Digix/announcing-kovan-a-stable-ethereum-public-testnet-10ac7cb6c85f#.6o8sz8cct) and [here](https://medium.com/@Digix/letter-from-the-ceo-some-context-regarding-kovan-7b5121adb901#.kfv7zhw83). As with Ropsten, the default remote node to connect to Kovan is managed by Infura.\n","id":1}] \ No newline at end of file From 61a62038084c29fb29c8445feca36bce9e756471 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Wed, 22 Mar 2017 21:34:39 -0400 Subject: [PATCH 034/372] Reset disabled state for confirm button after every notice. --- ui/app/components/notice.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ui/app/components/notice.js b/ui/app/components/notice.js index 23ded9d5d..8a953a6b5 100644 --- a/ui/app/components/notice.js +++ b/ui/app/components/notice.js @@ -99,7 +99,10 @@ Notice.prototype.render = function () { h('button', { disabled, - onClick: onConfirm, + onClick: () => { + this.setState({disclaimerDisabled: true}) + onConfirm() + }, style: { marginTop: '18px', }, From bcaf0864c18df4f8d8cb92c739669447fa5aa059 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Thu, 23 Mar 2017 09:58:57 -0400 Subject: [PATCH 035/372] Remove notice body after reading. --- app/scripts/notice-controller.js | 1 + 1 file changed, 1 insertion(+) diff --git a/app/scripts/notice-controller.js b/app/scripts/notice-controller.js index 0d72760fe..57aad40c5 100644 --- a/app/scripts/notice-controller.js +++ b/app/scripts/notice-controller.js @@ -41,6 +41,7 @@ module.exports = class NoticeController extends EventEmitter { var notices = this.getNoticesList() var index = notices.findIndex((currentNotice) => currentNotice.id === noticeToMark.id) notices[index].read = true + notices[index].body = '' this.setNoticesList(notices) const latestNotice = this.getLatestUnreadNotice() cb(null, latestNotice) From d99b5a9e5a1dfc62e16e6a80d51e8f3af5b289e4 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Thu, 23 Mar 2017 10:15:42 -0400 Subject: [PATCH 036/372] Add migration tests to ensure that bodies are erased properly in notices. --- test/lib/migrations/004.json | 7 +++++++ test/unit/migrations-test.js | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/test/lib/migrations/004.json b/test/lib/migrations/004.json index 0e2075c46..a6487c1e2 100644 --- a/test/lib/migrations/004.json +++ b/test/lib/migrations/004.json @@ -48,6 +48,13 @@ "title":"Ending Morden Support", "body":"Due to [recent events](https://blog.ethereum.org/2016/11/20/from-morden-to-ropsten/), MetaMask is now deprecating support for the Morden Test Network.\n\nUsers will still be able to access Morden through a locally hosted node, but we will no longer be providing hosted access to this network through [Infura](http://infura.io/).\n\nPlease use the new Ropsten Network as your new default test network.\n\nYou can fund your Ropsten account using the buy button on your account page.\n\nBest wishes!\nThe MetaMask Team\n\n", "id":0 + }, + { + "read":false, + "date":"Sat Dec 17 2016", + "title":"Keeping It Real", + "body":"nonempty", + "id":1 } ], "conversionRate":12.66441492, diff --git a/test/unit/migrations-test.js b/test/unit/migrations-test.js index d2a83be77..ccd1477b0 100644 --- a/test/unit/migrations-test.js +++ b/test/unit/migrations-test.js @@ -15,6 +15,8 @@ const migration8 = require(path.join('..', '..', 'app', 'scripts', 'migrations', const migration9 = require(path.join('..', '..', 'app', 'scripts', 'migrations', '009')) const migration10 = require(path.join('..', '..', 'app', 'scripts', 'migrations', '010')) const migration11 = require(path.join('..', '..', 'app', 'scripts', 'migrations', '011')) +const migration12 = require(path.join('..', '..', 'app', 'scripts', 'migrations', '012')) + const oldTestRpc = 'https://rawtestrpc.metamask.io/' const newTestRpc = 'https://testrpc.metamask.io/' @@ -91,6 +93,11 @@ describe('wallet1 is migrated successfully', () => { }).then((eleventhResult) => { assert.equal(eleventhResult.data.isDisclaimerConfirmed, null, 'isDisclaimerConfirmed should not exist') assert.equal(eleventhResult.data.TOSHash, null, 'TOSHash should not exist') + + return migration12.migrate(eleventhResult) + }).then((twelfthResult) => { + assert.equal(twelfthResult.data.NoticeController.noticesList[0].body, '', 'notices that have been read should have an empty body.') + assert.equal(twelfthResult.data.NoticeController.noticesList[1].body, 'nonempty', 'notices that have not been read should not have an empty body.') }) }) From 16c76d522960298476492b53a02d1b490b84b858 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Thu, 23 Mar 2017 10:15:59 -0400 Subject: [PATCH 037/372] Create migration to erase body in read notices. --- app/scripts/migrations/012.js | 36 +++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 app/scripts/migrations/012.js diff --git a/app/scripts/migrations/012.js b/app/scripts/migrations/012.js new file mode 100644 index 000000000..8361b3793 --- /dev/null +++ b/app/scripts/migrations/012.js @@ -0,0 +1,36 @@ +const version = 12 + +/* + +This migration modifies our notices to delete their body after being read. + +*/ + +const clone = require('clone') + +module.exports = { + version, + + migrate: function (originalVersionedData) { + let versionedData = clone(originalVersionedData) + versionedData.meta.version = version + try { + const state = versionedData.data + const newState = transformState(state) + versionedData.data = newState + } catch (err) { + console.warn(`MetaMask Migration #${version}` + err.stack) + } + return Promise.resolve(versionedData) + }, +} + +function transformState (state) { + const newState = state + newState.NoticeController.noticesList.forEach((notice) => { + if (notice.read) { + notice.body = '' + } + }) + return newState +} From 525c32ae604a148b803be307b6ac3cb7f9b8617a Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Thu, 23 Mar 2017 11:26:39 -0400 Subject: [PATCH 038/372] Enable etherscan linking on Kovan transaction list items. --- ui/app/components/transaction-list-item.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/app/components/transaction-list-item.js b/ui/app/components/transaction-list-item.js index 44d2dc587..ee32be7d3 100644 --- a/ui/app/components/transaction-list-item.js +++ b/ui/app/components/transaction-list-item.js @@ -28,7 +28,7 @@ TransactionListItem.prototype.render = function () { let isLinkable = false const numericNet = parseInt(network) - isLinkable = numericNet === 1 || numericNet === 3 + isLinkable = numericNet === 1 || numericNet === 3 || numericNet === 42 var isMsg = ('msgParams' in transaction) var isTx = ('txParams' in transaction) From fa0bbd66b6896188c5a47ed6978fc992068ae22c Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Thu, 23 Mar 2017 11:28:06 -0400 Subject: [PATCH 039/372] Fix persistence of transactions between networks. --- app/scripts/transaction-manager.js | 32 ++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/app/scripts/transaction-manager.js b/app/scripts/transaction-manager.js index 31c1c8431..7227bdc87 100644 --- a/app/scripts/transaction-manager.js +++ b/app/scripts/transaction-manager.js @@ -47,27 +47,39 @@ module.exports = class TransactionManager extends EventEmitter { // Returns the tx list getTxList () { let network = this.getNetwork() - let fullTxList = this.store.getState().transactions + let fullTxList = this.getFullTxList() return fullTxList.filter(txMeta => txMeta.metamaskNetworkId === network) } + // Returns the number of txs for the current network. + getTxCount () { + return this.getTxList().length + } + + // Returns the full tx list across all networks + getFullTxList () { + return this.store.getState().transactions + } + // Adds a tx to the txlist addTx (txMeta) { - var txList = this.getTxList() - var txHistoryLimit = this.txHistoryLimit + let txCount = this.getTxCount() + let network = this.getNetwork() + let fullTxList = this.getFullTxList() + let txHistoryLimit = this.txHistoryLimit - // checks if the length of th tx history is + // checks if the length of the tx history is // longer then desired persistence limit // and then if it is removes only confirmed // or rejected tx's. // not tx's that are pending or unapproved - if (txList.length > txHistoryLimit - 1) { - var index = txList.findIndex((metaTx) => metaTx.status === 'confirmed' || metaTx.status === 'rejected') - txList.splice(index, 1) + if (txCount > txHistoryLimit - 1) { + var index = fullTxList.findIndex((metaTx) => ((metaTx.status === 'confirmed' || metaTx.status === 'rejected') && network === txMeta.metamaskNetworkId)) + fullTxList.splice(index, 1) } - txList.push(txMeta) + fullTxList.push(txMeta) - this._saveTxList(txList) + this._saveTxList(fullTxList) this.once(`${txMeta.id}:signed`, function (txId) { this.removeAllListeners(`${txMeta.id}:rejected`) }) @@ -89,7 +101,7 @@ module.exports = class TransactionManager extends EventEmitter { // updateTx (txMeta) { var txId = txMeta.id - var txList = this.getTxList() + var txList = this.getFullTxList() var index = txList.findIndex(txData => txData.id === txId) txList[index] = txMeta this._saveTxList(txList) From 2e446eb5880396716d919e10e97a7ab824cc0fc1 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Thu, 23 Mar 2017 11:28:52 -0400 Subject: [PATCH 040/372] Add to changelog. --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b77ceb864..2490dfb8e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ - Fixed bug where spinner wouldn't disappear on incorrect password submission on seed word reveal. - Polish the private key UI. - Add Kovan as an option on our network list. +- Fixed bug where transactions on other networks would disappear when submitting a transaction on another network. ## 3.4.0 2017-3-8 From 45ab81fb45ec1043d617f66c308e2e7911c8e0ad Mon Sep 17 00:00:00 2001 From: frankiebee Date: Thu, 23 Mar 2017 11:06:38 -0700 Subject: [PATCH 041/372] WIP: streams->indexdb --- app/scripts/metamask-controller.js | 1 - app/scripts/popup-core.js | 1 + library/controller.js | 30 ++++++------- library/lib/setup-iframe.js | 1 + library/popup.js | 18 ++++++-- library/server.js | 2 +- library/sw-controller.js | 67 +++++++++++++++++++++--------- library/sw-core.js | 24 +++++++---- package.json | 4 +- ui/index.js | 2 +- 10 files changed, 96 insertions(+), 54 deletions(-) diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 536891dc6..a26c0e45d 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -204,7 +204,6 @@ module.exports = class MetamaskController extends EventEmitter { // getState () { - const wallet = this.configManager.getWallet() const vault = this.keyringController.store.getState().vault const isInitialized = (!!wallet || !!vault) diff --git a/app/scripts/popup-core.js b/app/scripts/popup-core.js index b1e521a7a..7ade66922 100644 --- a/app/scripts/popup-core.js +++ b/app/scripts/popup-core.js @@ -11,6 +11,7 @@ module.exports = initializePopup function initializePopup (connectionStream) { // setup app + debugger connectToAccountManager(connectionStream, setupApp) } diff --git a/library/controller.js b/library/controller.js index bad8d33cd..f0aa7172c 100644 --- a/library/controller.js +++ b/library/controller.js @@ -1,19 +1,13 @@ -const SWcontroller = require('./sw-controller') -console.log('outside:open') -const background = new SWcontroller({ - fileName: 'sw-build.js', - registerOpts: { - scope: './', - } -}) +// const SWcontroller = require('./sw-controller') +// const SwStream = require('sw-stream/lib/sw-stream.js') +// const startPopup = require('../app/scripts/popup-core') -background.startWorker() -.then(registerdWorker => { - return background.sendMessage('connect') -}) -.then((port) => { - debugger -}) -.catch(err => { - console.error(`SW Controller: ${err}`) -}) +// console.log('outside:open') +// const background = new SWcontroller({ +// fileName: 'sw-build.js', +// }) +// background.on('ready', (readSw) => { +// startPopup(SwStream(background.controller)) +// }) +// background.startWorker() +console.log('hello from controller') diff --git a/library/lib/setup-iframe.js b/library/lib/setup-iframe.js index db67163df..0aa001dcf 100644 --- a/library/lib/setup-iframe.js +++ b/library/lib/setup-iframe.js @@ -5,6 +5,7 @@ module.exports = setupIframe function setupIframe(opts) { + debugger opts = opts || {} var frame = Iframe({ src: opts.zeroClientProvider || 'https://zero.metamask.io/', diff --git a/library/popup.js b/library/popup.js index 59b70edbb..3825661cf 100644 --- a/library/popup.js +++ b/library/popup.js @@ -1,7 +1,10 @@ const injectCss = require('inject-css') const MetaMaskUiCss = require('../ui/css') -const startPopup = require('../app/scripts/popup-core') const setupIframe = require('./lib/setup-iframe.js') +const MetamaskInpageProvider = require('../app/scripts/lib/inpage-provider.js') +const SWcontroller = require('./sw-controller') +const SwStream = require('sw-stream/lib/sw-stream.js') +const startPopup = require('../app/scripts/popup-core') var css = MetaMaskUiCss() @@ -15,5 +18,14 @@ var iframeStream = setupIframe({ sandboxAttributes: ['allow-scripts', 'allow-popups', 'allow-same-origin'], container: document.body, }) - -startPopup(iframeStream) +console.log('outside:open') +const background = new SWcontroller({ + fileName: '/popup/sw-build.js', +}) +background.on('ready', (readSw) => { + // var inpageProvider = new MetamaskInpageProvider(SwStream(background.controller)) + // startPopup(inpageProvider) + startPopup(SwStream(background.controller)) +}) +background.startWorker() +console.log('hello from /library/popup.js') diff --git a/library/server.js b/library/server.js index b9f59c2a1..a284d3e05 100644 --- a/library/server.js +++ b/library/server.js @@ -25,7 +25,7 @@ iframeServer.use('/popup', express.static('../dist/chrome')) iframeServer.get('/controller.js', function(req, res){ res.send(controllerBundle.latest) }) -iframeServer.get('/sw-build.js', function(req, res){ +iframeServer.get('/popup/sw-build.js', function(req, res){ console.log('/sw-build.js') res.setHeader('Content-Type', 'application/javascript') res.send(swBuild.latest) diff --git a/library/sw-controller.js b/library/sw-controller.js index d5065a933..23d67026e 100644 --- a/library/sw-controller.js +++ b/library/sw-controller.js @@ -1,34 +1,63 @@ const EventEmitter = require('events') -module.exports = class serviceWorkerController extends EventEmitter{ +module.exports = class ClientSideServiceWorker extends EventEmitter{ constructor (opts) { super() this.fileName = opts.fileName - this.registerOpts = opts.registerOpts - this.serviceWorker = navigator.serviceWorker - } + this.startDelay = opts.startDelay + this.serviceWorkerApi = navigator.serviceWorker + this.serviceWorkerApi.onmessage = (event) => this.emit('message', event) + this.serviceWorkerApi.onerror = (event) => this.emit('error') - startWorker () { - // check to see if their is a preregistered service worker - if (!this.serviceWorker.controller) { - return Promise.resolve(this.registerWorker()) - } else { - return Promise.resolve(this.serviceWorker.ready) + // temporary function + this.askForId = (message) => { + this.sendMessage('check-in') + .then((data) => console.log(`${message}----${data}`)) } + + // if (!!this.serviceWorkerApi) this.askForId('before') + + if (opts.initStart) this.startWorker() + + this.on('ready', (sw) => { + this.askForId('ready') + }) } - registerWorker () { - return this.serviceWorker.register(this.fileName, this.registerOpts) - .then(sw => { - return sw + get controller () { + return this.sw || this.serviceWorkerApi.controller + } + + + startWorker () { + return this.registerWorker() + .then((sw) => { + this.sw = sw + this.askForId('after register:') + this.sw.onerror = (err) => this.emit('error', err) + this.sw = sw + this.emit('ready', this.sw) }) + .catch((err) => this.emit('error', err)) } - syncSW (registeredSW) { - return registeredSW.sync.register('sync') - .then(() => { - console.log('sync') + registerWorker () { + return this.serviceWorkerApi.register(this.fileName) + .then((registerdWorker) => { + return new Promise((resolve, reject) => { + this.askForId('after') + let timeOutId = setTimeout(() => { + if (this.serviceWorkerApi.controller) return resolve(this.serviceWorkerApi.controller) + if (registerdWorker.active) return resolve(registerdWorker.active) + return reject(new Error('ClientSideServiceWorker: No controller found and onupdatefound timed out')) + }, this.startDelay || 1000 ) + + registerdWorker.onupdatefound = (event) => { + this.emit('updatefound') + registerdWorker.update() + } + }) }) } @@ -43,7 +72,7 @@ module.exports = class serviceWorkerController extends EventEmitter{ resolve(event.data.data) } } - self.serviceWorker.controller.postMessage(message, [messageChannel.port2]) + this.controller.postMessage(message, [messageChannel.port2]) }) } } diff --git a/library/sw-core.js b/library/sw-core.js index 41fce637f..9ceaf1dc5 100644 --- a/library/sw-core.js +++ b/library/sw-core.js @@ -25,6 +25,10 @@ const STORAGE_KEY = 'metamask-config' const METAMASK_DEBUG = 'GULP_METAMASK_DEBUG' let popupIsOpen = false +const log = require('loglevel') +global.log = log +log.setDefaultLevel(METAMASK_DEBUG ? 'debug' : 'warn') + self.addEventListener('install', function(event) { event.waitUntil(self.skipWaiting()) }) @@ -132,12 +136,16 @@ function setupController (initState, client) { global.metamaskController = controller // setup state persistence - pipe( - controller.store, - storeTransform(versionifyData), - diskStore - ) - + // pipe( + // controller.store, + // storeTransform(versionifyData), + // diskStore + // ) + controller.store.subscribe((store) => { + dbController.put('dataStore', store) + // .then((event) => {debugger}) + // .catch((err) => {debugger}) + }) function versionifyData(state) { let versionedData = diskStore.getState() versionedData.data = state @@ -150,10 +158,8 @@ function setupController (initState, client) { /* need to write a service worker stream for this */ - // var connectionStream = new ParentStream() connectionListener.on('remote', (portStream, messageEvent) => { - debugger - connectRemote(connectionStream, messageEvent.origin) + connectRemote(portStream, messageEvent.origin) }) function connectRemote (connectionStream, originDomain) { diff --git a/package.json b/package.json index 30e89356f..1d19d5e75 100644 --- a/package.json +++ b/package.json @@ -104,14 +104,14 @@ "request-promise": "^4.1.1", "sandwich-expando": "^1.0.5", "semaphore": "^1.0.5", - "sw-stream": "^1.0.1", + "sw-stream": "^1.0.2", "textarea-caret": "^3.0.1", "three.js": "^0.73.2", "through2": "^2.0.1", "valid-url": "^1.0.9", "vreme": "^3.0.2", "web3": "0.18.2", - "web3-provider-engine": "^10.0.1", + "web3-provider-engine": "^11.0.0", "web3-stream-provider": "^2.0.6", "xtend": "^4.0.1" }, diff --git a/ui/index.js b/ui/index.js index 1a65f813c..932054027 100644 --- a/ui/index.js +++ b/ui/index.js @@ -14,7 +14,7 @@ log.setLevel(debugMode ? 'debug' : 'warn') function launchApp (opts) { var accountManager = opts.accountManager actions._setBackgroundConnection(accountManager) - + debugger // check if we are unlocked first accountManager.getState(function (err, metamaskState) { if (err) throw err From 197e6e0b77868b0bec76cef303b2eeffda5797bf Mon Sep 17 00:00:00 2001 From: frankiebee Date: Thu, 23 Mar 2017 11:41:32 -0700 Subject: [PATCH 042/372] WIP: indexdb is at an err state "provider is undefined" --- app/scripts/popup-core.js | 1 - library/controllers/index-db-controller.js | 32 ++++++++++++++++------ library/lib/setup-iframe.js | 1 - library/sw-core.js | 4 +-- ui/index.js | 1 - 5 files changed, 26 insertions(+), 13 deletions(-) diff --git a/app/scripts/popup-core.js b/app/scripts/popup-core.js index 7ade66922..b1e521a7a 100644 --- a/app/scripts/popup-core.js +++ b/app/scripts/popup-core.js @@ -11,7 +11,6 @@ module.exports = initializePopup function initializePopup (connectionStream) { // setup app - debugger connectToAccountManager(connectionStream, setupApp) } diff --git a/library/controllers/index-db-controller.js b/library/controllers/index-db-controller.js index 3373de113..f7225c00b 100644 --- a/library/controllers/index-db-controller.js +++ b/library/controllers/index-db-controller.js @@ -51,19 +51,35 @@ module.exports = class IndexDbController extends EventEmitter { return this.requestObjectStore(key) .then((dataObject)=> { return new Promise((resolve, reject) => { - const getRequest = dataObject.get(key) - getRequest.onsuccess = (event) => resolve(event.currentTarget.result) - getRequest.onerror = (event) => reject(event) + const getRequest = dataObject.get(key) + getRequest.onsuccess = (event) => { + const serialized = event.currentTarget.result + try { + console.log('serialized:',serialized) + const state = serialized ? JSON.parse(serialized) : {} + resolve(state) + } catch (err) { + reject(err) + } + } + getRequest.onerror = (event) => reject(event) + }) }) - }) } - put (key, store) { + put (key, state) { return this.requestObjectStore(key, 'readwrite') .then((dataObject)=> { - const putRequest = dataObject.put(store) - putRequest.onsuccess = (event) => Promise.resolve(event.currentTarget.result) - putRequest.onerror = (event) => Promise.reject(event) + return new Promise((resolve, reject) => { + try { + const serialized = JSON.stringify(state) + const putRequest = dataObject.put(serialized) + putRequest.onsuccess = (event) => resolve(event.currentTarget.result) + putRequest.onerror = (event) => reject(event) + } catch (err) { + reject(err) + } + }) }) } diff --git a/library/lib/setup-iframe.js b/library/lib/setup-iframe.js index 0aa001dcf..db67163df 100644 --- a/library/lib/setup-iframe.js +++ b/library/lib/setup-iframe.js @@ -5,7 +5,6 @@ module.exports = setupIframe function setupIframe(opts) { - debugger opts = opts || {} var frame = Iframe({ src: opts.zeroClientProvider || 'https://zero.metamask.io/', diff --git a/library/sw-core.js b/library/sw-core.js index 9ceaf1dc5..60fc88039 100644 --- a/library/sw-core.js +++ b/library/sw-core.js @@ -141,8 +141,8 @@ function setupController (initState, client) { // storeTransform(versionifyData), // diskStore // ) - controller.store.subscribe((store) => { - dbController.put('dataStore', store) + controller.store.subscribe((state) => { + dbController.put('dataStore', state) // .then((event) => {debugger}) // .catch((err) => {debugger}) }) diff --git a/ui/index.js b/ui/index.js index 932054027..16875fce4 100644 --- a/ui/index.js +++ b/ui/index.js @@ -14,7 +14,6 @@ log.setLevel(debugMode ? 'debug' : 'warn') function launchApp (opts) { var accountManager = opts.accountManager actions._setBackgroundConnection(accountManager) - debugger // check if we are unlocked first accountManager.getState(function (err, metamaskState) { if (err) throw err From e7a3330b980b150d4b7eae9c628e4ad2b0a1af34 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Thu, 23 Mar 2017 13:44:09 -0700 Subject: [PATCH 043/372] Combine pending-tx-details component into pending-tx-details These were only separated originally so we could make the notification-based TX approval work, which provided its own buttons. This two templates are logically highly coupled, and keeping them working while separate has been difficult at times, and has even required resorting to dubious practices, like using React's `refs` pattern. This combines them into one fairly large component, but I think it's ok, we can still break this up into components, just not the separation that it had previously. --- ui/app/components/pending-tx-details.js | 364 ------------------------ ui/app/components/pending-tx.js | 352 ++++++++++++++++++++++- 2 files changed, 346 insertions(+), 370 deletions(-) delete mode 100644 ui/app/components/pending-tx-details.js diff --git a/ui/app/components/pending-tx-details.js b/ui/app/components/pending-tx-details.js deleted file mode 100644 index 375a50f22..000000000 --- a/ui/app/components/pending-tx-details.js +++ /dev/null @@ -1,364 +0,0 @@ -const Component = require('react').Component -const h = require('react-hyperscript') -const inherits = require('util').inherits -const extend = require('xtend') -const ethUtil = require('ethereumjs-util') -const BN = ethUtil.BN - -const MiniAccountPanel = require('./mini-account-panel') -const EthBalance = require('./eth-balance') -const util = require('../util') -const addressSummary = util.addressSummary -const nameForAddress = require('../../lib/contract-namer') -const HexInput = require('./hex-as-decimal-input') - -const DEFAULT_GAS_PRICE_BN = new BN(20000000000, '10') -const DEFAULT_GAS_PRICE = DEFAULT_GAS_PRICE_BN.toString(16) - -const MIN_GAS_PRICE_BN = new BN(20000000) - -module.exports = PendingTxDetails - -inherits(PendingTxDetails, Component) -function PendingTxDetails () { - Component.call(this) - this.state = { valid: true } -} - -const PTXP = PendingTxDetails.prototype - -PTXP.render = function () { - var props = this.props - var state = this.state || {} - var txData = state.txMeta || props.txData - - var txParams = txData.txParams || {} - var address = txParams.from || props.selectedAddress - var identity = props.identities[address] || { address: address } - var account = props.accounts[address] - var balance = account ? account.balance : '0x0' - - const gas = (state.gas === undefined) ? txParams.gas : state.gas - const gasPrice = (state.gasPrice === undefined) ? txData.gasPrice : state.gasPrice - - var txFee = state.txFee || txData.txFee || '' - var txFeeBn = new BN(txFee, 16) - var maxCost = state.maxCost || txData.maxCost || '' - var dataLength = txParams.data ? (txParams.data.length - 2) / 2 : 0 - var imageify = props.imageifyIdenticons === undefined ? true : props.imageifyIdenticons - - log.debug(`rendering gas: ${gas}, gasPrice: ${gasPrice}, txFee: ${txFee}, maxCost: ${maxCost}`) - - return ( - h('div', [ - - h('.flex-row.flex-center', { - style: { - maxWidth: '100%', - }, - }, [ - - h(MiniAccountPanel, { - imageSeed: address, - imageifyIdenticons: imageify, - picOrder: 'right', - }, [ - h('span.font-small', { - style: { - fontFamily: 'Montserrat Bold, Montserrat, sans-serif', - }, - }, identity.name), - h('span.font-small', { - style: { - fontFamily: 'Montserrat Light, Montserrat, sans-serif', - }, - }, addressSummary(address, 6, 4, false)), - - h('span.font-small', { - style: { - fontFamily: 'Montserrat Light, Montserrat, sans-serif', - }, - }, [ - h(EthBalance, { - value: balance, - inline: true, - labelColor: '#F7861C', - }), - ]), - ]), - - forwardCarrat(), - - this.miniAccountPanelForRecipient(), - ]), - - h('style', ` - .table-box { - margin: 7px 0px 0px 0px; - width: 100%; - } - .table-box .row { - margin: 0px; - background: rgb(236,236,236); - display: flex; - justify-content: space-between; - font-family: Montserrat Light, sans-serif; - font-size: 13px; - padding: 5px 25px; - } - .table-box .row .value { - font-family: Montserrat Regular; - } - `), - - h('.table-box', [ - - // Ether Value - // Currently not customizable, but easily modified - // in the way that gas and gasLimit currently are. - h('.row', [ - h('.cell.label', 'Amount'), - h(EthBalance, { value: txParams.value }), - ]), - - // Gas Limit (customizable) - h('.cell.row', [ - h('.cell.label', 'Gas Limit'), - h('.cell.value', { - }, [ - h(HexInput, { - name: 'Gas Limit', - value: gas, - min: 21000, // The hard lower limit for gas. - suffix: 'UNITS', - style: { - position: 'relative', - top: '5px', - }, - onChange: (newHex) => { - log.info(`Gas limit changed to ${newHex}`) - this.setState({ gas: newHex }) - }, - }), - ]), - ]), - - // Gas Price (customizable) - h('.cell.row', [ - h('.cell.label', 'Gas Price'), - h('.cell.value', { - }, [ - h(HexInput, { - name: 'Gas Price', - value: gasPrice, - suffix: 'WEI', - min: MIN_GAS_PRICE_BN.toString(10), - style: { - position: 'relative', - top: '5px', - }, - onChange: (newHex) => { - log.info(`Gas price changed to: ${newHex}`) - this.setState({ gasPrice: newHex }) - }, - }), - ]), - ]), - - // Max Transaction Fee (calculated) - h('.cell.row', [ - h('.cell.label', 'Max Transaction Fee'), - h(EthBalance, { value: txFeeBn.toString(16) }), - ]), - - h('.cell.row', { - style: { - fontFamily: 'Montserrat Regular', - background: 'white', - padding: '10px 25px', - }, - }, [ - h('.cell.label', 'Max Total'), - h('.cell.value', { - style: { - display: 'flex', - alignItems: 'center', - }, - }, [ - h(EthBalance, { - value: maxCost.toString(16), - inline: true, - labelColor: 'black', - fontSize: '16px', - }), - ]), - ]), - - // Data size row: - h('.cell.row', { - style: { - background: '#f7f7f7', - paddingBottom: '0px', - }, - }, [ - h('.cell.label'), - h('.cell.value', { - style: { - fontFamily: 'Montserrat Light', - fontSize: '11px', - }, - }, `Data included: ${dataLength} bytes`), - ]), - ]), // End of Table - - ]) - ) -} - -PTXP.miniAccountPanelForRecipient = function () { - var props = this.props - var txData = props.txData - var txParams = txData.txParams || {} - var isContractDeploy = !('to' in txParams) - var imageify = props.imageifyIdenticons === undefined ? true : props.imageifyIdenticons - - // If it's not a contract deploy, send to the account - if (!isContractDeploy) { - return h(MiniAccountPanel, { - imageSeed: txParams.to, - imageifyIdenticons: imageify, - picOrder: 'left', - }, [ - h('span.font-small', { - style: { - fontFamily: 'Montserrat Bold, Montserrat, sans-serif', - }, - }, nameForAddress(txParams.to, props.identities)), - h('span.font-small', { - style: { - fontFamily: 'Montserrat Light, Montserrat, sans-serif', - }, - }, addressSummary(txParams.to, 6, 4, false)), - ]) - } else { - return h(MiniAccountPanel, { - imageifyIdenticons: imageify, - picOrder: 'left', - }, [ - - h('span.font-small', { - style: { - fontFamily: 'Montserrat Bold, Montserrat, sans-serif', - }, - }, 'New Contract'), - - ]) - } -} - -PTXP.componentDidUpdate = function (prevProps, previousState) { - log.debug(`pending-tx-details componentDidUpdate`) - const state = this.state || {} - const prevState = previousState || {} - const { gas, gasPrice } = state - - // Only if gas or gasPrice changed: - if (!prevState || - (gas !== prevState.gas || - gasPrice !== prevState.gasPrice)) { - log.debug(`recalculating gas since prev state change: ${JSON.stringify({ prevState, state })}`) - this.calculateGas() - } -} - -PTXP.isValid = function () { - return this.state.valid -} - -PTXP.calculateGas = function () { - const txMeta = this.gatherParams() - log.debug(`pending-tx-details calculating gas for ${JSON.stringify(txMeta)}`) - - var txParams = txMeta.txParams - var gasCost = new BN(ethUtil.stripHexPrefix(txParams.gas || txMeta.estimatedGas), 16) - var gasPrice = new BN(ethUtil.stripHexPrefix(txParams.gasPrice || DEFAULT_GAS_PRICE), 16) - - const valid = !gasPrice.lt(MIN_GAS_PRICE_BN) - this.props.validChanged(valid) - - var txFee = gasCost.mul(gasPrice) - var txValue = new BN(ethUtil.stripHexPrefix(txParams.value || '0x0'), 16) - var maxCost = txValue.add(txFee) - - const txFeeHex = '0x' + txFee.toString('hex') - const maxCostHex = '0x' + maxCost.toString('hex') - const gasPriceHex = '0x' + gasPrice.toString('hex') - - txMeta.txFee = txFeeHex - txMeta.maxCost = maxCostHex - txMeta.txParams.gasPrice = gasPriceHex - - const newState = { - txFee: '0x' + txFee.toString('hex'), - maxCost: '0x' + maxCost.toString('hex'), - } - log.info(`tx form updating local state with ${JSON.stringify(newState)}`) - this.setState(newState) - - if (this.props.onTxChange) { - this.props.onTxChange(txMeta) - } -} - -PTXP.resetGasFields = function () { - log.debug(`pending-tx-details#resetGasFields`) - const txData = this.props.txData - this.setState({ - gas: txData.txParams.gas, - gasPrice: txData.gasPrice, - }) -} - -// After a customizable state value has been updated, -PTXP.gatherParams = function () { - log.debug(`pending-tx-details#gatherParams`) - const props = this.props - const state = this.state || {} - const txData = state.txData || props.txData - const txParams = txData.txParams - const gas = state.gas || txParams.gas - const gasPrice = state.gasPrice || txParams.gasPrice - const resultTx = extend(txParams, { - gas, - gasPrice, - }) - const resultTxMeta = extend(txData, { - txParams: resultTx, - }) - log.debug(`UI has computed tx params ${JSON.stringify(resultTx)}`) - return resultTxMeta -} - -PTXP.verifyGasParams = function () { - // We call this in case the gas has not been modified at all - if (!this.state) { return true } - return this._notZeroOrEmptyString(this.state.gas) && this._notZeroOrEmptyString(this.state.gasPrice) -} - -PTXP._notZeroOrEmptyString = function (obj) { - return obj !== '' && obj !== '0x0' -} - -function forwardCarrat () { - return ( - - h('img', { - src: 'images/forward-carrat.svg', - style: { - padding: '5px 6px 0px 10px', - height: '37px', - }, - }) - - ) -} diff --git a/ui/app/components/pending-tx.js b/ui/app/components/pending-tx.js index f6fb6f85e..f550c1d35 100644 --- a/ui/app/components/pending-tx.js +++ b/ui/app/components/pending-tx.js @@ -2,10 +2,25 @@ const Component = require('react').Component const connect = require('react-redux').connect const h = require('react-hyperscript') const inherits = require('util').inherits -const PendingTxDetails = require('./pending-tx-details') const extend = require('xtend') const actions = require('../actions') +const ethUtil = require('ethereumjs-util') +const BN = ethUtil.BN + +const MiniAccountPanel = require('./mini-account-panel') +const EthBalance = require('./eth-balance') +const util = require('../util') +const addressSummary = util.addressSummary +const nameForAddress = require('../../lib/contract-namer') +const HexInput = require('./hex-as-decimal-input') + +const DEFAULT_GAS_PRICE_BN = new BN(20000000000, '10') +const DEFAULT_GAS_PRICE = DEFAULT_GAS_PRICE_BN.toString(16) + +const MIN_GAS_PRICE_BN = new BN(20000000) + + module.exports = connect(mapStateToProps)(PendingTx) function mapStateToProps (state) { @@ -22,12 +37,28 @@ function PendingTx () { PendingTx.prototype.render = function () { const props = this.props - const newProps = extend(props, { - ref: 'details', - validChanged: this.validChanged.bind(this), - }) const txData = props.txData + const state = this.state + + const txParams = txData.txParams || {} + const address = txParams.from || props.selectedAddress + const identity = props.identities[address] || { address: address } + const account = props.accounts[address] + const balance = account ? account.balance : '0x0' + + const gas = (state.gas === undefined) ? txParams.gas : state.gas + const gasPrice = (state.gasPrice === undefined) ? txData.gasPrice : state.gasPrice + + const txFee = state.txFee || txData.txFee || '' + const txFeeBn = new BN(txFee, 16) + const maxCost = state.maxCost || txData.maxCost || '' + const dataLength = txParams.data ? (txParams.data.length - 2) / 2 : 0 + const imageify = props.imageifyIdenticons === undefined ? true : props.imageifyIdenticons + + console.log('miniaccountpanelforrecipient?') + console.dir(this.miniAccountPanelForRecipient) + return ( h('div', { @@ -50,7 +81,168 @@ PendingTx.prototype.render = function () { }, [ // tx info - h(PendingTxDetails, newProps), + h('div', [ + + h('.flex-row.flex-center', { + style: { + maxWidth: '100%', + }, + }, [ + + h(MiniAccountPanel, { + imageSeed: address, + imageifyIdenticons: imageify, + picOrder: 'right', + }, [ + h('span.font-small', { + style: { + fontFamily: 'Montserrat Bold, Montserrat, sans-serif', + }, + }, identity.name), + h('span.font-small', { + style: { + fontFamily: 'Montserrat Light, Montserrat, sans-serif', + }, + }, addressSummary(address, 6, 4, false)), + + h('span.font-small', { + style: { + fontFamily: 'Montserrat Light, Montserrat, sans-serif', + }, + }, [ + h(EthBalance, { + value: balance, + inline: true, + labelColor: '#F7861C', + }), + ]), + ]), + + forwardCarrat(), + + this.miniAccountPanelForRecipient(), + ]), + + h('style', ` + .table-box { + margin: 7px 0px 0px 0px; + width: 100%; + } + .table-box .row { + margin: 0px; + background: rgb(236,236,236); + display: flex; + justify-content: space-between; + font-family: Montserrat Light, sans-serif; + font-size: 13px; + padding: 5px 25px; + } + .table-box .row .value { + font-family: Montserrat Regular; + } + `), + + h('.table-box', [ + + // Ether Value + // Currently not customizable, but easily modified + // in the way that gas and gasLimit currently are. + h('.row', [ + h('.cell.label', 'Amount'), + h(EthBalance, { value: txParams.value }), + ]), + + // Gas Limit (customizable) + h('.cell.row', [ + h('.cell.label', 'Gas Limit'), + h('.cell.value', { + }, [ + h(HexInput, { + name: 'Gas Limit', + value: gas, + min: 21000, // The hard lower limit for gas. + suffix: 'UNITS', + style: { + position: 'relative', + top: '5px', + }, + onChange: (newHex) => { + log.info(`Gas limit changed to ${newHex}`) + this.setState({ gas: newHex }) + }, + }), + ]), + ]), + + // Gas Price (customizable) + h('.cell.row', [ + h('.cell.label', 'Gas Price'), + h('.cell.value', { + }, [ + h(HexInput, { + name: 'Gas Price', + value: gasPrice, + suffix: 'WEI', + min: MIN_GAS_PRICE_BN.toString(10), + style: { + position: 'relative', + top: '5px', + }, + onChange: (newHex) => { + log.info(`Gas price changed to: ${newHex}`) + this.setState({ gasPrice: newHex }) + }, + }), + ]), + ]), + + // Max Transaction Fee (calculated) + h('.cell.row', [ + h('.cell.label', 'Max Transaction Fee'), + h(EthBalance, { value: txFeeBn.toString(16) }), + ]), + + h('.cell.row', { + style: { + fontFamily: 'Montserrat Regular', + background: 'white', + padding: '10px 25px', + }, + }, [ + h('.cell.label', 'Max Total'), + h('.cell.value', { + style: { + display: 'flex', + alignItems: 'center', + }, + }, [ + h(EthBalance, { + value: maxCost.toString(16), + inline: true, + labelColor: 'black', + fontSize: '16px', + }), + ]), + ]), + + // Data size row: + h('.cell.row', { + style: { + background: '#f7f7f7', + paddingBottom: '0px', + }, + }, [ + h('.cell.label'), + h('.cell.value', { + style: { + fontFamily: 'Montserrat Light', + fontSize: '11px', + }, + }, `Data included: ${dataLength} bytes`), + ]), + ]), // End of Table + + ]), h('style', ` .conf-buttons button { @@ -118,3 +310,151 @@ PendingTx.prototype.render = function () { PendingTx.prototype.validChanged = function (newValid) { this.setState({ valid: newValid }) } + +PendingTx.prototype.miniAccountPanelForRecipient = function () { + const props = this.props + const txData = props.txData + const txParams = txData.txParams || {} + const isContractDeploy = !('to' in txParams) + const imageify = props.imageifyIdenticons === undefined ? true : props.imageifyIdenticons + + // If it's not a contract deploy, send to the account + if (!isContractDeploy) { + return h(MiniAccountPanel, { + imageSeed: txParams.to, + imageifyIdenticons: imageify, + picOrder: 'left', + }, [ + h('span.font-small', { + style: { + fontFamily: 'Montserrat Bold, Montserrat, sans-serif', + }, + }, nameForAddress(txParams.to, props.identities)), + h('span.font-small', { + style: { + fontFamily: 'Montserrat Light, Montserrat, sans-serif', + }, + }, addressSummary(txParams.to, 6, 4, false)), + ]) + } else { + return h(MiniAccountPanel, { + imageifyIdenticons: imageify, + picOrder: 'left', + }, [ + + h('span.font-small', { + style: { + fontFamily: 'Montserrat Bold, Montserrat, sans-serif', + }, + }, 'New Contract'), + + ]) + } +} + +PendingTx.prototype.componentDidUpdate = function (prevProps, previousState) { + log.debug(`pending-tx-details componentDidUpdate`) + const state = this.state || {} + const prevState = previousState || {} + const { gas, gasPrice } = state + + // Only if gas or gasPrice changed: + if (!prevState || + (gas !== prevState.gas || + gasPrice !== prevState.gasPrice)) { + log.debug(`recalculating gas since prev state change: ${JSON.stringify({ prevState, state })}`) + this.calculateGas() + } +} + +PendingTx.prototype.isValid = function () { + return this.state.valid +} + +PendingTx.prototype.calculateGas = function () { + const txMeta = this.gatherParams() + log.debug(`pending-tx-details calculating gas for ${JSON.stringify(txMeta)}`) + + const txParams = txMeta.txParams + const gasCost = new BN(ethUtil.stripHexPrefix(txParams.gas || txMeta.estimatedGas), 16) + const gasPrice = new BN(ethUtil.stripHexPrefix(txParams.gasPrice || DEFAULT_GAS_PRICE), 16) + + const valid = !gasPrice.lt(MIN_GAS_PRICE_BN) + this.validChanged(valid) + + const txFee = gasCost.mul(gasPrice) + const txValue = new BN(ethUtil.stripHexPrefix(txParams.value || '0x0'), 16) + const maxCost = txValue.add(txFee) + + const txFeeHex = '0x' + txFee.toString('hex') + const maxCostHex = '0x' + maxCost.toString('hex') + const gasPriceHex = '0x' + gasPrice.toString('hex') + + txMeta.txFee = txFeeHex + txMeta.maxCost = maxCostHex + txMeta.txParams.gasPrice = gasPriceHex + + const newState = { + txFee: '0x' + txFee.toString('hex'), + maxCost: '0x' + maxCost.toString('hex'), + } + log.info(`tx form updating local state with ${JSON.stringify(newState)}`) + this.setState(newState) + + if (this.props.onTxChange) { + this.props.onTxChange(txMeta) + } +} + +PendingTx.prototype.resetGasFields = function () { + log.debug(`pending-tx-details#resetGasFields`) + const txData = this.props.txData + this.setState({ + gas: txData.txParams.gas, + gasPrice: txData.gasPrice, + }) +} + +// After a customizable state value has been updated, +PendingTx.prototype.gatherParams = function () { + log.debug(`pending-tx-details#gatherParams`) + const props = this.props + const state = this.state || {} + const txData = state.txData || props.txData + const txParams = txData.txParams + const gas = state.gas || txParams.gas + const gasPrice = state.gasPrice || txParams.gasPrice + const resultTx = extend(txParams, { + gas, + gasPrice, + }) + const resultTxMeta = extend(txData, { + txParams: resultTx, + }) + log.debug(`UI has computed tx params ${JSON.stringify(resultTx)}`) + return resultTxMeta +} + +PendingTx.prototype.verifyGasParams = function () { + // We call this in case the gas has not been modified at all + if (!this.state) { return true } + return this._notZeroOrEmptyString(this.state.gas) && this._notZeroOrEmptyString(this.state.gasPrice) +} + +PendingTx.prototype._notZeroOrEmptyString = function (obj) { + return obj !== '' && obj !== '0x0' +} + +function forwardCarrat () { + return ( + + h('img', { + src: 'images/forward-carrat.svg', + style: { + padding: '5px 6px 0px 10px', + height: '37px', + }, + }) + + ) +} From 2cab2f767c4cfe6d42899ca51ef5aa36d5eaf456 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Thu, 23 Mar 2017 13:56:32 -0700 Subject: [PATCH 044/372] Remove gas limit param Fixes #1256 by removing redundant param. --- app/scripts/lib/tx-utils.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/scripts/lib/tx-utils.js b/app/scripts/lib/tx-utils.js index c6814c05f..7988f83e9 100644 --- a/app/scripts/lib/tx-utils.js +++ b/app/scripts/lib/tx-utils.js @@ -63,7 +63,7 @@ module.exports = class txProviderUtils { const initialGasLimitBn = hexToBn(initialGasLimitHex) const blockGasLimitBn = hexToBn(blockGasLimitHex) const bufferedGasLimitBn = initialGasLimitBn.muln(1.5) - + // if initialGasLimit is above blockGasLimit, dont modify it if (initialGasLimitBn.gt(blockGasLimitBn)) return bnToHex(initialGasLimitBn) // if bufferedGasLimit is below blockGasLimit, use bufferedGasLimit @@ -99,7 +99,7 @@ module.exports = class txProviderUtils { txParams.from = normalize(txParams.from) txParams.value = normalize(txParams.value) txParams.data = normalize(txParams.data) - txParams.gasLimit = normalize(txParams.gasLimit || txParams.gas) + txParams.gas = normalize(txParams.gas || txParams.gasLimit) txParams.nonce = normalize(txParams.nonce) // build ethTx log.info(`Prepared tx for signing: ${JSON.stringify(txParams)}`) @@ -134,4 +134,4 @@ function bnToHex(inputBn) { function hexToBn(inputHex) { return new BN(ethUtil.stripHexPrefix(inputHex), 16) -} \ No newline at end of file +} From 412cc2394a89a3ba1c0ca82cecf03c29254b7381 Mon Sep 17 00:00:00 2001 From: frankiebee Date: Thu, 23 Mar 2017 14:22:05 -0700 Subject: [PATCH 045/372] Get mascara to send a transaction in the ui --- app/scripts/popup-core.js | 1 - library/controllers/index-db-controller.js | 12 ++++-------- library/lib/setup-iframe.js | 1 - library/sw-core.js | 14 +++++++------- ui/index.js | 1 - 5 files changed, 11 insertions(+), 18 deletions(-) diff --git a/app/scripts/popup-core.js b/app/scripts/popup-core.js index 7ade66922..b1e521a7a 100644 --- a/app/scripts/popup-core.js +++ b/app/scripts/popup-core.js @@ -11,7 +11,6 @@ module.exports = initializePopup function initializePopup (connectionStream) { // setup app - debugger connectToAccountManager(connectionStream, setupApp) } diff --git a/library/controllers/index-db-controller.js b/library/controllers/index-db-controller.js index 3373de113..bf840b98a 100644 --- a/library/controllers/index-db-controller.js +++ b/library/controllers/index-db-controller.js @@ -58,21 +58,17 @@ module.exports = class IndexDbController extends EventEmitter { }) } - put (key, store) { - return this.requestObjectStore(key, 'readwrite') + put (state) { + return this.requestObjectStore('dataStore', 'readwrite') .then((dataObject)=> { - const putRequest = dataObject.put(store) + const putRequest = dataObject.put(state, 'dataStore') putRequest.onsuccess = (event) => Promise.resolve(event.currentTarget.result) putRequest.onerror = (event) => Promise.reject(event) }) } - update (key, value) { - - } - migrate () { - this.db.createObjectStore(this.name) + this.db.createObjectStore('dataStore') } _add (key, objStore, cb = logger) { diff --git a/library/lib/setup-iframe.js b/library/lib/setup-iframe.js index 0aa001dcf..db67163df 100644 --- a/library/lib/setup-iframe.js +++ b/library/lib/setup-iframe.js @@ -5,7 +5,6 @@ module.exports = setupIframe function setupIframe(opts) { - debugger opts = opts || {} var frame = Iframe({ src: opts.zeroClientProvider || 'https://zero.metamask.io/', diff --git a/library/sw-core.js b/library/sw-core.js index 9ceaf1dc5..3972e361b 100644 --- a/library/sw-core.js +++ b/library/sw-core.js @@ -87,7 +87,7 @@ function loadStateFromPersistence() { const initialState = migrator.generateInitialState(firstTimeState) dbController.initialState = initialState return dbController.open() - .then((stuff) => { + .then((openRequest) => { return dbController.get('dataStore') }) .then((data) => { @@ -141,14 +141,14 @@ function setupController (initState, client) { // storeTransform(versionifyData), // diskStore // ) - controller.store.subscribe((store) => { - dbController.put('dataStore', store) - // .then((event) => {debugger}) - // .catch((err) => {debugger}) + controller.store.subscribe((state) => { + dbController.put(versionifyData(controller.store)) + .catch((err) => {console.error(err)}) }) function versionifyData(state) { - let versionedData = diskStore.getState() - versionedData.data = state + // let versionedData = diskStore.getState() + // versionedData.data = state + let versionedData = {data: state} return versionedData } diff --git a/ui/index.js b/ui/index.js index 932054027..16875fce4 100644 --- a/ui/index.js +++ b/ui/index.js @@ -14,7 +14,6 @@ log.setLevel(debugMode ? 'debug' : 'warn') function launchApp (opts) { var accountManager = opts.accountManager actions._setBackgroundConnection(accountManager) - debugger // check if we are unlocked first accountManager.getState(function (err, metamaskState) { if (err) throw err From 445159386798579f3ed449b8bc4efa00f52a26ac Mon Sep 17 00:00:00 2001 From: frankiebee Date: Thu, 23 Mar 2017 14:35:01 -0700 Subject: [PATCH 046/372] Clean up messy merge --- library/controllers/index-db-controller.js | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/library/controllers/index-db-controller.js b/library/controllers/index-db-controller.js index 4c03eca46..6bcae9845 100644 --- a/library/controllers/index-db-controller.js +++ b/library/controllers/index-db-controller.js @@ -50,18 +50,9 @@ module.exports = class IndexDbController extends EventEmitter { get (key) { return this.requestObjectStore(key) .then((dataObject)=> { - return new Promise((resolve, reject) => { + return new Promise((resolve, reject) => { const getRequest = dataObject.get(key) - getRequest.onsuccess = (event) => { - const serialized = event.currentTarget.result - try { - console.log('serialized:',serialized) - const state = serialized ? JSON.parse(serialized) : {} - resolve(state) - } catch (err) { - reject(err) - } - } + getRequest.onsuccess = (event) => resolve(event.currentTarget.result) getRequest.onerror = (event) => reject(event) }) }) From 55e8a717e6a1187381c10d7ad2f9da57888e4fd8 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Thu, 23 Mar 2017 14:55:59 -0700 Subject: [PATCH 047/372] Fix some broken refs --- ui/app/components/pending-tx.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/app/components/pending-tx.js b/ui/app/components/pending-tx.js index f550c1d35..75004b208 100644 --- a/ui/app/components/pending-tx.js +++ b/ui/app/components/pending-tx.js @@ -72,7 +72,7 @@ PendingTx.prototype.render = function () { const valid = form.checkValidity() this.setState({ valid }) - if (valid && this.refs.details.verifyGasParams()) { + if (valid && this.verifyGasParams()) { props.sendTransaction(txData, event) } else { this.props.dispatch(actions.displayWarning('Invalid Gas Parameters')) @@ -287,7 +287,7 @@ PendingTx.prototype.render = function () { h('button', { onClick: () => { - this.refs.details.resetGasFields() + this.resetGasFields() }, }, 'Reset'), From bda821f14478b67b5fee6e862fc2a5458594e672 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Thu, 23 Mar 2017 14:57:35 -0700 Subject: [PATCH 048/372] Override browser default validation message --- ui/app/components/hex-as-decimal-input.js | 1 + 1 file changed, 1 insertion(+) diff --git a/ui/app/components/hex-as-decimal-input.js b/ui/app/components/hex-as-decimal-input.js index bbd86b254..e39805787 100644 --- a/ui/app/components/hex-as-decimal-input.js +++ b/ui/app/components/hex-as-decimal-input.js @@ -72,6 +72,7 @@ HexAsDecimalInput.prototype.render = function () { } this.setState({ invalid: msg }) event.preventDefault() + return false }, }), h('div', { From 612bace17d8fb84fd755a30f03fc9075b4d967f1 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Thu, 23 Mar 2017 15:01:05 -0700 Subject: [PATCH 049/372] Prevent default for reset and reject buttons --- ui/app/components/pending-tx.js | 3 ++- ui/app/conf-tx.js | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/ui/app/components/pending-tx.js b/ui/app/components/pending-tx.js index 75004b208..6753fc098 100644 --- a/ui/app/components/pending-tx.js +++ b/ui/app/components/pending-tx.js @@ -286,8 +286,9 @@ PendingTx.prototype.render = function () { : null, h('button', { - onClick: () => { + onClick: (event) => { this.resetGasFields() + event.preventDefault() }, }, 'Reset'), diff --git a/ui/app/conf-tx.js b/ui/app/conf-tx.js index ce7422f9c..83e2c7482 100644 --- a/ui/app/conf-tx.js +++ b/ui/app/conf-tx.js @@ -180,6 +180,7 @@ ConfirmTxScreen.prototype.sendTransaction = function (txData, event) { ConfirmTxScreen.prototype.cancelTransaction = function (txData, event) { this.stopPropagation(event) + event.preventDefault() this.props.dispatch(actions.cancelTx(txData)) } From 31c1839ed76c56c3e988d1f77957ae71f433836c Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Thu, 23 Mar 2017 15:10:57 -0700 Subject: [PATCH 050/372] Fix initial gas price estimate --- ui/app/components/pending-tx.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/ui/app/components/pending-tx.js b/ui/app/components/pending-tx.js index 6753fc098..c0786d83e 100644 --- a/ui/app/components/pending-tx.js +++ b/ui/app/components/pending-tx.js @@ -15,9 +15,6 @@ const addressSummary = util.addressSummary const nameForAddress = require('../../lib/contract-namer') const HexInput = require('./hex-as-decimal-input') -const DEFAULT_GAS_PRICE_BN = new BN(20000000000, '10') -const DEFAULT_GAS_PRICE = DEFAULT_GAS_PRICE_BN.toString(16) - const MIN_GAS_PRICE_BN = new BN(20000000) @@ -373,12 +370,16 @@ PendingTx.prototype.isValid = function () { } PendingTx.prototype.calculateGas = function () { + const state = this.state + const props = this.props + const txData = props.txData + const txMeta = this.gatherParams() log.debug(`pending-tx-details calculating gas for ${JSON.stringify(txMeta)}`) const txParams = txMeta.txParams const gasCost = new BN(ethUtil.stripHexPrefix(txParams.gas || txMeta.estimatedGas), 16) - const gasPrice = new BN(ethUtil.stripHexPrefix(txParams.gasPrice || DEFAULT_GAS_PRICE), 16) + const gasPrice = (state.gasPrice === undefined) ? txData.gasPrice : state.gasPrice const valid = !gasPrice.lt(MIN_GAS_PRICE_BN) this.validChanged(valid) From c8540261c1c9b60c98845bdd19095efc064fdde8 Mon Sep 17 00:00:00 2001 From: frankiebee Date: Thu, 23 Mar 2017 15:12:39 -0700 Subject: [PATCH 051/372] Fix state persistence --- library/sw-core.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/sw-core.js b/library/sw-core.js index 3972e361b..8c7de2c32 100644 --- a/library/sw-core.js +++ b/library/sw-core.js @@ -142,7 +142,7 @@ function setupController (initState, client) { // diskStore // ) controller.store.subscribe((state) => { - dbController.put(versionifyData(controller.store)) + dbController.put(versionifyData(state)) .catch((err) => {console.error(err)}) }) function versionifyData(state) { From 018b1d006f30f7022252c39849abaf8be6fffa45 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Thu, 23 Mar 2017 15:14:18 -0700 Subject: [PATCH 052/372] Make reset button clear errors --- ui/app/components/pending-tx.js | 1 + 1 file changed, 1 insertion(+) diff --git a/ui/app/components/pending-tx.js b/ui/app/components/pending-tx.js index c0786d83e..b2195804e 100644 --- a/ui/app/components/pending-tx.js +++ b/ui/app/components/pending-tx.js @@ -414,6 +414,7 @@ PendingTx.prototype.resetGasFields = function () { this.setState({ gas: txData.txParams.gas, gasPrice: txData.gasPrice, + valid: true, }) } From 3400ed0955b7f31ddb0be182736fc1e7ae20d4da Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Thu, 23 Mar 2017 16:02:40 -0700 Subject: [PATCH 053/372] Fix a couple things Sorry apparently the gas fixes weren't in the last commit, but are in this one. As reported in previous commit, fixes a bug where initial estimate is not derived from the network. Also fixes a bug where clicking "reset" does not clear our custom validation warnings. --- ui/app/components/hex-as-decimal-input.js | 29 +++++++++++++++++++---- ui/app/components/pending-tx.js | 28 ++++++++++++++-------- 2 files changed, 42 insertions(+), 15 deletions(-) diff --git a/ui/app/components/hex-as-decimal-input.js b/ui/app/components/hex-as-decimal-input.js index e39805787..96a11b84f 100644 --- a/ui/app/components/hex-as-decimal-input.js +++ b/ui/app/components/hex-as-decimal-input.js @@ -56,12 +56,11 @@ HexAsDecimalInput.prototype.render = function () { }, style), value: parseInt(decimalValue), + onBlur: (event) => { + this.updateValidity(event) + }, onChange: (event) => { - const target = event.target - const valid = target.checkValidity() - if (valid) { - this.setState({ invalid: null }) - } + this.updateValidity(event) const hexString = (event.target.value === '') ? '' : hexify(event.target.value) onChange(hexString) }, @@ -103,6 +102,26 @@ HexAsDecimalInput.prototype.render = function () { ) } +HexAsDecimalInput.prototype.setValid = function (message) { + this.setState({ invalid: null }) +} + +HexAsDecimalInput.prototype.updateValidity = function (event) { + const target = event.target + const value = this.props.value + const newValue = target.value + + if (value === newValue) { + return + } + + const valid = target.checkValidity() + console.log('change triggered checking validity and found ' + valid) + if (valid) { + this.setState({ invalid: null }) + } +} + HexAsDecimalInput.prototype.constructWarning = function () { const { name, min, max } = this.props let message = name ? name + ' ' : '' diff --git a/ui/app/components/pending-tx.js b/ui/app/components/pending-tx.js index b2195804e..82d9b9fe7 100644 --- a/ui/app/components/pending-tx.js +++ b/ui/app/components/pending-tx.js @@ -53,8 +53,7 @@ PendingTx.prototype.render = function () { const dataLength = txParams.data ? (txParams.data.length - 2) / 2 : 0 const imageify = props.imageifyIdenticons === undefined ? true : props.imageifyIdenticons - console.log('miniaccountpanelforrecipient?') - console.dir(this.miniAccountPanelForRecipient) + this.inputs = [] return ( @@ -167,6 +166,7 @@ PendingTx.prototype.render = function () { log.info(`Gas limit changed to ${newHex}`) this.setState({ gas: newHex }) }, + ref: (hexInput) => { this.inputs.push(hexInput) }, }), ]), ]), @@ -189,6 +189,7 @@ PendingTx.prototype.render = function () { log.info(`Gas price changed to: ${newHex}`) this.setState({ gasPrice: newHex }) }, + ref: (hexInput) => { this.inputs.push(hexInput) }, }), ]), ]), @@ -351,7 +352,7 @@ PendingTx.prototype.miniAccountPanelForRecipient = function () { } PendingTx.prototype.componentDidUpdate = function (prevProps, previousState) { - log.debug(`pending-tx-details componentDidUpdate`) + log.debug(`pending-tx componentDidUpdate`) const state = this.state || {} const prevState = previousState || {} const { gas, gasPrice } = state @@ -375,22 +376,22 @@ PendingTx.prototype.calculateGas = function () { const txData = props.txData const txMeta = this.gatherParams() - log.debug(`pending-tx-details calculating gas for ${JSON.stringify(txMeta)}`) + log.debug(`pending-tx calculating gas for ${JSON.stringify(txMeta)}`) const txParams = txMeta.txParams - const gasCost = new BN(ethUtil.stripHexPrefix(txParams.gas || txMeta.estimatedGas), 16) - const gasPrice = (state.gasPrice === undefined) ? txData.gasPrice : state.gasPrice + const gasLimit = new BN(ethUtil.stripHexPrefix(txParams.gas || txMeta.estimatedGas), 16) + const gasPriceHex = state.gasPrice || txData.gasPrice + const gasPrice = new BN(ethUtil.stripHexPrefix(gasPriceHex), 16) const valid = !gasPrice.lt(MIN_GAS_PRICE_BN) this.validChanged(valid) - const txFee = gasCost.mul(gasPrice) + const txFee = gasLimit.mul(gasPrice) const txValue = new BN(ethUtil.stripHexPrefix(txParams.value || '0x0'), 16) const maxCost = txValue.add(txFee) const txFeeHex = '0x' + txFee.toString('hex') const maxCostHex = '0x' + maxCost.toString('hex') - const gasPriceHex = '0x' + gasPrice.toString('hex') txMeta.txFee = txFeeHex txMeta.maxCost = maxCostHex @@ -409,8 +410,15 @@ PendingTx.prototype.calculateGas = function () { } PendingTx.prototype.resetGasFields = function () { - log.debug(`pending-tx-details#resetGasFields`) + log.debug(`pending-tx resetGasFields`) const txData = this.props.txData + + this.inputs.forEach((hexInput) => { + if (hexInput) { + hexInput.setValid() + } + }) + this.setState({ gas: txData.txParams.gas, gasPrice: txData.gasPrice, @@ -420,7 +428,7 @@ PendingTx.prototype.resetGasFields = function () { // After a customizable state value has been updated, PendingTx.prototype.gatherParams = function () { - log.debug(`pending-tx-details#gatherParams`) + log.debug(`pending-tx gatherParams`) const props = this.props const state = this.state || {} const txData = state.txData || props.txData From 9bea31a402d0b343bae6a9ef055efa2b83be9071 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Thu, 23 Mar 2017 16:36:33 -0700 Subject: [PATCH 054/372] Fix initial tx fee estimation --- ui/app/components/hex-as-decimal-input.js | 1 - ui/app/components/pending-tx.js | 8 +++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/ui/app/components/hex-as-decimal-input.js b/ui/app/components/hex-as-decimal-input.js index 96a11b84f..e37aaa8c3 100644 --- a/ui/app/components/hex-as-decimal-input.js +++ b/ui/app/components/hex-as-decimal-input.js @@ -116,7 +116,6 @@ HexAsDecimalInput.prototype.updateValidity = function (event) { } const valid = target.checkValidity() - console.log('change triggered checking validity and found ' + valid) if (valid) { this.setState({ invalid: null }) } diff --git a/ui/app/components/pending-tx.js b/ui/app/components/pending-tx.js index 82d9b9fe7..9a3ef272d 100644 --- a/ui/app/components/pending-tx.js +++ b/ui/app/components/pending-tx.js @@ -44,11 +44,13 @@ PendingTx.prototype.render = function () { const account = props.accounts[address] const balance = account ? account.balance : '0x0' + const gas = (state.gas === undefined) ? txParams.gas : state.gas const gasPrice = (state.gasPrice === undefined) ? txData.gasPrice : state.gasPrice + const gasBn = new BN(gas, 16) + const gasPriceBn = new BN(gasPrice, 16) - const txFee = state.txFee || txData.txFee || '' - const txFeeBn = new BN(txFee, 16) + const txFeeBn = gasBn.mul(gasPriceBn) const maxCost = state.maxCost || txData.maxCost || '' const dataLength = txParams.data ? (txParams.data.length - 2) / 2 : 0 const imageify = props.imageifyIdenticons === undefined ? true : props.imageifyIdenticons @@ -380,7 +382,7 @@ PendingTx.prototype.calculateGas = function () { const txParams = txMeta.txParams const gasLimit = new BN(ethUtil.stripHexPrefix(txParams.gas || txMeta.estimatedGas), 16) - const gasPriceHex = state.gasPrice || txData.gasPrice + const gasPriceHex = (state.gasPrice === undefined) ? txData.gasPrice : state.gasPrice const gasPrice = new BN(ethUtil.stripHexPrefix(gasPriceHex), 16) const valid = !gasPrice.lt(MIN_GAS_PRICE_BN) From 6a46e9ce0662413f6351756658b2bae8a895a33b Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Thu, 23 Mar 2017 17:00:59 -0700 Subject: [PATCH 055/372] Make gas calculations on render more consistent --- ui/app/components/pending-tx.js | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/ui/app/components/pending-tx.js b/ui/app/components/pending-tx.js index 9a3ef272d..2f9e9885b 100644 --- a/ui/app/components/pending-tx.js +++ b/ui/app/components/pending-tx.js @@ -21,40 +21,46 @@ const MIN_GAS_PRICE_BN = new BN(20000000) module.exports = connect(mapStateToProps)(PendingTx) function mapStateToProps (state) { - return { - - } + return {} } inherits(PendingTx, Component) function PendingTx () { Component.call(this) - this.state = { valid: true } + this.state = { + valid: true, + gas: null, + gasPrice: null, + txData: null, + } } PendingTx.prototype.render = function () { const props = this.props - const txData = props.txData - const state = this.state + const txData = state.txData || props.txData const txParams = txData.txParams || {} + const address = txParams.from || props.selectedAddress const identity = props.identities[address] || { address: address } const account = props.accounts[address] const balance = account ? account.balance : '0x0' - - const gas = (state.gas === undefined) ? txParams.gas : state.gas - const gasPrice = (state.gasPrice === undefined) ? txData.gasPrice : state.gasPrice + const gas = state.gas || txParams.gas + const gasPrice = state.gasPrice || txData.gasPrice const gasBn = new BN(gas, 16) const gasPriceBn = new BN(gasPrice, 16) const txFeeBn = gasBn.mul(gasPriceBn) - const maxCost = state.maxCost || txData.maxCost || '' + const valueBn = new BN(ethUtil.stripHexPrefix(txParams.value), 16) + const maxCost = txFeeBn.add(valueBn) + const dataLength = txParams.data ? (txParams.data.length - 2) / 2 : 0 const imageify = props.imageifyIdenticons === undefined ? true : props.imageifyIdenticons + log.info(`rendering pending-tx form with gas limit ${gas.toString()}, gasPrice ${gasPrice.toString()}, `) + this.inputs = [] return ( From 282775c52f558bbd90d4d0a1706a835dde6c3202 Mon Sep 17 00:00:00 2001 From: frankiebee Date: Thu, 23 Mar 2017 19:14:08 -0700 Subject: [PATCH 056/372] add versioning and migrateing data --- library/controllers/index-db-controller.js | 25 +++++----- library/sw-core.js | 54 ++++++---------------- 2 files changed, 28 insertions(+), 51 deletions(-) diff --git a/library/controllers/index-db-controller.js b/library/controllers/index-db-controller.js index 6bcae9845..041ddae2e 100644 --- a/library/controllers/index-db-controller.js +++ b/library/controllers/index-db-controller.js @@ -23,20 +23,25 @@ module.exports = class IndexDbController extends EventEmitter { } dbOpenRequest.onsuccess = (event) => { this.db = dbOpenRequest.result - if (!this.db.objectStoreNames.length) { - Object.keys(this.initialState).forEach((key) => { - this._add(key, this.initialState[key]) - }) - } this.emit('success') resolve(this.db) } dbOpenRequest.onupgradeneeded = (event) => { - // if (this.migrators) this.db = event.target.result - this.migrate() + this.db.createObjectStore('dataStore') } }) + .then((openRequest) => { + return this.get('dataStore') + }) + .then((data) => { + if (!data) { + return this._add('dataStore', this.initialState) + .then(() => this.get('dataStore')) + .then((versionedData) => Promise.resolve(versionedData.data)) + } + return Promise.resolve(data) + }) } requestObjectStore (key, type = 'readonly') { @@ -47,7 +52,7 @@ module.exports = class IndexDbController extends EventEmitter { }) } - get (key) { + get (key = 'dataStore') { return this.requestObjectStore(key) .then((dataObject)=> { return new Promise((resolve, reject) => { @@ -67,10 +72,6 @@ module.exports = class IndexDbController extends EventEmitter { }) } - migrate () { - this.db.createObjectStore('dataStore') - } - _add (key, objStore, cb = logger) { return this.requestObjectStore(key, 'readwrite') .then((dataObject)=> { diff --git a/library/sw-core.js b/library/sw-core.js index 8c7de2c32..46ef95e21 100644 --- a/library/sw-core.js +++ b/library/sw-core.js @@ -59,12 +59,6 @@ const dbController = new DbController({ key: STORAGE_KEY, global: self, version: 2, - initialState: { - dataStore: { - meta: 2, - data: firstTimeState, - }, - }, }) asyncQ.waterfall([ () => loadStateFromPersistence(), @@ -87,36 +81,12 @@ function loadStateFromPersistence() { const initialState = migrator.generateInitialState(firstTimeState) dbController.initialState = initialState return dbController.open() - .then((openRequest) => { - return dbController.get('dataStore') + .then((versionedData) => migrator.migrateData(versionedData)) + .then((versionedData) => { + dbController.put(versionedData) + return Promise.resolve(versionedData) }) - .then((data) => { - if (!data) { - return dbController._add('dataStore', initialState) - .then(() => dbController.get('dataStore')) - .then((versionedData) => Promise.resolve(versionedData.data)) - } - - return Promise.resolve(data.data) - }) - .catch((err) => console.error(err)) - /* - need to get migrations working - */ - - // return asyncQ.waterfall([ - // // read from disk - // () => Promise.resolve(diskStore || initialState), - // // migrate data - // (versionedData) => migrator.migrateData(versionedData), - // // write to disk - // (versionedData) => { - // diskStore.put(versionedData) - // return Promise.resolve(versionedData) - // }, - // // resolve to just data - // (versionedData) => Promise.resolve(versionedData.data), - // ]) + .then((versionedData) => Promise.resolve(versionedData.data)) } function setupController (initState, client) { @@ -142,14 +112,20 @@ function setupController (initState, client) { // diskStore // ) controller.store.subscribe((state) => { - dbController.put(versionifyData(state)) + versionifyData(state) + .then((versionedData) => dbController.put(versionedData)) .catch((err) => {console.error(err)}) }) function versionifyData(state) { - // let versionedData = diskStore.getState() + // let versionedData // versionedData.data = state - let versionedData = {data: state} - return versionedData + return dbController.get() + .then((rawData) => { + return Promise.resolve({ + data: state, + meta: rawData.meta, + })} + ) } // From 0e74cf2cba206520dbb8d7cc7b2a989566317201 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Fri, 24 Mar 2017 09:45:03 -0700 Subject: [PATCH 057/372] Disable accept button when gas limit is too low --- ui/app/components/pending-tx.js | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/ui/app/components/pending-tx.js b/ui/app/components/pending-tx.js index 2f9e9885b..cfccf2a89 100644 --- a/ui/app/components/pending-tx.js +++ b/ui/app/components/pending-tx.js @@ -16,7 +16,7 @@ const nameForAddress = require('../../lib/contract-namer') const HexInput = require('./hex-as-decimal-input') const MIN_GAS_PRICE_BN = new BN(20000000) - +const MIN_GAS_LIMIT_BN = new BN(21000) module.exports = connect(mapStateToProps)(PendingTx) @@ -164,7 +164,7 @@ PendingTx.prototype.render = function () { h(HexInput, { name: 'Gas Limit', value: gas, - min: 21000, // The hard lower limit for gas. + min: MIN_GAS_LIMIT_BN.toString(10), // The hard lower limit for gas. suffix: 'UNITS', style: { position: 'relative', @@ -298,6 +298,7 @@ PendingTx.prototype.render = function () { }, }, 'Reset'), + // Accept Button h('input.confirm.btn-green', { type: 'submit', value: 'ACCEPT', @@ -374,10 +375,6 @@ PendingTx.prototype.componentDidUpdate = function (prevProps, previousState) { } } -PendingTx.prototype.isValid = function () { - return this.state.valid -} - PendingTx.prototype.calculateGas = function () { const state = this.state const props = this.props @@ -388,10 +385,10 @@ PendingTx.prototype.calculateGas = function () { const txParams = txMeta.txParams const gasLimit = new BN(ethUtil.stripHexPrefix(txParams.gas || txMeta.estimatedGas), 16) - const gasPriceHex = (state.gasPrice === undefined) ? txData.gasPrice : state.gasPrice + const gasPriceHex = state.gasPrice || txData.gasPrice const gasPrice = new BN(ethUtil.stripHexPrefix(gasPriceHex), 16) - const valid = !gasPrice.lt(MIN_GAS_PRICE_BN) + const valid = !gasPrice.lt(MIN_GAS_PRICE_BN) && !gasLimit.lt(MIN_GAS_LIMIT_BN) this.validChanged(valid) const txFee = gasLimit.mul(gasPrice) From 8e7b5d6a13e949435fade115f5bf69897c340be1 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Fri, 24 Mar 2017 10:26:50 -0700 Subject: [PATCH 058/372] Remove unnecessary log --- ui/app/components/pending-tx.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/ui/app/components/pending-tx.js b/ui/app/components/pending-tx.js index cfccf2a89..bd3ec6377 100644 --- a/ui/app/components/pending-tx.js +++ b/ui/app/components/pending-tx.js @@ -59,8 +59,6 @@ PendingTx.prototype.render = function () { const dataLength = txParams.data ? (txParams.data.length - 2) / 2 : 0 const imageify = props.imageifyIdenticons === undefined ? true : props.imageifyIdenticons - log.info(`rendering pending-tx form with gas limit ${gas.toString()}, gasPrice ${gasPrice.toString()}, `) - this.inputs = [] return ( From 1dfcc5438151fb8be6e28477e624d2abe20b7c42 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Fri, 24 Mar 2017 13:57:04 -0400 Subject: [PATCH 059/372] Remove goHome action causing erratic UI behavior. --- ui/app/actions.js | 1 - 1 file changed, 1 deletion(-) diff --git a/ui/app/actions.js b/ui/app/actions.js index d02b7dcaa..7288db256 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -397,7 +397,6 @@ function signTx (txData) { dispatch(actions.hideLoadingIndication()) if (err) return dispatch(actions.displayWarning(err.message)) dispatch(actions.hideWarning()) - dispatch(actions.goHome()) }) dispatch(this.showConfTxPage()) } From 5cc934f18ccb64d97f6046ecb1adee5860b3b3fa Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Fri, 24 Mar 2017 12:50:39 -0700 Subject: [PATCH 060/372] Fix tx selecting bug --- ui/app/components/pending-tx.js | 1 + ui/app/conf-tx.js | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/ui/app/components/pending-tx.js b/ui/app/components/pending-tx.js index bd3ec6377..5bb088af9 100644 --- a/ui/app/components/pending-tx.js +++ b/ui/app/components/pending-tx.js @@ -472,3 +472,4 @@ function forwardCarrat () { ) } + diff --git a/ui/app/conf-tx.js b/ui/app/conf-tx.js index 83e2c7482..54c171a8a 100644 --- a/ui/app/conf-tx.js +++ b/ui/app/conf-tx.js @@ -43,8 +43,8 @@ ConfirmTxScreen.prototype.render = function () { unapprovedMsgs, unapprovedPersonalMsgs } = props var unconfTxList = txHelper(unapprovedTxs, unapprovedMsgs, unapprovedPersonalMsgs, network) - var index = props.index !== undefined && unconfTxList[index] ? props.index : 0 - var txData = unconfTxList[index] || {} + + var txData = unconfTxList[props.index] || {} var txParams = txData.params || {} var isNotification = isPopupOrNotification() === 'notification' From 5cd917b0e94d770e416eb2ec67013c7404ab2fbc Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Fri, 24 Mar 2017 16:39:55 -0400 Subject: [PATCH 061/372] Add personalMessages to function to calculate pending tx index. --- ui/app/reducers/app.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ui/app/reducers/app.js b/ui/app/reducers/app.js index b9e3f7b16..3a6baca91 100644 --- a/ui/app/reducers/app.js +++ b/ui/app/reducers/app.js @@ -592,8 +592,9 @@ function hasPendingTxs (state) { function indexForPending (state, txId) { var unapprovedTxs = state.metamask.unapprovedTxs var unapprovedMsgs = state.metamask.unapprovedMsgs + var unapprovedPersonalMsgs = state.metamask.unapprovedPersonalMsgs var network = state.metamask.network - var unconfTxList = txHelper(unapprovedTxs, unapprovedMsgs, network) + var unconfTxList = txHelper(unapprovedTxs, unapprovedMsgs, unapprovedPersonalMsgs, network) let idx unconfTxList.forEach((tx, i) => { if (tx.id === txId) { From 360afacd7093b16ae5dbfeb77a9f305e15b30297 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Fri, 24 Mar 2017 17:21:58 -0400 Subject: [PATCH 062/372] Add tests. --- test/unit/tx-manager-test.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/test/unit/tx-manager-test.js b/test/unit/tx-manager-test.js index f64f048e3..2912a03d3 100644 --- a/test/unit/tx-manager-test.js +++ b/test/unit/tx-manager-test.js @@ -59,6 +59,17 @@ describe('Transaction Manager', function() { assert.equal(result[0].id, 1) }) + it('does not override txs from other networks', function() { + var tx = { id: 1, status: 'confirmed', metamaskNetworkId: 'unit test', txParams: {} } + var tx2 = { id: 2, status: 'confirmed', metamaskNetworkId: 'another net', txParams: {} } + txManager.addTx(tx, noop) + txManager.addTx(tx2, noop) + var result = txManager.getFullTxList() + var result2 = txManager.getTxList() + assert.equal(result.length, 2, 'txs were deleted') + assert.equal(result.length, 1, 'incorrect number of txs on network.') + }) + it('cuts off early txs beyond a limit', function() { const limit = txManager.txHistoryLimit for (let i = 0; i < limit + 1; i++) { From f8b404a478caa3057c1a7271afa70599755b8eab Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Fri, 24 Mar 2017 17:23:56 -0400 Subject: [PATCH 063/372] correct bug in test. --- test/unit/tx-manager-test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/tx-manager-test.js b/test/unit/tx-manager-test.js index 2912a03d3..d4bffdd9b 100644 --- a/test/unit/tx-manager-test.js +++ b/test/unit/tx-manager-test.js @@ -67,7 +67,7 @@ describe('Transaction Manager', function() { var result = txManager.getFullTxList() var result2 = txManager.getTxList() assert.equal(result.length, 2, 'txs were deleted') - assert.equal(result.length, 1, 'incorrect number of txs on network.') + assert.equal(result2.length, 1, 'incorrect number of txs on network.') }) it('cuts off early txs beyond a limit', function() { From 0faddb2ed2a2161be5b2af1630d583e04b750f94 Mon Sep 17 00:00:00 2001 From: Stefaan Ponnet Date: Sun, 26 Mar 2017 13:57:44 +0200 Subject: [PATCH 064/372] Allow injection in IFrames + dynamic IFrames --- app/manifest.json | 2 +- app/scripts/contentscript.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/manifest.json b/app/manifest.json index 910a5701e..bc1d2f866 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -51,7 +51,7 @@ "scripts/contentscript.js" ], "run_at": "document_start", - "all_frames": false + "all_frames": true } ], "permissions": [ diff --git a/app/scripts/contentscript.js b/app/scripts/contentscript.js index 020208ceb..09c1841bf 100644 --- a/app/scripts/contentscript.js +++ b/app/scripts/contentscript.js @@ -73,6 +73,6 @@ function isAllowedSuffix (testCase) { if (doctype) { return doctype.name === 'html' } else { - return false + return true } } From bd4a68531bc915e70964e28002c3a59d85853dfa Mon Sep 17 00:00:00 2001 From: kumavis Date: Mon, 27 Mar 2017 10:55:40 -0700 Subject: [PATCH 065/372] block explorer - account link - ropsten etherscan differentiates between "attacked ropsten" vs "revived ropsten" https://ropsten.etherscan.io/ is the revived ropsten --- ui/lib/account-link.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/lib/account-link.js b/ui/lib/account-link.js index 948f32da1..4f27b35c0 100644 --- a/ui/lib/account-link.js +++ b/ui/lib/account-link.js @@ -9,7 +9,7 @@ module.exports = function (address, network) { link = `http://morden.etherscan.io/address/${address}` break case 3: // ropsten test net - link = `http://testnet.etherscan.io/address/${address}` + link = `http://ropsten.etherscan.io/address/${address}` break case 42: // kovan test net link = `http://kovan.etherscan.io/address/${address}` From dca4486a651a19c72203219effd54b14ad989a90 Mon Sep 17 00:00:00 2001 From: kumavis Date: Mon, 27 Mar 2017 10:57:04 -0700 Subject: [PATCH 066/372] block explorer - ropsten etherscan differentiates between "attacked ropsten" vs "revived ropsten" https://ropsten.etherscan.io/ is the revived ropsten --- ui/lib/explorer-link.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/lib/explorer-link.js b/ui/lib/explorer-link.js index 7ae19cca0..ca89f8b25 100644 --- a/ui/lib/explorer-link.js +++ b/ui/lib/explorer-link.js @@ -6,7 +6,7 @@ module.exports = function (hash, network) { prefix = '' break case 3: // ropsten test net - prefix = 'testnet.' + prefix = 'ropsten.' break case 42: // kovan test net prefix = 'kovan.' From 4b9f1c0e0cb31824a430547c1354331f946386c1 Mon Sep 17 00:00:00 2001 From: kumavis Date: Mon, 27 Mar 2017 11:01:10 -0700 Subject: [PATCH 067/372] tests - fix ropsten link check --- test/unit/account-link-test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/unit/account-link-test.js b/test/unit/account-link-test.js index 4ea12e002..5be3a072e 100644 --- a/test/unit/account-link-test.js +++ b/test/unit/account-link-test.js @@ -5,13 +5,13 @@ describe('account-link', function() { it('adds morden prefix to morden test network', function() { var result = linkGen('account', '2') - assert.notEqual(result.indexOf('morden'), -1, 'testnet included') + assert.notEqual(result.indexOf('morden'), -1, 'morden included') assert.notEqual(result.indexOf('account'), -1, 'account included') }) it('adds testnet prefix to ropsten test network', function() { var result = linkGen('account', '3') - assert.notEqual(result.indexOf('testnet'), -1, 'testnet included') + assert.notEqual(result.indexOf('ropsten'), -1, 'ropsten included') assert.notEqual(result.indexOf('account'), -1, 'account included') }) From 918f388463d4afaf6429f6432bdd364b780f8d6c Mon Sep 17 00:00:00 2001 From: kumavis Date: Mon, 27 Mar 2017 11:32:00 -0700 Subject: [PATCH 068/372] explorer - fix ropsten explorer link tests --- test/unit/account-link-test.js | 12 ++++++------ test/unit/explorer-link-test.js | 9 +++++++-- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/test/unit/account-link-test.js b/test/unit/account-link-test.js index 5be3a072e..803a70f37 100644 --- a/test/unit/account-link-test.js +++ b/test/unit/account-link-test.js @@ -3,15 +3,15 @@ var linkGen = require('../../ui/lib/account-link') describe('account-link', function() { - it('adds morden prefix to morden test network', function() { - var result = linkGen('account', '2') - assert.notEqual(result.indexOf('morden'), -1, 'morden included') + it('adds ropsten prefix to ropsten test network', function() { + var result = linkGen('account', '3') + assert.notEqual(result.indexOf('ropsten'), -1, 'ropsten included') assert.notEqual(result.indexOf('account'), -1, 'account included') }) - it('adds testnet prefix to ropsten test network', function() { - var result = linkGen('account', '3') - assert.notEqual(result.indexOf('ropsten'), -1, 'ropsten included') + it('adds kovan prefix to kovan test network', function() { + var result = linkGen('account', '42') + assert.notEqual(result.indexOf('kovan'), -1, 'kovan included') assert.notEqual(result.indexOf('account'), -1, 'account included') }) diff --git a/test/unit/explorer-link-test.js b/test/unit/explorer-link-test.js index 8aa58bff9..4f0230c2c 100644 --- a/test/unit/explorer-link-test.js +++ b/test/unit/explorer-link-test.js @@ -3,9 +3,14 @@ var linkGen = require('../../ui/lib/explorer-link') describe('explorer-link', function() { - it('adds testnet prefix to morden test network', function() { + it('adds ropsten prefix to ropsten test network', function() { var result = linkGen('hash', '3') - assert.notEqual(result.indexOf('testnet'), -1, 'testnet injected') + assert.notEqual(result.indexOf('ropsten'), -1, 'ropsten injected') + }) + + it('adds kovan prefix to kovan test network', function() { + var result = linkGen('hash', '42') + assert.notEqual(result.indexOf('kovan'), -1, 'kovan injected') }) }) From d9c2f4f5e8dc9ec8a88a02eb5a73ea78b4a564ac Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Mon, 27 Mar 2017 11:48:10 -0700 Subject: [PATCH 069/372] Version 3.5.0 --- CHANGELOG.md | 2 ++ app/manifest.json | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2dcbb7290..1e6256100 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Current Master +## 3.5.0 2017-3-27 + - Add better error messages for when a transaction fails on approval - Allow sending to ENS names in send form on Ropsten. - Added an address book functionality that remembers the last 15 unique addresses sent to. diff --git a/app/manifest.json b/app/manifest.json index 910a5701e..70d701fc5 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -1,7 +1,7 @@ { "name": "MetaMask", "short_name": "Metamask", - "version": "3.4.0", + "version": "3.5.0", "manifest_version": 2, "author": "https://metamask.io", "description": "Ethereum Browser Extension", From c1136a6317399ec033bf17a9ce0b13a2a1c4b0d2 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Mon, 27 Mar 2017 16:05:21 -0400 Subject: [PATCH 070/372] Add link to kovan faucet instructions. --- app/scripts/metamask-controller.js | 4 ++++ ui/app/components/buy-button-subview.js | 11 +++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 92533e022..bab17dabd 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -623,6 +623,10 @@ module.exports = class MetamaskController extends EventEmitter { case '3': url = 'https://faucet.metamask.io/' break + + case '42': + url = 'https://github.com/kovan-testnet/faucet' + break } if (url) extension.tabs.create({ url }) diff --git a/ui/app/components/buy-button-subview.js b/ui/app/components/buy-button-subview.js index 3074bd7cd..7b993110d 100644 --- a/ui/app/components/buy-button-subview.js +++ b/ui/app/components/buy-button-subview.js @@ -121,15 +121,22 @@ BuyButtonSubview.prototype.formVersionSubview = function () { h('h3.text-transform-uppercase', { style: { width: '225px', + marginBottom: '15px', }, }, 'In order to access this feature, please switch to the Main Network'), - (this.props.network === '3') ? h('h3.text-transform-uppercase', 'or:') : null, + ((this.props.network === '3') || (this.props.network === '42')) ? h('h3.text-transform-uppercase', 'or go to the') : null, (this.props.network === '3') ? h('button.text-transform-uppercase', { onClick: () => this.props.dispatch(actions.buyEth()), style: { marginTop: '15px', }, - }, 'Go To Test Faucet') : null, + }, 'Ropsten Test Faucet') : null, + (this.props.network === '42') ? h('button.text-transform-uppercase', { + onClick: () => this.props.dispatch(actions.buyEth()), + style: { + marginTop: '15px', + }, + }, 'Kovan Test Faucet') : null, ]) } } From 6fd7e133bd0966e2a15a802872844a11b1c81ae6 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Mon, 27 Mar 2017 16:06:05 -0400 Subject: [PATCH 071/372] Add to changelog. --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e6256100..8a0f866bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Current Master +- Add link to Kovan Test Faucet instructions on buy view. + ## 3.5.0 2017-3-27 - Add better error messages for when a transaction fails on approval From 9a8bf5a605738c49301c1ebe1793e2e3ebce61fc Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Mon, 27 Mar 2017 16:33:04 -0400 Subject: [PATCH 072/372] Fix edge case where notice does not require scrollbar. --- ui/app/components/notice.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ui/app/components/notice.js b/ui/app/components/notice.js index 8a953a6b5..b85787033 100644 --- a/ui/app/components/notice.js +++ b/ui/app/components/notice.js @@ -92,6 +92,7 @@ Notice.prototype.render = function () { }, }, [ h(ReactMarkdown, { + className: 'notice-box', source: body, skipHtml: true, }), @@ -114,6 +115,8 @@ Notice.prototype.render = function () { Notice.prototype.componentDidMount = function () { var node = findDOMNode(this) linker.setupListener(node) + if (document.getElementsByClassName('notice-box')[0].clientHeight < 310) { this.setState({disclaimerDisabled: false}) } + } Notice.prototype.componentWillUnmount = function () { From c69ebf34204d8fa932e0ac5240e7907f33d0afbb Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Mon, 27 Mar 2017 16:33:54 -0400 Subject: [PATCH 073/372] Add to changelog. --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e6256100..c7432bdc2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Current Master +- Fix edge case where users were unable to enable the notice button if notices were short enough to not require a scrollbar. + ## 3.5.0 2017-3-27 - Add better error messages for when a transaction fails on approval From d41c8ef5986e1f39350f4ea1f7664415ea236c7d Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Mon, 27 Mar 2017 13:39:19 -0700 Subject: [PATCH 074/372] Version 3.5.1 --- CHANGELOG.md | 2 ++ app/manifest.json | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c7432bdc2..93824e71c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Current Master +## 3.5.1 2017-3-27 + - Fix edge case where users were unable to enable the notice button if notices were short enough to not require a scrollbar. ## 3.5.0 2017-3-27 diff --git a/app/manifest.json b/app/manifest.json index 70d701fc5..97cc7dd6d 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -1,7 +1,7 @@ { "name": "MetaMask", "short_name": "Metamask", - "version": "3.5.0", + "version": "3.5.1", "manifest_version": 2, "author": "https://metamask.io", "description": "Ethereum Browser Extension", From 3a1f1b3bc4a11c060f0002ffa4428310ae01ba19 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Mon, 27 Mar 2017 17:43:15 -0700 Subject: [PATCH 075/372] Bump changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 93824e71c..2ac434739 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Current Master +- Inject web3 into loaded iFrames. + ## 3.5.1 2017-3-27 - Fix edge case where users were unable to enable the notice button if notices were short enough to not require a scrollbar. From 6af932904d5cc320a2ef1e25ef5cdbfa23bbddb2 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Tue, 28 Mar 2017 10:26:06 -0400 Subject: [PATCH 076/372] Remove seedWords from UI state dump. --- ui/app/reducers.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ui/app/reducers.js b/ui/app/reducers.js index 4d10e2b39..c656af849 100644 --- a/ui/app/reducers.js +++ b/ui/app/reducers.js @@ -42,6 +42,10 @@ function rootReducer (state, action) { } window.logState = function () { - var stateString = JSON.stringify(window.METAMASK_CACHED_LOG_STATE, null, 2) + var stateString = JSON.stringify(window.METAMASK_CACHED_LOG_STATE, removeSeedWords, 2) console.log(stateString) } + +function removeSeedWords (key, value) { + return key === 'seedWords' ? undefined : value +} From c227d1944f9ee5c1143f6da9e04ab98e3da79aad Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Tue, 28 Mar 2017 10:27:03 -0400 Subject: [PATCH 077/372] Add to changelog. --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 93824e71c..ed824cfdf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Current Master +- Remove seedWords from UI state dump. + ## 3.5.1 2017-3-27 - Fix edge case where users were unable to enable the notice button if notices were short enough to not require a scrollbar. From a95d96d507b9b20e0457baae9f9d38e26b23d8b3 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Tue, 28 Mar 2017 11:23:25 -0400 Subject: [PATCH 078/372] Assure that seed words are placed into state tree upon request. --- app/scripts/metamask-controller.js | 2 +- ui/app/actions.js | 4 ++- .../keychains/hd/recover-seed/confirmation.js | 32 +------------------ ui/app/reducers/metamask.js | 1 + 4 files changed, 6 insertions(+), 33 deletions(-) diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 92533e022..c0c113a45 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -386,7 +386,7 @@ module.exports = class MetamaskController extends EventEmitter { .then((serialized) => { const seedWords = serialized.mnemonic this.configManager.setSeedWords(seedWords) - cb() + cb(null, seedWords) }) } diff --git a/ui/app/actions.js b/ui/app/actions.js index 7288db256..9c3ab059c 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -273,8 +273,10 @@ function requestRevealSeed (password) { return dispatch(actions.displayWarning(err.message)) } log.debug(`background.placeSeedWords`) - background.placeSeedWords((err) => { + background.placeSeedWords((err, result) => { if (err) return dispatch(actions.displayWarning(err.message)) + dispatch(actions.hideLoadingIndication()) + dispatch(actions.showNewVaultSeed(result)) }) }) } diff --git a/ui/app/keychains/hd/recover-seed/confirmation.js b/ui/app/keychains/hd/recover-seed/confirmation.js index 56ac461ea..4ccbec9fc 100644 --- a/ui/app/keychains/hd/recover-seed/confirmation.js +++ b/ui/app/keychains/hd/recover-seed/confirmation.js @@ -18,11 +18,8 @@ function mapStateToProps (state) { } } -RevealSeedConfirmation.prototype.confirmationPhrase = 'I understand' - RevealSeedConfirmation.prototype.render = function () { const props = this.props - const state = this.state return ( @@ -64,31 +61,13 @@ RevealSeedConfirmation.prototype.render = function () { }, }), - h(`h4${state && state.confirmationWrong ? '.error' : ''}`, { - style: { - marginTop: '12px', - }, - }, `Enter the phrase "${this.confirmationPhrase}" to proceed.`), - - // confirm confirmation - h('input.large-input.letter-spacey', { - type: 'text', - id: 'confirm-box', - placeholder: this.confirmationPhrase, - onKeyPress: this.checkConfirmation.bind(this), - style: { - width: 260, - marginTop: 16, - }, - }), - h('.flex-row.flex-space-between', { style: { marginTop: 30, width: '50%', }, }, [ -// cancel + // cancel h('button.primary', { onClick: this.goHome.bind(this), }, 'CANCEL'), @@ -134,15 +113,6 @@ RevealSeedConfirmation.prototype.checkConfirmation = function (event) { } RevealSeedConfirmation.prototype.revealSeedWords = function () { - this.setState({ confirmationWrong: false }) - - const confirmBox = document.getElementById('confirm-box') - const confirmation = confirmBox.value - if (confirmation !== this.confirmationPhrase) { - confirmBox.value = '' - return this.setState({ confirmationWrong: true }) - } - var password = document.getElementById('password-box').value this.props.dispatch(actions.requestRevealSeed(password)) } diff --git a/ui/app/reducers/metamask.js b/ui/app/reducers/metamask.js index 2b5151466..e0c416c2d 100644 --- a/ui/app/reducers/metamask.js +++ b/ui/app/reducers/metamask.js @@ -94,6 +94,7 @@ function reduceMetamask (state, action) { return extend(metamaskState, { isUnlocked: true, isInitialized: false, + seedWords: action.value, }) case actions.CLEAR_SEED_WORD_CACHE: From 0625b4a11038307673a4fcd9689e0955e10ebacf Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Tue, 28 Mar 2017 14:30:39 -0400 Subject: [PATCH 079/372] Fix injection logic. --- app/scripts/contentscript.js | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/app/scripts/contentscript.js b/app/scripts/contentscript.js index 09c1841bf..9a390e580 100644 --- a/app/scripts/contentscript.js +++ b/app/scripts/contentscript.js @@ -65,14 +65,27 @@ function setupStreams () { } function shouldInjectWeb3 () { - return isAllowedSuffix(window.location.href) + return doctypeCheck() || suffixCheck() } -function isAllowedSuffix (testCase) { +function doctypeCheck () { const doctype = window.document.doctype if (doctype) { return doctype.name === 'html' } else { - return true + return false } } + +function suffixCheck() { + var prohibitedTypes = ['xml', 'pdf'] + var currentUrl = window.location.href + var currentRegex + for (let i = 0; i < prohibitedTypes.length; i++) { + currentRegex = new RegExp(`\.${prohibitedTypes[i]}$`) + if (currentRegex.test(currentUrl)) { + return false + } + } + return true +} From 81d3658343bbdf8dd85b175f759c372d9ee00fb8 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Tue, 28 Mar 2017 11:46:33 -0700 Subject: [PATCH 080/372] Improve UI gas calculation logic - Now striping hex prefixed gas values, which may have been causing mis-estimation. - Unified calculation logic to be entirely functional. - Greatly simplified how the pending-tx form keeps updated form state. Still needs a commit from @kumavis to ensure the background passes in a txMeta.txParams.gasPrice value. --- app/scripts/lib/hex-to-bn.js | 7 ++ ui/app/actions.js | 1 + ui/app/components/pending-tx.js | 135 +++++++++----------------------- ui/app/conf-tx.js | 30 +------ 4 files changed, 46 insertions(+), 127 deletions(-) create mode 100644 app/scripts/lib/hex-to-bn.js diff --git a/app/scripts/lib/hex-to-bn.js b/app/scripts/lib/hex-to-bn.js new file mode 100644 index 000000000..184217279 --- /dev/null +++ b/app/scripts/lib/hex-to-bn.js @@ -0,0 +1,7 @@ +const ethUtil = require('ethereumjs-util') +const BN = ethUtil.BN + +module.exports = function hexToBn (hex) { + return new BN(ethUtil.stripHexPrefix(hex), 16) +} + diff --git a/ui/app/actions.js b/ui/app/actions.js index 7288db256..19c0e66ad 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -421,6 +421,7 @@ function updateAndApproveTx (txData) { return (dispatch) => { log.debug(`actions calling background.updateAndApproveTx`) background.updateAndApproveTransaction(txData, (err) => { + dispatch(actions.hideLoadingIndication()) if (err) { dispatch(actions.txError(err)) return console.error(err.message) diff --git a/ui/app/components/pending-tx.js b/ui/app/components/pending-tx.js index 5bb088af9..1b83f5043 100644 --- a/ui/app/components/pending-tx.js +++ b/ui/app/components/pending-tx.js @@ -2,11 +2,11 @@ const Component = require('react').Component const connect = require('react-redux').connect const h = require('react-hyperscript') const inherits = require('util').inherits -const extend = require('xtend') const actions = require('../actions') const ethUtil = require('ethereumjs-util') const BN = ethUtil.BN +const hexToBn = require('../../../app/scripts/lib/hex-to-bn') const MiniAccountPanel = require('./mini-account-panel') const EthBalance = require('./eth-balance') @@ -29,42 +29,43 @@ function PendingTx () { Component.call(this) this.state = { valid: true, - gas: null, - gasPrice: null, txData: null, } } PendingTx.prototype.render = function () { const props = this.props - const state = this.state - const txData = state.txData || props.txData - const txParams = txData.txParams || {} + const txMeta = this.gatherTxMeta() + const txParams = txMeta.txParams || {} const address = txParams.from || props.selectedAddress const identity = props.identities[address] || { address: address } const account = props.accounts[address] const balance = account ? account.balance : '0x0' - const gas = state.gas || txParams.gas - const gasPrice = state.gasPrice || txData.gasPrice - const gasBn = new BN(gas, 16) - const gasPriceBn = new BN(gasPrice, 16) + const gas = txParams.gas + const gasPrice = txParams.gasPrice + + const gasBn = hexToBn(gas) + const gasPriceBn = hexToBn(gasPrice) const txFeeBn = gasBn.mul(gasPriceBn) - const valueBn = new BN(ethUtil.stripHexPrefix(txParams.value), 16) + const valueBn = hexToBn(txParams.value) const maxCost = txFeeBn.add(valueBn) const dataLength = txParams.data ? (txParams.data.length - 2) / 2 : 0 const imageify = props.imageifyIdenticons === undefined ? true : props.imageifyIdenticons + const balanceBn = hexToBn(balance) + const insufficientBalance = balanceBn.lt(maxCost) + this.inputs = [] return ( h('div', { - key: txData.id, + key: txMeta.id, }, [ h('form#pending-tx-form', { @@ -73,9 +74,8 @@ PendingTx.prototype.render = function () { const form = document.querySelector('form#pending-tx-form') const valid = form.checkValidity() this.setState({ valid }) - if (valid && this.verifyGasParams()) { - props.sendTransaction(txData, event) + props.sendTransaction(txMeta, event) } else { this.props.dispatch(actions.displayWarning('Invalid Gas Parameters')) } @@ -162,7 +162,8 @@ PendingTx.prototype.render = function () { h(HexInput, { name: 'Gas Limit', value: gas, - min: MIN_GAS_LIMIT_BN.toString(10), // The hard lower limit for gas. + // The hard lower limit for gas. + min: MIN_GAS_LIMIT_BN.toString(10), suffix: 'UNITS', style: { position: 'relative', @@ -170,7 +171,9 @@ PendingTx.prototype.render = function () { }, onChange: (newHex) => { log.info(`Gas limit changed to ${newHex}`) - this.setState({ gas: newHex }) + const txMeta = this.gatherTxMeta() + txMeta.txParams.gas = newHex + this.setState({ txData: txMeta }) }, ref: (hexInput) => { this.inputs.push(hexInput) }, }), @@ -193,7 +196,9 @@ PendingTx.prototype.render = function () { }, onChange: (newHex) => { log.info(`Gas price changed to: ${newHex}`) - this.setState({ gasPrice: newHex }) + const txMeta = this.gatherTxMeta() + txMeta.txParams.gasPrice = newHex + this.setState({ txData: txMeta }) }, ref: (hexInput) => { this.inputs.push(hexInput) }, }), @@ -255,7 +260,7 @@ PendingTx.prototype.render = function () { } `), - txData.simulationFails ? + txMeta.simulationFails ? h('.error', { style: { marginLeft: 50, @@ -264,7 +269,7 @@ PendingTx.prototype.render = function () { }, 'Transaction Error. Exception thrown in contract code.') : null, - props.insufficientBalance ? + insufficientBalance ? h('span.error', { style: { marginLeft: 50, @@ -283,7 +288,7 @@ PendingTx.prototype.render = function () { }, [ - props.insufficientBalance ? + insufficientBalance ? h('button', { onClick: props.buyEth, }, 'Buy Ether') @@ -301,7 +306,7 @@ PendingTx.prototype.render = function () { type: 'submit', value: 'ACCEPT', style: { marginLeft: '10px' }, - disabled: props.insufficientBalance || !this.state.valid, + disabled: insufficientBalance || !this.state.valid, }), h('button.cancel.btn-red', { @@ -313,10 +318,6 @@ PendingTx.prototype.render = function () { ) } -PendingTx.prototype.validChanged = function (newValid) { - this.setState({ valid: newValid }) -} - PendingTx.prototype.miniAccountPanelForRecipient = function () { const props = this.props const txData = props.txData @@ -358,63 +359,8 @@ PendingTx.prototype.miniAccountPanelForRecipient = function () { } } -PendingTx.prototype.componentDidUpdate = function (prevProps, previousState) { - log.debug(`pending-tx componentDidUpdate`) - const state = this.state || {} - const prevState = previousState || {} - const { gas, gasPrice } = state - - // Only if gas or gasPrice changed: - if (!prevState || - (gas !== prevState.gas || - gasPrice !== prevState.gasPrice)) { - log.debug(`recalculating gas since prev state change: ${JSON.stringify({ prevState, state })}`) - this.calculateGas() - } -} - -PendingTx.prototype.calculateGas = function () { - const state = this.state - const props = this.props - const txData = props.txData - - const txMeta = this.gatherParams() - log.debug(`pending-tx calculating gas for ${JSON.stringify(txMeta)}`) - - const txParams = txMeta.txParams - const gasLimit = new BN(ethUtil.stripHexPrefix(txParams.gas || txMeta.estimatedGas), 16) - const gasPriceHex = state.gasPrice || txData.gasPrice - const gasPrice = new BN(ethUtil.stripHexPrefix(gasPriceHex), 16) - - const valid = !gasPrice.lt(MIN_GAS_PRICE_BN) && !gasLimit.lt(MIN_GAS_LIMIT_BN) - this.validChanged(valid) - - const txFee = gasLimit.mul(gasPrice) - const txValue = new BN(ethUtil.stripHexPrefix(txParams.value || '0x0'), 16) - const maxCost = txValue.add(txFee) - - const txFeeHex = '0x' + txFee.toString('hex') - const maxCostHex = '0x' + maxCost.toString('hex') - - txMeta.txFee = txFeeHex - txMeta.maxCost = maxCostHex - txMeta.txParams.gasPrice = gasPriceHex - - const newState = { - txFee: '0x' + txFee.toString('hex'), - maxCost: '0x' + maxCost.toString('hex'), - } - log.info(`tx form updating local state with ${JSON.stringify(newState)}`) - this.setState(newState) - - if (this.props.onTxChange) { - this.props.onTxChange(txMeta) - } -} - PendingTx.prototype.resetGasFields = function () { log.debug(`pending-tx resetGasFields`) - const txData = this.props.txData this.inputs.forEach((hexInput) => { if (hexInput) { @@ -423,36 +369,29 @@ PendingTx.prototype.resetGasFields = function () { }) this.setState({ - gas: txData.txParams.gas, - gasPrice: txData.gasPrice, + txData: null, valid: true, }) } // After a customizable state value has been updated, -PendingTx.prototype.gatherParams = function () { - log.debug(`pending-tx gatherParams`) +PendingTx.prototype.gatherTxMeta = function () { + log.debug(`pending-tx gatherTxMeta`) const props = this.props - const state = this.state || {} + const state = this.state const txData = state.txData || props.txData - const txParams = txData.txParams - const gas = state.gas || txParams.gas - const gasPrice = state.gasPrice || txParams.gasPrice - const resultTx = extend(txParams, { - gas, - gasPrice, - }) - const resultTxMeta = extend(txData, { - txParams: resultTx, - }) - log.debug(`UI has computed tx params ${JSON.stringify(resultTx)}`) - return resultTxMeta + + log.debug(`UI has defaulted to tx meta ${JSON.stringify(txData)}`) + return txData } PendingTx.prototype.verifyGasParams = function () { // We call this in case the gas has not been modified at all if (!this.state) { return true } - return this._notZeroOrEmptyString(this.state.gas) && this._notZeroOrEmptyString(this.state.gasPrice) + return ( + this._notZeroOrEmptyString(this.state.gas) && + this._notZeroOrEmptyString(this.state.gasPrice) + ) } PendingTx.prototype._notZeroOrEmptyString = function (obj) { diff --git a/ui/app/conf-tx.js b/ui/app/conf-tx.js index 54c171a8a..3b8618992 100644 --- a/ui/app/conf-tx.js +++ b/ui/app/conf-tx.js @@ -7,8 +7,6 @@ const actions = require('./actions') const NetworkIndicator = require('./components/network') const txHelper = require('../lib/tx-helper') const isPopupOrNotification = require('../../app/scripts/lib/is-popup-or-notification') -const ethUtil = require('ethereumjs-util') -const BN = ethUtil.BN const PendingTx = require('./components/pending-tx') const PendingMsg = require('./components/pending-msg') @@ -104,9 +102,6 @@ ConfirmTxScreen.prototype.render = function () { selectedAddress: props.selectedAddress, accounts: props.accounts, identities: props.identities, - insufficientBalance: this.checkBalanceAgainstTx(txData), - // State actions - onTxChange: this.onTxChange.bind(this), // Actions buyEth: this.buyEth.bind(this, txParams.from || props.selectedAddress), sendTransaction: this.sendTransaction.bind(this, txData), @@ -145,37 +140,14 @@ function currentTxView (opts) { } } -ConfirmTxScreen.prototype.checkBalanceAgainstTx = function (txData) { - if (!txData.txParams) return false - var props = this.props - var address = txData.txParams.from || props.selectedAddress - var account = props.accounts[address] - var balance = account ? account.balance : '0x0' - var maxCost = new BN(txData.maxCost, 16) - - var balanceBn = new BN(ethUtil.stripHexPrefix(balance), 16) - return maxCost.gt(balanceBn) -} - ConfirmTxScreen.prototype.buyEth = function (address, event) { this.stopPropagation(event) this.props.dispatch(actions.buyEthView(address)) } -// Allows the detail view to update the gas calculations, -// for manual gas controls. -ConfirmTxScreen.prototype.onTxChange = function (txData) { - log.debug(`conf-tx onTxChange triggered with ${JSON.stringify(txData)}`) - this.setState({ txData }) -} - -// Must default to any local state txData, -// to allow manual override of gas calculations. ConfirmTxScreen.prototype.sendTransaction = function (txData, event) { this.stopPropagation(event) - const state = this.state || {} - const txMeta = state.txData - this.props.dispatch(actions.updateAndApproveTx(txMeta || txData)) + this.props.dispatch(actions.updateAndApproveTx(txData)) } ConfirmTxScreen.prototype.cancelTransaction = function (txData, event) { From 1495240969f8b4931259f487c3eb75aca36c68d7 Mon Sep 17 00:00:00 2001 From: kumavis Date: Tue, 28 Mar 2017 13:35:27 -0700 Subject: [PATCH 081/372] tx manager - adjust new tx flow and txMeta decorations --- app/scripts/lib/tx-utils.js | 32 +++++++++++------------ app/scripts/transaction-manager.js | 41 +++++++++++++----------------- 2 files changed, 32 insertions(+), 41 deletions(-) diff --git a/app/scripts/lib/tx-utils.js b/app/scripts/lib/tx-utils.js index 7988f83e9..72df53631 100644 --- a/app/scripts/lib/tx-utils.js +++ b/app/scripts/lib/tx-utils.js @@ -12,48 +12,49 @@ and used to do things like calculate gas of a tx. */ module.exports = class txProviderUtils { + constructor (provider) { this.provider = provider this.query = new EthQuery(provider) } - analyzeGasUsage (txData, cb) { + analyzeGasUsage (txMeta, cb) { var self = this this.query.getBlockByNumber('latest', true, (err, block) => { if (err) return cb(err) async.waterfall([ - self.estimateTxGas.bind(self, txData, block.gasLimit), - self.setTxGas.bind(self, txData, block.gasLimit), + self.estimateTxGas.bind(self, txMeta, block.gasLimit), + self.setTxGas.bind(self, txMeta, block.gasLimit), ], cb) }) } - estimateTxGas (txData, blockGasLimitHex, cb) { - const txParams = txData.txParams + estimateTxGas (txMeta, blockGasLimitHex, cb) { + const txParams = txMeta.txParams // check if gasLimit is already specified - txData.gasLimitSpecified = Boolean(txParams.gas) + txMeta.gasLimitSpecified = Boolean(txParams.gas) // if not, fallback to block gasLimit - if (!txData.gasLimitSpecified) { + if (!txMeta.gasLimitSpecified) { txParams.gas = blockGasLimitHex } // run tx, see if it will OOG this.query.estimateGas(txParams, cb) } - setTxGas (txData, blockGasLimitHex, estimatedGasHex, cb) { - txData.estimatedGas = estimatedGasHex - const txParams = txData.txParams + setTxGas (txMeta, blockGasLimitHex, estimatedGasHex, cb) { + txMeta.estimatedGas = estimatedGasHex + const txParams = txMeta.txParams // if gasLimit was specified and doesnt OOG, // use original specified amount - if (txData.gasLimitSpecified) { - txData.estimatedGas = txParams.gas + if (txMeta.gasLimitSpecified) { + txMeta.estimatedGas = txParams.gas cb() return } // if gasLimit not originally specified, // try adding an additional gas buffer to our estimation for safety - const recommendedGasHex = this.addGasBuffer(txData.estimatedGas, blockGasLimitHex) + const recommendedGasHex = this.addGasBuffer(txMeta.estimatedGas, blockGasLimitHex) txParams.gas = recommendedGasHex cb() return @@ -90,16 +91,13 @@ module.exports = class txProviderUtils { // builds ethTx from txParams object buildEthTxFromParams (txParams) { - // apply gas multiplyer - let gasPrice = hexToBn(txParams.gasPrice) - // multiply and divide by 100 so as to add percision to integer mul - txParams.gasPrice = ethUtil.intToHex(gasPrice.toNumber()) // normalize values txParams.to = normalize(txParams.to) txParams.from = normalize(txParams.from) txParams.value = normalize(txParams.value) txParams.data = normalize(txParams.data) txParams.gas = normalize(txParams.gas || txParams.gasLimit) + txParams.gasPrice = normalize(txParams.gasPrice) txParams.nonce = normalize(txParams.nonce) // build ethTx log.info(`Prepared tx for signing: ${JSON.stringify(txParams)}`) diff --git a/app/scripts/transaction-manager.js b/app/scripts/transaction-manager.js index 7227bdc87..571fb8ae0 100644 --- a/app/scripts/transaction-manager.js +++ b/app/scripts/transaction-manager.js @@ -4,7 +4,6 @@ const extend = require('xtend') const Semaphore = require('semaphore') const ObservableStore = require('obs-store') const ethUtil = require('ethereumjs-util') -const BN = require('ethereumjs-util').BN const TxProviderUtil = require('./lib/tx-utils') const createId = require('./lib/random-id') @@ -121,44 +120,38 @@ module.exports = class TransactionManager extends EventEmitter { async.waterfall([ // validate (cb) => this.txProviderUtils.validateTxParams(txParams, cb), - // prepare txMeta + // construct txMeta (cb) => { - // create txMeta obj with parameters and meta data - let time = (new Date()).getTime() - let txId = createId() - txParams.metamaskId = txId - txParams.metamaskNetworkId = this.getNetwork() txMeta = { - id: txId, - time: time, + id: createId(), + time: (new Date()).getTime(), status: 'unapproved', metamaskNetworkId: this.getNetwork(), txParams: txParams, } - // calculate metadata for tx - this.txProviderUtils.analyzeGasUsage(txMeta, cb) + cb() }, + // add default tx params + (cb) => this.addTxDefaults(txMeta, cb), // save txMeta (cb) => { this.addTx(txMeta) - this.setMaxTxCostAndFee(txMeta) cb(null, txMeta) }, ], done) } - setMaxTxCostAndFee (txMeta) { - var txParams = txMeta.txParams - var gasCost = new BN(ethUtil.stripHexPrefix(txParams.gas || txMeta.estimatedGas), 16) - var gasPrice = new BN(ethUtil.stripHexPrefix(txParams.gasPrice || '0x4a817c800'), 16) - var txFee = gasCost.mul(gasPrice) - var txValue = new BN(ethUtil.stripHexPrefix(txParams.value || '0x0'), 16) - var maxCost = txValue.add(txFee) - txMeta.txFee = txFee - txMeta.txValue = txValue - txMeta.maxCost = maxCost - txMeta.gasPrice = gasPrice - this.updateTx(txMeta) + addTxDefaults (txMeta, cb) { + const txParams = txMeta.txParams + // ensure value + txParams.value = txParams.value || '0x0' + this.query.gasPrice((err, gasPrice) => { + if (err) return cb(err) + // set gasPrice + txParams.gasPrice = gasPrice + // set gasLimit + this.txProviderUtils.analyzeGasUsage(txMeta, cb) + }) } getUnapprovedTxList () { From e864623d3cfc70ff10ef1b84d5853dc7aa50fa36 Mon Sep 17 00:00:00 2001 From: kumavis Date: Tue, 28 Mar 2017 13:56:35 -0700 Subject: [PATCH 082/372] tx manager - add eth-query --- app/scripts/transaction-manager.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/scripts/transaction-manager.js b/app/scripts/transaction-manager.js index 571fb8ae0..690d44808 100644 --- a/app/scripts/transaction-manager.js +++ b/app/scripts/transaction-manager.js @@ -4,6 +4,7 @@ const extend = require('xtend') const Semaphore = require('semaphore') const ObservableStore = require('obs-store') const ethUtil = require('ethereumjs-util') +const EthQuery = require('eth-query') const TxProviderUtil = require('./lib/tx-utils') const createId = require('./lib/random-id') @@ -19,6 +20,7 @@ module.exports = class TransactionManager extends EventEmitter { this.txHistoryLimit = opts.txHistoryLimit this.provider = opts.provider this.blockTracker = opts.blockTracker + this.query = new EthQuery(this.provider) this.txProviderUtils = new TxProviderUtil(this.provider) this.blockTracker.on('block', this.checkForTxInBlock.bind(this)) this.signEthTx = opts.signTransaction @@ -329,7 +331,7 @@ module.exports = class TransactionManager extends EventEmitter { } return this.setTxStatusFailed(txId, errReason) } - this.txProviderUtils.query.getTransactionByHash(txHash, (err, txParams) => { + this.query.getTransactionByHash(txHash, (err, txParams) => { if (err || !txParams) { if (!txParams) return txMeta.err = { From 0d20b548544c284fb6338dc55cf0c864f3a08b14 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Tue, 28 Mar 2017 14:23:01 -0700 Subject: [PATCH 083/372] Detect tx network from txMeta --- ui/lib/tx-helper.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/lib/tx-helper.js b/ui/lib/tx-helper.js index 2eefdff68..6c549dbe3 100644 --- a/ui/lib/tx-helper.js +++ b/ui/lib/tx-helper.js @@ -4,7 +4,7 @@ module.exports = function (unapprovedTxs, unapprovedMsgs, personalMsgs, network) log.debug('tx-helper called with params:') log.debug({ unapprovedTxs, unapprovedMsgs, personalMsgs, network }) - const txValues = network ? valuesFor(unapprovedTxs).filter(tx => tx.txParams.metamaskNetworkId === network) : valuesFor(unapprovedTxs) + const txValues = network ? valuesFor(unapprovedTxs).filter(tx => tx.metamaskNetworkId === network) : valuesFor(unapprovedTxs) log.debug(`tx helper found ${txValues.length} unapproved txs`) const msgValues = valuesFor(unapprovedMsgs) log.debug(`tx helper found ${msgValues.length} unsigned messages`) From 8681e2540e77162827a941160fb048251e728398 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Tue, 28 Mar 2017 14:24:05 -0700 Subject: [PATCH 084/372] Filter txs by txMeta network value in account detail --- ui/app/accounts/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/app/accounts/index.js b/ui/app/accounts/index.js index e236a4e85..d911ff04c 100644 --- a/ui/app/accounts/index.js +++ b/ui/app/accounts/index.js @@ -11,7 +11,7 @@ module.exports = connect(mapStateToProps)(AccountsScreen) function mapStateToProps (state) { const pendingTxs = valuesFor(state.metamask.unapprovedTxs) - .filter(tx => tx.txParams.metamaskNetworkId === state.metamask.network) + .filter(tx => tx.metamaskNetworkId === state.metamask.network) const pendingMsgs = valuesFor(state.metamask.unapprovedMsgs) const pending = pendingTxs.concat(pendingMsgs) From 6310a05daeb6a4934de6976a6a9a38be85044aa0 Mon Sep 17 00:00:00 2001 From: kumavis Date: Tue, 28 Mar 2017 14:39:29 -0700 Subject: [PATCH 085/372] tx manager - emit update on new unapproved tx --- app/scripts/transaction-manager.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/scripts/transaction-manager.js b/app/scripts/transaction-manager.js index 690d44808..a70159680 100644 --- a/app/scripts/transaction-manager.js +++ b/app/scripts/transaction-manager.js @@ -79,8 +79,9 @@ module.exports = class TransactionManager extends EventEmitter { fullTxList.splice(index, 1) } fullTxList.push(txMeta) - this._saveTxList(fullTxList) + this.emit('update') + this.once(`${txMeta.id}:signed`, function (txId) { this.removeAllListeners(`${txMeta.id}:rejected`) }) From 067459da4c005ef89cc1ef4103b557e81e8927d7 Mon Sep 17 00:00:00 2001 From: kumavis Date: Tue, 28 Mar 2017 14:39:45 -0700 Subject: [PATCH 086/372] Filter txs by txMeta network value in account detail --- ui/app/accounts/index.js | 2 +- ui/lib/tx-helper.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ui/app/accounts/index.js b/ui/app/accounts/index.js index e236a4e85..ae69d9297 100644 --- a/ui/app/accounts/index.js +++ b/ui/app/accounts/index.js @@ -11,7 +11,7 @@ module.exports = connect(mapStateToProps)(AccountsScreen) function mapStateToProps (state) { const pendingTxs = valuesFor(state.metamask.unapprovedTxs) - .filter(tx => tx.txParams.metamaskNetworkId === state.metamask.network) + .filter(txMeta => txMeta.metamaskNetworkId === state.metamask.network) const pendingMsgs = valuesFor(state.metamask.unapprovedMsgs) const pending = pendingTxs.concat(pendingMsgs) diff --git a/ui/lib/tx-helper.js b/ui/lib/tx-helper.js index 6c549dbe3..ec19daf64 100644 --- a/ui/lib/tx-helper.js +++ b/ui/lib/tx-helper.js @@ -4,7 +4,7 @@ module.exports = function (unapprovedTxs, unapprovedMsgs, personalMsgs, network) log.debug('tx-helper called with params:') log.debug({ unapprovedTxs, unapprovedMsgs, personalMsgs, network }) - const txValues = network ? valuesFor(unapprovedTxs).filter(tx => tx.metamaskNetworkId === network) : valuesFor(unapprovedTxs) + const txValues = network ? valuesFor(unapprovedTxs).filter(txMeta => txMeta.metamaskNetworkId === network) : valuesFor(unapprovedTxs) log.debug(`tx helper found ${txValues.length} unapproved txs`) const msgValues = valuesFor(unapprovedMsgs) log.debug(`tx helper found ${msgValues.length} unsigned messages`) @@ -13,5 +13,5 @@ module.exports = function (unapprovedTxs, unapprovedMsgs, personalMsgs, network) log.debug(`tx helper found ${personalValues.length} unsigned personal messages`) allValues = allValues.concat(personalValues) - return allValues.sort(tx => tx.time) + return allValues.sort(txMeta => txMeta.time) } From a8ef68b1da3597244a3b51ade11c11c08c36f9cf Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Tue, 28 Mar 2017 14:43:24 -0700 Subject: [PATCH 087/372] Remove a note To reduce security concerns. --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 66de9ede5..2ac434739 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,6 @@ ## Current Master -- Remove seedWords from UI state dump. - Inject web3 into loaded iFrames. ## 3.5.1 2017-3-27 From 3b6b06db9276968acf5af35f9e855991bfb5b8ec Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Tue, 28 Mar 2017 15:01:01 -0700 Subject: [PATCH 088/372] Version 2.5.2 --- CHANGELOG.md | 3 +++ app/manifest.json | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f6383fa51..6e27f5c04 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## Current Master +## 3.5.2 2017-3-28 + +- Fix bug where gas estimate totals were sometimes wrong. - Add link to Kovan Test Faucet instructions on buy view. - Inject web3 into loaded iFrames. diff --git a/app/manifest.json b/app/manifest.json index a163d4c06..75e72c295 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -1,7 +1,7 @@ { "name": "MetaMask", "short_name": "Metamask", - "version": "3.5.1", + "version": "3.5.2", "manifest_version": 2, "author": "https://metamask.io", "description": "Ethereum Browser Extension", From 79248ae5cd3fb1314c5a7ff71c05f9dbe7b3a4cd Mon Sep 17 00:00:00 2001 From: frankiebee Date: Tue, 28 Mar 2017 18:02:08 -0700 Subject: [PATCH 089/372] WIP: Untrusted external connections eg: dapps --- library/controller.js | 192 ++++++++++++++++++++-- library/example/index.js | 4 +- library/index.js | 2 + library/lib/setup-provider.js | 1 - library/lib/setup-untrusted-connection.js | 27 +++ library/popup.js | 2 + library/sw-controller.js | 20 +-- library/sw-core.js | 25 +-- package.json | 2 +- 9 files changed, 221 insertions(+), 54 deletions(-) create mode 100644 library/lib/setup-untrusted-connection.js diff --git a/library/controller.js b/library/controller.js index f0aa7172c..cbba9584a 100644 --- a/library/controller.js +++ b/library/controller.js @@ -1,13 +1,181 @@ -// const SWcontroller = require('./sw-controller') -// const SwStream = require('sw-stream/lib/sw-stream.js') -// const startPopup = require('../app/scripts/popup-core') - -// console.log('outside:open') -// const background = new SWcontroller({ -// fileName: 'sw-build.js', -// }) -// background.on('ready', (readSw) => { -// startPopup(SwStream(background.controller)) -// }) -// background.startWorker() +const ParentStream = require('iframe-stream').ParentStream +const SWcontroller = require('./sw-controller') +const SwStream = require('sw-stream/lib/sw-stream.js') +const SetupUntrustedComunication = ('./lib/setup-untrusted-connection.js') +const background = new SWcontroller({ + fileName: '/popup/sw-build.js', +}) + +background.on('ready', (readSw) => { + // var inpageProvider = new MetamaskInpageProvider(SwStream(background.controller)) + let pageStream = new ParentStream() + let swStream = SwStream(background.controller) + pageStream.pipe(swStream).pipe(pageStream) +}) + +background.on('error', console.error) +background.startWorker() + console.log('hello from controller') +/* +const urlUtil = require('url') +const extend = require('xtend') +const Dnode = require('dnode') +const eos = require('end-of-stream') +const ParentStream = require('iframe-stream').ParentStream +const PortStream = require('../app/scripts/lib/port-stream.js') +const notification = require('../app/scripts/lib/notifications.js') +const messageManager = require('../app/scripts/lib/message-manager') +const setupMultiplex = require('../app/scripts/lib/stream-utils.js').setupMultiplex +const MetamaskController = require('../app/scripts/metamask-controller') +const extension = require('../app/scripts/lib/extension') + +const STORAGE_KEY = 'metamask-config' + + +initializeZeroClient() + +function initializeZeroClient() { + + const controller = new MetamaskController({ + // User confirmation callbacks: + showUnconfirmedMessage, + unlockAccountMessage, + showUnapprovedTx, + // Persistence Methods: + setData, + loadData, + }) + const idStore = controller.idStore + + function unlockAccountMessage () { + console.log('notif stub - unlockAccountMessage') + } + + function showUnconfirmedMessage (msgParams, msgId) { + console.log('notif stub - showUnconfirmedMessage') + } + + function showUnapprovedTx (txParams, txData, onTxDoneCb) { + console.log('notif stub - showUnapprovedTx') + } + + // + // connect to other contexts + // + + var connectionStream = new ParentStream() + + connectRemote(connectionStream, getParentHref()) + + function connectRemote (connectionStream, originDomain) { + var isMetaMaskInternalProcess = (originDomain === '127.0.0.1:9001') + if (isMetaMaskInternalProcess) { + // communication with popup + setupTrustedCommunication(connectionStream, 'MetaMask') + } else { + // communication with page + setupUntrustedCommunication(connectionStream, originDomain) + } + } + + function setupUntrustedCommunication (connectionStream, originDomain) { + // setup multiplexing + var mx = setupMultiplex(connectionStream) + // connect features + controller.setupProviderConnection(mx.createStream('provider'), originDomain) + controller.setupPublicConfig(mx.createStream('publicConfig')) + } + + function setupTrustedCommunication (connectionStream, originDomain) { + // setup multiplexing + var mx = setupMultiplex(connectionStream) + // connect features + setupControllerConnection(mx.createStream('controller')) + controller.setupProviderConnection(mx.createStream('provider'), originDomain) + } + + // + // remote features + // + + function setupControllerConnection (stream) { + controller.stream = stream + var api = controller.getApi() + var dnode = Dnode(api) + stream.pipe(dnode).pipe(stream) + dnode.on('remote', (remote) => { + // push updates to popup + controller.ethStore.on('update', controller.sendUpdate.bind(controller)) + controller.listeners.push(remote) + idStore.on('update', controller.sendUpdate.bind(controller)) + + // teardown on disconnect + eos(stream, () => { + controller.ethStore.removeListener('update', controller.sendUpdate.bind(controller)) + }) + }) + } + + function loadData () { + var oldData = getOldStyleData() + var newData + try { + newData = JSON.parse(window.localStorage[STORAGE_KEY]) + } catch (e) {} + + var data = extend({ + meta: { + version: 0, + }, + data: { + config: { + provider: { + type: 'testnet', + }, + }, + }, + }, oldData || null, newData || null) + return data + } + + function getOldStyleData () { + var config, wallet, seedWords + + var result = { + meta: { version: 0 }, + data: {}, + } + + try { + config = JSON.parse(window.localStorage['config']) + result.data.config = config + } catch (e) {} + try { + wallet = JSON.parse(window.localStorage['lightwallet']) + result.data.wallet = wallet + } catch (e) {} + try { + seedWords = window.localStorage['seedWords'] + result.data.seedWords = seedWords + } catch (e) {} + + return result + } + + function setData (data) { + window.localStorage[STORAGE_KEY] = JSON.stringify(data) + } + + function getParentHref(){ + try { + var parentLocation = window.parent.location + return parentLocation.hostname + ':' + parentLocation.port + } catch (err) { + return 'unknown' + } + } + +} + +*/ diff --git a/library/example/index.js b/library/example/index.js index 4a107df6a..329302a4d 100644 --- a/library/example/index.js +++ b/library/example/index.js @@ -1,4 +1,3 @@ - window.addEventListener('load', web3Detect) function web3Detect() { @@ -18,6 +17,7 @@ function startApp(){ web3.eth.getAccounts(function(err, addresses){ if (err) throw err console.log('set address', addresses[0]) + debugger primaryAccount = addresses[0] }) @@ -53,4 +53,4 @@ function startApp(){ function logToDom(message){ document.body.appendChild(document.createTextNode(message)) console.log(message) -} \ No newline at end of file +} diff --git a/library/index.js b/library/index.js index af82c6546..44ee401d8 100644 --- a/library/index.js +++ b/library/index.js @@ -41,3 +41,5 @@ function hijackProvider(provider){ _super(payload, cb) } } + + diff --git a/library/lib/setup-provider.js b/library/lib/setup-provider.js index 9efd209cb..68be99c9e 100644 --- a/library/lib/setup-provider.js +++ b/library/lib/setup-provider.js @@ -22,4 +22,3 @@ function getProvider(){ return inpageProvider } - diff --git a/library/lib/setup-untrusted-connection.js b/library/lib/setup-untrusted-connection.js new file mode 100644 index 000000000..b2aeb7905 --- /dev/null +++ b/library/lib/setup-untrusted-connection.js @@ -0,0 +1,27 @@ + +/* +IFRAME + var pageStream = new LocalMessageDuplexStream({ + name: 'contentscript', + target: 'inpage', + }) +SERVICEWORKER + pageStream.on('error', console.error) + var pluginPort = extension.runtime.connect({name: 'contentscript'}) + var pluginStream = new PortStream(pluginPort) + pluginStream.on('error', console.error) +IFRAME --> SW + // forward communication plugin->inpage + pageStream.pipe(pluginStream).pipe(pageStream) +*/ + +module.exports = SetupUntrustedComunicationWithSW + +function SetupUntrustedComunicationWithSW (connectionStream, readySwStream) { + pageStream.on('error', console.error) + var pluginPort = extension.runtime.connect({name: 'contentscript'}) + var pluginStream = new PortStream(pluginPort) + pluginStream.on('error', console.error) + // forward communication plugin->inpage + pageStream.pipe(pluginStream).pipe(pageStream) +} diff --git a/library/popup.js b/library/popup.js index 3825661cf..d956dc0b1 100644 --- a/library/popup.js +++ b/library/popup.js @@ -19,6 +19,7 @@ var iframeStream = setupIframe({ container: document.body, }) console.log('outside:open') + const background = new SWcontroller({ fileName: '/popup/sw-build.js', }) @@ -27,5 +28,6 @@ background.on('ready', (readSw) => { // startPopup(inpageProvider) startPopup(SwStream(background.controller)) }) +background.on('message', (messageEvent) => {debugger}) background.startWorker() console.log('hello from /library/popup.js') diff --git a/library/sw-controller.js b/library/sw-controller.js index 23d67026e..1a7b1cad3 100644 --- a/library/sw-controller.js +++ b/library/sw-controller.js @@ -7,22 +7,10 @@ module.exports = class ClientSideServiceWorker extends EventEmitter{ this.startDelay = opts.startDelay this.serviceWorkerApi = navigator.serviceWorker - this.serviceWorkerApi.onmessage = (event) => this.emit('message', event) - this.serviceWorkerApi.onerror = (event) => this.emit('error') - - // temporary function - this.askForId = (message) => { - this.sendMessage('check-in') - .then((data) => console.log(`${message}----${data}`)) - } - - // if (!!this.serviceWorkerApi) this.askForId('before') - + this.serviceWorkerApi.onmessage = (messageEvent) => this.emit('message', messageEvent) + this.serviceWorkerApi.onerror = (err) => this.emit('error', err) + this.on('message', (messageEvent) => {debugger}) if (opts.initStart) this.startWorker() - - this.on('ready', (sw) => { - this.askForId('ready') - }) } get controller () { @@ -34,7 +22,6 @@ module.exports = class ClientSideServiceWorker extends EventEmitter{ return this.registerWorker() .then((sw) => { this.sw = sw - this.askForId('after register:') this.sw.onerror = (err) => this.emit('error', err) this.sw = sw this.emit('ready', this.sw) @@ -46,7 +33,6 @@ module.exports = class ClientSideServiceWorker extends EventEmitter{ return this.serviceWorkerApi.register(this.fileName) .then((registerdWorker) => { return new Promise((resolve, reject) => { - this.askForId('after') let timeOutId = setTimeout(() => { if (this.serviceWorkerApi.controller) return resolve(this.serviceWorkerApi.controller) if (registerdWorker.active) return resolve(registerdWorker.active) diff --git a/library/sw-core.js b/library/sw-core.js index 46ef95e21..1d31b2acd 100644 --- a/library/sw-core.js +++ b/library/sw-core.js @@ -1,7 +1,4 @@ global.window = global -const SWGlobal = self -const urlUtil = require('url') -const endOfStream = require('end-of-stream') const asyncQ = require('async-q') const pipe = require('pump') @@ -14,7 +11,7 @@ const PortStream = require('../app/scripts/lib/port-stream.js') const DbController = require('./controllers/index-db-controller') const MetamaskController = require('../app/scripts/metamask-controller') -// const extension = require('../app/scripts/lib/extension') +const extension = {} //require('../app/scripts/lib/extension') // const LocalStorageStore = require('obs-store/lib/localStorage') const storeTransform = require('obs-store/lib/transform') const Migrator = require('../app/scripts/lib/migrator/') @@ -36,20 +33,6 @@ self.addEventListener('activate', function(event) { event.waitUntil(self.clients.claim()) }) -self.onsync = function (syncEvent) { -// What is done when a sync even is fired - console.log('inside:sync') - var focused - self.clients.matchAll() - .then(clients => { - clients.forEach(function(client) { - - }) - }) -} - - - console.log('inside:open') @@ -117,8 +100,6 @@ function setupController (initState, client) { .catch((err) => {console.error(err)}) }) function versionifyData(state) { - // let versionedData - // versionedData.data = state return dbController.get() .then((rawData) => { return Promise.resolve({ @@ -143,6 +124,7 @@ function setupController (initState, client) { if (isMetaMaskInternalProcess) { // communication with popup controller.setupTrustedCommunication(connectionStream, 'MetaMask') + popupIsOpen = true } else { // communication with page setupUntrustedCommunication(connectionStream, originDomain) @@ -175,6 +157,7 @@ function setupController (initState, client) { // // // // // // popup trigger + +/*send a message to the client that has focus and tell it to open a window*/ function triggerUi () { - if (!popupIsOpen) notification.show() } diff --git a/package.json b/package.json index c08d92339..cc51ef032 100644 --- a/package.json +++ b/package.json @@ -110,7 +110,7 @@ "valid-url": "^1.0.9", "vreme": "^3.0.2", "web3": "0.18.2", - "web3-provider-engine": "^11.0.0", + "web3-provider-engine": "^11.0.1", "web3-stream-provider": "^2.0.6", "xtend": "^4.0.1" }, From 576fb26c1558e7e639f28bed07da72d662a5039e Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Wed, 29 Mar 2017 11:08:15 -0400 Subject: [PATCH 090/372] Add missing migration. --- app/scripts/migrations/index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/app/scripts/migrations/index.js b/app/scripts/migrations/index.js index a3dd48c17..019b4d13d 100644 --- a/app/scripts/migrations/index.js +++ b/app/scripts/migrations/index.js @@ -22,4 +22,5 @@ module.exports = [ require('./009'), require('./010'), require('./011'), + require('./012'), ] From cb34eda6c6e320297c8616248e7e57a6926a24a6 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Wed, 29 Mar 2017 11:51:04 -0400 Subject: [PATCH 091/372] Fix potential formatting issues for seed word display. --- ui/app/css/index.css | 1 - ui/app/keychains/hd/create-vault-complete.js | 11 ++++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/ui/app/css/index.css b/ui/app/css/index.css index de8ae0e92..033502f5a 100644 --- a/ui/app/css/index.css +++ b/ui/app/css/index.css @@ -148,7 +148,6 @@ h2.page-subtitle { } textarea.twelve-word-phrase { - margin-top: 20px; padding: 12px; width: 300px; height: 140px; diff --git a/ui/app/keychains/hd/create-vault-complete.js b/ui/app/keychains/hd/create-vault-complete.js index 7272ebdbd..5230797ad 100644 --- a/ui/app/keychains/hd/create-vault-complete.js +++ b/ui/app/keychains/hd/create-vault-complete.js @@ -45,12 +45,17 @@ CreateVaultCompleteScreen.prototype.render = function () { 'Vault Created', ]), - h('span.error', { // Error for the right red + h('div', { style: { - padding: '12px 20px 0px 20px', + width: '360px', + height: '78px', + fontSize: '1em', + marginTop: '10px', textAlign: 'center', }, - }, 'These 12 words can restore all of your MetaMask accounts for this vault.\nSave them somewhere safe and secret.'), + }, [ + h('span.error', 'These 12 words can restore all of your MetaMask accounts for this vault.\nSave them somewhere safe and secret.'), + ]), h('textarea.twelve-word-phrase', { readOnly: true, From 610ec2bdf5841c8275073992e67c59fb47faa48c Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Wed, 29 Mar 2017 10:40:57 -0700 Subject: [PATCH 092/372] Fix popup behavior for Firefox Firefox does not support the `focused` parameter when opening a new window, and we don't actually require it for Chrome either, new popups are at the foreground by default already. --- app/scripts/lib/extension.js | 5 ++++- app/scripts/lib/notifications.js | 4 +++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/app/scripts/lib/extension.js b/app/scripts/lib/extension.js index 4b670490f..6f8b5d800 100644 --- a/app/scripts/lib/extension.js +++ b/app/scripts/lib/extension.js @@ -11,4 +11,7 @@ */ const Extension = require('./extension-instance') -module.exports = new Extension() +const instance = new Extension() +window.METAMASK_EXTENSION = instance +module.exports = instance + diff --git a/app/scripts/lib/notifications.js b/app/scripts/lib/notifications.js index 3db1ac6b5..33914846c 100644 --- a/app/scripts/lib/notifications.js +++ b/app/scripts/lib/notifications.js @@ -22,10 +22,12 @@ function show () { extension.windows.create({ url: 'notification.html', type: 'popup', - focused: true, width, height, }) + .catch((reason) => { + log.error("failed to create poupup", reason) + }) } }) } From daf22168b63d70669cf0bce17a17e0648aca584f Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Wed, 29 Mar 2017 10:42:02 -0700 Subject: [PATCH 093/372] Bump changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e27f5c04..0a54cf886 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Current Master +- Popup new transactions in Firefox. + ## 3.5.2 2017-3-28 - Fix bug where gas estimate totals were sometimes wrong. From 98dd684524fde7548442830fe01e1f3e24a6e67d Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Wed, 29 Mar 2017 10:42:43 -0700 Subject: [PATCH 094/372] Linted --- app/scripts/lib/notifications.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/scripts/lib/notifications.js b/app/scripts/lib/notifications.js index 33914846c..0ec01f3a7 100644 --- a/app/scripts/lib/notifications.js +++ b/app/scripts/lib/notifications.js @@ -26,7 +26,7 @@ function show () { height, }) .catch((reason) => { - log.error("failed to create poupup", reason) + log.error('failed to create poupup', reason) }) } }) From 84b3415b4479eb849b69d77a7e178c67fcaaf3fb Mon Sep 17 00:00:00 2001 From: frankiebee Date: Wed, 29 Mar 2017 10:53:43 -0700 Subject: [PATCH 095/372] WIP: lost enital call from dapp to getAccounts --- app/scripts/controllers/preferences.js | 1 + app/scripts/metamask-controller.js | 1 + library/controller.js | 12 ++---------- library/example/index.js | 9 +++++---- library/index.js | 5 ++--- library/lib/setup-provider.js | 4 ++-- library/popup.js | 5 ----- library/sw-core.js | 1 + 8 files changed, 14 insertions(+), 24 deletions(-) diff --git a/app/scripts/controllers/preferences.js b/app/scripts/controllers/preferences.js index c7f675a41..dbd5aeaaa 100644 --- a/app/scripts/controllers/preferences.js +++ b/app/scripts/controllers/preferences.js @@ -24,6 +24,7 @@ class PreferencesController { } getSelectedAddress (_address) { + console.log('PREFERNCES: getSelectedAddress was called') return this.store.getState().selectedAddress } diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 0393caeab..db80d5f1b 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -165,6 +165,7 @@ module.exports = class MetamaskController extends EventEmitter { rpcUrl: this.configManager.getCurrentRpcAddress(), // account mgmt getAccounts: (cb) => { + console.log('METAMASK CONTROLLER: getAccounts was called') let selectedAddress = this.preferencesController.getSelectedAddress() let result = selectedAddress ? [selectedAddress] : [] cb(null, result) diff --git a/library/controller.js b/library/controller.js index cbba9584a..c3f2fa5fa 100644 --- a/library/controller.js +++ b/library/controller.js @@ -6,17 +6,17 @@ const background = new SWcontroller({ fileName: '/popup/sw-build.js', }) -background.on('ready', (readSw) => { +background.on('ready', (_) => { // var inpageProvider = new MetamaskInpageProvider(SwStream(background.controller)) let pageStream = new ParentStream() let swStream = SwStream(background.controller) pageStream.pipe(swStream).pipe(pageStream) + console.log('********************WOOP*********************') }) background.on('error', console.error) background.startWorker() -console.log('hello from controller') /* const urlUtil = require('url') const extend = require('xtend') @@ -167,14 +167,6 @@ function initializeZeroClient() { window.localStorage[STORAGE_KEY] = JSON.stringify(data) } - function getParentHref(){ - try { - var parentLocation = window.parent.location - return parentLocation.hostname + ':' + parentLocation.port - } catch (err) { - return 'unknown' - } - } } diff --git a/library/example/index.js b/library/example/index.js index 329302a4d..aae7ccd19 100644 --- a/library/example/index.js +++ b/library/example/index.js @@ -1,4 +1,5 @@ window.addEventListener('load', web3Detect) +window.addEventListener('message', console.warn) function web3Detect() { if (global.web3) { @@ -12,18 +13,18 @@ function web3Detect() { function startApp(){ console.log('app started') - var primaryAccount = null + var primaryAccount console.log('getting main account...') - web3.eth.getAccounts(function(err, addresses){ - if (err) throw err + web3.eth.getAccounts((err, addresses) => { + if (err) console.error(err) console.log('set address', addresses[0]) - debugger primaryAccount = addresses[0] }) document.querySelector('.action-button-1').addEventListener('click', function(){ console.log('saw click') console.log('sending tx') + primaryAccount web3.eth.sendTransaction({ from: primaryAccount, to: primaryAccount, diff --git a/library/index.js b/library/index.js index 44ee401d8..3503d15cc 100644 --- a/library/index.js +++ b/library/index.js @@ -5,13 +5,12 @@ const setupProvider = require('./lib/setup-provider.js') // setup web3 // var provider = setupProvider() -hijackProvider(provider) +// hijackProvider(provider) var web3 = new Web3(provider) web3.setProvider = function(){ console.log('MetaMask - overrode web3.setProvider') } -console.log('metamask lib hijacked provider') - +// // // export web3 // diff --git a/library/lib/setup-provider.js b/library/lib/setup-provider.js index 68be99c9e..a99fc1c68 100644 --- a/library/lib/setup-provider.js +++ b/library/lib/setup-provider.js @@ -10,15 +10,15 @@ function getProvider(){ console.log('MetaMask ZeroClient - using environmental web3 provider') return global.web3.currentProvider } - console.log('MetaMask ZeroClient - injecting zero-client iframe!') var iframeStream = setupIframe({ - zeroClientProvider: 'http://127.0.0.1:9001', + zeroClientProvider: 'http://localhost:9001', sandboxAttributes: ['allow-scripts', 'allow-popups', 'allow-same-origin'], container: document.body, }) var inpageProvider = new MetamaskInpageProvider(iframeStream) + console.log('ABOUT TO RETURN INPAGE') return inpageProvider } diff --git a/library/popup.js b/library/popup.js index d956dc0b1..bb7051055 100644 --- a/library/popup.js +++ b/library/popup.js @@ -13,11 +13,6 @@ injectCss(css) var name = 'popup' window.METAMASK_UI_TYPE = name -var iframeStream = setupIframe({ - zeroClientProvider: 'http://localhost:9001', - sandboxAttributes: ['allow-scripts', 'allow-popups', 'allow-same-origin'], - container: document.body, -}) console.log('outside:open') const background = new SWcontroller({ diff --git a/library/sw-core.js b/library/sw-core.js index 1d31b2acd..4d95898d9 100644 --- a/library/sw-core.js +++ b/library/sw-core.js @@ -116,6 +116,7 @@ function setupController (initState, client) { need to write a service worker stream for this */ connectionListener.on('remote', (portStream, messageEvent) => { + console.log('REMOTE CONECTION FOUND***********') connectRemote(portStream, messageEvent.origin) }) From 2b1a9c5ae880afe1a5bf2d11eefbbdf26dc6e27c Mon Sep 17 00:00:00 2001 From: frankiebee Date: Wed, 29 Mar 2017 11:02:50 -0700 Subject: [PATCH 096/372] Create a Transaction from within the dapp --- library/controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/controller.js b/library/controller.js index c3f2fa5fa..126e972c8 100644 --- a/library/controller.js +++ b/library/controller.js @@ -6,9 +6,9 @@ const background = new SWcontroller({ fileName: '/popup/sw-build.js', }) +const pageStream = new ParentStream() background.on('ready', (_) => { // var inpageProvider = new MetamaskInpageProvider(SwStream(background.controller)) - let pageStream = new ParentStream() let swStream = SwStream(background.controller) pageStream.pipe(swStream).pipe(pageStream) console.log('********************WOOP*********************') From f0a2b4d1b0ad0b87e8101850790d4ee30fa6116f Mon Sep 17 00:00:00 2001 From: kumavis Date: Wed, 29 Mar 2017 12:36:23 -0700 Subject: [PATCH 097/372] deps - bump provider-engine uses fetch instead of xhr, fixes a race condition in cache subp --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 488e7e90d..9e04bc6f7 100644 --- a/package.json +++ b/package.json @@ -109,7 +109,7 @@ "valid-url": "^1.0.9", "vreme": "^3.0.2", "web3": "0.18.2", - "web3-provider-engine": "^10.0.1", + "web3-provider-engine": "^11.0.2", "web3-stream-provider": "^2.0.6", "xtend": "^4.0.1" }, From 8ade8bdf32933ea9449d06aa6a1fc71c10b461c4 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Wed, 29 Mar 2017 13:35:51 -0700 Subject: [PATCH 098/372] Fix currency checking for firefox By adding cryptonator to our permitted CORS hosts. Fixes #1285. Will probably trigger additional permissions requests to our users. --- app/manifest.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/manifest.json b/app/manifest.json index 75e72c295..a3242149b 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -57,7 +57,8 @@ "permissions": [ "storage", "clipboardWrite", - "http://localhost:8545/" + "http://localhost:8545/", + "https://www.cryptonator.com/" ], "web_accessible_resources": [ "scripts/inpage.js" From 4cf3beda25e7883aac37ee188d3bb102dea4afa3 Mon Sep 17 00:00:00 2001 From: frankiebee Date: Wed, 29 Mar 2017 15:19:46 -0700 Subject: [PATCH 099/372] Bump sw-stream and setup "untrusted and trusted comunication" --- library/controller.js | 5 ++++- library/popup.js | 5 ++++- library/sw-core.js | 6 +++--- package.json | 2 +- 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/library/controller.js b/library/controller.js index 126e972c8..d4a76d316 100644 --- a/library/controller.js +++ b/library/controller.js @@ -9,7 +9,10 @@ const background = new SWcontroller({ const pageStream = new ParentStream() background.on('ready', (_) => { // var inpageProvider = new MetamaskInpageProvider(SwStream(background.controller)) - let swStream = SwStream(background.controller) + let swStream = SwStream({ + serviceWorker: background.controller, + context: 'dapp', + }) pageStream.pipe(swStream).pipe(pageStream) console.log('********************WOOP*********************') }) diff --git a/library/popup.js b/library/popup.js index bb7051055..a9d934c43 100644 --- a/library/popup.js +++ b/library/popup.js @@ -21,7 +21,10 @@ const background = new SWcontroller({ background.on('ready', (readSw) => { // var inpageProvider = new MetamaskInpageProvider(SwStream(background.controller)) // startPopup(inpageProvider) - startPopup(SwStream(background.controller)) + swStream = SwStream({ + serviceWorker: background.controller, + }) + startPopup() }) background.on('message', (messageEvent) => {debugger}) background.startWorker() diff --git a/library/sw-core.js b/library/sw-core.js index 4d95898d9..9f399cccf 100644 --- a/library/sw-core.js +++ b/library/sw-core.js @@ -117,11 +117,11 @@ function setupController (initState, client) { */ connectionListener.on('remote', (portStream, messageEvent) => { console.log('REMOTE CONECTION FOUND***********') - connectRemote(portStream, messageEvent.origin) + connectRemote(portStream, messageEvent.data.context) }) - function connectRemote (connectionStream, originDomain) { - var isMetaMaskInternalProcess = (originDomain === 'http://localhost:9001') + function connectRemote (connectionStream, context) { + var isMetaMaskInternalProcess = (context !== 'dapp') if (isMetaMaskInternalProcess) { // communication with popup controller.setupTrustedCommunication(connectionStream, 'MetaMask') diff --git a/package.json b/package.json index cc51ef032..d6c80496a 100644 --- a/package.json +++ b/package.json @@ -103,7 +103,7 @@ "request-promise": "^4.1.1", "sandwich-expando": "^1.0.5", "semaphore": "^1.0.5", - "sw-stream": "^1.0.2", + "sw-stream": "^2.0.0", "textarea-caret": "^3.0.1", "three.js": "^0.73.2", "through2": "^2.0.1", From 19302778ee0dd0535867cde29d4a5ce56d573dec Mon Sep 17 00:00:00 2001 From: frankiebee Date: Wed, 29 Mar 2017 16:19:49 -0700 Subject: [PATCH 100/372] fix stream --- library/popup.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/popup.js b/library/popup.js index a9d934c43..0c297d9a2 100644 --- a/library/popup.js +++ b/library/popup.js @@ -21,7 +21,7 @@ const background = new SWcontroller({ background.on('ready', (readSw) => { // var inpageProvider = new MetamaskInpageProvider(SwStream(background.controller)) // startPopup(inpageProvider) - swStream = SwStream({ + let swStream = SwStream({ serviceWorker: background.controller, }) startPopup() From ff49e5e5cf3aadf63dbdf6570407c23e1ae6cdb9 Mon Sep 17 00:00:00 2001 From: kumavis Date: Wed, 29 Mar 2017 23:21:31 -0700 Subject: [PATCH 101/372] tx-utils - gas buffer ceiling at 90% of block gas limit --- app/scripts/lib/tx-utils.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/scripts/lib/tx-utils.js b/app/scripts/lib/tx-utils.js index 72df53631..e8e23f8b5 100644 --- a/app/scripts/lib/tx-utils.js +++ b/app/scripts/lib/tx-utils.js @@ -63,14 +63,15 @@ module.exports = class txProviderUtils { addGasBuffer (initialGasLimitHex, blockGasLimitHex) { const initialGasLimitBn = hexToBn(initialGasLimitHex) const blockGasLimitBn = hexToBn(blockGasLimitHex) + const upperGasLimitBn = blockGasLimitBn.muln(0.9) const bufferedGasLimitBn = initialGasLimitBn.muln(1.5) // if initialGasLimit is above blockGasLimit, dont modify it - if (initialGasLimitBn.gt(blockGasLimitBn)) return bnToHex(initialGasLimitBn) + if (initialGasLimitBn.gt(upperGasLimitBn)) return bnToHex(initialGasLimitBn) // if bufferedGasLimit is below blockGasLimit, use bufferedGasLimit - if (bufferedGasLimitBn.lt(blockGasLimitBn)) return bnToHex(bufferedGasLimitBn) + if (bufferedGasLimitBn.lt(upperGasLimitBn)) return bnToHex(bufferedGasLimitBn) // otherwise use blockGasLimit - return bnToHex(blockGasLimitBn) + return bnToHex(upperGasLimitBn) } fillInTxParams (txParams, cb) { From 16b5f4a210cd496b9160efbfa5e2cc44ae0c8f5c Mon Sep 17 00:00:00 2001 From: kumavis Date: Wed, 29 Mar 2017 23:59:42 -0700 Subject: [PATCH 102/372] tests - tx-utils gasBuffer calc - fix bug and user easier numbers --- test/unit/tx-utils-test.js | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/test/unit/tx-utils-test.js b/test/unit/tx-utils-test.js index e57b25e83..8b4d2f059 100644 --- a/test/unit/tx-utils-test.js +++ b/test/unit/tx-utils-test.js @@ -14,8 +14,8 @@ describe('txUtils', function() { describe('addGasBuffer', function() { it('multiplies by 1.5, when within block gas limit', function() { - // naive estimatedGas: 0x123fad (~1.2 mil) - const inputHex = '0x123fad' + // naive estimatedGas: 0x16e360 (1.5 mil) + const inputHex = '0x16e360' // dummy gas limit: 0x3d4c52 (4 mil) const blockGasLimitHex = '0x3d4c52' const output = txUtils.addGasBuffer(inputHex, blockGasLimitHex) @@ -26,8 +26,8 @@ describe('txUtils', function() { }) it('uses original estimatedGas, when above block gas limit', function() { - // naive estimatedGas: 0x123fad (~1.2 mil) - const inputHex = '0x123fad' + // naive estimatedGas: 0x16e360 (1.5 mil) + const inputHex = '0x16e360' // dummy gas limit: 0x0f4240 (1 mil) const blockGasLimitHex = '0x0f4240' const output = txUtils.addGasBuffer(inputHex, blockGasLimitHex) @@ -37,16 +37,18 @@ describe('txUtils', function() { assert(outputBn.eq(expectedBn), 'returns the original estimatedGas value') }) - it('buffers up to block gas limit', function() { - // naive estimatedGas: 0x123fad (~1.2 mil) - const inputHex = '0x1e8480' + it('buffers up to reccomend gas limit reccomended ceiling', function() { + // naive estimatedGas: 0x16e360 (1.5 mil) + const inputHex = '0x16e360' // dummy gas limit: 0x1e8480 (2 mil) const blockGasLimitHex = '0x1e8480' + const blockGasLimitBn = hexToBn(blockGasLimitHex) + const ceilGasLimitBn = blockGasLimitBn.muln(0.9) const output = txUtils.addGasBuffer(inputHex, blockGasLimitHex) - const inputBn = hexToBn(inputHex) - const outputBn = hexToBn(output) - const expectedBn = hexToBn(blockGasLimitHex) - assert(outputBn.eq(expectedBn), 'returns the block gas limit value') + // const inputBn = hexToBn(inputHex) + // const outputBn = hexToBn(output) + const expectedHex = bnToHex(ceilGasLimitBn) + assert.equal(output, expectedHex, 'returns the gas limit reccomended ceiling value') }) }) }) @@ -55,4 +57,8 @@ describe('txUtils', function() { function hexToBn(inputHex) { return new BN(ethUtil.stripHexPrefix(inputHex), 16) +} + +function bnToHex(inputBn) { + return ethUtil.addHexPrefix(inputBn.toString(16)) } \ No newline at end of file From 29a602a89b176e7af3f15297c2f586a4ece0a726 Mon Sep 17 00:00:00 2001 From: frankiebee Date: Thu, 30 Mar 2017 11:28:22 -0700 Subject: [PATCH 103/372] Clean up --- app/scripts/controllers/preferences.js | 1 - library/controller.js | 157 +------------------------ library/index.js | 2 +- library/lib/setup-provider.js | 2 - library/popup.js | 8 +- library/sw-core.js | 22 +--- 6 files changed, 11 insertions(+), 181 deletions(-) diff --git a/app/scripts/controllers/preferences.js b/app/scripts/controllers/preferences.js index dbd5aeaaa..c7f675a41 100644 --- a/app/scripts/controllers/preferences.js +++ b/app/scripts/controllers/preferences.js @@ -24,7 +24,6 @@ class PreferencesController { } getSelectedAddress (_address) { - console.log('PREFERNCES: getSelectedAddress was called') return this.store.getState().selectedAddress } diff --git a/library/controller.js b/library/controller.js index d4a76d316..1e9bc84d2 100644 --- a/library/controller.js +++ b/library/controller.js @@ -14,163 +14,8 @@ background.on('ready', (_) => { context: 'dapp', }) pageStream.pipe(swStream).pipe(pageStream) - console.log('********************WOOP*********************') + }) background.on('error', console.error) background.startWorker() - -/* -const urlUtil = require('url') -const extend = require('xtend') -const Dnode = require('dnode') -const eos = require('end-of-stream') -const ParentStream = require('iframe-stream').ParentStream -const PortStream = require('../app/scripts/lib/port-stream.js') -const notification = require('../app/scripts/lib/notifications.js') -const messageManager = require('../app/scripts/lib/message-manager') -const setupMultiplex = require('../app/scripts/lib/stream-utils.js').setupMultiplex -const MetamaskController = require('../app/scripts/metamask-controller') -const extension = require('../app/scripts/lib/extension') - -const STORAGE_KEY = 'metamask-config' - - -initializeZeroClient() - -function initializeZeroClient() { - - const controller = new MetamaskController({ - // User confirmation callbacks: - showUnconfirmedMessage, - unlockAccountMessage, - showUnapprovedTx, - // Persistence Methods: - setData, - loadData, - }) - const idStore = controller.idStore - - function unlockAccountMessage () { - console.log('notif stub - unlockAccountMessage') - } - - function showUnconfirmedMessage (msgParams, msgId) { - console.log('notif stub - showUnconfirmedMessage') - } - - function showUnapprovedTx (txParams, txData, onTxDoneCb) { - console.log('notif stub - showUnapprovedTx') - } - - // - // connect to other contexts - // - - var connectionStream = new ParentStream() - - connectRemote(connectionStream, getParentHref()) - - function connectRemote (connectionStream, originDomain) { - var isMetaMaskInternalProcess = (originDomain === '127.0.0.1:9001') - if (isMetaMaskInternalProcess) { - // communication with popup - setupTrustedCommunication(connectionStream, 'MetaMask') - } else { - // communication with page - setupUntrustedCommunication(connectionStream, originDomain) - } - } - - function setupUntrustedCommunication (connectionStream, originDomain) { - // setup multiplexing - var mx = setupMultiplex(connectionStream) - // connect features - controller.setupProviderConnection(mx.createStream('provider'), originDomain) - controller.setupPublicConfig(mx.createStream('publicConfig')) - } - - function setupTrustedCommunication (connectionStream, originDomain) { - // setup multiplexing - var mx = setupMultiplex(connectionStream) - // connect features - setupControllerConnection(mx.createStream('controller')) - controller.setupProviderConnection(mx.createStream('provider'), originDomain) - } - - // - // remote features - // - - function setupControllerConnection (stream) { - controller.stream = stream - var api = controller.getApi() - var dnode = Dnode(api) - stream.pipe(dnode).pipe(stream) - dnode.on('remote', (remote) => { - // push updates to popup - controller.ethStore.on('update', controller.sendUpdate.bind(controller)) - controller.listeners.push(remote) - idStore.on('update', controller.sendUpdate.bind(controller)) - - // teardown on disconnect - eos(stream, () => { - controller.ethStore.removeListener('update', controller.sendUpdate.bind(controller)) - }) - }) - } - - function loadData () { - var oldData = getOldStyleData() - var newData - try { - newData = JSON.parse(window.localStorage[STORAGE_KEY]) - } catch (e) {} - - var data = extend({ - meta: { - version: 0, - }, - data: { - config: { - provider: { - type: 'testnet', - }, - }, - }, - }, oldData || null, newData || null) - return data - } - - function getOldStyleData () { - var config, wallet, seedWords - - var result = { - meta: { version: 0 }, - data: {}, - } - - try { - config = JSON.parse(window.localStorage['config']) - result.data.config = config - } catch (e) {} - try { - wallet = JSON.parse(window.localStorage['lightwallet']) - result.data.wallet = wallet - } catch (e) {} - try { - seedWords = window.localStorage['seedWords'] - result.data.seedWords = seedWords - } catch (e) {} - - return result - } - - function setData (data) { - window.localStorage[STORAGE_KEY] = JSON.stringify(data) - } - - -} - -*/ diff --git a/library/index.js b/library/index.js index 3503d15cc..759353c1b 100644 --- a/library/index.js +++ b/library/index.js @@ -5,7 +5,7 @@ const setupProvider = require('./lib/setup-provider.js') // setup web3 // var provider = setupProvider() -// hijackProvider(provider) +hijackProvider(provider) var web3 = new Web3(provider) web3.setProvider = function(){ console.log('MetaMask - overrode web3.setProvider') diff --git a/library/lib/setup-provider.js b/library/lib/setup-provider.js index a99fc1c68..1b53e7f54 100644 --- a/library/lib/setup-provider.js +++ b/library/lib/setup-provider.js @@ -5,7 +5,6 @@ module.exports = getProvider function getProvider(){ - if (global.web3) { console.log('MetaMask ZeroClient - using environmental web3 provider') return global.web3.currentProvider @@ -18,7 +17,6 @@ function getProvider(){ }) var inpageProvider = new MetamaskInpageProvider(iframeStream) - console.log('ABOUT TO RETURN INPAGE') return inpageProvider } diff --git a/library/popup.js b/library/popup.js index 0c297d9a2..f294e5c69 100644 --- a/library/popup.js +++ b/library/popup.js @@ -19,13 +19,11 @@ const background = new SWcontroller({ fileName: '/popup/sw-build.js', }) background.on('ready', (readSw) => { - // var inpageProvider = new MetamaskInpageProvider(SwStream(background.controller)) - // startPopup(inpageProvider) let swStream = SwStream({ serviceWorker: background.controller, - }) - startPopup() + }) + startPopup(swStream) }) -background.on('message', (messageEvent) => {debugger}) + background.startWorker() console.log('hello from /library/popup.js') diff --git a/library/sw-core.js b/library/sw-core.js index 9f399cccf..d39bfdbfb 100644 --- a/library/sw-core.js +++ b/library/sw-core.js @@ -6,13 +6,12 @@ const SwGlobalListener = require('sw-stream/lib/sw-global-listener.js') const connectionListener = new SwGlobalListener(self) const setupMultiplex = require('../app/scripts/lib/stream-utils.js').setupMultiplex const PortStream = require('../app/scripts/lib/port-stream.js') -// const notification = require('../app/scripts/lib/notifications.js') const DbController = require('./controllers/index-db-controller') const MetamaskController = require('../app/scripts/metamask-controller') const extension = {} //require('../app/scripts/lib/extension') -// const LocalStorageStore = require('obs-store/lib/localStorage') + const storeTransform = require('obs-store/lib/transform') const Migrator = require('../app/scripts/lib/migrator/') const migrations = require('../app/scripts/migrations/') @@ -80,9 +79,9 @@ function setupController (initState, client) { const controller = new MetamaskController({ // User confirmation callbacks: - showUnconfirmedMessage: triggerUi, - unlockAccountMessage: triggerUi, - showUnapprovedTx: triggerUi, + showUnconfirmedMessage: noop, + unlockAccountMessage: noop, + showUnapprovedTx: noop, // initial state initState, }) @@ -128,7 +127,7 @@ function setupController (initState, client) { popupIsOpen = true } else { // communication with page - setupUntrustedCommunication(connectionStream, originDomain) + setupUntrustedCommunication(connectionStream, context) } } @@ -152,13 +151,4 @@ function setupController (initState, client) { return Promise.resolve() } - -// // // -// // // Etc... -// // // - -// // // popup trigger - -/*send a message to the client that has focus and tell it to open a window*/ -function triggerUi () { -} +function noop () {} From 1dce352523a1f79f052ef79c707d9bc44787b706 Mon Sep 17 00:00:00 2001 From: kumavis Date: Thu, 30 Mar 2017 14:23:23 -0700 Subject: [PATCH 104/372] tx-manager - add eip155 support --- app/scripts/transaction-manager.js | 20 +++++-- test/unit/tx-manager-test.js | 89 +++++++++++++++++++----------- test/unit/tx-utils-test.js | 17 ++++++ 3 files changed, 91 insertions(+), 35 deletions(-) diff --git a/app/scripts/transaction-manager.js b/app/scripts/transaction-manager.js index a70159680..d7051b2cb 100644 --- a/app/scripts/transaction-manager.js +++ b/app/scripts/transaction-manager.js @@ -205,11 +205,23 @@ module.exports = class TransactionManager extends EventEmitter { }) } + getChainId() { + const networkState = this.networkStore.getState() + const getChainId = parseInt(networkState.network) + if (Number.isNaN(getChainId)) { + return 0 + } else { + return getChainId + } + } + signTransaction (txId, cb) { - let txMeta = this.getTx(txId) - let txParams = txMeta.txParams - let fromAddress = txParams.from - let ethTx = this.txProviderUtils.buildEthTxFromParams(txParams) + const txMeta = this.getTx(txId) + const txParams = txMeta.txParams + const fromAddress = txParams.from + // add network/chain id + txParams.chainId = this.getChainId() + const ethTx = this.txProviderUtils.buildEthTxFromParams(txParams) this.signEthTx(ethTx, fromAddress).then(() => { this.setTxStatusSigned(txMeta.id) cb(null, ethUtil.bufferToHex(ethTx.serialize())) diff --git a/test/unit/tx-manager-test.js b/test/unit/tx-manager-test.js index d4bffdd9b..20f89a226 100644 --- a/test/unit/tx-manager-test.js +++ b/test/unit/tx-manager-test.js @@ -1,19 +1,30 @@ -const assert = require('assert') +// const assert = require('assert') +const assert = require('chai').assert const extend = require('xtend') const EventEmitter = require('events') +const ethUtil = require('ethereumjs-util') +const EthTx = require('ethereumjs-tx') const ObservableStore = require('obs-store') const STORAGE_KEY = 'metamask-persistance-key' const TransactionManager = require('../../app/scripts/transaction-manager') const noop = () => true +const currentNetworkId = 42 +const otherNetworkId = 36 +const privKey = new Buffer('8718b9618a37d1fc78c436511fc6df3c8258d3250635bba617f33003270ec03e', 'hex') describe('Transaction Manager', function() { let txManager beforeEach(function() { txManager = new TransactionManager({ - networkStore: new ObservableStore({ network: 'unit test' }), + networkStore: new ObservableStore({ network: currentNetworkId }), txHistoryLimit: 10, blockTracker: new EventEmitter(), + signTransaction: (ethTx) => new Promise((resolve) => { + ethTx.sign(privKey) + const result = ethTx.serialize() + resolve() + }) }) }) @@ -27,7 +38,6 @@ describe('Transaction Manager', function() { }) }) - it('returns error for negative values', function() { var sample = { value: '-0x01' @@ -51,7 +61,7 @@ describe('Transaction Manager', function() { describe('#addTx', function() { it('adds a tx returned in getTxList', function() { - var tx = { id: 1, status: 'confirmed', metamaskNetworkId: 'unit test', txParams: {} } + var tx = { id: 1, status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} } txManager.addTx(tx, noop) var result = txManager.getTxList() assert.ok(Array.isArray(result)) @@ -60,8 +70,8 @@ describe('Transaction Manager', function() { }) it('does not override txs from other networks', function() { - var tx = { id: 1, status: 'confirmed', metamaskNetworkId: 'unit test', txParams: {} } - var tx2 = { id: 2, status: 'confirmed', metamaskNetworkId: 'another net', txParams: {} } + var tx = { id: 1, status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} } + var tx2 = { id: 2, status: 'confirmed', metamaskNetworkId: otherNetworkId, txParams: {} } txManager.addTx(tx, noop) txManager.addTx(tx2, noop) var result = txManager.getFullTxList() @@ -73,7 +83,7 @@ describe('Transaction Manager', function() { it('cuts off early txs beyond a limit', function() { const limit = txManager.txHistoryLimit for (let i = 0; i < limit + 1; i++) { - let tx = { id: i, time: new Date(), status: 'confirmed', metamaskNetworkId: 'unit test', txParams: {} } + let tx = { id: i, time: new Date(), status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} } txManager.addTx(tx, noop) } var result = txManager.getTxList() @@ -84,7 +94,7 @@ describe('Transaction Manager', function() { it('cuts off early txs beyond a limit whether or not it is confirmed or rejected', function() { const limit = txManager.txHistoryLimit for (let i = 0; i < limit + 1; i++) { - let tx = { id: i, time: new Date(), status: 'rejected', metamaskNetworkId: 'unit test', txParams: {} } + let tx = { id: i, time: new Date(), status: 'rejected', metamaskNetworkId: currentNetworkId, txParams: {} } txManager.addTx(tx, noop) } var result = txManager.getTxList() @@ -93,11 +103,11 @@ describe('Transaction Manager', function() { }) it('cuts off early txs beyond a limit but does not cut unapproved txs', function() { - var unconfirmedTx = { id: 0, time: new Date(), status: 'unapproved', metamaskNetworkId: 'unit test', txParams: {} } + var unconfirmedTx = { id: 0, time: new Date(), status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} } txManager.addTx(unconfirmedTx, noop) const limit = txManager.txHistoryLimit for (let i = 1; i < limit + 1; i++) { - let tx = { id: i, time: new Date(), status: 'confirmed', metamaskNetworkId: 'unit test', txParams: {} } + let tx = { id: i, time: new Date(), status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} } txManager.addTx(tx, noop) } var result = txManager.getTxList() @@ -110,7 +120,7 @@ describe('Transaction Manager', function() { describe('#setTxStatusSigned', function() { it('sets the tx status to signed', function() { - var tx = { id: 1, status: 'unapproved', metamaskNetworkId: 'unit test', txParams: {} } + var tx = { id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} } txManager.addTx(tx, noop) txManager.setTxStatusSigned(1) var result = txManager.getTxList() @@ -121,7 +131,7 @@ describe('Transaction Manager', function() { it('should emit a signed event to signal the exciton of callback', (done) => { this.timeout(10000) - var tx = { id: 1, status: 'unapproved', metamaskNetworkId: 'unit test', txParams: {} } + var tx = { id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} } let noop = function () { assert(true, 'event listener has been triggered and noop executed') done() @@ -134,7 +144,7 @@ describe('Transaction Manager', function() { describe('#setTxStatusRejected', function() { it('sets the tx status to rejected', function() { - var tx = { id: 1, status: 'unapproved', metamaskNetworkId: 'unit test', txParams: {} } + var tx = { id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} } txManager.addTx(tx) txManager.setTxStatusRejected(1) var result = txManager.getTxList() @@ -145,7 +155,7 @@ describe('Transaction Manager', function() { it('should emit a rejected event to signal the exciton of callback', (done) => { this.timeout(10000) - var tx = { id: 1, status: 'unapproved', metamaskNetworkId: 'unit test', txParams: {} } + var tx = { id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} } txManager.addTx(tx) let noop = function (err, txId) { assert(true, 'event listener has been triggered and noop executed') @@ -159,9 +169,9 @@ describe('Transaction Manager', function() { describe('#updateTx', function() { it('replaces the tx with the same id', function() { - txManager.addTx({ id: '1', status: 'unapproved', metamaskNetworkId: 'unit test', txParams: {} }, noop) - txManager.addTx({ id: '2', status: 'confirmed', metamaskNetworkId: 'unit test', txParams: {} }, noop) - txManager.updateTx({ id: '1', status: 'blah', hash: 'foo', metamaskNetworkId: 'unit test', txParams: {} }) + txManager.addTx({ id: '1', status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }, noop) + txManager.addTx({ id: '2', status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} }, noop) + txManager.updateTx({ id: '1', status: 'blah', hash: 'foo', metamaskNetworkId: currentNetworkId, txParams: {} }) var result = txManager.getTx('1') assert.equal(result.hash, 'foo') }) @@ -169,8 +179,8 @@ describe('Transaction Manager', function() { describe('#getUnapprovedTxList', function() { it('returns unapproved txs in a hash', function() { - txManager.addTx({ id: '1', status: 'unapproved', metamaskNetworkId: 'unit test', txParams: {} }, noop) - txManager.addTx({ id: '2', status: 'confirmed', metamaskNetworkId: 'unit test', txParams: {} }, noop) + txManager.addTx({ id: '1', status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }, noop) + txManager.addTx({ id: '2', status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} }, noop) let result = txManager.getUnapprovedTxList() assert.equal(typeof result, 'object') assert.equal(result['1'].status, 'unapproved') @@ -180,8 +190,8 @@ describe('Transaction Manager', function() { describe('#getTx', function() { it('returns a tx with the requested id', function() { - txManager.addTx({ id: '1', status: 'unapproved', metamaskNetworkId: 'unit test', txParams: {} }, noop) - txManager.addTx({ id: '2', status: 'confirmed', metamaskNetworkId: 'unit test', txParams: {} }, noop) + txManager.addTx({ id: '1', status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }, noop) + txManager.addTx({ id: '2', status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} }, noop) assert.equal(txManager.getTx('1').status, 'unapproved') assert.equal(txManager.getTx('2').status, 'confirmed') }) @@ -190,16 +200,16 @@ describe('Transaction Manager', function() { describe('#getFilteredTxList', function() { it('returns a tx with the requested data', function() { let txMetas = [ - { id: 0, status: 'unapproved', txParams: { from: '0xaa', to: '0xbb' }, metamaskNetworkId: 'unit test' }, - { id: 1, status: 'unapproved', txParams: { from: '0xaa', to: '0xbb' }, metamaskNetworkId: 'unit test' }, - { id: 2, status: 'unapproved', txParams: { from: '0xaa', to: '0xbb' }, metamaskNetworkId: 'unit test' }, - { id: 3, status: 'unapproved', txParams: { from: '0xbb', to: '0xaa' }, metamaskNetworkId: 'unit test' }, - { id: 4, status: 'unapproved', txParams: { from: '0xbb', to: '0xaa' }, metamaskNetworkId: 'unit test' }, - { id: 5, status: 'confirmed', txParams: { from: '0xaa', to: '0xbb' }, metamaskNetworkId: 'unit test' }, - { id: 6, status: 'confirmed', txParams: { from: '0xaa', to: '0xbb' }, metamaskNetworkId: 'unit test' }, - { id: 7, status: 'confirmed', txParams: { from: '0xbb', to: '0xaa' }, metamaskNetworkId: 'unit test' }, - { id: 8, status: 'confirmed', txParams: { from: '0xbb', to: '0xaa' }, metamaskNetworkId: 'unit test' }, - { id: 9, status: 'confirmed', txParams: { from: '0xbb', to: '0xaa' }, metamaskNetworkId: 'unit test' }, + { id: 0, status: 'unapproved', txParams: { from: '0xaa', to: '0xbb' }, metamaskNetworkId: currentNetworkId }, + { id: 1, status: 'unapproved', txParams: { from: '0xaa', to: '0xbb' }, metamaskNetworkId: currentNetworkId }, + { id: 2, status: 'unapproved', txParams: { from: '0xaa', to: '0xbb' }, metamaskNetworkId: currentNetworkId }, + { id: 3, status: 'unapproved', txParams: { from: '0xbb', to: '0xaa' }, metamaskNetworkId: currentNetworkId }, + { id: 4, status: 'unapproved', txParams: { from: '0xbb', to: '0xaa' }, metamaskNetworkId: currentNetworkId }, + { id: 5, status: 'confirmed', txParams: { from: '0xaa', to: '0xbb' }, metamaskNetworkId: currentNetworkId }, + { id: 6, status: 'confirmed', txParams: { from: '0xaa', to: '0xbb' }, metamaskNetworkId: currentNetworkId }, + { id: 7, status: 'confirmed', txParams: { from: '0xbb', to: '0xaa' }, metamaskNetworkId: currentNetworkId }, + { id: 8, status: 'confirmed', txParams: { from: '0xbb', to: '0xaa' }, metamaskNetworkId: currentNetworkId }, + { id: 9, status: 'confirmed', txParams: { from: '0xbb', to: '0xaa' }, metamaskNetworkId: currentNetworkId }, ] txMetas.forEach((txMeta) => txManager.addTx(txMeta, noop)) let filterParams @@ -219,4 +229,21 @@ describe('Transaction Manager', function() { }) }) + + describe('#sign replay-protected tx', function() { + it('prepares a tx with the chainId set', function() { + txManager.addTx({ id: '1', status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }, noop) + txManager.signTransaction('1', (err, rawTx) => { + if (err) return assert.fail('it should not fail') + const ethTx = new EthTx(ethUtil.toBuffer(rawTx)) + console.log('------------------------------------------') + console.log('ethTx.getChainId(), currentNetworkId') + console.log(ethTx.getChainId(), currentNetworkId) + assert.equal(ethTx.getChainId(), currentNetworkId) + }) + }) + }) + + + }) diff --git a/test/unit/tx-utils-test.js b/test/unit/tx-utils-test.js index 8b4d2f059..93e9e4134 100644 --- a/test/unit/tx-utils-test.js +++ b/test/unit/tx-utils-test.js @@ -12,6 +12,23 @@ describe('txUtils', function() { txUtils = new TxUtils() }) + describe('chain Id', function() { + it('prepares a transaction with the provided chainId', function() { + const txParams = { + to: '0x70ad465e0bab6504002ad58c744ed89c7da38524', + from: '0x69ad465e0bab6504002ad58c744ed89c7da38525', + value: '0x0', + gas: '0x7b0c', + gasPrice: '0x199c82cc00', + data: '0x', + nonce: '0x3', + chainId: 42, + } + const ethTx = txUtils.buildEthTxFromParams(txParams) + assert.equal(ethTx.getChainId(), 42, 'chainId is set from tx params') + }) + }) + describe('addGasBuffer', function() { it('multiplies by 1.5, when within block gas limit', function() { // naive estimatedGas: 0x16e360 (1.5 mil) From e95ae43c8f0e40742540844984861a06f26999c6 Mon Sep 17 00:00:00 2001 From: kumavis Date: Thu, 30 Mar 2017 14:43:56 -0700 Subject: [PATCH 105/372] tests - unit - fail on unhandled promise rejection --- package.json | 1 + test/helper.js | 49 +++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 9e04bc6f7..70da129d2 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "async": "^1.5.2", "async-q": "^0.3.1", "bip39": "^2.2.0", + "bluebird": "^3.5.0", "browser-passworder": "^2.0.3", "browserify-derequire": "^0.9.4", "clone": "^1.0.2", diff --git a/test/helper.js b/test/helper.js index a01ea1e53..dd22d80b4 100644 --- a/test/helper.js +++ b/test/helper.js @@ -1,11 +1,52 @@ +// disallow promises from swallowing errors +enableFailureOnUnhandledPromiseRejection() + +// logging util var log = require('loglevel') log.setDefaultLevel(5) +global.log = log +// +// polyfills +// + +// dom require('jsdom-global')() + +// localStorage window.localStorage = {} -if (!('crypto' in window)) { window.crypto = {} } -window.crypto.getRandomValues = require('polyfill-crypto.getrandomvalues') +// crypto.getRandomValues +if (!window.crypto) window.crypto = {} +if (!window.crypto.getRandomValues) window.crypto.getRandomValues = require('polyfill-crypto.getrandomvalues') -window.log = log -global.log = log + + +function enableFailureOnUnhandledPromiseRejection() { + // overwrite node's promise with the stricter Bluebird promise + global.Promise = require('bluebird') + + // rethrow unhandledRejections + if (typeof process !== 'undefined') { + process.on('unhandledRejection', function (reason) { + throw reason; + }); + } else if (typeof window !== 'undefined') { + // 2016-02-01: No browsers support this natively, however bluebird, when.js, + // and probably other libraries do. + if (typeof window.addEventListener === 'function') { + window.addEventListener('unhandledrejection', function (evt) { + throw evt.detail.reason; + }); + } else { + var oldOHR = window.onunhandledrejection; + window.onunhandledrejection = function (evt) { + if (typeof oldOHR === 'function') oldOHR.apply(this, arguments); + throw evt.detail.reason; + }; + } + } else if (typeof console !== 'undefined' && + typeof (console.error || console.log) === 'function') { + (console.error || console.log)('Unhandled rejections will be ignored!'); + } +} \ No newline at end of file From 8ae37ae80de38728c8c878ab24249450a570e97a Mon Sep 17 00:00:00 2001 From: kumavis Date: Thu, 30 Mar 2017 14:49:39 -0700 Subject: [PATCH 106/372] tests - helper - add note --- test/helper.js | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/test/helper.js b/test/helper.js index dd22d80b4..aaac7580e 100644 --- a/test/helper.js +++ b/test/helper.js @@ -26,27 +26,29 @@ function enableFailureOnUnhandledPromiseRejection() { // overwrite node's promise with the stricter Bluebird promise global.Promise = require('bluebird') + // modified from https://github.com/mochajs/mocha/issues/1926#issuecomment-180842722 + // rethrow unhandledRejections if (typeof process !== 'undefined') { process.on('unhandledRejection', function (reason) { - throw reason; - }); + throw reason + }) } else if (typeof window !== 'undefined') { // 2016-02-01: No browsers support this natively, however bluebird, when.js, // and probably other libraries do. if (typeof window.addEventListener === 'function') { window.addEventListener('unhandledrejection', function (evt) { - throw evt.detail.reason; - }); + throw evt.detail.reason + }) } else { - var oldOHR = window.onunhandledrejection; + var oldOHR = window.onunhandledrejection window.onunhandledrejection = function (evt) { - if (typeof oldOHR === 'function') oldOHR.apply(this, arguments); - throw evt.detail.reason; - }; + if (typeof oldOHR === 'function') oldOHR.apply(this, arguments) + throw evt.detail.reason + } } } else if (typeof console !== 'undefined' && typeof (console.error || console.log) === 'function') { - (console.error || console.log)('Unhandled rejections will be ignored!'); + (console.error || console.log)('Unhandled rejections will be ignored!') } } \ No newline at end of file From 1079c20c8329610f124713a1b1dae2dfe9c99ba0 Mon Sep 17 00:00:00 2001 From: kumavis Date: Thu, 30 Mar 2017 14:50:01 -0700 Subject: [PATCH 107/372] meta - package.json - rename npm test scripts --- package.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 70da129d2..7dfe21f24 100644 --- a/package.json +++ b/package.json @@ -5,20 +5,20 @@ "private": true, "scripts": { "start": "npm run dev", - "lint": "gulp lint", - "buildCiUnits": "node test/integration/index.js", "dev": "gulp dev --debug", "disc": "gulp disc --debug", "dist": "gulp dist --disableLiveReload", - "test": "npm run lint && npm run fastTest && npm run ci", - "fastTest": "METAMASK_ENV=test mocha --require test/helper.js --recursive \"test/unit/**/*.js\"", + "test": "npm run lint && npm run test-unit && npm run test-integration", + "test-unit": "METAMASK_ENV=test mocha --require test/helper.js --recursive \"test/unit/**/*.js\"", + "test-integration": "npm run buildMock && npm run buildCiUnits && testem ci -P 2", + "lint": "gulp lint", + "buildCiUnits": "node test/integration/index.js", "watch": "mocha watch --recursive \"test/unit/**/*.js\"", "genStates": "node development/genStates.js", "ui": "npm run genStates && beefy ui-dev.js:bundle.js --live --open --index=./development/index.html --cwd ./", "mock": "beefy mock-dev.js:bundle.js --live --open --index=./development/index.html --cwd ./", "buildMock": "npm run genStates && browserify ./mock-dev.js -o ./development/bundle.js", "testem": "npm run buildMock && testem", - "ci": "npm run buildMock && npm run buildCiUnits && testem ci -P 2", "announce": "node development/announcer.js", "generateNotice": "node notices/notice-generator.js", "deleteNotice": "node notices/notice-delete.js" From 47ea5452414ef4c126ff6c6ccb40615f852f8bed Mon Sep 17 00:00:00 2001 From: kumavis Date: Thu, 30 Mar 2017 15:43:01 -0700 Subject: [PATCH 108/372] tests - add missing done --- test/unit/keyring-controller-test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/keyring-controller-test.js b/test/unit/keyring-controller-test.js index aae4cdfd6..efd0a3546 100644 --- a/test/unit/keyring-controller-test.js +++ b/test/unit/keyring-controller-test.js @@ -135,7 +135,7 @@ describe('KeyringController', function() { }) describe('#getAccounts', function() { - it('returns the result of getAccounts for each keyring', function() { + it('returns the result of getAccounts for each keyring', function(done) { keyringController.keyrings = [ { getAccounts() { return Promise.resolve([1,2,3]) } }, { getAccounts() { return Promise.resolve([4,5,6]) } }, From 12918e18942a72223d7d97334934c31442e51caf Mon Sep 17 00:00:00 2001 From: kumavis Date: Thu, 30 Mar 2017 16:06:27 -0700 Subject: [PATCH 109/372] tests - tx-manager - fix assert and clean formatting --- package.json | 2 +- test/unit/tx-manager-test.js | 10 +--------- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index 9e04bc6f7..ba6a81da9 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,7 @@ "eth-query": "^1.0.3", "eth-sig-util": "^1.1.1", "eth-simple-keyring": "^1.1.1", - "ethereumjs-tx": "^1.0.0", + "ethereumjs-tx": "^1.2.5", "ethereumjs-util": "ethereumjs/ethereumjs-util#ac5d0908536b447083ea422b435da27f26615de9", "ethereumjs-wallet": "^0.6.0", "ethjs-ens": "^1.0.2", diff --git a/test/unit/tx-manager-test.js b/test/unit/tx-manager-test.js index 20f89a226..21e94357b 100644 --- a/test/unit/tx-manager-test.js +++ b/test/unit/tx-manager-test.js @@ -1,5 +1,4 @@ -// const assert = require('assert') -const assert = require('chai').assert +const assert = require('assert') const extend = require('xtend') const EventEmitter = require('events') const ethUtil = require('ethereumjs-util') @@ -22,7 +21,6 @@ describe('Transaction Manager', function() { blockTracker: new EventEmitter(), signTransaction: (ethTx) => new Promise((resolve) => { ethTx.sign(privKey) - const result = ethTx.serialize() resolve() }) }) @@ -229,21 +227,15 @@ describe('Transaction Manager', function() { }) }) - describe('#sign replay-protected tx', function() { it('prepares a tx with the chainId set', function() { txManager.addTx({ id: '1', status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }, noop) txManager.signTransaction('1', (err, rawTx) => { if (err) return assert.fail('it should not fail') const ethTx = new EthTx(ethUtil.toBuffer(rawTx)) - console.log('------------------------------------------') - console.log('ethTx.getChainId(), currentNetworkId') - console.log(ethTx.getChainId(), currentNetworkId) assert.equal(ethTx.getChainId(), currentNetworkId) }) }) }) - - }) From be88c87b2560f0ae2afd668d9c10cadde0a4ebc3 Mon Sep 17 00:00:00 2001 From: kumavis Date: Thu, 30 Mar 2017 18:30:24 -0700 Subject: [PATCH 110/372] extension - prefer extensionizer module --- app/scripts/contentscript.js | 2 +- development/mockExtension.js | 2 +- ui/app/components/account-info-link.js | 2 +- ui/app/components/buy-button-subview.js | 2 +- ui/app/components/shift-list-item.js | 2 +- ui/app/components/transaction-list-item.js | 2 +- ui/app/info.js | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/scripts/contentscript.js b/app/scripts/contentscript.js index 9a390e580..4d7e682d3 100644 --- a/app/scripts/contentscript.js +++ b/app/scripts/contentscript.js @@ -2,7 +2,7 @@ const LocalMessageDuplexStream = require('post-message-stream') const PongStream = require('ping-pong-stream/pong') const PortStream = require('./lib/port-stream.js') const ObjectMultiplex = require('./lib/obj-multiplex') -const extension = require('./lib/extension') +const extension = require('extensionizer') const fs = require('fs') const path = require('path') diff --git a/development/mockExtension.js b/development/mockExtension.js index 509487cce..55799b2bf 100644 --- a/development/mockExtension.js +++ b/development/mockExtension.js @@ -4,7 +4,7 @@ * and stubbing out all the extension methods with appropriate mocks. */ -const extension = require('../app/scripts/lib/extension') +const extension = require('extensionizer') const noop = function () {} const apis = [ diff --git a/ui/app/components/account-info-link.js b/ui/app/components/account-info-link.js index 49c42e9ec..74bc7f40e 100644 --- a/ui/app/components/account-info-link.js +++ b/ui/app/components/account-info-link.js @@ -3,7 +3,7 @@ const h = require('react-hyperscript') const inherits = require('util').inherits const Tooltip = require('./tooltip') const genAccountLink = require('../../lib/account-link') -const extension = require('../../../app/scripts/lib/extension') +const extension = require('extensionizer') module.exports = AccountInfoLink diff --git a/ui/app/components/buy-button-subview.js b/ui/app/components/buy-button-subview.js index 7b993110d..19279d1f6 100644 --- a/ui/app/components/buy-button-subview.js +++ b/ui/app/components/buy-button-subview.js @@ -5,7 +5,7 @@ const connect = require('react-redux').connect const actions = require('../actions') const CoinbaseForm = require('./coinbase-form') const ShapeshiftForm = require('./shapeshift-form') -const extension = require('../../../app/scripts/lib/extension') +const extension = require('extensionizer') const Loading = require('./loading') const TabBar = require('./tab-bar') diff --git a/ui/app/components/shift-list-item.js b/ui/app/components/shift-list-item.js index e0243e247..da991f75f 100644 --- a/ui/app/components/shift-list-item.js +++ b/ui/app/components/shift-list-item.js @@ -4,7 +4,7 @@ const h = require('react-hyperscript') const connect = require('react-redux').connect const vreme = new (require('vreme')) const explorerLink = require('../../lib/explorer-link') -const extension = require('../../../app/scripts/lib/extension') +const extension = require('extensionizer') const actions = require('../actions') const addressSummary = require('../util').addressSummary diff --git a/ui/app/components/transaction-list-item.js b/ui/app/components/transaction-list-item.js index ee32be7d3..0052d9c70 100644 --- a/ui/app/components/transaction-list-item.js +++ b/ui/app/components/transaction-list-item.js @@ -7,7 +7,7 @@ const addressSummary = require('../util').addressSummary const explorerLink = require('../../lib/explorer-link') const CopyButton = require('./copyButton') const vreme = new (require('vreme')) -const extension = require('../../../app/scripts/lib/extension') +const extension = require('extensionizer') const Tooltip = require('./tooltip') const TransactionIcon = require('./transaction-list-item-icon') diff --git a/ui/app/info.js b/ui/app/info.js index e79580be4..fa51ae41c 100644 --- a/ui/app/info.js +++ b/ui/app/info.js @@ -3,7 +3,7 @@ const Component = require('react').Component const h = require('react-hyperscript') const connect = require('react-redux').connect const actions = require('./actions') -const extension = require('../../app/scripts/lib/extension') +const extension = require('extensionizer') module.exports = connect(mapStateToProps)(InfoScreen) From 5036263f88a1f61957982b64d27472a516c28def Mon Sep 17 00:00:00 2001 From: kumavis Date: Thu, 30 Mar 2017 18:33:19 -0700 Subject: [PATCH 111/372] introduce platform api and rename notifications to notification-manager --- app/scripts/background.js | 13 ++++- app/scripts/lib/notification-manager.js | 74 +++++++++++++++++++++++++ app/scripts/lib/notifications.js | 67 ---------------------- app/scripts/metamask-controller.js | 14 +++-- app/scripts/platforms/extension.js | 19 +++++++ app/scripts/popup.js | 8 ++- ui/app/reducers/app.js | 2 +- 7 files changed, 117 insertions(+), 80 deletions(-) create mode 100644 app/scripts/lib/notification-manager.js delete mode 100644 app/scripts/lib/notifications.js create mode 100644 app/scripts/platforms/extension.js diff --git a/app/scripts/background.js b/app/scripts/background.js index 254737dec..5fb56d497 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -4,12 +4,13 @@ const asyncQ = require('async-q') const pipe = require('pump') const LocalStorageStore = require('obs-store/lib/localStorage') const storeTransform = require('obs-store/lib/transform') +const ExtensionPlatform = require('./platforms/extension') const Migrator = require('./lib/migrator/') const migrations = require('./migrations/') const PortStream = require('./lib/port-stream.js') -const notification = require('./lib/notifications.js') +const NotificationManager = require('./lib/notification-manager.js') const MetamaskController = require('./metamask-controller') -const extension = require('./lib/extension') +const extension = require('extensionizer') const firstTimeState = require('./first-time-state') const STORAGE_KEY = 'metamask-config' @@ -19,6 +20,10 @@ const log = require('loglevel') window.log = log log.setDefaultLevel(METAMASK_DEBUG ? 'debug' : 'warn') +const platform = new ExtensionPlatform() +const notificationManager = new NotificationManager() +global.METAMASK_NOTIFIER = notificationManager + let popupIsOpen = false // state persistence @@ -68,6 +73,8 @@ function setupController (initState) { showUnapprovedTx: triggerUi, // initial state initState, + // platform specific api + platform, }) global.metamaskController = controller @@ -140,7 +147,7 @@ function setupController (initState) { // popup trigger function triggerUi () { - if (!popupIsOpen) notification.show() + if (!popupIsOpen) notificationManager.show() } // On first install, open a window to MetaMask website to how-it-works. diff --git a/app/scripts/lib/notification-manager.js b/app/scripts/lib/notification-manager.js new file mode 100644 index 000000000..ff8a4b2e5 --- /dev/null +++ b/app/scripts/lib/notification-manager.js @@ -0,0 +1,74 @@ +const extension = require('extensionizer') +const height = 520 +const width = 360 + + +class NotificationManager { + + // + // Public + // + + show () { + this.getPopup((err, popup) => { + if (err) throw err + + if (popup) { + // bring focus to existing popup + extension.windows.update(popup.id, { focused: true }) + } else { + // create new popup + extension.windows.create({ + url: 'notification.html', + type: 'popup', + width, + height, + }) + .catch((reason) => { + log.error('failed to create poupup', reason) + }) + } + }) + } + + getPopup (cb) { + this._getWindows((err, windows) => { + if (err) throw err + cb(null, this._getPopupIn(windows)) + }) + } + + closePopup () { + this.getPopup((err, popup) => { + if (err) throw err + if (!popup) return + extension.windows.remove(popup.id, console.error) + }) + } + + // + // Private + // + + _getWindows (cb) { + // Ignore in test environment + if (!extension.windows) { + return cb() + } + + extension.windows.getAll({}, (windows) => { + cb(null, windows) + }) + } + + _getPopupIn (windows) { + return windows ? windows.find((win) => { + return (win && win.type === 'popup' && + win.height === height && + win.width === width) + }) : null + } + +} + +module.exports = NotificationManager \ No newline at end of file diff --git a/app/scripts/lib/notifications.js b/app/scripts/lib/notifications.js deleted file mode 100644 index 0ec01f3a7..000000000 --- a/app/scripts/lib/notifications.js +++ /dev/null @@ -1,67 +0,0 @@ -const extension = require('./extension') -const height = 520 -const width = 360 - -const notifications = { - show, - getPopup, - closePopup, -} -module.exports = notifications -window.METAMASK_NOTIFIER = notifications - -function show () { - getPopup((err, popup) => { - if (err) throw err - - if (popup) { - // bring focus to existing popup - extension.windows.update(popup.id, { focused: true }) - } else { - // create new popup - extension.windows.create({ - url: 'notification.html', - type: 'popup', - width, - height, - }) - .catch((reason) => { - log.error('failed to create poupup', reason) - }) - } - }) -} - -function getWindows (cb) { - // Ignore in test environment - if (!extension.windows) { - return cb() - } - - extension.windows.getAll({}, (windows) => { - cb(null, windows) - }) -} - -function getPopup (cb) { - getWindows((err, windows) => { - if (err) throw err - cb(null, getPopupIn(windows)) - }) -} - -function getPopupIn (windows) { - return windows ? windows.find((win) => { - return (win && win.type === 'popup' && - win.height === height && - win.width === width) - }) : null -} - -function closePopup () { - getPopup((err, popup) => { - if (err) throw err - if (!popup) return - extension.windows.remove(popup.id, console.error) - }) -} diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 03ea104e0..af8b4688d 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -20,7 +20,6 @@ const MessageManager = require('./lib/message-manager') const PersonalMessageManager = require('./lib/personal-message-manager') const TxManager = require('./transaction-manager') const ConfigManager = require('./lib/config-manager') -const extension = require('./lib/extension') const autoFaucet = require('./lib/auto-faucet') const nodeify = require('./lib/nodeify') const accountImporter = require('./account-import-strategies') @@ -34,6 +33,9 @@ module.exports = class MetamaskController extends EventEmitter { this.opts = opts let initState = opts.initState || {} + // platform-specific api + this.platform = opts.platform + // observable state store this.store = new ObservableStore(initState) @@ -629,7 +631,7 @@ module.exports = class MetamaskController extends EventEmitter { break } - if (url) extension.tabs.create({ url }) + if (url) this.platform.openWindow({ url }) } createShapeShiftTx (depositAddress, depositType) { @@ -647,7 +649,7 @@ module.exports = class MetamaskController extends EventEmitter { setDefaultRpc () { this.configManager.setRpcTarget('http://localhost:8545') - extension.runtime.reload() + this.platform.reload() this.lookupNetwork() return Promise.resolve('http://localhost:8545') } @@ -656,7 +658,7 @@ module.exports = class MetamaskController extends EventEmitter { this.configManager.setRpcTarget(rpcTarget) return this.preferencesController.updateFrequentRpcList(rpcTarget) .then(() => { - extension.runtime.reload() + this.platform.reload() this.lookupNetwork() return Promise.resolve(rpcTarget) }) @@ -664,13 +666,13 @@ module.exports = class MetamaskController extends EventEmitter { setProviderType (type) { this.configManager.setProviderType(type) - extension.runtime.reload() + this.platform.reload() this.lookupNetwork() } useEtherscanProvider () { this.configManager.useEtherscanProvider() - extension.runtime.reload() + this.platform.reload() } getNetworkState () { diff --git a/app/scripts/platforms/extension.js b/app/scripts/platforms/extension.js new file mode 100644 index 000000000..cbb35768e --- /dev/null +++ b/app/scripts/platforms/extension.js @@ -0,0 +1,19 @@ +const extension = require('extensionizer') + +class ExtensionPlatform { + + // + // Public + // + + reload () { + extension.runtime.reload() + } + + openWindow ({ url }) { + extension.tabs.create({ url }) + } + +} + +module.exports = ExtensionPlatform diff --git a/app/scripts/popup.js b/app/scripts/popup.js index 62db68c10..6606c9584 100644 --- a/app/scripts/popup.js +++ b/app/scripts/popup.js @@ -3,8 +3,10 @@ const MetaMaskUiCss = require('../../ui/css') const startPopup = require('./popup-core') const PortStream = require('./lib/port-stream.js') const isPopupOrNotification = require('./lib/is-popup-or-notification') -const extension = require('./lib/extension') -const notification = require('./lib/notifications') +const extension = require('extensionizer') +const NotificationManager = require('./lib/notification-manager') + +const notificationManager = new NotificationManager() var css = MetaMaskUiCss() injectCss(css) @@ -20,6 +22,6 @@ startPopup(portStream) function closePopupIfOpen (name) { if (name !== 'notification') { - notification.closePopup() + notificationManager.closePopup() } } diff --git a/ui/app/reducers/app.js b/ui/app/reducers/app.js index 3a6baca91..6f633ab4e 100644 --- a/ui/app/reducers/app.js +++ b/ui/app/reducers/app.js @@ -1,7 +1,7 @@ const extend = require('xtend') const actions = require('../actions') const txHelper = require('../../lib/tx-helper') -const notification = require('../../../app/scripts/lib/notifications') +const notification = require('../../../app/scripts/lib/notification-manager') module.exports = reduceApp From 6259ffaef4f2045d2de069ea83de47b64b5fba86 Mon Sep 17 00:00:00 2001 From: kumavis Date: Thu, 30 Mar 2017 18:35:22 -0700 Subject: [PATCH 112/372] extension - remove local extension files, we use extensionizer --- app/scripts/lib/extension-instance.js | 68 --------------------------- app/scripts/lib/extension.js | 17 ------- 2 files changed, 85 deletions(-) delete mode 100644 app/scripts/lib/extension-instance.js delete mode 100644 app/scripts/lib/extension.js diff --git a/app/scripts/lib/extension-instance.js b/app/scripts/lib/extension-instance.js deleted file mode 100644 index 628b62e3f..000000000 --- a/app/scripts/lib/extension-instance.js +++ /dev/null @@ -1,68 +0,0 @@ -const apis = [ - 'alarms', - 'bookmarks', - 'browserAction', - 'commands', - 'contextMenus', - 'cookies', - 'downloads', - 'events', - 'extension', - 'extensionTypes', - 'history', - 'i18n', - 'idle', - 'notifications', - 'pageAction', - 'runtime', - 'storage', - 'tabs', - 'webNavigation', - 'webRequest', - 'windows', -] - -function Extension () { - const _this = this - - apis.forEach(function (api) { - - _this[api] = null - - try { - if (chrome[api]) { - _this[api] = chrome[api] - } - } catch (e) {} - - try { - if (window[api]) { - _this[api] = window[api] - } - } catch (e) {} - - try { - if (browser[api]) { - _this[api] = browser[api] - } - } catch (e) {} - try { - _this.api = browser.extension[api] - } catch (e) {} - }) - - try { - if (browser && browser.runtime) { - this.runtime = browser.runtime - } - } catch (e) {} - - try { - if (browser && browser.browserAction) { - this.browserAction = browser.browserAction - } - } catch (e) {} - -} - -module.exports = Extension diff --git a/app/scripts/lib/extension.js b/app/scripts/lib/extension.js deleted file mode 100644 index 6f8b5d800..000000000 --- a/app/scripts/lib/extension.js +++ /dev/null @@ -1,17 +0,0 @@ -/* Extension.js - * - * A module for unifying browser differences in the WebExtension API. - * - * Initially implemented because Chrome hides all of their WebExtension API - * behind a global `chrome` variable, but we'd like to start grooming - * the code-base for cross-browser extension support. - * - * You can read more about the WebExtension API here: - * https://developer.mozilla.org/en-US/Add-ons/WebExtensions - */ - -const Extension = require('./extension-instance') -const instance = new Extension() -window.METAMASK_EXTENSION = instance -module.exports = instance - From bd704b1d7e7208fe1989bd7dde6ab81cb39bbfcc Mon Sep 17 00:00:00 2001 From: kumavis Date: Thu, 30 Mar 2017 19:05:11 -0700 Subject: [PATCH 113/372] etc - fix notification-manager ref, remove duplicated test file --- test/unit/extension-test.js | 77 ------------------------------------- ui/app/reducers/app.js | 6 ++- 2 files changed, 4 insertions(+), 79 deletions(-) delete mode 100644 test/unit/extension-test.js diff --git a/test/unit/extension-test.js b/test/unit/extension-test.js deleted file mode 100644 index 8f259f05c..000000000 --- a/test/unit/extension-test.js +++ /dev/null @@ -1,77 +0,0 @@ -var assert = require('assert') -var sinon = require('sinon') -const ethUtil = require('ethereumjs-util') -global.chrome = {} -global.browser = {} - -var path = require('path') -var Extension = require(path.join(__dirname, '..', '..', 'app', 'scripts', 'lib', 'extension-instance.js')) - -describe('extension', function() { - - describe('extension.getURL', function() { - const desiredResult = 'http://the-desired-result.io' - - describe('in Chrome or Firefox', function() { - global.chrome.extension = { - getURL: () => desiredResult - } - - it('returns the desired result', function() { - const extension = new Extension() - const result = extension.extension.getURL() - assert.equal(result, desiredResult) - }) - }) - - describe('in Microsoft Edge', function() { - global.browser.extension = { - getURL: () => desiredResult - } - - it('returns the desired result', function() { - const extension = new Extension() - const result = extension.extension.getURL() - assert.equal(result, desiredResult) - }) - }) - }) - - describe('with chrome global', function() { - let extension - - beforeEach(function() { - global.chrome = { - alarms: 'foo' - } - extension = new Extension() - }) - - it('should use the chrome global apis', function() { - assert.equal(extension.alarms, 'foo') - }) - }) - - describe('without chrome global', function() { - let extension - let realWindow - - beforeEach(function() { - realWindow = window - window = global - global.chrome = undefined - global.alarms = 'foo' - extension = new Extension() - }) - - after(function() { - window = realWindow - }) - - it('should use the global apis', function() { - assert.equal(extension.alarms, 'foo') - }) - }) - - -}) diff --git a/ui/app/reducers/app.js b/ui/app/reducers/app.js index 6f633ab4e..7595c60b3 100644 --- a/ui/app/reducers/app.js +++ b/ui/app/reducers/app.js @@ -1,7 +1,9 @@ const extend = require('xtend') const actions = require('../actions') const txHelper = require('../../lib/tx-helper') -const notification = require('../../../app/scripts/lib/notification-manager') +const NotificationManager = require('../../../app/scripts/lib/notification-manager') + +const notificationManager = new NotificationManager() module.exports = reduceApp @@ -332,7 +334,7 @@ function reduceApp (state, action) { }) } else { log.debug('attempting to close popup') - notification.closePopup() + notificationManager.closePopup() return extend(appState, { transForward: false, From 965c806a45bfc52f66de38a727a87dcde25f5607 Mon Sep 17 00:00:00 2001 From: kumavis Date: Thu, 30 Mar 2017 19:26:01 -0700 Subject: [PATCH 114/372] test - fix notice-controller test --- test/unit/notice-controller-test.js | 33 ++++++++++++++++------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/test/unit/notice-controller-test.js b/test/unit/notice-controller-test.js index 73fdb2f2e..ea37108bb 100644 --- a/test/unit/notice-controller-test.js +++ b/test/unit/notice-controller-test.js @@ -11,8 +11,6 @@ describe('notice-controller', function() { beforeEach(function() { // simple localStorage polyfill - window.localStorage = {} - if (window.localStorage.clear) window.localStorage.clear() let configManager = configManagerGen() noticeController = new NoticeController({ configManager: configManager, @@ -21,7 +19,7 @@ describe('notice-controller', function() { describe('notices', function() { describe('#getNoticesList', function() { - it('should return an empty array when new', function() { + it('should return an empty array when new', function(done) { var testList = [{ id:0, read:false, @@ -29,11 +27,12 @@ describe('notice-controller', function() { }] var result = noticeController.getNoticesList() assert.equal(result.length, 0) + done() }) }) describe('#setNoticesList', function() { - it('should set data appropriately', function () { + it('should set data appropriately', function (done) { var testList = [{ id:0, read:false, @@ -42,11 +41,12 @@ describe('notice-controller', function() { noticeController.setNoticesList(testList) var testListId = noticeController.getNoticesList()[0].id assert.equal(testListId, 0) + done() }) }) describe('#updateNoticeslist', function() { - it('should integrate the latest changes from the source', function() { + it('should integrate the latest changes from the source', function(done) { var testList = [{ id:55, read:false, @@ -57,26 +57,26 @@ describe('notice-controller', function() { var newList = noticeController.getNoticesList() assert.ok(newList[0].id === 55) assert.ok(newList[1]) + done() }) }) - it('should not overwrite any existing fields', function () { + it('should not overwrite any existing fields', function (done) { var testList = [{ id:0, read:false, title:"Futuristic Notice" }] noticeController.setNoticesList(testList) - noticeController.updateNoticesList().then(() => { - var newList = noticeController.getNoticesList() - assert.equal(newList[0].id, 0) - assert.equal(newList[0].title, "Futuristic Notice") - assert.equal(newList.length, 1) - }) + var newList = noticeController.getNoticesList() + assert.equal(newList[0].id, 0) + assert.equal(newList[0].title, "Futuristic Notice") + assert.equal(newList.length, 1) + done() }) }) describe('#markNoticeRead', function () { - it('should mark a notice as read', function () { + it('should mark a notice as read', function (done) { var testList = [{ id:0, read:false, @@ -86,11 +86,12 @@ describe('notice-controller', function() { noticeController.markNoticeRead(testList[0]) var newList = noticeController.getNoticesList() assert.ok(newList[0].read) + done() }) }) describe('#getLatestUnreadNotice', function () { - it('should retrieve the latest unread notice', function () { + it('should retrieve the latest unread notice', function (done) { var testList = [ {id:0,read:true,title:"Past Notice"}, {id:1,read:false,title:"Current Notice"}, @@ -99,8 +100,9 @@ describe('notice-controller', function() { noticeController.setNoticesList(testList) var latestUnread = noticeController.getLatestUnreadNotice() assert.equal(latestUnread.id, 2) + done() }) - it('should return undefined if no unread notices exist.', function () { + it('should return undefined if no unread notices exist.', function (done) { var testList = [ {id:0,read:true,title:"Past Notice"}, {id:1,read:true,title:"Current Notice"}, @@ -109,6 +111,7 @@ describe('notice-controller', function() { noticeController.setNoticesList(testList) var latestUnread = noticeController.getLatestUnreadNotice() assert.ok(!latestUnread) + done() }) }) }) From 0ef679388a9604c39a432408826c080d2d17c221 Mon Sep 17 00:00:00 2001 From: kumavis Date: Fri, 31 Mar 2017 12:38:20 -0700 Subject: [PATCH 115/372] ui - reducer - app - code cleanup --- ui/app/reducers/app.js | 47 ++++++++++++++++++------------------------ 1 file changed, 20 insertions(+), 27 deletions(-) diff --git a/ui/app/reducers/app.js b/ui/app/reducers/app.js index 7595c60b3..e0c001546 100644 --- a/ui/app/reducers/app.js +++ b/ui/app/reducers/app.js @@ -11,12 +11,12 @@ function reduceApp (state, action) { log.debug('App Reducer got ' + action.type) // clone and defaults const selectedAddress = state.metamask.selectedAddress - let pendingTxs = hasPendingTxs(state) + const hasUnconfActions = checkUnconfActions(state) let name = 'accounts' if (selectedAddress) { name = 'accountDetail' } - if (pendingTxs) { + if (hasUnconfActions) { log.debug('pending txs detected, defaulting to conf-tx view.') name = 'confTx' } @@ -304,7 +304,7 @@ function reduceApp (state, action) { case actions.SHOW_CONF_MSG_PAGE: return extend(appState, { currentView: { - name: pendingTxs ? 'confTx' : 'account-detail', + name: hasUnconfActions ? 'confTx' : 'account-detail', context: 0, }, transForward: true, @@ -314,15 +314,11 @@ function reduceApp (state, action) { case actions.COMPLETED_TX: log.debug('reducing COMPLETED_TX for tx ' + action.value) - var { unapprovedTxs, unapprovedMsgs, - unapprovedPersonalMsgs, network } = state.metamask - - var unconfTxList = txHelper(unapprovedTxs, unapprovedMsgs, unapprovedPersonalMsgs, network) + const otherUnconfActions = getUnconfActionList(state) .filter(tx => tx.id !== action.value ) + const hasOtherUnconfActions = otherUnconfActions.length > 0 - pendingTxs = unconfTxList.length > 0 - - if (pendingTxs) { + if (hasOtherUnconfActions) { log.debug('reducer detected txs - rendering confTx view') return extend(appState, { transForward: false, @@ -582,26 +578,23 @@ function reduceApp (state, action) { } } -function hasPendingTxs (state) { - var { unapprovedTxs, unapprovedMsgs, +function checkUnconfActions (state) { + const unconfActionList = getUnconfActionList(state) + const hasUnconfActions = unconfActionList.length > 0 + return hasUnconfActions +} + +function getUnconfActionList (state) { + const { unapprovedTxs, unapprovedMsgs, unapprovedPersonalMsgs, network } = state.metamask - var unconfTxList = txHelper(unapprovedTxs, unapprovedMsgs, unapprovedPersonalMsgs, network) - var has = unconfTxList.length > 0 - return has + const unconfActionList = txHelper(unapprovedTxs, unapprovedMsgs, unapprovedPersonalMsgs, network) + return unconfActionList } function indexForPending (state, txId) { - var unapprovedTxs = state.metamask.unapprovedTxs - var unapprovedMsgs = state.metamask.unapprovedMsgs - var unapprovedPersonalMsgs = state.metamask.unapprovedPersonalMsgs - var network = state.metamask.network - var unconfTxList = txHelper(unapprovedTxs, unapprovedMsgs, unapprovedPersonalMsgs, network) - let idx - unconfTxList.forEach((tx, i) => { - if (tx.id === txId) { - idx = i - } - }) - return idx + const unconfTxList = getUnconfActionList(state) + const match = unconfTxList.find((tx) => tx.id === txId) + const index = unconfTxList.indexOf(match) + return index } From 49d8877fd78b8251b6856292ca71a55773a74b0e Mon Sep 17 00:00:00 2001 From: kumavis Date: Fri, 31 Mar 2017 13:20:16 -0700 Subject: [PATCH 116/372] ui - startPopup returns store after boot --- app/scripts/popup-core.js | 27 ++++++++------------------- app/scripts/popup.js | 34 ++++++++++++++++++++++++---------- ui/app/reducers/app.js | 1 + ui/index.js | 23 +++++++++++++---------- 4 files changed, 46 insertions(+), 39 deletions(-) diff --git a/app/scripts/popup-core.js b/app/scripts/popup-core.js index b1e521a7a..1e5d70e8b 100644 --- a/app/scripts/popup-core.js +++ b/app/scripts/popup-core.js @@ -1,7 +1,8 @@ const EventEmitter = require('events').EventEmitter +const async = require('async') const Dnode = require('dnode') const Web3 = require('web3') -const MetaMaskUi = require('../../ui') +const launchMetamaskUi = require('../../ui') const StreamProvider = require('web3-stream-provider') const setupMultiplex = require('./lib/stream-utils.js').setupMultiplex @@ -9,9 +10,13 @@ const setupMultiplex = require('./lib/stream-utils.js').setupMultiplex module.exports = initializePopup -function initializePopup (connectionStream) { +function initializePopup ({ container, connectionStream }, cb) { // setup app - connectToAccountManager(connectionStream, setupApp) + async.waterfall([ + (cb) => connectToAccountManager(connectionStream, cb), + (accountManager, cb) => launchMetamaskUi({ container, accountManager }, cb), + ], cb) + } function connectToAccountManager (connectionStream, cb) { @@ -47,19 +52,3 @@ function setupControllerConnection (connectionStream, cb) { cb(null, accountManager) }) } - -function setupApp (err, accountManager) { - var container = document.getElementById('app-content') - if (err) { - container.innerHTML = '
The MetaMask app failed to load: please open and close MetaMask again to restart.
' - container.style.height = '80px' - log.error(err.stack) - throw err - } - - - MetaMaskUi({ - container: container, - accountManager: accountManager, - }) -} diff --git a/app/scripts/popup.js b/app/scripts/popup.js index 6606c9584..f6dcb829b 100644 --- a/app/scripts/popup.js +++ b/app/scripts/popup.js @@ -5,23 +5,37 @@ const PortStream = require('./lib/port-stream.js') const isPopupOrNotification = require('./lib/is-popup-or-notification') const extension = require('extensionizer') const NotificationManager = require('./lib/notification-manager') - const notificationManager = new NotificationManager() -var css = MetaMaskUiCss() +// inject css +const css = MetaMaskUiCss() injectCss(css) -var name = isPopupOrNotification() -closePopupIfOpen(name) -window.METAMASK_UI_TYPE = name +// identify window type (popup, notification) +const windowType = isPopupOrNotification() +global.METAMASK_UI_TYPE = windowType +closePopupIfOpen(windowType) + +// setup stream to background +const extensionPort = extension.runtime.connect({ windowType }) +const connectionStream = new PortStream(extensionPort) -var pluginPort = extension.runtime.connect({ name }) -var portStream = new PortStream(pluginPort) +// start ui +const container = document.getElementById('app-content') +startPopup({ container, connectionStream }, (err, store) => { + if (err) return displayCriticalError(err) +}) -startPopup(portStream) -function closePopupIfOpen (name) { - if (name !== 'notification') { +function closePopupIfOpen (windowType) { + if (windowType !== 'notification') { notificationManager.closePopup() } } + +function displayCriticalError(err) { + container.innerHTML = '
The MetaMask app failed to load: please open and close MetaMask again to restart.
' + container.style.height = '80px' + log.error(err.stack) + throw err +} diff --git a/ui/app/reducers/app.js b/ui/app/reducers/app.js index e0c001546..46b27fe25 100644 --- a/ui/app/reducers/app.js +++ b/ui/app/reducers/app.js @@ -34,6 +34,7 @@ function reduceApp (state, action) { seedWords, } + // default state var appState = extend({ menuOpen: false, currentView: seedWords ? seedConfView : defaultView, diff --git a/ui/index.js b/ui/index.js index 1a65f813c..e3648c374 100644 --- a/ui/index.js +++ b/ui/index.js @@ -4,27 +4,28 @@ const Root = require('./app/root') const actions = require('./app/actions') const configureStore = require('./app/store') const txHelper = require('./lib/tx-helper') -module.exports = launchApp +global.log = require('loglevel') -let debugMode = window.METAMASK_DEBUG -const log = require('loglevel') -window.log = log -log.setLevel(debugMode ? 'debug' : 'warn') +module.exports = launchMetamaskUi -function launchApp (opts) { + +log.setLevel(global.METAMASK_DEBUG ? 'debug' : 'warn') + +function launchMetamaskUi (opts, cb) { var accountManager = opts.accountManager actions._setBackgroundConnection(accountManager) // check if we are unlocked first accountManager.getState(function (err, metamaskState) { - if (err) throw err - startApp(metamaskState, accountManager, opts) + if (err) return cb(err) + const store = startApp(metamaskState, accountManager, opts) + cb(null, store) }) } function startApp (metamaskState, accountManager, opts) { // parse opts - var store = configureStore({ + const store = configureStore({ // metamaskState represents the cross-tab state metamask: metamaskState, @@ -37,7 +38,7 @@ function startApp (metamaskState, accountManager, opts) { }) // if unconfirmed txs, start on txConf page - var unapprovedTxsAll = txHelper(metamaskState.unapprovedTxs, metamaskState.unapprovedMsgs, metamaskState.unapprovedPersonalMsgs, metamaskState.network) + const unapprovedTxsAll = txHelper(metamaskState.unapprovedTxs, metamaskState.unapprovedMsgs, metamaskState.unapprovedPersonalMsgs, metamaskState.network) if (unapprovedTxsAll.length > 0) { store.dispatch(actions.showConfTxPage()) } @@ -53,4 +54,6 @@ function startApp (metamaskState, accountManager, opts) { store: store, } ), opts.container) + + return store } From 60a48e713fb341c0eba893bd0c37e02315c8b320 Mon Sep 17 00:00:00 2001 From: kumavis Date: Fri, 31 Mar 2017 13:32:47 -0700 Subject: [PATCH 117/372] ui - move popup auto-close after tx conf to ui entrypoint --- app/scripts/popup.js | 6 +++++- ui/app/reducers/app.js | 9 ++++----- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/app/scripts/popup.js b/app/scripts/popup.js index f6dcb829b..ce18dc422 100644 --- a/app/scripts/popup.js +++ b/app/scripts/popup.js @@ -17,13 +17,17 @@ global.METAMASK_UI_TYPE = windowType closePopupIfOpen(windowType) // setup stream to background -const extensionPort = extension.runtime.connect({ windowType }) +const extensionPort = extension.runtime.connect({ name: windowType }) const connectionStream = new PortStream(extensionPort) // start ui const container = document.getElementById('app-content') startPopup({ container, connectionStream }, (err, store) => { if (err) return displayCriticalError(err) + store.subscribe(() => { + const state = store.getState() + if (state.appState.shouldClose) notificationManager.closePopup() + }) }) diff --git a/ui/app/reducers/app.js b/ui/app/reducers/app.js index 46b27fe25..7ad1229e5 100644 --- a/ui/app/reducers/app.js +++ b/ui/app/reducers/app.js @@ -1,12 +1,10 @@ const extend = require('xtend') const actions = require('../actions') const txHelper = require('../../lib/tx-helper') -const NotificationManager = require('../../../app/scripts/lib/notification-manager') - -const notificationManager = new NotificationManager() module.exports = reduceApp + function reduceApp (state, action) { log.debug('App Reducer got ' + action.type) // clone and defaults @@ -36,6 +34,7 @@ function reduceApp (state, action) { // default state var appState = extend({ + shouldClose: false, menuOpen: false, currentView: seedWords ? seedConfView : defaultView, accountDetail: { @@ -331,9 +330,9 @@ function reduceApp (state, action) { }) } else { log.debug('attempting to close popup') - notificationManager.closePopup() - return extend(appState, { + // indicate notification should close + shouldClose: true, transForward: false, warning: null, currentView: { From aa06183c64b79316a4622e25791c29e785c7bce0 Mon Sep 17 00:00:00 2001 From: kumavis Date: Fri, 31 Mar 2017 18:04:13 -0700 Subject: [PATCH 118/372] ui - use global.platform for extension interaction --- app/scripts/platforms/extension.js | 4 ++++ app/scripts/popup.js | 4 ++++ ui/app/components/account-info-link.js | 3 +-- ui/app/components/buy-button-subview.js | 3 +-- ui/app/components/shift-list-item.js | 5 +---- ui/app/info.js | 24 ++++++++++-------------- 6 files changed, 21 insertions(+), 22 deletions(-) diff --git a/app/scripts/platforms/extension.js b/app/scripts/platforms/extension.js index cbb35768e..00c2aa275 100644 --- a/app/scripts/platforms/extension.js +++ b/app/scripts/platforms/extension.js @@ -14,6 +14,10 @@ class ExtensionPlatform { extension.tabs.create({ url }) } + getVersion () { + return extension.runtime.getManifest().version + } + } module.exports = ExtensionPlatform diff --git a/app/scripts/popup.js b/app/scripts/popup.js index ce18dc422..0fbde54b3 100644 --- a/app/scripts/popup.js +++ b/app/scripts/popup.js @@ -4,9 +4,13 @@ const startPopup = require('./popup-core') const PortStream = require('./lib/port-stream.js') const isPopupOrNotification = require('./lib/is-popup-or-notification') const extension = require('extensionizer') +const ExtensionPlatform = require('./platforms/extension') const NotificationManager = require('./lib/notification-manager') const notificationManager = new NotificationManager() +// create platform global +global.platform = new ExtensionPlatform() + // inject css const css = MetaMaskUiCss() injectCss(css) diff --git a/ui/app/components/account-info-link.js b/ui/app/components/account-info-link.js index 74bc7f40e..6526ab502 100644 --- a/ui/app/components/account-info-link.js +++ b/ui/app/components/account-info-link.js @@ -3,7 +3,6 @@ const h = require('react-hyperscript') const inherits = require('util').inherits const Tooltip = require('./tooltip') const genAccountLink = require('../../lib/account-link') -const extension = require('extensionizer') module.exports = AccountInfoLink @@ -35,7 +34,7 @@ AccountInfoLink.prototype.render = function () { style: { margin: '5px', }, - onClick () { extension.tabs.create({ url }) }, + onClick () { global.platform.openWindow({ url }) }, }), ]), ]) diff --git a/ui/app/components/buy-button-subview.js b/ui/app/components/buy-button-subview.js index 19279d1f6..2b1675b6d 100644 --- a/ui/app/components/buy-button-subview.js +++ b/ui/app/components/buy-button-subview.js @@ -5,7 +5,6 @@ const connect = require('react-redux').connect const actions = require('../actions') const CoinbaseForm = require('./coinbase-form') const ShapeshiftForm = require('./shapeshift-form') -const extension = require('extensionizer') const Loading = require('./loading') const TabBar = require('./tab-bar') @@ -142,7 +141,7 @@ BuyButtonSubview.prototype.formVersionSubview = function () { } BuyButtonSubview.prototype.navigateTo = function (url) { - extension.tabs.create({ url }) + global.platform.openWindow({ url }) } BuyButtonSubview.prototype.backButtonContext = function () { diff --git a/ui/app/components/shift-list-item.js b/ui/app/components/shift-list-item.js index da991f75f..96a7cba6e 100644 --- a/ui/app/components/shift-list-item.js +++ b/ui/app/components/shift-list-item.js @@ -4,7 +4,6 @@ const h = require('react-hyperscript') const connect = require('react-redux').connect const vreme = new (require('vreme')) const explorerLink = require('../../lib/explorer-link') -const extension = require('extensionizer') const actions = require('../actions') const addressSummary = require('../util').addressSummary @@ -172,9 +171,7 @@ ShiftListItem.prototype.renderInfo = function () { width: '200px', overflow: 'hidden', }, - onClick: () => extension.tabs.create({ - url, - }), + onClick: () => global.platform.openWindow({ url }), }, [ h('div', { style: { diff --git a/ui/app/info.js b/ui/app/info.js index fa51ae41c..a6fdeb315 100644 --- a/ui/app/info.js +++ b/ui/app/info.js @@ -3,7 +3,6 @@ const Component = require('react').Component const h = require('react-hyperscript') const connect = require('react-redux').connect const actions = require('./actions') -const extension = require('extensionizer') module.exports = connect(mapStateToProps)(InfoScreen) @@ -17,13 +16,8 @@ function InfoScreen () { } InfoScreen.prototype.render = function () { - var state = this.props - var manifest - try { - manifest = extension.runtime.getManifest() - } catch (e) { - manifest = { version: '2.0.0' } - } + const state = this.props + const version = global.platform.getVersion() return ( h('.flex-column.flex-grow', [ @@ -53,7 +47,7 @@ InfoScreen.prototype.render = function () { style: { marginBottom: '10px', }, - }, `Version: ${manifest.version}`), + }, `Version: ${version}`), ]), h('div', { @@ -110,10 +104,12 @@ InfoScreen.prototype.render = function () { onClick (event) { this.navigateTo(event.target.href) }, }, [ h('img.icon-size', { - src: manifest.icons['128'], + src: 'images/icon-128.png', style: { - filter: 'grayscale(100%)', /* IE6-9 */ - WebkitFilter: 'grayscale(100%)', /* Microsoft Edge and Firefox 35+ */ + // IE6-9 + filter: 'grayscale(100%)', + // Microsoft Edge and Firefox 35+ + WebkitFilter: 'grayscale(100%)', }, }), h('div.info', 'Visit our web site'), @@ -139,7 +135,7 @@ InfoScreen.prototype.render = function () { h('a.info', { target: '_blank', style: { width: '85vw' }, - onClick () { extension.tabs.create({url: 'mailto:help@metamask.io?subject=Feedback'}) }, + onClick () { this.navigateTo('mailto:help@metamask.io?subject=Feedback') }, }, 'Email us!'), ]), @@ -158,5 +154,5 @@ InfoScreen.prototype.render = function () { } InfoScreen.prototype.navigateTo = function (url) { - extension.tabs.create({ url }) + global.platform.openWindow({ url }) } From 86e882c51afca3a44bf20bcd1025161e214e6998 Mon Sep 17 00:00:00 2001 From: kumavis Date: Fri, 31 Mar 2017 18:41:51 -0700 Subject: [PATCH 119/372] notification-manager - rename show -> showPoup + make getPopup private --- app/scripts/background.js | 2 +- app/scripts/lib/notification-manager.js | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/app/scripts/background.js b/app/scripts/background.js index 5fb56d497..7211f1e0c 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -147,7 +147,7 @@ function setupController (initState) { // popup trigger function triggerUi () { - if (!popupIsOpen) notificationManager.show() + if (!popupIsOpen) notificationManager.showPopup() } // On first install, open a window to MetaMask website to how-it-works. diff --git a/app/scripts/lib/notification-manager.js b/app/scripts/lib/notification-manager.js index ff8a4b2e5..55e5b8dd2 100644 --- a/app/scripts/lib/notification-manager.js +++ b/app/scripts/lib/notification-manager.js @@ -9,8 +9,8 @@ class NotificationManager { // Public // - show () { - this.getPopup((err, popup) => { + showPopup () { + this._getPopup((err, popup) => { if (err) throw err if (popup) { @@ -31,15 +31,8 @@ class NotificationManager { }) } - getPopup (cb) { - this._getWindows((err, windows) => { - if (err) throw err - cb(null, this._getPopupIn(windows)) - }) - } - closePopup () { - this.getPopup((err, popup) => { + this._getPopup((err, popup) => { if (err) throw err if (!popup) return extension.windows.remove(popup.id, console.error) @@ -50,6 +43,13 @@ class NotificationManager { // Private // + _getPopup (cb) { + this._getWindows((err, windows) => { + if (err) throw err + cb(null, this._getPopupIn(windows)) + }) + } + _getWindows (cb) { // Ignore in test environment if (!extension.windows) { From 8d49d519c090d287484008346aba1df6d7ad77e7 Mon Sep 17 00:00:00 2001 From: frankiebee Date: Sun, 2 Apr 2017 22:01:44 -0700 Subject: [PATCH 120/372] Rename and move around files --- library/lib/setup-untrusted-connection.js | 27 -------- library/server.js | 9 +-- library/{sw-core.js => src/background.js} | 16 ++--- .../{controller.js => src/dapp-connection.js} | 4 +- .../lib}/index-db-controller.js | 0 library/{ => src}/lib/setup-iframe.js | 0 library/{ => src}/lib/setup-provider.js | 2 +- library/{index.js => src/mascara.js} | 0 library/{ => src}/popup.js | 8 +-- library/sw-controller.js | 64 ------------------- package.json | 1 + 11 files changed, 21 insertions(+), 110 deletions(-) delete mode 100644 library/lib/setup-untrusted-connection.js rename library/{sw-core.js => src/background.js} (87%) rename library/{controller.js => src/dapp-connection.js} (80%) rename library/{controllers => src/lib}/index-db-controller.js (100%) rename library/{ => src}/lib/setup-iframe.js (100%) rename library/{ => src}/lib/setup-provider.js (87%) rename library/{index.js => src/mascara.js} (100%) rename library/{ => src}/popup.js (68%) delete mode 100644 library/sw-controller.js diff --git a/library/lib/setup-untrusted-connection.js b/library/lib/setup-untrusted-connection.js deleted file mode 100644 index b2aeb7905..000000000 --- a/library/lib/setup-untrusted-connection.js +++ /dev/null @@ -1,27 +0,0 @@ - -/* -IFRAME - var pageStream = new LocalMessageDuplexStream({ - name: 'contentscript', - target: 'inpage', - }) -SERVICEWORKER - pageStream.on('error', console.error) - var pluginPort = extension.runtime.connect({name: 'contentscript'}) - var pluginStream = new PortStream(pluginPort) - pluginStream.on('error', console.error) -IFRAME --> SW - // forward communication plugin->inpage - pageStream.pipe(pluginStream).pipe(pageStream) -*/ - -module.exports = SetupUntrustedComunicationWithSW - -function SetupUntrustedComunicationWithSW (connectionStream, readySwStream) { - pageStream.on('error', console.error) - var pluginPort = extension.runtime.connect({name: 'contentscript'}) - var pluginStream = new PortStream(pluginPort) - pluginStream.on('error', console.error) - // forward communication plugin->inpage - pageStream.pipe(pluginStream).pipe(pageStream) -} diff --git a/library/server.js b/library/server.js index a284d3e05..67c89f11b 100644 --- a/library/server.js +++ b/library/server.js @@ -3,11 +3,12 @@ const browserify = require('browserify') const watchify = require('watchify') const babelify = require('babelify') -const zeroBundle = createBundle('./index.js') -const controllerBundle = createBundle('./controller.js') -const popupBundle = createBundle('./popup.js') +const zeroBundle = createBundle('./src/mascara.js') +const controllerBundle = createBundle('./src/dapp-connection.js') +const popupBundle = createBundle('./src/popup.js') +const swBuild = createBundle('./src/background.js') + const appBundle = createBundle('./example/index.js') -const swBuild = createBundle('./sw-core.js') // // Iframe Server diff --git a/library/sw-core.js b/library/src/background.js similarity index 87% rename from library/sw-core.js rename to library/src/background.js index d39bfdbfb..0af692ab8 100644 --- a/library/sw-core.js +++ b/library/src/background.js @@ -4,18 +4,18 @@ const pipe = require('pump') const SwGlobalListener = require('sw-stream/lib/sw-global-listener.js') const connectionListener = new SwGlobalListener(self) -const setupMultiplex = require('../app/scripts/lib/stream-utils.js').setupMultiplex -const PortStream = require('../app/scripts/lib/port-stream.js') +const setupMultiplex = require('../../app/scripts/lib/stream-utils.js').setupMultiplex +const PortStream = require('../../app/scripts/lib/port-stream.js') -const DbController = require('./controllers/index-db-controller') +const DbController = require('./lib/index-db-controller') -const MetamaskController = require('../app/scripts/metamask-controller') -const extension = {} //require('../app/scripts/lib/extension') +const MetamaskController = require('../../app/scripts/metamask-controller') +const extension = {} //require('../../app/scripts/lib/extension') const storeTransform = require('obs-store/lib/transform') -const Migrator = require('../app/scripts/lib/migrator/') -const migrations = require('../app/scripts/migrations/') -const firstTimeState = require('../app/scripts/first-time-state') +const Migrator = require('../../app/scripts/lib/migrator/') +const migrations = require('../../app/scripts/migrations/') +const firstTimeState = require('../../app/scripts/first-time-state') const STORAGE_KEY = 'metamask-config' const METAMASK_DEBUG = 'GULP_METAMASK_DEBUG' diff --git a/library/controller.js b/library/src/dapp-connection.js similarity index 80% rename from library/controller.js rename to library/src/dapp-connection.js index 1e9bc84d2..30680c9d7 100644 --- a/library/controller.js +++ b/library/src/dapp-connection.js @@ -1,14 +1,14 @@ const ParentStream = require('iframe-stream').ParentStream -const SWcontroller = require('./sw-controller') +const SWcontroller = require('client-sw-ready-event/lib/sw-client.js') const SwStream = require('sw-stream/lib/sw-stream.js') const SetupUntrustedComunication = ('./lib/setup-untrusted-connection.js') + const background = new SWcontroller({ fileName: '/popup/sw-build.js', }) const pageStream = new ParentStream() background.on('ready', (_) => { - // var inpageProvider = new MetamaskInpageProvider(SwStream(background.controller)) let swStream = SwStream({ serviceWorker: background.controller, context: 'dapp', diff --git a/library/controllers/index-db-controller.js b/library/src/lib/index-db-controller.js similarity index 100% rename from library/controllers/index-db-controller.js rename to library/src/lib/index-db-controller.js diff --git a/library/lib/setup-iframe.js b/library/src/lib/setup-iframe.js similarity index 100% rename from library/lib/setup-iframe.js rename to library/src/lib/setup-iframe.js diff --git a/library/lib/setup-provider.js b/library/src/lib/setup-provider.js similarity index 87% rename from library/lib/setup-provider.js rename to library/src/lib/setup-provider.js index 1b53e7f54..4f2432ae4 100644 --- a/library/lib/setup-provider.js +++ b/library/src/lib/setup-provider.js @@ -1,5 +1,5 @@ const setupIframe = require('./setup-iframe.js') -const MetamaskInpageProvider = require('../../app/scripts/lib/inpage-provider.js') +const MetamaskInpageProvider = require('../../../app/scripts/lib/inpage-provider.js') module.exports = getProvider diff --git a/library/index.js b/library/src/mascara.js similarity index 100% rename from library/index.js rename to library/src/mascara.js diff --git a/library/popup.js b/library/src/popup.js similarity index 68% rename from library/popup.js rename to library/src/popup.js index f294e5c69..3dcc508bf 100644 --- a/library/popup.js +++ b/library/src/popup.js @@ -1,10 +1,10 @@ const injectCss = require('inject-css') -const MetaMaskUiCss = require('../ui/css') +const MetaMaskUiCss = require('../../ui/css') const setupIframe = require('./lib/setup-iframe.js') -const MetamaskInpageProvider = require('../app/scripts/lib/inpage-provider.js') -const SWcontroller = require('./sw-controller') +const MetamaskInpageProvider = require('../../app/scripts/lib/inpage-provider.js') +const SWcontroller = require('client-sw-ready-event/lib/sw-client.js') const SwStream = require('sw-stream/lib/sw-stream.js') -const startPopup = require('../app/scripts/popup-core') +const startPopup = require('../../app/scripts/popup-core') var css = MetaMaskUiCss() diff --git a/library/sw-controller.js b/library/sw-controller.js deleted file mode 100644 index 1a7b1cad3..000000000 --- a/library/sw-controller.js +++ /dev/null @@ -1,64 +0,0 @@ -const EventEmitter = require('events') - -module.exports = class ClientSideServiceWorker extends EventEmitter{ - constructor (opts) { - super() - this.fileName = opts.fileName - this.startDelay = opts.startDelay - - this.serviceWorkerApi = navigator.serviceWorker - this.serviceWorkerApi.onmessage = (messageEvent) => this.emit('message', messageEvent) - this.serviceWorkerApi.onerror = (err) => this.emit('error', err) - this.on('message', (messageEvent) => {debugger}) - if (opts.initStart) this.startWorker() - } - - get controller () { - return this.sw || this.serviceWorkerApi.controller - } - - - startWorker () { - return this.registerWorker() - .then((sw) => { - this.sw = sw - this.sw.onerror = (err) => this.emit('error', err) - this.sw = sw - this.emit('ready', this.sw) - }) - .catch((err) => this.emit('error', err)) - } - - registerWorker () { - return this.serviceWorkerApi.register(this.fileName) - .then((registerdWorker) => { - return new Promise((resolve, reject) => { - let timeOutId = setTimeout(() => { - if (this.serviceWorkerApi.controller) return resolve(this.serviceWorkerApi.controller) - if (registerdWorker.active) return resolve(registerdWorker.active) - return reject(new Error('ClientSideServiceWorker: No controller found and onupdatefound timed out')) - }, this.startDelay || 1000 ) - - registerdWorker.onupdatefound = (event) => { - this.emit('updatefound') - registerdWorker.update() - } - }) - }) - } - - sendMessage (message) { - const self = this - return new Promise((resolve, reject) => { - var messageChannel = new MessageChannel() - messageChannel.port1.onmessage = (event) => { - if (event.data.err) { - reject(event.data.error) - } else { - resolve(event.data.data) - } - } - this.controller.postMessage(message, [messageChannel.port2]) - }) - } -} diff --git a/package.json b/package.json index d6c80496a..d69dcbb91 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "bip39": "^2.2.0", "browser-passworder": "^2.0.3", "browserify-derequire": "^0.9.4", + "client-sw-ready-event": "^1.0.2", "clone": "^1.0.2", "copy-to-clipboard": "^2.0.0", "debounce": "^1.0.0", From 12065a10aab05690b45c83648ee26f85f403b864 Mon Sep 17 00:00:00 2001 From: frankiebee Date: Mon, 3 Apr 2017 11:03:58 +0200 Subject: [PATCH 121/372] Make Popup close after approving a transaction --- library/src/popup.js | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/library/src/popup.js b/library/src/popup.js index 3dcc508bf..ef7759a81 100644 --- a/library/src/popup.js +++ b/library/src/popup.js @@ -1,28 +1,35 @@ const injectCss = require('inject-css') +const SWcontroller = require('client-sw-ready-event/lib/sw-client.js') +const SwStream = require('sw-stream/lib/sw-stream.js') const MetaMaskUiCss = require('../../ui/css') const setupIframe = require('./lib/setup-iframe.js') const MetamaskInpageProvider = require('../../app/scripts/lib/inpage-provider.js') -const SWcontroller = require('client-sw-ready-event/lib/sw-client.js') -const SwStream = require('sw-stream/lib/sw-stream.js') const startPopup = require('../../app/scripts/popup-core') - var css = MetaMaskUiCss() injectCss(css) +const container = document.getElementById('app-content') var name = 'popup' window.METAMASK_UI_TYPE = name -console.log('outside:open') - const background = new SWcontroller({ fileName: '/popup/sw-build.js', }) + +// Setup listener for when the service worker is read background.on('ready', (readSw) => { - let swStream = SwStream({ + let connectionStream = SwStream({ serviceWorker: background.controller, + context: name, + }) + startPopup({container, connectionStream}, (err, store) => { + if (err) return displayCriticalError(err) + store.subscribe(() => { + const state = store.getState() + if (state.appState.shouldClose) window.close() + }) }) - startPopup(swStream) }) background.startWorker() From 9736e96a93799f081ede01bc7ecd295fa264ffd5 Mon Sep 17 00:00:00 2001 From: frankiebee Date: Mon, 3 Apr 2017 11:04:48 +0200 Subject: [PATCH 122/372] Check to see if connection is from popup --- library/src/background.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/src/background.js b/library/src/background.js index 0af692ab8..dd0e13239 100644 --- a/library/src/background.js +++ b/library/src/background.js @@ -120,7 +120,7 @@ function setupController (initState, client) { }) function connectRemote (connectionStream, context) { - var isMetaMaskInternalProcess = (context !== 'dapp') + var isMetaMaskInternalProcess = (context === 'popup') if (isMetaMaskInternalProcess) { // communication with popup controller.setupTrustedCommunication(connectionStream, 'MetaMask') From 57655073f6ab4e93b9947302d46a20bd2c7288f1 Mon Sep 17 00:00:00 2001 From: frankiebee Date: Mon, 3 Apr 2017 11:08:31 +0200 Subject: [PATCH 123/372] Rename mascara root directory --- {library => mascara}/README.md | 0 {library => mascara}/example/index.html | 0 {library => mascara}/example/index.js | 0 {library => mascara}/server.js | 0 {library => mascara}/server/index.html | 0 {library => mascara}/src/background.js | 0 {library => mascara}/src/dapp-connection.js | 0 {library => mascara}/src/lib/index-db-controller.js | 0 {library => mascara}/src/lib/setup-iframe.js | 0 {library => mascara}/src/lib/setup-provider.js | 0 {library => mascara}/src/mascara.js | 0 {library => mascara}/src/popup.js | 0 12 files changed, 0 insertions(+), 0 deletions(-) rename {library => mascara}/README.md (100%) rename {library => mascara}/example/index.html (100%) rename {library => mascara}/example/index.js (100%) rename {library => mascara}/server.js (100%) rename {library => mascara}/server/index.html (100%) rename {library => mascara}/src/background.js (100%) rename {library => mascara}/src/dapp-connection.js (100%) rename {library => mascara}/src/lib/index-db-controller.js (100%) rename {library => mascara}/src/lib/setup-iframe.js (100%) rename {library => mascara}/src/lib/setup-provider.js (100%) rename {library => mascara}/src/mascara.js (100%) rename {library => mascara}/src/popup.js (100%) diff --git a/library/README.md b/mascara/README.md similarity index 100% rename from library/README.md rename to mascara/README.md diff --git a/library/example/index.html b/mascara/example/index.html similarity index 100% rename from library/example/index.html rename to mascara/example/index.html diff --git a/library/example/index.js b/mascara/example/index.js similarity index 100% rename from library/example/index.js rename to mascara/example/index.js diff --git a/library/server.js b/mascara/server.js similarity index 100% rename from library/server.js rename to mascara/server.js diff --git a/library/server/index.html b/mascara/server/index.html similarity index 100% rename from library/server/index.html rename to mascara/server/index.html diff --git a/library/src/background.js b/mascara/src/background.js similarity index 100% rename from library/src/background.js rename to mascara/src/background.js diff --git a/library/src/dapp-connection.js b/mascara/src/dapp-connection.js similarity index 100% rename from library/src/dapp-connection.js rename to mascara/src/dapp-connection.js diff --git a/library/src/lib/index-db-controller.js b/mascara/src/lib/index-db-controller.js similarity index 100% rename from library/src/lib/index-db-controller.js rename to mascara/src/lib/index-db-controller.js diff --git a/library/src/lib/setup-iframe.js b/mascara/src/lib/setup-iframe.js similarity index 100% rename from library/src/lib/setup-iframe.js rename to mascara/src/lib/setup-iframe.js diff --git a/library/src/lib/setup-provider.js b/mascara/src/lib/setup-provider.js similarity index 100% rename from library/src/lib/setup-provider.js rename to mascara/src/lib/setup-provider.js diff --git a/library/src/mascara.js b/mascara/src/mascara.js similarity index 100% rename from library/src/mascara.js rename to mascara/src/mascara.js diff --git a/library/src/popup.js b/mascara/src/popup.js similarity index 100% rename from library/src/popup.js rename to mascara/src/popup.js From 0adbc87316402349d462bbd7bc89e887d4b3b3c2 Mon Sep 17 00:00:00 2001 From: frankiebee Date: Mon, 3 Apr 2017 11:34:01 +0200 Subject: [PATCH 124/372] Update MetaMascara README --- mascara/README.md | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/mascara/README.md b/mascara/README.md index 6a6574dbd..7045ea8bb 100644 --- a/mascara/README.md +++ b/mascara/README.md @@ -3,22 +3,14 @@ start the dual servers (dapp + mascara) node server.js ``` -open the example dapp at `http://localhost:9002/` - -*You will need to build MetaMask in order for this to work* -``` -gulp dev -``` -to build MetaMask and have it live reload if you make changes - - ## First time use: -- navigate to: http://127.0.0.1:9001/popup/popup.html +- navigate to: http://localhost:9001/popup/popup.html - Create an Account - go back to http://localhost:9002/ - open devTools - click Sync Tx ### Todos -- Look into using [Service Workers](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API) + + - [ ] Figure out user flows and UI redesign From 5baa4fd8968092d605a8768c9934cf4e9b8a820d Mon Sep 17 00:00:00 2001 From: frankiebee Date: Mon, 3 Apr 2017 13:29:19 +0200 Subject: [PATCH 125/372] Update todos on README --- mascara/README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/mascara/README.md b/mascara/README.md index 7045ea8bb..cdeb4795c 100644 --- a/mascara/README.md +++ b/mascara/README.md @@ -14,3 +14,7 @@ node server.js ### Todos - [ ] Figure out user flows and UI redesign + - [ ] Figure out FireFox + Standing problems: + - [ ] IndexDb + From 16c24db13b1c5da7afc222022af4bebf0f42f3aa Mon Sep 17 00:00:00 2001 From: frankiebee Date: Tue, 4 Apr 2017 11:08:10 +0200 Subject: [PATCH 126/372] Fix first install flow --- mascara/src/background.js | 13 +++---------- mascara/src/lib/index-db-controller.js | 2 +- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/mascara/src/background.js b/mascara/src/background.js index dd0e13239..fbfffa9b8 100644 --- a/mascara/src/background.js +++ b/mascara/src/background.js @@ -1,5 +1,4 @@ global.window = global -const asyncQ = require('async-q') const pipe = require('pump') const SwGlobalListener = require('sw-stream/lib/sw-global-listener.js') @@ -39,18 +38,12 @@ console.log('inside:open') let diskStore const dbController = new DbController({ key: STORAGE_KEY, - global: self, version: 2, }) -asyncQ.waterfall([ - () => loadStateFromPersistence(), - (initState) => setupController(initState), -]) +loadStateFromPersistence() +.then((initState) => setupController(initState)) .then(() => console.log('MetaMask initialization complete.')) -.catch((err) => { - console.log('WHILE SETTING UP:') - console.error(err) -}) +.catch((err) => console.error('WHILE SETTING UP:', err)) // initialization flow diff --git a/mascara/src/lib/index-db-controller.js b/mascara/src/lib/index-db-controller.js index 041ddae2e..8db1d5d21 100644 --- a/mascara/src/lib/index-db-controller.js +++ b/mascara/src/lib/index-db-controller.js @@ -38,7 +38,7 @@ module.exports = class IndexDbController extends EventEmitter { if (!data) { return this._add('dataStore', this.initialState) .then(() => this.get('dataStore')) - .then((versionedData) => Promise.resolve(versionedData.data)) + .then((versionedData) => Promise.resolve(versionedData)) } return Promise.resolve(data) }) From 5e939540e575147ae7d1bca16e36701ce0a61340 Mon Sep 17 00:00:00 2001 From: frankiebee Date: Tue, 4 Apr 2017 14:25:52 +0200 Subject: [PATCH 127/372] Remove out of date comments --- mascara/src/background.js | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/mascara/src/background.js b/mascara/src/background.js index fbfffa9b8..6f9fb3d13 100644 --- a/mascara/src/background.js +++ b/mascara/src/background.js @@ -80,12 +80,6 @@ function setupController (initState, client) { }) global.metamaskController = controller - // setup state persistence - // pipe( - // controller.store, - // storeTransform(versionifyData), - // diskStore - // ) controller.store.subscribe((state) => { versionifyData(state) .then((versionedData) => dbController.put(versionedData)) @@ -104,9 +98,7 @@ function setupController (initState, client) { // // connect to other contexts // - /* - need to write a service worker stream for this - */ + connectionListener.on('remote', (portStream, messageEvent) => { console.log('REMOTE CONECTION FOUND***********') connectRemote(portStream, messageEvent.data.context) From f80d1ce3e61fec8948d418a470c00226773b3b38 Mon Sep 17 00:00:00 2001 From: frankiebee Date: Tue, 4 Apr 2017 14:55:48 +0200 Subject: [PATCH 128/372] Switch auto-faucet from XMLHttpRequest to fetch --- app/scripts/lib/auto-faucet.js | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/app/scripts/lib/auto-faucet.js b/app/scripts/lib/auto-faucet.js index 1e86f735e..73b73dfe6 100644 --- a/app/scripts/lib/auto-faucet.js +++ b/app/scripts/lib/auto-faucet.js @@ -4,9 +4,15 @@ const env = process.env.METAMASK_ENV module.exports = function (address) { if (METAMASK_DEBUG || env === 'test') return // Don't faucet in development or test - var http = new XMLHttpRequest() - var data = address - http.open('POST', uri, true) - http.setRequestHeader('Content-type', 'application/rawdata') - http.send(data) + let data = address + let headers = new Headers() + headers.append('Content-type', 'application/rawdata') + fetch(uri, { + method: 'POST', + headers, + body: data, + }) + .catch((err) => { + console.error(err) + }) } From 0a7b388b39d86039e4e4cf5df8dfe5104e7b6137 Mon Sep 17 00:00:00 2001 From: frankiebee Date: Tue, 4 Apr 2017 18:38:56 +0200 Subject: [PATCH 129/372] Fix faucet to only autoFaucet first account --- app/scripts/keyring-controller.js | 1 + app/scripts/metamask-controller.js | 2 ++ 2 files changed, 3 insertions(+) diff --git a/app/scripts/keyring-controller.js b/app/scripts/keyring-controller.js index 72f613641..16df6efa6 100644 --- a/app/scripts/keyring-controller.js +++ b/app/scripts/keyring-controller.js @@ -324,6 +324,7 @@ class KeyringController extends EventEmitter { if (!firstAccount) throw new Error('KeyringController - No account found on keychain.') const hexAccount = normalizeAddress(firstAccount) this.emit('newAccount', hexAccount) + this.emit('newVault', hexAccount) return this.setupAccounts(accounts) }) .then(this.persistAllKeyrings.bind(this)) diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index af8b4688d..5f71fc369 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -79,6 +79,8 @@ module.exports = class MetamaskController extends EventEmitter { }) this.keyringController.on('newAccount', (address) => { this.preferencesController.setSelectedAddress(address) + }) + this.keyringController.on('newVault', (address) => { autoFaucet(address) }) From 4779999bfc7e03eedf3fd2702f7f448d751218f8 Mon Sep 17 00:00:00 2001 From: kumavis Date: Tue, 4 Apr 2017 11:13:07 -0700 Subject: [PATCH 130/372] clean - metamask controller - remove log --- app/scripts/metamask-controller.js | 1 - 1 file changed, 1 deletion(-) diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index d360e7d95..040c093df 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -167,7 +167,6 @@ module.exports = class MetamaskController extends EventEmitter { rpcUrl: this.configManager.getCurrentRpcAddress(), // account mgmt getAccounts: (cb) => { - console.log('METAMASK CONTROLLER: getAccounts was called') let selectedAddress = this.preferencesController.getSelectedAddress() let result = selectedAddress ? [selectedAddress] : [] cb(null, result) From 5a91adf7d802097805938e3d54fe7256b19724d1 Mon Sep 17 00:00:00 2001 From: kumavis Date: Tue, 4 Apr 2017 18:23:46 -0700 Subject: [PATCH 131/372] add platforms to mascara + move buyEther window open to ui --- app/scripts/lib/auto-faucet.js | 8 +++++--- app/scripts/lib/buy-eth-url.js | 19 +++++++++++++++++++ app/scripts/metamask-controller.js | 19 ++----------------- app/scripts/platforms/sw.js | 24 ++++++++++++++++++++++++ app/scripts/platforms/window.js | 22 ++++++++++++++++++++++ mascara/src/background.js | 8 +++++++- mascara/src/popup.js | 5 +++++ ui/app/actions.js | 8 +++++--- ui/app/components/buy-button-subview.js | 13 +++++++------ ui/app/components/coinbase-form.js | 2 +- 10 files changed, 97 insertions(+), 31 deletions(-) create mode 100644 app/scripts/lib/buy-eth-url.js create mode 100644 app/scripts/platforms/sw.js create mode 100644 app/scripts/platforms/window.js diff --git a/app/scripts/lib/auto-faucet.js b/app/scripts/lib/auto-faucet.js index 73b73dfe6..1e059cf73 100644 --- a/app/scripts/lib/auto-faucet.js +++ b/app/scripts/lib/auto-faucet.js @@ -3,9 +3,11 @@ const METAMASK_DEBUG = 'GULP_METAMASK_DEBUG' const env = process.env.METAMASK_ENV module.exports = function (address) { - if (METAMASK_DEBUG || env === 'test') return // Don't faucet in development or test - let data = address - let headers = new Headers() + // Don't faucet in development or test + if (METAMASK_DEBUG || env === 'test') return + global.log.info('auto-fauceting:', address) + const data = address + const headers = new Headers() headers.append('Content-type', 'application/rawdata') fetch(uri, { method: 'POST', diff --git a/app/scripts/lib/buy-eth-url.js b/app/scripts/lib/buy-eth-url.js new file mode 100644 index 000000000..91a1ec322 --- /dev/null +++ b/app/scripts/lib/buy-eth-url.js @@ -0,0 +1,19 @@ +module.exports = getBuyEthUrl + +function getBuyEthUrl({ network, amount, address }){ + let url + switch (network) { + case '1': + url = `https://buy.coinbase.com/?code=9ec56d01-7e81-5017-930c-513daa27bb6a&amount=${amount}&address=${address}&crypto_currency=ETH` + break + + case '3': + url = 'https://faucet.metamask.io/' + break + + case '42': + url = 'https://github.com/kovan-testnet/faucet' + break + } + return url +} \ No newline at end of file diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index edb9bbbd9..2b8fc9cb8 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -23,6 +23,7 @@ const ConfigManager = require('./lib/config-manager') const autoFaucet = require('./lib/auto-faucet') const nodeify = require('./lib/nodeify') const accountImporter = require('./account-import-strategies') +const getBuyEthUrl = require('./lib/buy-eth-url') const version = require('../manifest.json').version @@ -614,24 +615,8 @@ module.exports = class MetamaskController extends EventEmitter { buyEth (address, amount) { if (!amount) amount = '5' - const network = this.getNetworkState() - let url - - switch (network) { - case '1': - url = `https://buy.coinbase.com/?code=9ec56d01-7e81-5017-930c-513daa27bb6a&amount=${amount}&address=${address}&crypto_currency=ETH` - break - - case '3': - url = 'https://faucet.metamask.io/' - break - - case '42': - url = 'https://github.com/kovan-testnet/faucet' - break - } - + const url = getBuyEthUrl({ network, address, amount }) if (url) this.platform.openWindow({ url }) } diff --git a/app/scripts/platforms/sw.js b/app/scripts/platforms/sw.js new file mode 100644 index 000000000..007d8dc5b --- /dev/null +++ b/app/scripts/platforms/sw.js @@ -0,0 +1,24 @@ + +class SwPlatform { + + // + // Public + // + + reload () { + // you cant actually do this + global.location.reload() + } + + openWindow ({ url }) { + // this doesnt actually work + global.open(url, '_blank') + } + + getVersion () { + return '' + } + +} + +module.exports = SwPlatform diff --git a/app/scripts/platforms/window.js b/app/scripts/platforms/window.js new file mode 100644 index 000000000..1527c008b --- /dev/null +++ b/app/scripts/platforms/window.js @@ -0,0 +1,22 @@ + +class WindowPlatform { + + // + // Public + // + + reload () { + global.location.reload() + } + + openWindow ({ url }) { + global.open(url, '_blank') + } + + getVersion () { + return '' + } + +} + +module.exports = WindowPlatform diff --git a/mascara/src/background.js b/mascara/src/background.js index 6f9fb3d13..957570050 100644 --- a/mascara/src/background.js +++ b/mascara/src/background.js @@ -8,6 +8,7 @@ const PortStream = require('../../app/scripts/lib/port-stream.js') const DbController = require('./lib/index-db-controller') +const SwPlatform = require('../../app/scripts/platforms/sw') const MetamaskController = require('../../app/scripts/metamask-controller') const extension = {} //require('../../app/scripts/lib/extension') @@ -17,7 +18,8 @@ const migrations = require('../../app/scripts/migrations/') const firstTimeState = require('../../app/scripts/first-time-state') const STORAGE_KEY = 'metamask-config' -const METAMASK_DEBUG = 'GULP_METAMASK_DEBUG' +// const METAMASK_DEBUG = 'GULP_METAMASK_DEBUG' +const METAMASK_DEBUG = true let popupIsOpen = false const log = require('loglevel') @@ -70,7 +72,11 @@ function setupController (initState, client) { // MetaMask Controller // + const platform = new SwPlatform() + const controller = new MetamaskController({ + // platform specific implementation + platform, // User confirmation callbacks: showUnconfirmedMessage: noop, unlockAccountMessage: noop, diff --git a/mascara/src/popup.js b/mascara/src/popup.js index ef7759a81..b740e81a5 100644 --- a/mascara/src/popup.js +++ b/mascara/src/popup.js @@ -4,8 +4,13 @@ const SwStream = require('sw-stream/lib/sw-stream.js') const MetaMaskUiCss = require('../../ui/css') const setupIframe = require('./lib/setup-iframe.js') const MetamaskInpageProvider = require('../../app/scripts/lib/inpage-provider.js') +const MetamascaraPlatform = require('../../app/scripts/platforms/window') const startPopup = require('../../app/scripts/popup-core') +// create platform global +global.platform = new MetamascaraPlatform() + + var css = MetaMaskUiCss() injectCss(css) const container = document.getElementById('app-content') diff --git a/ui/app/actions.js b/ui/app/actions.js index 60b4c6f03..8934299e7 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -1,3 +1,5 @@ +const getBuyEthUrl = require('../../app/scripts/lib/buy-eth-url') + var actions = { _setBackgroundConnection: _setBackgroundConnection, @@ -833,10 +835,10 @@ function showSendPage () { } } -function buyEth (address, amount) { +function buyEth (opts) { return (dispatch) => { - log.debug(`background.buyEth`) - background.buyEth(address, amount) + const url = getBuyEthUrl(opts) + global.platform.openWindow({ url }) dispatch({ type: actions.BUY_ETH, }) diff --git a/ui/app/components/buy-button-subview.js b/ui/app/components/buy-button-subview.js index 2b1675b6d..6810303e1 100644 --- a/ui/app/components/buy-button-subview.js +++ b/ui/app/components/buy-button-subview.js @@ -104,7 +104,8 @@ BuyButtonSubview.prototype.render = function () { } BuyButtonSubview.prototype.formVersionSubview = function () { - if (this.props.network === '1') { + const network = this.props.network + if (network === '1') { if (this.props.buyView.formView.coinbase) { return h(CoinbaseForm, this.props) } else if (this.props.buyView.formView.shapeshift) { @@ -123,15 +124,15 @@ BuyButtonSubview.prototype.formVersionSubview = function () { marginBottom: '15px', }, }, 'In order to access this feature, please switch to the Main Network'), - ((this.props.network === '3') || (this.props.network === '42')) ? h('h3.text-transform-uppercase', 'or go to the') : null, - (this.props.network === '3') ? h('button.text-transform-uppercase', { - onClick: () => this.props.dispatch(actions.buyEth()), + ((network === '3') || (network === '42')) ? h('h3.text-transform-uppercase', 'or go to the') : null, + (network === '3') ? h('button.text-transform-uppercase', { + onClick: () => this.props.dispatch(actions.buyEth({ network })), style: { marginTop: '15px', }, }, 'Ropsten Test Faucet') : null, - (this.props.network === '42') ? h('button.text-transform-uppercase', { - onClick: () => this.props.dispatch(actions.buyEth()), + (network === '42') ? h('button.text-transform-uppercase', { + onClick: () => this.props.dispatch(actions.buyEth({ network })), style: { marginTop: '15px', }, diff --git a/ui/app/components/coinbase-form.js b/ui/app/components/coinbase-form.js index 40f5719bb..fd5816a21 100644 --- a/ui/app/components/coinbase-form.js +++ b/ui/app/components/coinbase-form.js @@ -112,7 +112,7 @@ CoinbaseForm.prototype.toCoinbase = function () { var message if (isValidAddress(address) && isValidAmountforCoinBase(amount).valid) { - props.dispatch(actions.buyEth(address, props.buyView.amount)) + props.dispatch(actions.buyEth({ network: '1', address, amount: props.buyView.amount })) } else if (!isValidAmountforCoinBase(amount).valid) { message = isValidAmountforCoinBase(amount).message return props.dispatch(actions.displayWarning(message)) From 9b9570fd2be5a7bdaf37a663628147976d8c94b4 Mon Sep 17 00:00:00 2001 From: kumavis Date: Tue, 4 Apr 2017 18:48:33 -0700 Subject: [PATCH 132/372] auto-faucet - only skip faucet on explicit test environment --- app/scripts/lib/auto-faucet.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/scripts/lib/auto-faucet.js b/app/scripts/lib/auto-faucet.js index 1e059cf73..38d54ba5e 100644 --- a/app/scripts/lib/auto-faucet.js +++ b/app/scripts/lib/auto-faucet.js @@ -4,7 +4,7 @@ const env = process.env.METAMASK_ENV module.exports = function (address) { // Don't faucet in development or test - if (METAMASK_DEBUG || env === 'test') return + if (METAMASK_DEBUG === true || env === 'test') return global.log.info('auto-fauceting:', address) const data = address const headers = new Headers() From 8b146663483849cb76219bbf610c9ff6c5add68d Mon Sep 17 00:00:00 2001 From: kumavis Date: Tue, 4 Apr 2017 22:43:55 -0700 Subject: [PATCH 133/372] clean - remove unused extension ref --- ui/app/components/transaction-list-item.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/ui/app/components/transaction-list-item.js b/ui/app/components/transaction-list-item.js index 0052d9c70..9fef52355 100644 --- a/ui/app/components/transaction-list-item.js +++ b/ui/app/components/transaction-list-item.js @@ -7,7 +7,6 @@ const addressSummary = require('../util').addressSummary const explorerLink = require('../../lib/explorer-link') const CopyButton = require('./copyButton') const vreme = new (require('vreme')) -const extension = require('extensionizer') const Tooltip = require('./tooltip') const TransactionIcon = require('./transaction-list-item-icon') @@ -50,7 +49,7 @@ TransactionListItem.prototype.render = function () { event.stopPropagation() if (!transaction.hash || !isLinkable) return var url = explorerLink(transaction.hash, parseInt(network)) - extension.tabs.create({ url }) + global.platform.openWindow({ url }) }, style: { padding: '20px 0', @@ -63,7 +62,7 @@ TransactionListItem.prototype.render = function () { event.stopPropagation() if (!isTx || isPending) return var url = `https://metamask.github.io/eth-tx-viz/?tx=${transaction.hash}` - extension.tabs.create({ url }) + global.platform.openWindow({ url }) }, }, [ h(TransactionIcon, { txParams, transaction, isTx, isMsg }), From 0cfad78f5decbce04a7fa56e8f80e4fcecd12a79 Mon Sep 17 00:00:00 2001 From: kumavis Date: Tue, 4 Apr 2017 22:45:39 -0700 Subject: [PATCH 134/372] mascara - rename things + break out mascara asset server --- mascara/example/{index.js => app.js} | 0 mascara/example/{ => app}/index.html | 4 +- mascara/example/server.js | 31 ++++++ mascara/{server => proxy}/index.html | 2 +- mascara/server.js | 103 ------------------- mascara/server/index.js | 32 ++++++ mascara/server/util.js | 45 ++++++++ mascara/src/lib/setup-provider.js | 4 +- mascara/src/mascara.js | 15 ++- mascara/src/{dapp-connection.js => proxy.js} | 2 +- mascara/ui/index.html | 11 ++ 11 files changed, 135 insertions(+), 114 deletions(-) rename mascara/example/{index.js => app.js} (100%) rename mascara/example/{ => app}/index.html (79%) create mode 100644 mascara/example/server.js rename mascara/{server => proxy}/index.html (88%) delete mode 100644 mascara/server.js create mode 100644 mascara/server/index.js create mode 100644 mascara/server/util.js rename mascara/src/{dapp-connection.js => proxy.js} (94%) create mode 100644 mascara/ui/index.html diff --git a/mascara/example/index.js b/mascara/example/app.js similarity index 100% rename from mascara/example/index.js rename to mascara/example/app.js diff --git a/mascara/example/index.html b/mascara/example/app/index.html similarity index 79% rename from mascara/example/index.html rename to mascara/example/app/index.html index 47d6da34f..02323e5f9 100644 --- a/mascara/example/index.html +++ b/mascara/example/app/index.html @@ -3,15 +3,13 @@ - MetaMask ZeroClient Example - + - \ No newline at end of file diff --git a/mascara/example/server.js b/mascara/example/server.js new file mode 100644 index 000000000..d39c19600 --- /dev/null +++ b/mascara/example/server.js @@ -0,0 +1,31 @@ +const express = require('express') +const createMetamascaraServer = require('../server/') +const createBundle = require('../server/util').createBundle +const serveBundle = require('../server/util').serveBundle + +// +// Iframe Server +// + +const mascaraServer = createMetamascaraServer() + +// start the server +const mascaraPort = 9001 +mascaraServer.listen(mascaraPort) +console.log(`Mascara service listening on port ${mascaraPort}`) + + +// +// Dapp Server +// + +const dappServer = express() + +// serve dapp bundle +serveBundle(dappServer, '/app.js', createBundle(require.resolve('./app.js'))) +dappServer.use(express.static(__dirname + '/app/')) + +// start the server +const dappPort = '9002' +dappServer.listen(dappPort) +console.log(`Dapp listening on port ${dappPort}`) diff --git a/mascara/server/index.html b/mascara/proxy/index.html similarity index 88% rename from mascara/server/index.html rename to mascara/proxy/index.html index 2308dd98b..b83fc41af 100644 --- a/mascara/server/index.html +++ b/mascara/proxy/index.html @@ -15,6 +15,6 @@ Hello! I am the MetaMask iframe. - + \ No newline at end of file diff --git a/mascara/server.js b/mascara/server.js deleted file mode 100644 index 67c89f11b..000000000 --- a/mascara/server.js +++ /dev/null @@ -1,103 +0,0 @@ -const express = require('express') -const browserify = require('browserify') -const watchify = require('watchify') -const babelify = require('babelify') - -const zeroBundle = createBundle('./src/mascara.js') -const controllerBundle = createBundle('./src/dapp-connection.js') -const popupBundle = createBundle('./src/popup.js') -const swBuild = createBundle('./src/background.js') - -const appBundle = createBundle('./example/index.js') - -// -// Iframe Server -// - -const iframeServer = express() - -// serve popup window -iframeServer.get('/popup/scripts/popup.js', function(req, res){ - res.send(popupBundle.latest) -}) -iframeServer.use('/popup', express.static('../dist/chrome')) - -// serve controller bundle -iframeServer.get('/controller.js', function(req, res){ - res.send(controllerBundle.latest) -}) -iframeServer.get('/popup/sw-build.js', function(req, res){ - console.log('/sw-build.js') - res.setHeader('Content-Type', 'application/javascript') - res.send(swBuild.latest) -}) - -// serve background controller -iframeServer.use(express.static('./server')) - -// start the server -const mascaraPort = 9001 -iframeServer.listen(mascaraPort) -console.log(`Mascara service listening on port ${mascaraPort}`) - - -// -// Dapp Server -// - -const dappServer = express() - -// serve metamask-lib bundle -dappServer.get('/zero.js', function(req, res){ - res.send(zeroBundle.latest) -}) - -// serve dapp bundle -dappServer.get('/app.js', function(req, res){ - res.send(appBundle.latest) -}) - -// serve static -dappServer.use(express.static('./example')) - -// start the server -const dappPort = '9002' -dappServer.listen(dappPort) -console.log(`Dapp listening on port ${dappPort}`) - -// -// util -// - -function serveBundle(entryPoint){ - const bundle = createBundle(entryPoint) - return function(req, res){ - res.send(bundle.latest) - } -} - -function createBundle(entryPoint){ - - var bundleContainer = {} - - var bundler = browserify({ - entries: [entryPoint], - cache: {}, - packageCache: {}, - plugin: [watchify], - }) - - bundler.on('update', bundle) - bundle() - - return bundleContainer - - function bundle() { - bundler.bundle(function(err, result){ - if (err) throw err - console.log(`Bundle updated! (${entryPoint})`) - bundleContainer.latest = result.toString() - }) - } - -} diff --git a/mascara/server/index.js b/mascara/server/index.js new file mode 100644 index 000000000..61dc61a02 --- /dev/null +++ b/mascara/server/index.js @@ -0,0 +1,32 @@ +const express = require('express') +const createBundle = require('./util').createBundle +const serveBundle = require('./util').serveBundle + +module.exports = createMetamascaraServer + + +function createMetamascaraServer(){ + + // start bundlers + const metamascaraBundle = createBundle('./src/mascara.js') + const proxyBundle = createBundle('./src/proxy.js') + const uiBundle = createBundle('./src/popup.js') + const backgroundBuild = createBundle('./src/background.js') + + // serve bundles + const server = express() + // ui window + serveBundle(server, '/ui.js', uiBundle) + server.use(express.static(__dirname+'/../ui/')) + server.use(express.static(__dirname+'/../../dist/chrome')) + // metamascara + serveBundle(server, '/metamascara.js', metamascaraBundle) + // proxy + serveBundle(server, '/proxy/proxy.js', proxyBundle) + server.use('/proxy/', express.static(__dirname+'/../proxy')) + // background + serveBundle(server, '/background.js', backgroundBuild) + + return server + +} diff --git a/mascara/server/util.js b/mascara/server/util.js new file mode 100644 index 000000000..6e25b35d8 --- /dev/null +++ b/mascara/server/util.js @@ -0,0 +1,45 @@ +const browserify = require('browserify') +const watchify = require('watchify') + +module.exports = { + serveBundle, + createBundle, +} + + +function serveBundle(server, path, bundle){ + server.get(path, function(req, res){ + res.setHeader('Content-Type', 'application/javascript; charset=UTF-8') + res.send(bundle.latest) + }) +} + +function createBundle(entryPoint){ + + var bundleContainer = {} + + var bundler = browserify({ + entries: [entryPoint], + cache: {}, + packageCache: {}, + plugin: [watchify], + }) + + bundler.on('update', bundle) + bundle() + + return bundleContainer + + function bundle() { + bundler.bundle(function(err, result){ + if (err) { + console.log(`Bundle failed! (${entryPoint})`) + console.error(err) + return + } + console.log(`Bundle updated! (${entryPoint})`) + bundleContainer.latest = result.toString() + }) + } + +} diff --git a/mascara/src/lib/setup-provider.js b/mascara/src/lib/setup-provider.js index 4f2432ae4..62335b18d 100644 --- a/mascara/src/lib/setup-provider.js +++ b/mascara/src/lib/setup-provider.js @@ -4,14 +4,14 @@ const MetamaskInpageProvider = require('../../../app/scripts/lib/inpage-provider module.exports = getProvider -function getProvider(){ +function getProvider(opts){ if (global.web3) { console.log('MetaMask ZeroClient - using environmental web3 provider') return global.web3.currentProvider } console.log('MetaMask ZeroClient - injecting zero-client iframe!') var iframeStream = setupIframe({ - zeroClientProvider: 'http://localhost:9001', + zeroClientProvider: opts.mascaraUrl, sandboxAttributes: ['allow-scripts', 'allow-popups', 'allow-same-origin'], container: document.body, }) diff --git a/mascara/src/mascara.js b/mascara/src/mascara.js index 759353c1b..f9bed7e52 100644 --- a/mascara/src/mascara.js +++ b/mascara/src/mascara.js @@ -1,15 +1,22 @@ const Web3 = require('web3') const setupProvider = require('./lib/setup-provider.js') +const MASACARA_DOMAIN = 'http://localhost:9001' + // // setup web3 // -var provider = setupProvider() -hijackProvider(provider) + +var provider = setupProvider({ + mascaraUrl: MASACARA_DOMAIN + '/proxy/', +}) +instrumentForUserInteractionTriggers(provider) + var web3 = new Web3(provider) web3.setProvider = function(){ console.log('MetaMask - overrode web3.setProvider') } + // // // export web3 @@ -25,12 +32,12 @@ var shouldPop = false window.addEventListener('click', function(){ if (!shouldPop) return shouldPop = false - window.open('http://localhost:9001/popup/popup.html', '', 'width=360 height=500') + window.open(MASACARA_DOMAIN, '', 'width=360 height=500') console.log('opening window...') }) -function hijackProvider(provider){ +function instrumentForUserInteractionTriggers(provider){ var _super = provider.sendAsync.bind(provider) provider.sendAsync = function(payload, cb){ if (payload.method === 'eth_sendTransaction') { diff --git a/mascara/src/dapp-connection.js b/mascara/src/proxy.js similarity index 94% rename from mascara/src/dapp-connection.js rename to mascara/src/proxy.js index 30680c9d7..e580076c1 100644 --- a/mascara/src/dapp-connection.js +++ b/mascara/src/proxy.js @@ -4,7 +4,7 @@ const SwStream = require('sw-stream/lib/sw-stream.js') const SetupUntrustedComunication = ('./lib/setup-untrusted-connection.js') const background = new SWcontroller({ - fileName: '/popup/sw-build.js', + fileName: '/background.js', }) const pageStream = new ParentStream() diff --git a/mascara/ui/index.html b/mascara/ui/index.html new file mode 100644 index 000000000..c5eeb05ef --- /dev/null +++ b/mascara/ui/index.html @@ -0,0 +1,11 @@ + + + + + MetaMask Plugin + + +
+ + + \ No newline at end of file From 1aac162b462424f5b6ce6685f337670368bd10b5 Mon Sep 17 00:00:00 2001 From: kumavis Date: Tue, 4 Apr 2017 23:08:46 -0700 Subject: [PATCH 135/372] mascara - rename popup to ui --- mascara/server/index.js | 2 +- mascara/src/{popup.js => ui.js} | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) rename mascara/src/{popup.js => ui.js} (93%) diff --git a/mascara/server/index.js b/mascara/server/index.js index 61dc61a02..9fd664eee 100644 --- a/mascara/server/index.js +++ b/mascara/server/index.js @@ -10,7 +10,7 @@ function createMetamascaraServer(){ // start bundlers const metamascaraBundle = createBundle('./src/mascara.js') const proxyBundle = createBundle('./src/proxy.js') - const uiBundle = createBundle('./src/popup.js') + const uiBundle = createBundle('./src/ui.js') const backgroundBuild = createBundle('./src/background.js') // serve bundles diff --git a/mascara/src/popup.js b/mascara/src/ui.js similarity index 93% rename from mascara/src/popup.js rename to mascara/src/ui.js index b740e81a5..c4866867b 100644 --- a/mascara/src/popup.js +++ b/mascara/src/ui.js @@ -19,7 +19,7 @@ var name = 'popup' window.METAMASK_UI_TYPE = name const background = new SWcontroller({ - fileName: '/popup/sw-build.js', + fileName: '/background.js', }) // Setup listener for when the service worker is read @@ -38,4 +38,4 @@ background.on('ready', (readSw) => { }) background.startWorker() -console.log('hello from /library/popup.js') +console.log('hello from MetaMascara ui!') From c1e14cd75b3a873ffa9e691911818b68b3d62e07 Mon Sep 17 00:00:00 2001 From: kumavis Date: Wed, 5 Apr 2017 10:24:30 -0700 Subject: [PATCH 136/372] build - add envify to bundle process --- package.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.json b/package.json index b96197915..78a8520f4 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ ] } ], + "envify", "brfs" ] }, @@ -127,6 +128,7 @@ "clone": "^1.0.2", "deep-freeze-strict": "^1.1.1", "del": "^2.2.0", + "envify": "^4.0.0", "fs-promise": "^1.0.0", "gulp": "github:gulpjs/gulp#4.0", "gulp-if": "^2.0.1", From a6c6ecf2759d300f660da4e9058b29a0f5f07a8b Mon Sep 17 00:00:00 2001 From: kumavis Date: Wed, 5 Apr 2017 10:27:04 -0700 Subject: [PATCH 137/372] mascara - mascara lib - read mascara origin from env var --- mascara/src/mascara.js | 31 ++++++++++++++----------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/mascara/src/mascara.js b/mascara/src/mascara.js index f9bed7e52..0fc2868e1 100644 --- a/mascara/src/mascara.js +++ b/mascara/src/mascara.js @@ -1,44 +1,41 @@ const Web3 = require('web3') const setupProvider = require('./lib/setup-provider.js') -const MASACARA_DOMAIN = 'http://localhost:9001' +const MASCARA_ORIGIN = process.env.MASCARA_ORIGIN || 'http://localhost:9001' +console.log('MASCARA_ORIGIN:', MASCARA_ORIGIN) // // setup web3 // -var provider = setupProvider({ - mascaraUrl: MASACARA_DOMAIN + '/proxy/', +const provider = setupProvider({ + mascaraUrl: MASCARA_ORIGIN + '/proxy/', }) instrumentForUserInteractionTriggers(provider) -var web3 = new Web3(provider) -web3.setProvider = function(){ - console.log('MetaMask - overrode web3.setProvider') -} +const web3 = new Web3(provider) +global.web3 = web3 // -// -// export web3 +// ui stuff // -global.web3 = web3 +let shouldPop = false +window.addEventListener('click', maybeTriggerPopup) // -// ui stuff +// util // -var shouldPop = false -window.addEventListener('click', function(){ +function maybeTriggerPopup(){ if (!shouldPop) return shouldPop = false - window.open(MASACARA_DOMAIN, '', 'width=360 height=500') + window.open(MASCARA_ORIGIN, '', 'width=360 height=500') console.log('opening window...') -}) - +} function instrumentForUserInteractionTriggers(provider){ - var _super = provider.sendAsync.bind(provider) + const _super = provider.sendAsync.bind(provider) provider.sendAsync = function(payload, cb){ if (payload.method === 'eth_sendTransaction') { console.log('saw send') From 088bfd5e8d4d9735b5a4cbb892d776d0ecf71dc0 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Wed, 5 Apr 2017 13:38:33 -0700 Subject: [PATCH 138/372] Fix account detail transition fix. --- ui/app/account-detail.js | 2 +- ui/app/accounts/index.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/app/account-detail.js b/ui/app/account-detail.js index 514be7f25..018e74893 100644 --- a/ui/app/account-detail.js +++ b/ui/app/account-detail.js @@ -60,7 +60,7 @@ AccountDetailScreen.prototype.render = function () { // header - identicon + nav h('div', { style: { - marginTop: '20px', + paddingTop: '20px', display: 'flex', justifyContent: 'flex-start', alignItems: 'flex-start', diff --git a/ui/app/accounts/index.js b/ui/app/accounts/index.js index ae69d9297..9584ebad9 100644 --- a/ui/app/accounts/index.js +++ b/ui/app/accounts/index.js @@ -97,7 +97,7 @@ AccountsScreen.prototype.render = function () { style: { display: 'flex', height: '40px', - paddint: '10px', + padding: '10px', justifyContent: 'center', alignItems: 'center', }, From ddbbc645d3cb5bb04d0afb7b30696788ab93480c Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Wed, 5 Apr 2017 13:39:49 -0700 Subject: [PATCH 139/372] Changelog. --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a54cf886..fd754cadd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Current Master - Popup new transactions in Firefox. +- Fix transition issue from account detail screen. ## 3.5.2 2017-3-28 From 4b8324620e4f850b1674692cf8c45a4ca70101b4 Mon Sep 17 00:00:00 2001 From: kumavis Date: Wed, 5 Apr 2017 22:59:25 -0700 Subject: [PATCH 140/372] mascara - ready to deploy via docker --- .dockerignore | 3 +++ Dockerfile | 22 ++++++++++++++++++++++ docker-compose.yml | 11 +++++++++++ mascara/server/index.js | 8 ++++---- 4 files changed, 40 insertions(+), 4 deletions(-) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100644 docker-compose.yml diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..ea6720feb --- /dev/null +++ b/.dockerignore @@ -0,0 +1,3 @@ +node_modules +builds +development \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..d06f5377b --- /dev/null +++ b/Dockerfile @@ -0,0 +1,22 @@ +FROM node:6 +MAINTAINER kumavis + +# setup app dir +RUN mkdir -p /www/ +WORKDIR /www/ + +# install dependencies +COPY ./package.json /www/package.json +RUN npm install + +# copy over app dir +COPY ./ /www/ + +# run tests +# RUN npm test + +# build app +RUN npm run dist + +# start server +CMD node mascara/example/server.js diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 000000000..58c046c32 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,11 @@ +metamascara: + build: ./ + restart: always + ports: + - "9001" + environment: + MASCARA_ORIGIN: "https://zero.metamask.io" + VIRTUAL_PORT: "9001" + VIRTUAL_HOST: "zero.metamask.io" + LETSENCRYPT_HOST: "zero.metamask.io" + LETSENCRYPT_EMAIL: "admin@metamask.io" \ No newline at end of file diff --git a/mascara/server/index.js b/mascara/server/index.js index 9fd664eee..14e3fa18e 100644 --- a/mascara/server/index.js +++ b/mascara/server/index.js @@ -8,10 +8,10 @@ module.exports = createMetamascaraServer function createMetamascaraServer(){ // start bundlers - const metamascaraBundle = createBundle('./src/mascara.js') - const proxyBundle = createBundle('./src/proxy.js') - const uiBundle = createBundle('./src/ui.js') - const backgroundBuild = createBundle('./src/background.js') + const metamascaraBundle = createBundle(__dirname + '/../src/mascara.js') + const proxyBundle = createBundle(__dirname + '/../src/proxy.js') + const uiBundle = createBundle(__dirname + '/../src/ui.js') + const backgroundBuild = createBundle(__dirname + '/../src/background.js') // serve bundles const server = express() From 719d0f4ad5ba7da2b807641a2afdc969d74d913c Mon Sep 17 00:00:00 2001 From: frankiebee Date: Thu, 6 Apr 2017 18:56:13 +0200 Subject: [PATCH 141/372] Ping service worker to keep it from going idle --- mascara/src/proxy.js | 10 +++++++++- mascara/src/ui.js | 5 ++++- package.json | 2 +- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/mascara/src/proxy.js b/mascara/src/proxy.js index e580076c1..8d91e4553 100644 --- a/mascara/src/proxy.js +++ b/mascara/src/proxy.js @@ -3,10 +3,18 @@ const SWcontroller = require('client-sw-ready-event/lib/sw-client.js') const SwStream = require('sw-stream/lib/sw-stream.js') const SetupUntrustedComunication = ('./lib/setup-untrusted-connection.js') +let intervalDelay = Math.floor(Math.random() * (60000 - 1000)) + 1000 const background = new SWcontroller({ fileName: '/background.js', + letBeIdle: false, + intervalDelay, }) - +window.onfocus = () => { + background.sendMessage('wakeUp') + .catch((err) => { + console.error('problem with wake up', err) + }) +} const pageStream = new ParentStream() background.on('ready', (_) => { let swStream = SwStream({ diff --git a/mascara/src/ui.js b/mascara/src/ui.js index c4866867b..37bb043b8 100644 --- a/mascara/src/ui.js +++ b/mascara/src/ui.js @@ -18,10 +18,13 @@ const container = document.getElementById('app-content') var name = 'popup' window.METAMASK_UI_TYPE = name +let intervalDelay = Math.floor(Math.random() * (60000 - 1000)) + 1000 + const background = new SWcontroller({ fileName: '/background.js', + letBeIdel: false, + intervalDelay, }) - // Setup listener for when the service worker is read background.on('ready', (readSw) => { let connectionStream = SwStream({ diff --git a/package.json b/package.json index 78a8520f4..c0e1057a4 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "bluebird": "^3.5.0", "browser-passworder": "^2.0.3", "browserify-derequire": "^0.9.4", - "client-sw-ready-event": "^1.0.2", + "client-sw-ready-event": "^2.1.1", "clone": "^1.0.2", "copy-to-clipboard": "^2.0.0", "debounce": "^1.0.0", From 478a07ea81556a62a39c229d4feecf9c0d9a7cdb Mon Sep 17 00:00:00 2001 From: frankiebee Date: Thu, 6 Apr 2017 18:58:41 +0200 Subject: [PATCH 142/372] remove unnecessary listner --- mascara/src/proxy.js | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/mascara/src/proxy.js b/mascara/src/proxy.js index 8d91e4553..5ead75f94 100644 --- a/mascara/src/proxy.js +++ b/mascara/src/proxy.js @@ -9,12 +9,7 @@ const background = new SWcontroller({ letBeIdle: false, intervalDelay, }) -window.onfocus = () => { - background.sendMessage('wakeUp') - .catch((err) => { - console.error('problem with wake up', err) - }) -} + const pageStream = new ParentStream() background.on('ready', (_) => { let swStream = SwStream({ From 94df1681133fde038c76095d45e4203ca6afe4e8 Mon Sep 17 00:00:00 2001 From: frankiebee Date: Thu, 6 Apr 2017 19:32:45 +0200 Subject: [PATCH 143/372] Bump sw-client version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c0e1057a4..67648d781 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "bluebird": "^3.5.0", "browser-passworder": "^2.0.3", "browserify-derequire": "^0.9.4", - "client-sw-ready-event": "^2.1.1", + "client-sw-ready-event": "^2.2.1", "clone": "^1.0.2", "copy-to-clipboard": "^2.0.0", "debounce": "^1.0.0", From a1c39006de566205870cdedbd9cb33d0a933f5a4 Mon Sep 17 00:00:00 2001 From: frankiebee Date: Thu, 6 Apr 2017 20:01:56 +0200 Subject: [PATCH 144/372] Fix wakeUps: fix spelling cut wakeup time in half --- mascara/src/proxy.js | 2 +- mascara/src/ui.js | 8 ++++---- package.json | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/mascara/src/proxy.js b/mascara/src/proxy.js index 5ead75f94..2316f4e18 100644 --- a/mascara/src/proxy.js +++ b/mascara/src/proxy.js @@ -3,7 +3,7 @@ const SWcontroller = require('client-sw-ready-event/lib/sw-client.js') const SwStream = require('sw-stream/lib/sw-stream.js') const SetupUntrustedComunication = ('./lib/setup-untrusted-connection.js') -let intervalDelay = Math.floor(Math.random() * (60000 - 1000)) + 1000 +let intervalDelay = Math.floor(Math.random() * (20000 - 1000)) + 1000 const background = new SWcontroller({ fileName: '/background.js', letBeIdle: false, diff --git a/mascara/src/ui.js b/mascara/src/ui.js index 37bb043b8..4948a1727 100644 --- a/mascara/src/ui.js +++ b/mascara/src/ui.js @@ -18,12 +18,13 @@ const container = document.getElementById('app-content') var name = 'popup' window.METAMASK_UI_TYPE = name -let intervalDelay = Math.floor(Math.random() * (60000 - 1000)) + 1000 +let intervalDelay = Math.floor(Math.random() * (30000 - 1000)) + 1000 const background = new SWcontroller({ fileName: '/background.js', - letBeIdel: false, - intervalDelay, + letBeIdle: false, + // intervalDelay, + wakeUpInterval: 30000 }) // Setup listener for when the service worker is read background.on('ready', (readSw) => { @@ -39,6 +40,5 @@ background.on('ready', (readSw) => { }) }) }) - background.startWorker() console.log('hello from MetaMascara ui!') diff --git a/package.json b/package.json index 67648d781..b892653fa 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "bluebird": "^3.5.0", "browser-passworder": "^2.0.3", "browserify-derequire": "^0.9.4", - "client-sw-ready-event": "^2.2.1", + "client-sw-ready-event": "^3.0.1", "clone": "^1.0.2", "copy-to-clipboard": "^2.0.0", "debounce": "^1.0.0", From e9a223931abb3a583f4c640cfe3791d7d9944436 Mon Sep 17 00:00:00 2001 From: frankiebee Date: Thu, 6 Apr 2017 21:51:42 +0200 Subject: [PATCH 145/372] match intervals --- mascara/src/proxy.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mascara/src/proxy.js b/mascara/src/proxy.js index 2316f4e18..809fc7402 100644 --- a/mascara/src/proxy.js +++ b/mascara/src/proxy.js @@ -3,10 +3,11 @@ const SWcontroller = require('client-sw-ready-event/lib/sw-client.js') const SwStream = require('sw-stream/lib/sw-stream.js') const SetupUntrustedComunication = ('./lib/setup-untrusted-connection.js') -let intervalDelay = Math.floor(Math.random() * (20000 - 1000)) + 1000 +let intervalDelay = Math.floor(Math.random() * (30000 - 1000)) + 1000 const background = new SWcontroller({ fileName: '/background.js', letBeIdle: false, + wakeUpInterval: 30000 intervalDelay, }) From c46f9a7105fa2c5a1f918476f77a573d542fc4e7 Mon Sep 17 00:00:00 2001 From: frankiebee Date: Thu, 6 Apr 2017 21:55:36 +0200 Subject: [PATCH 146/372] uncomment intervalDelay --- mascara/src/ui.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mascara/src/ui.js b/mascara/src/ui.js index 4948a1727..2873017cb 100644 --- a/mascara/src/ui.js +++ b/mascara/src/ui.js @@ -23,7 +23,7 @@ let intervalDelay = Math.floor(Math.random() * (30000 - 1000)) + 1000 const background = new SWcontroller({ fileName: '/background.js', letBeIdle: false, - // intervalDelay, + intervalDelay, wakeUpInterval: 30000 }) // Setup listener for when the service worker is read From 65979ba86e68264657a593cd106d40fbe700b216 Mon Sep 17 00:00:00 2001 From: frankiebee Date: Thu, 6 Apr 2017 21:57:02 +0200 Subject: [PATCH 147/372] fix syntax --- mascara/src/proxy.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mascara/src/proxy.js b/mascara/src/proxy.js index 809fc7402..ec5665240 100644 --- a/mascara/src/proxy.js +++ b/mascara/src/proxy.js @@ -7,7 +7,7 @@ let intervalDelay = Math.floor(Math.random() * (30000 - 1000)) + 1000 const background = new SWcontroller({ fileName: '/background.js', letBeIdle: false, - wakeUpInterval: 30000 + wakeUpInterval: 30000, intervalDelay, }) From 94984437dbfb19d8a39750a0331e09a380456c53 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Mon, 17 Apr 2017 13:05:40 -0700 Subject: [PATCH 148/372] Remove kovan notice. --- notices/archive/notice_1.md | 1 - notices/notices.json | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) delete mode 100644 notices/archive/notice_1.md diff --git a/notices/archive/notice_1.md b/notices/archive/notice_1.md deleted file mode 100644 index 488b60cc9..000000000 --- a/notices/archive/notice_1.md +++ /dev/null @@ -1 +0,0 @@ -MetaMask now lists a new network on our dropdown list: Kovan, a [Proof of Authority](https://github.com/paritytech/parity/wiki/Proof-of-Authority-Chains) testchain managed by several blockchain organizations such as Digix, Etherscan, and Parity. It is designed to be a more stable and reliable testnet alternative to Ropsten and was created in response to recent attacks that slowed down the Ropsten network. You can read more about Kovan [here](https://medium.com/@Digix/announcing-kovan-a-stable-ethereum-public-testnet-10ac7cb6c85f#.6o8sz8cct) and [here](https://medium.com/@Digix/letter-from-the-ceo-some-context-regarding-kovan-7b5121adb901#.kfv7zhw83). As with Ropsten, the default remote node to connect to Kovan is managed by Infura. diff --git a/notices/notices.json b/notices/notices.json index 5503c7855..9f28b32a6 100644 --- a/notices/notices.json +++ b/notices/notices.json @@ -1 +1 @@ -[{"read":false,"date":"Thu Feb 09 2017","title":"Terms of Use","body":"# Terms of Use #\n\n**THIS AGREEMENT IS SUBJECT TO BINDING ARBITRATION AND A WAIVER OF CLASS ACTION RIGHTS AS DETAILED IN SECTION 13. PLEASE READ THE AGREEMENT CAREFULLY.**\n\n_Our Terms of Use have been updated as of September 5, 2016_\n\n## 1. Acceptance of Terms ##\n\nMetaMask provides a platform for managing Ethereum (or \"ETH\") accounts, and allowing ordinary websites to interact with the Ethereum blockchain, while keeping the user in control over what transactions they approve, through our website located at[ ](http://metamask.io)[https://metamask.io/](https://metamask.io/) and browser plugin (the \"Site\") — which includes text, images, audio, code and other materials (collectively, the “Content”) and all of the features, and services provided. The Site, and any other features, tools, materials, or other services offered from time to time by MetaMask are referred to here as the “Service.” Please read these Terms of Use (the “Terms” or “Terms of Use”) carefully before using the Service. By using or otherwise accessing the Services, or clicking to accept or agree to these Terms where that option is made available, you (1) accept and agree to these Terms (2) consent to the collection, use, disclosure and other handling of information as described in our Privacy Policy and (3) any additional terms, rules and conditions of participation issued by MetaMask from time to time. If you do not agree to the Terms, then you may not access or use the Content or Services.\n\n## 2. Modification of Terms of Use ##\n\nExcept for Section 13, providing for binding arbitration and waiver of class action rights, MetaMask reserves the right, at its sole discretion, to modify or replace the Terms of Use at any time. The most current version of these Terms will be posted on our Site. You shall be responsible for reviewing and becoming familiar with any such modifications. Use of the Services by you after any modification to the Terms constitutes your acceptance of the Terms of Use as modified.\n\n\n\n## 3. Eligibility ##\n\nYou hereby represent and warrant that you are fully able and competent to enter into the terms, conditions, obligations, affirmations, representations and warranties set forth in these Terms and to abide by and comply with these Terms.\n\nMetaMask is a global platform and by accessing the Content or Services, you are representing and warranting that, you are of the legal age of majority in your jurisdiction as is required to access such Services and Content and enter into arrangements as provided by the Service. You further represent that you are otherwise legally permitted to use the service in your jurisdiction including owning cryptographic tokens of value, and interacting with the Services or Content in any way. You further represent you are responsible for ensuring compliance with the laws of your jurisdiction and acknowledge that MetaMask is not liable for your compliance with such laws.\n\n## 4 Account Password and Security ##\n\nWhen setting up an account within MetaMask, you will be responsible for keeping your own account secrets, which may be a twelve-word seed phrase, an account file, or other locally stored secret information. MetaMask encrypts this information locally with a password you provide, that we never send to our servers. You agree to (a) never use the same password for MetaMask that you have ever used outside of this service; (b) keep your secret information and password confidential and do not share them with anyone else; (c) immediately notify MetaMask of any unauthorized use of your account or breach of security. MetaMask cannot and will not be liable for any loss or damage arising from your failure to comply with this section.\n\n## 5. Representations, Warranties, and Risks ##\n\n### 5.1. Warranty Disclaimer ###\n\nYou expressly understand and agree that your use of the Service is at your sole risk. The Service (including the Service and the Content) are provided on an \"AS IS\" and \"as available\" basis, without warranties of any kind, either express or implied, including, without limitation, implied warranties of merchantability, fitness for a particular purpose or non-infringement. You acknowledge that MetaMask has no control over, and no duty to take any action regarding: which users gain access to or use the Service; what effects the Content may have on you; how you may interpret or use the Content; or what actions you may take as a result of having been exposed to the Content. You release MetaMask from all liability for you having acquired or not acquired Content through the Service. MetaMask makes no representations concerning any Content contained in or accessed through the Service, and MetaMask will not be responsible or liable for the accuracy, copyright compliance, legality or decency of material contained in or accessed through the Service.\n\n### 5.2 Sophistication and Risk of Cryptographic Systems ###\n\nBy utilizing the Service or interacting with the Content or platform in any way, you represent that you understand the inherent risks associated with cryptographic systems; and warrant that you have an understanding of the usage and intricacies of native cryptographic tokens, like Ether (ETH) and Bitcoin (BTC), smart contract based tokens such as those that follow the Ethereum Token Standard (https://github.com/ethereum/EIPs/issues/20), and blockchain-based software systems.\n\n### 5.3 Risk of Regulatory Actions in One or More Jurisdictions ###\n\nMetaMask and ETH could be impacted by one or more regulatory inquiries or regulatory action, which could impede or limit the ability of MetaMask to continue to develop, or which could impede or limit your ability to access or use the Service or Ethereum blockchain.\n\n### 5.4 Risk of Weaknesses or Exploits in the Field of Cryptography ###\n\nYou acknowledge and understand that Cryptography is a progressing field. Advances in code cracking or technical advances such as the development of quantum computers may present risks to cryptocurrencies and Services of Content, which could result in the theft or loss of your cryptographic tokens or property. To the extent possible, MetaMask intends to update the protocol underlying Services to account for any advances in cryptography and to incorporate additional security measures, but does not guarantee or otherwise represent full security of the system. By using the Service or accessing Content, you acknowledge these inherent risks.\n\n### 5.5 Volatility of Crypto Currencies ###\n\nYou understand that Ethereum and other blockchain technologies and associated currencies or tokens are highly volatile due to many factors including but not limited to adoption, speculation, technology and security risks. You also acknowledge that the cost of transacting on such technologies is variable and may increase at any time causing impact to any activities taking place on the Ethereum blockchain. You acknowledge these risks and represent that MetaMask cannot be held liable for such fluctuations or increased costs.\n\n### 5.6 Application Security ###\n\nYou acknowledge that Ethereum applications are code subject to flaws and acknowledge that you are solely responsible for evaluating any code provided by the Services or Content and the trustworthiness of any third-party websites, products, smart-contracts, or Content you access or use through the Service. You further expressly acknowledge and represent that Ethereum applications can be written maliciously or negligently, that MetaMask cannot be held liable for your interaction with such applications and that such applications may cause the loss of property or even identity. This warning and others later provided by MetaMask in no way evidence or represent an on-going duty to alert you to all of the potential risks of utilizing the Service or Content.\n\n## 6. Indemnity ##\n\nYou agree to release and to indemnify, defend and hold harmless MetaMask and its parents, subsidiaries, affiliates and agencies, as well as the officers, directors, employees, shareholders and representatives of any of the foregoing entities, from and against any and all losses, liabilities, expenses, damages, costs (including attorneys’ fees and court costs) claims or actions of any kind whatsoever arising or resulting from your use of the Service, your violation of these Terms of Use, and any of your acts or omissions that implicate publicity rights, defamation or invasion of privacy. MetaMask reserves the right, at its own expense, to assume exclusive defense and control of any matter otherwise subject to indemnification by you and, in such case, you agree to cooperate with MetaMask in the defense of such matter.\n\n## 7. Limitation on liability ##\n\nYOU ACKNOWLEDGE AND AGREE THAT YOU ASSUME FULL RESPONSIBILITY FOR YOUR USE OF THE SITE AND SERVICE. YOU ACKNOWLEDGE AND AGREE THAT ANY INFORMATION YOU SEND OR RECEIVE DURING YOUR USE OF THE SITE AND SERVICE MAY NOT BE SECURE AND MAY BE INTERCEPTED OR LATER ACQUIRED BY UNAUTHORIZED PARTIES. YOU ACKNOWLEDGE AND AGREE THAT YOUR USE OF THE SITE AND SERVICE IS AT YOUR OWN RISK. RECOGNIZING SUCH, YOU UNDERSTAND AND AGREE THAT, TO THE FULLEST EXTENT PERMITTED BY APPLICABLE LAW, NEITHER METAMASK NOR ITS SUPPLIERS OR LICENSORS WILL BE LIABLE TO YOU FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY OR OTHER DAMAGES OF ANY KIND, INCLUDING WITHOUT LIMITATION DAMAGES FOR LOSS OF PROFITS, GOODWILL, USE, DATA OR OTHER TANGIBLE OR INTANGIBLE LOSSES OR ANY OTHER DAMAGES BASED ON CONTRACT, TORT, STRICT LIABILITY OR ANY OTHER THEORY (EVEN IF METAMASK HAD BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES), RESULTING FROM THE SITE OR SERVICE; THE USE OR THE INABILITY TO USE THE SITE OR SERVICE; UNAUTHORIZED ACCESS TO OR ALTERATION OF YOUR TRANSMISSIONS OR DATA; STATEMENTS OR CONDUCT OF ANY THIRD PARTY ON THE SITE OR SERVICE; ANY ACTIONS WE TAKE OR FAIL TO TAKE AS A RESULT OF COMMUNICATIONS YOU SEND TO US; HUMAN ERRORS; TECHNICAL MALFUNCTIONS; FAILURES, INCLUDING PUBLIC UTILITY OR TELEPHONE OUTAGES; OMISSIONS, INTERRUPTIONS, LATENCY, DELETIONS OR DEFECTS OF ANY DEVICE OR NETWORK, PROVIDERS, OR SOFTWARE (INCLUDING, BUT NOT LIMITED TO, THOSE THAT DO NOT PERMIT PARTICIPATION IN THE SERVICE); ANY INJURY OR DAMAGE TO COMPUTER EQUIPMENT; INABILITY TO FULLY ACCESS THE SITE OR SERVICE OR ANY OTHER WEBSITE; THEFT, TAMPERING, DESTRUCTION, OR UNAUTHORIZED ACCESS TO, IMAGES OR OTHER CONTENT OF ANY KIND; DATA THAT IS PROCESSED LATE OR INCORRECTLY OR IS INCOMPLETE OR LOST; TYPOGRAPHICAL, PRINTING OR OTHER ERRORS, OR ANY COMBINATION THEREOF; OR ANY OTHER MATTER RELATING TO THE SITE OR SERVICE.\n\nSOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF CERTAIN WARRANTIES OR THE LIMITATION OR EXCLUSION OF LIABILITY FOR INCIDENTAL OR CONSEQUENTIAL DAMAGES. ACCORDINGLY, SOME OF THE ABOVE LIMITATIONS MAY NOT APPLY TO YOU.\n\n## 8. Our Proprietary Rights ##\n\nAll title, ownership and intellectual property rights in and to the Service are owned by MetaMask or its licensors. You acknowledge and agree that the Service contains proprietary and confidential information that is protected by applicable intellectual property and other laws. Except as expressly authorized by MetaMask, you agree not to copy, modify, rent, lease, loan, sell, distribute, perform, display or create derivative works based on the Service, in whole or in part. MetaMask issues a license for MetaMask, found [here](https://github.com/MetaMask/metamask-plugin/blob/master/LICENSE). For information on other licenses utilized in the development of MetaMask, please see our attribution page at: [https://metamask.io/attributions.html](https://metamask.io/attributions.html)\n\n## 9. Links ##\n\nThe Service provides, or third parties may provide, links to other World Wide Web or accessible sites, applications or resources. Because MetaMask has no control over such sites, applications and resources, you acknowledge and agree that MetaMask is not responsible for the availability of such external sites, applications or resources, and does not endorse and is not responsible or liable for any content, advertising, products or other materials on or available from such sites or resources. You further acknowledge and agree that MetaMask shall not be responsible or liable, directly or indirectly, for any damage or loss caused or alleged to be caused by or in connection with use of or reliance on any such content, goods or services available on or through any such site or resource.\n\n## 10. Termination and Suspension ##\n\nMetaMask may terminate or suspend all or part of the Service and your MetaMask access immediately, without prior notice or liability, if you breach any of the terms or conditions of the Terms. Upon termination of your access, your right to use the Service will immediately cease.\n\nThe following provisions of the Terms survive any termination of these Terms: INDEMNITY; WARRANTY DISCLAIMERS; LIMITATION ON LIABILITY; OUR PROPRIETARY RIGHTS; LINKS; TERMINATION; NO THIRD PARTY BENEFICIARIES; BINDING ARBITRATION AND CLASS ACTION WAIVER; GENERAL INFORMATION.\n\n## 11. No Third Party Beneficiaries ##\n\nYou agree that, except as otherwise expressly provided in these Terms, there shall be no third party beneficiaries to the Terms.\n\n## 12. Notice and Procedure For Making Claims of Copyright Infringement ##\n\nIf you believe that your copyright or the copyright of a person on whose behalf you are authorized to act has been infringed, please provide MetaMask’s Copyright Agent a written Notice containing the following information:\n\n· an electronic or physical signature of the person authorized to act on behalf of the owner of the copyright or other intellectual property interest;\n\n· a description of the copyrighted work or other intellectual property that you claim has been infringed;\n\n· a description of where the material that you claim is infringing is located on the Service;\n\n· your address, telephone number, and email address;\n\n· a statement by you that you have a good faith belief that the disputed use is not authorized by the copyright owner, its agent, or the law;\n\n· a statement by you, made under penalty of perjury, that the above information in your Notice is accurate and that you are the copyright or intellectual property owner or authorized to act on the copyright or intellectual property owner's behalf.\n\nMetaMask’s Copyright Agent can be reached at:\n\nEmail: copyright [at] metamask [dot] io\n\nMail:\n\nAttention:\n\nMetaMask Copyright ℅ ConsenSys\n\n49 Bogart Street\n\nBrooklyn, NY 11206\n\n## 13. Binding Arbitration and Class Action Waiver ##\n\nPLEASE READ THIS SECTION CAREFULLY – IT MAY SIGNIFICANTLY AFFECT YOUR LEGAL RIGHTS, INCLUDING YOUR RIGHT TO FILE A LAWSUIT IN COURT\n\n### 13.1 Initial Dispute Resolution ###\n\nThe parties shall use their best efforts to engage directly to settle any dispute, claim, question, or disagreement and engage in good faith negotiations which shall be a condition to either party initiating a lawsuit or arbitration.\n\n### 13.2 Binding Arbitration ###\n\nIf the parties do not reach an agreed upon solution within a period of 30 days from the time informal dispute resolution under the Initial Dispute Resolution provision begins, then either party may initiate binding arbitration as the sole means to resolve claims, subject to the terms set forth below. Specifically, all claims arising out of or relating to these Terms (including their formation, performance and breach), the parties’ relationship with each other and/or your use of the Service shall be finally settled by binding arbitration administered by the American Arbitration Association in accordance with the provisions of its Commercial Arbitration Rules and the supplementary procedures for consumer related disputes of the American Arbitration Association (the \"AAA\"), excluding any rules or procedures governing or permitting class actions.\n\nThe arbitrator, and not any federal, state or local court or agency, shall have exclusive authority to resolve all disputes arising out of or relating to the interpretation, applicability, enforceability or formation of these Terms, including, but not limited to any claim that all or any part of these Terms are void or voidable, or whether a claim is subject to arbitration. The arbitrator shall be empowered to grant whatever relief would be available in a court under law or in equity. The arbitrator’s award shall be written, and binding on the parties and may be entered as a judgment in any court of competent jurisdiction.\n\nThe parties understand that, absent this mandatory provision, they would have the right to sue in court and have a jury trial. They further understand that, in some instances, the costs of arbitration could exceed the costs of litigation and the right to discovery may be more limited in arbitration than in court.\n\n### 13.3 Location ###\n\nBinding arbitration shall take place in New York. You agree to submit to the personal jurisdiction of any federal or state court in New York County, New York, in order to compel arbitration, to stay proceedings pending arbitration, or to confirm, modify, vacate or enter judgment on the award entered by the arbitrator.\n\n### 13.4 Class Action Waiver ###\n\nThe parties further agree that any arbitration shall be conducted in their individual capacities only and not as a class action or other representative action, and the parties expressly waive their right to file a class action or seek relief on a class basis. YOU AND METAMASK AGREE THAT EACH MAY BRING CLAIMS AGAINST THE OTHER ONLY IN YOUR OR ITS INDIVIDUAL CAPACITY, AND NOT AS A PLAINTIFF OR CLASS MEMBER IN ANY PURPORTED CLASS OR REPRESENTATIVE PROCEEDING. If any court or arbitrator determines that the class action waiver set forth in this paragraph is void or unenforceable for any reason or that an arbitration can proceed on a class basis, then the arbitration provision set forth above shall be deemed null and void in its entirety and the parties shall be deemed to have not agreed to arbitrate disputes.\n\n### 13.5 Exception - Litigation of Intellectual Property and Small Claims Court Claims ###\n\nNotwithstanding the parties' decision to resolve all disputes through arbitration, either party may bring an action in state or federal court to protect its intellectual property rights (\"intellectual property rights\" means patents, copyrights, moral rights, trademarks, and trade secrets, but not privacy or publicity rights). Either party may also seek relief in a small claims court for disputes or claims within the scope of that court’s jurisdiction.\n\n### 13.6 30-Day Right to Opt Out ###\n\nYou have the right to opt-out and not be bound by the arbitration and class action waiver provisions set forth above by sending written notice of your decision to opt-out to the following address: MetaMask ℅ ConsenSys, 49 Bogart Street, Brooklyn NY 11206 and via email at legal-opt@metamask.io. The notice must be sent within 30 days of September 6, 2016 or your first use of the Service, whichever is later, otherwise you shall be bound to arbitrate disputes in accordance with the terms of those paragraphs. If you opt-out of these arbitration provisions, MetaMask also will not be bound by them.\n\n### 13.7 Changes to This Section ###\n\nMetaMask will provide 60-days’ notice of any changes to this section. Changes will become effective on the 60th day, and will apply prospectively only to any claims arising after the 60th day.\n\nFor any dispute not subject to arbitration you and MetaMask agree to submit to the personal and exclusive jurisdiction of and venue in the federal and state courts located in New York, New York. You further agree to accept service of process by mail, and hereby waive any and all jurisdictional and venue defenses otherwise available.\n\nThe Terms and the relationship between you and MetaMask shall be governed by the laws of the State of New York without regard to conflict of law provisions.\n\n## 14. General Information ##\n\n### 14.1 Entire Agreement ###\n\nThese Terms (and any additional terms, rules and conditions of participation that MetaMask may post on the Service) constitute the entire agreement between you and MetaMask with respect to the Service and supersedes any prior agreements, oral or written, between you and MetaMask. In the event of a conflict between these Terms and the additional terms, rules and conditions of participation, the latter will prevail over the Terms to the extent of the conflict.\n\n### 14.2 Waiver and Severability of Terms ###\n\nThe failure of MetaMask to exercise or enforce any right or provision of the Terms shall not constitute a waiver of such right or provision. If any provision of the Terms is found by an arbitrator or court of competent jurisdiction to be invalid, the parties nevertheless agree that the arbitrator or court should endeavor to give effect to the parties' intentions as reflected in the provision, and the other provisions of the Terms remain in full force and effect.\n\n### 14.3 Statute of Limitations ###\n\nYou agree that regardless of any statute or law to the contrary, any claim or cause of action arising out of or related to the use of the Service or the Terms must be filed within one (1) year after such claim or cause of action arose or be forever barred.\n\n### 14.4 Section Titles ###\n\nThe section titles in the Terms are for convenience only and have no legal or contractual effect.\n\n### 14.5 Communications ###\n\nUsers with questions, complaints or claims with respect to the Service may contact us using the relevant contact information set forth above and at communications@metamask.io.\n\n## 15 Related Links ##\n\n**[Terms of Use](https://metamask.io/terms.html)**\n\n**[Privacy](https://metamask.io/privacy.html)**\n\n**[Attributions](https://metamask.io/attributions.html)**\n\n","id":0},{"read":false,"date":"Wed Mar 22 2017","title":"Announcing Kovan Support","body":"MetaMask now lists a new network on our dropdown list: Kovan, a [Proof of Authority](https://github.com/paritytech/parity/wiki/Proof-of-Authority-Chains) testchain managed by several blockchain organizations such as Digix, Etherscan, and Parity. It is designed to be a more stable and reliable testnet alternative to Ropsten and was created in response to recent attacks that slowed down the Ropsten network. You can read more about Kovan [here](https://medium.com/@Digix/announcing-kovan-a-stable-ethereum-public-testnet-10ac7cb6c85f#.6o8sz8cct) and [here](https://medium.com/@Digix/letter-from-the-ceo-some-context-regarding-kovan-7b5121adb901#.kfv7zhw83). As with Ropsten, the default remote node to connect to Kovan is managed by Infura.\n","id":1}] \ No newline at end of file +[{"read":false,"date":"Thu Feb 09 2017","title":"Terms of Use","body":"# Terms of Use #\n\n**THIS AGREEMENT IS SUBJECT TO BINDING ARBITRATION AND A WAIVER OF CLASS ACTION RIGHTS AS DETAILED IN SECTION 13. PLEASE READ THE AGREEMENT CAREFULLY.**\n\n_Our Terms of Use have been updated as of September 5, 2016_\n\n## 1. Acceptance of Terms ##\n\nMetaMask provides a platform for managing Ethereum (or \"ETH\") accounts, and allowing ordinary websites to interact with the Ethereum blockchain, while keeping the user in control over what transactions they approve, through our website located at[ ](http://metamask.io)[https://metamask.io/](https://metamask.io/) and browser plugin (the \"Site\") — which includes text, images, audio, code and other materials (collectively, the “Content”) and all of the features, and services provided. The Site, and any other features, tools, materials, or other services offered from time to time by MetaMask are referred to here as the “Service.” Please read these Terms of Use (the “Terms” or “Terms of Use”) carefully before using the Service. By using or otherwise accessing the Services, or clicking to accept or agree to these Terms where that option is made available, you (1) accept and agree to these Terms (2) consent to the collection, use, disclosure and other handling of information as described in our Privacy Policy and (3) any additional terms, rules and conditions of participation issued by MetaMask from time to time. If you do not agree to the Terms, then you may not access or use the Content or Services.\n\n## 2. Modification of Terms of Use ##\n\nExcept for Section 13, providing for binding arbitration and waiver of class action rights, MetaMask reserves the right, at its sole discretion, to modify or replace the Terms of Use at any time. The most current version of these Terms will be posted on our Site. You shall be responsible for reviewing and becoming familiar with any such modifications. Use of the Services by you after any modification to the Terms constitutes your acceptance of the Terms of Use as modified.\n\n\n\n## 3. Eligibility ##\n\nYou hereby represent and warrant that you are fully able and competent to enter into the terms, conditions, obligations, affirmations, representations and warranties set forth in these Terms and to abide by and comply with these Terms.\n\nMetaMask is a global platform and by accessing the Content or Services, you are representing and warranting that, you are of the legal age of majority in your jurisdiction as is required to access such Services and Content and enter into arrangements as provided by the Service. You further represent that you are otherwise legally permitted to use the service in your jurisdiction including owning cryptographic tokens of value, and interacting with the Services or Content in any way. You further represent you are responsible for ensuring compliance with the laws of your jurisdiction and acknowledge that MetaMask is not liable for your compliance with such laws.\n\n## 4 Account Password and Security ##\n\nWhen setting up an account within MetaMask, you will be responsible for keeping your own account secrets, which may be a twelve-word seed phrase, an account file, or other locally stored secret information. MetaMask encrypts this information locally with a password you provide, that we never send to our servers. You agree to (a) never use the same password for MetaMask that you have ever used outside of this service; (b) keep your secret information and password confidential and do not share them with anyone else; (c) immediately notify MetaMask of any unauthorized use of your account or breach of security. MetaMask cannot and will not be liable for any loss or damage arising from your failure to comply with this section.\n\n## 5. Representations, Warranties, and Risks ##\n\n### 5.1. Warranty Disclaimer ###\n\nYou expressly understand and agree that your use of the Service is at your sole risk. The Service (including the Service and the Content) are provided on an \"AS IS\" and \"as available\" basis, without warranties of any kind, either express or implied, including, without limitation, implied warranties of merchantability, fitness for a particular purpose or non-infringement. You acknowledge that MetaMask has no control over, and no duty to take any action regarding: which users gain access to or use the Service; what effects the Content may have on you; how you may interpret or use the Content; or what actions you may take as a result of having been exposed to the Content. You release MetaMask from all liability for you having acquired or not acquired Content through the Service. MetaMask makes no representations concerning any Content contained in or accessed through the Service, and MetaMask will not be responsible or liable for the accuracy, copyright compliance, legality or decency of material contained in or accessed through the Service.\n\n### 5.2 Sophistication and Risk of Cryptographic Systems ###\n\nBy utilizing the Service or interacting with the Content or platform in any way, you represent that you understand the inherent risks associated with cryptographic systems; and warrant that you have an understanding of the usage and intricacies of native cryptographic tokens, like Ether (ETH) and Bitcoin (BTC), smart contract based tokens such as those that follow the Ethereum Token Standard (https://github.com/ethereum/EIPs/issues/20), and blockchain-based software systems.\n\n### 5.3 Risk of Regulatory Actions in One or More Jurisdictions ###\n\nMetaMask and ETH could be impacted by one or more regulatory inquiries or regulatory action, which could impede or limit the ability of MetaMask to continue to develop, or which could impede or limit your ability to access or use the Service or Ethereum blockchain.\n\n### 5.4 Risk of Weaknesses or Exploits in the Field of Cryptography ###\n\nYou acknowledge and understand that Cryptography is a progressing field. Advances in code cracking or technical advances such as the development of quantum computers may present risks to cryptocurrencies and Services of Content, which could result in the theft or loss of your cryptographic tokens or property. To the extent possible, MetaMask intends to update the protocol underlying Services to account for any advances in cryptography and to incorporate additional security measures, but does not guarantee or otherwise represent full security of the system. By using the Service or accessing Content, you acknowledge these inherent risks.\n\n### 5.5 Volatility of Crypto Currencies ###\n\nYou understand that Ethereum and other blockchain technologies and associated currencies or tokens are highly volatile due to many factors including but not limited to adoption, speculation, technology and security risks. You also acknowledge that the cost of transacting on such technologies is variable and may increase at any time causing impact to any activities taking place on the Ethereum blockchain. You acknowledge these risks and represent that MetaMask cannot be held liable for such fluctuations or increased costs.\n\n### 5.6 Application Security ###\n\nYou acknowledge that Ethereum applications are code subject to flaws and acknowledge that you are solely responsible for evaluating any code provided by the Services or Content and the trustworthiness of any third-party websites, products, smart-contracts, or Content you access or use through the Service. You further expressly acknowledge and represent that Ethereum applications can be written maliciously or negligently, that MetaMask cannot be held liable for your interaction with such applications and that such applications may cause the loss of property or even identity. This warning and others later provided by MetaMask in no way evidence or represent an on-going duty to alert you to all of the potential risks of utilizing the Service or Content.\n\n## 6. Indemnity ##\n\nYou agree to release and to indemnify, defend and hold harmless MetaMask and its parents, subsidiaries, affiliates and agencies, as well as the officers, directors, employees, shareholders and representatives of any of the foregoing entities, from and against any and all losses, liabilities, expenses, damages, costs (including attorneys’ fees and court costs) claims or actions of any kind whatsoever arising or resulting from your use of the Service, your violation of these Terms of Use, and any of your acts or omissions that implicate publicity rights, defamation or invasion of privacy. MetaMask reserves the right, at its own expense, to assume exclusive defense and control of any matter otherwise subject to indemnification by you and, in such case, you agree to cooperate with MetaMask in the defense of such matter.\n\n## 7. Limitation on liability ##\n\nYOU ACKNOWLEDGE AND AGREE THAT YOU ASSUME FULL RESPONSIBILITY FOR YOUR USE OF THE SITE AND SERVICE. YOU ACKNOWLEDGE AND AGREE THAT ANY INFORMATION YOU SEND OR RECEIVE DURING YOUR USE OF THE SITE AND SERVICE MAY NOT BE SECURE AND MAY BE INTERCEPTED OR LATER ACQUIRED BY UNAUTHORIZED PARTIES. YOU ACKNOWLEDGE AND AGREE THAT YOUR USE OF THE SITE AND SERVICE IS AT YOUR OWN RISK. RECOGNIZING SUCH, YOU UNDERSTAND AND AGREE THAT, TO THE FULLEST EXTENT PERMITTED BY APPLICABLE LAW, NEITHER METAMASK NOR ITS SUPPLIERS OR LICENSORS WILL BE LIABLE TO YOU FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY OR OTHER DAMAGES OF ANY KIND, INCLUDING WITHOUT LIMITATION DAMAGES FOR LOSS OF PROFITS, GOODWILL, USE, DATA OR OTHER TANGIBLE OR INTANGIBLE LOSSES OR ANY OTHER DAMAGES BASED ON CONTRACT, TORT, STRICT LIABILITY OR ANY OTHER THEORY (EVEN IF METAMASK HAD BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES), RESULTING FROM THE SITE OR SERVICE; THE USE OR THE INABILITY TO USE THE SITE OR SERVICE; UNAUTHORIZED ACCESS TO OR ALTERATION OF YOUR TRANSMISSIONS OR DATA; STATEMENTS OR CONDUCT OF ANY THIRD PARTY ON THE SITE OR SERVICE; ANY ACTIONS WE TAKE OR FAIL TO TAKE AS A RESULT OF COMMUNICATIONS YOU SEND TO US; HUMAN ERRORS; TECHNICAL MALFUNCTIONS; FAILURES, INCLUDING PUBLIC UTILITY OR TELEPHONE OUTAGES; OMISSIONS, INTERRUPTIONS, LATENCY, DELETIONS OR DEFECTS OF ANY DEVICE OR NETWORK, PROVIDERS, OR SOFTWARE (INCLUDING, BUT NOT LIMITED TO, THOSE THAT DO NOT PERMIT PARTICIPATION IN THE SERVICE); ANY INJURY OR DAMAGE TO COMPUTER EQUIPMENT; INABILITY TO FULLY ACCESS THE SITE OR SERVICE OR ANY OTHER WEBSITE; THEFT, TAMPERING, DESTRUCTION, OR UNAUTHORIZED ACCESS TO, IMAGES OR OTHER CONTENT OF ANY KIND; DATA THAT IS PROCESSED LATE OR INCORRECTLY OR IS INCOMPLETE OR LOST; TYPOGRAPHICAL, PRINTING OR OTHER ERRORS, OR ANY COMBINATION THEREOF; OR ANY OTHER MATTER RELATING TO THE SITE OR SERVICE.\n\nSOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF CERTAIN WARRANTIES OR THE LIMITATION OR EXCLUSION OF LIABILITY FOR INCIDENTAL OR CONSEQUENTIAL DAMAGES. ACCORDINGLY, SOME OF THE ABOVE LIMITATIONS MAY NOT APPLY TO YOU.\n\n## 8. Our Proprietary Rights ##\n\nAll title, ownership and intellectual property rights in and to the Service are owned by MetaMask or its licensors. You acknowledge and agree that the Service contains proprietary and confidential information that is protected by applicable intellectual property and other laws. Except as expressly authorized by MetaMask, you agree not to copy, modify, rent, lease, loan, sell, distribute, perform, display or create derivative works based on the Service, in whole or in part. MetaMask issues a license for MetaMask, found [here](https://github.com/MetaMask/metamask-plugin/blob/master/LICENSE). For information on other licenses utilized in the development of MetaMask, please see our attribution page at: [https://metamask.io/attributions.html](https://metamask.io/attributions.html)\n\n## 9. Links ##\n\nThe Service provides, or third parties may provide, links to other World Wide Web or accessible sites, applications or resources. Because MetaMask has no control over such sites, applications and resources, you acknowledge and agree that MetaMask is not responsible for the availability of such external sites, applications or resources, and does not endorse and is not responsible or liable for any content, advertising, products or other materials on or available from such sites or resources. You further acknowledge and agree that MetaMask shall not be responsible or liable, directly or indirectly, for any damage or loss caused or alleged to be caused by or in connection with use of or reliance on any such content, goods or services available on or through any such site or resource.\n\n## 10. Termination and Suspension ##\n\nMetaMask may terminate or suspend all or part of the Service and your MetaMask access immediately, without prior notice or liability, if you breach any of the terms or conditions of the Terms. Upon termination of your access, your right to use the Service will immediately cease.\n\nThe following provisions of the Terms survive any termination of these Terms: INDEMNITY; WARRANTY DISCLAIMERS; LIMITATION ON LIABILITY; OUR PROPRIETARY RIGHTS; LINKS; TERMINATION; NO THIRD PARTY BENEFICIARIES; BINDING ARBITRATION AND CLASS ACTION WAIVER; GENERAL INFORMATION.\n\n## 11. No Third Party Beneficiaries ##\n\nYou agree that, except as otherwise expressly provided in these Terms, there shall be no third party beneficiaries to the Terms.\n\n## 12. Notice and Procedure For Making Claims of Copyright Infringement ##\n\nIf you believe that your copyright or the copyright of a person on whose behalf you are authorized to act has been infringed, please provide MetaMask’s Copyright Agent a written Notice containing the following information:\n\n· an electronic or physical signature of the person authorized to act on behalf of the owner of the copyright or other intellectual property interest;\n\n· a description of the copyrighted work or other intellectual property that you claim has been infringed;\n\n· a description of where the material that you claim is infringing is located on the Service;\n\n· your address, telephone number, and email address;\n\n· a statement by you that you have a good faith belief that the disputed use is not authorized by the copyright owner, its agent, or the law;\n\n· a statement by you, made under penalty of perjury, that the above information in your Notice is accurate and that you are the copyright or intellectual property owner or authorized to act on the copyright or intellectual property owner's behalf.\n\nMetaMask’s Copyright Agent can be reached at:\n\nEmail: copyright [at] metamask [dot] io\n\nMail:\n\nAttention:\n\nMetaMask Copyright ℅ ConsenSys\n\n49 Bogart Street\n\nBrooklyn, NY 11206\n\n## 13. Binding Arbitration and Class Action Waiver ##\n\nPLEASE READ THIS SECTION CAREFULLY – IT MAY SIGNIFICANTLY AFFECT YOUR LEGAL RIGHTS, INCLUDING YOUR RIGHT TO FILE A LAWSUIT IN COURT\n\n### 13.1 Initial Dispute Resolution ###\n\nThe parties shall use their best efforts to engage directly to settle any dispute, claim, question, or disagreement and engage in good faith negotiations which shall be a condition to either party initiating a lawsuit or arbitration.\n\n### 13.2 Binding Arbitration ###\n\nIf the parties do not reach an agreed upon solution within a period of 30 days from the time informal dispute resolution under the Initial Dispute Resolution provision begins, then either party may initiate binding arbitration as the sole means to resolve claims, subject to the terms set forth below. Specifically, all claims arising out of or relating to these Terms (including their formation, performance and breach), the parties’ relationship with each other and/or your use of the Service shall be finally settled by binding arbitration administered by the American Arbitration Association in accordance with the provisions of its Commercial Arbitration Rules and the supplementary procedures for consumer related disputes of the American Arbitration Association (the \"AAA\"), excluding any rules or procedures governing or permitting class actions.\n\nThe arbitrator, and not any federal, state or local court or agency, shall have exclusive authority to resolve all disputes arising out of or relating to the interpretation, applicability, enforceability or formation of these Terms, including, but not limited to any claim that all or any part of these Terms are void or voidable, or whether a claim is subject to arbitration. The arbitrator shall be empowered to grant whatever relief would be available in a court under law or in equity. The arbitrator’s award shall be written, and binding on the parties and may be entered as a judgment in any court of competent jurisdiction.\n\nThe parties understand that, absent this mandatory provision, they would have the right to sue in court and have a jury trial. They further understand that, in some instances, the costs of arbitration could exceed the costs of litigation and the right to discovery may be more limited in arbitration than in court.\n\n### 13.3 Location ###\n\nBinding arbitration shall take place in New York. You agree to submit to the personal jurisdiction of any federal or state court in New York County, New York, in order to compel arbitration, to stay proceedings pending arbitration, or to confirm, modify, vacate or enter judgment on the award entered by the arbitrator.\n\n### 13.4 Class Action Waiver ###\n\nThe parties further agree that any arbitration shall be conducted in their individual capacities only and not as a class action or other representative action, and the parties expressly waive their right to file a class action or seek relief on a class basis. YOU AND METAMASK AGREE THAT EACH MAY BRING CLAIMS AGAINST THE OTHER ONLY IN YOUR OR ITS INDIVIDUAL CAPACITY, AND NOT AS A PLAINTIFF OR CLASS MEMBER IN ANY PURPORTED CLASS OR REPRESENTATIVE PROCEEDING. If any court or arbitrator determines that the class action waiver set forth in this paragraph is void or unenforceable for any reason or that an arbitration can proceed on a class basis, then the arbitration provision set forth above shall be deemed null and void in its entirety and the parties shall be deemed to have not agreed to arbitrate disputes.\n\n### 13.5 Exception - Litigation of Intellectual Property and Small Claims Court Claims ###\n\nNotwithstanding the parties' decision to resolve all disputes through arbitration, either party may bring an action in state or federal court to protect its intellectual property rights (\"intellectual property rights\" means patents, copyrights, moral rights, trademarks, and trade secrets, but not privacy or publicity rights). Either party may also seek relief in a small claims court for disputes or claims within the scope of that court’s jurisdiction.\n\n### 13.6 30-Day Right to Opt Out ###\n\nYou have the right to opt-out and not be bound by the arbitration and class action waiver provisions set forth above by sending written notice of your decision to opt-out to the following address: MetaMask ℅ ConsenSys, 49 Bogart Street, Brooklyn NY 11206 and via email at legal-opt@metamask.io. The notice must be sent within 30 days of September 6, 2016 or your first use of the Service, whichever is later, otherwise you shall be bound to arbitrate disputes in accordance with the terms of those paragraphs. If you opt-out of these arbitration provisions, MetaMask also will not be bound by them.\n\n### 13.7 Changes to This Section ###\n\nMetaMask will provide 60-days’ notice of any changes to this section. Changes will become effective on the 60th day, and will apply prospectively only to any claims arising after the 60th day.\n\nFor any dispute not subject to arbitration you and MetaMask agree to submit to the personal and exclusive jurisdiction of and venue in the federal and state courts located in New York, New York. You further agree to accept service of process by mail, and hereby waive any and all jurisdictional and venue defenses otherwise available.\n\nThe Terms and the relationship between you and MetaMask shall be governed by the laws of the State of New York without regard to conflict of law provisions.\n\n## 14. General Information ##\n\n### 14.1 Entire Agreement ###\n\nThese Terms (and any additional terms, rules and conditions of participation that MetaMask may post on the Service) constitute the entire agreement between you and MetaMask with respect to the Service and supersedes any prior agreements, oral or written, between you and MetaMask. In the event of a conflict between these Terms and the additional terms, rules and conditions of participation, the latter will prevail over the Terms to the extent of the conflict.\n\n### 14.2 Waiver and Severability of Terms ###\n\nThe failure of MetaMask to exercise or enforce any right or provision of the Terms shall not constitute a waiver of such right or provision. If any provision of the Terms is found by an arbitrator or court of competent jurisdiction to be invalid, the parties nevertheless agree that the arbitrator or court should endeavor to give effect to the parties' intentions as reflected in the provision, and the other provisions of the Terms remain in full force and effect.\n\n### 14.3 Statute of Limitations ###\n\nYou agree that regardless of any statute or law to the contrary, any claim or cause of action arising out of or related to the use of the Service or the Terms must be filed within one (1) year after such claim or cause of action arose or be forever barred.\n\n### 14.4 Section Titles ###\n\nThe section titles in the Terms are for convenience only and have no legal or contractual effect.\n\n### 14.5 Communications ###\n\nUsers with questions, complaints or claims with respect to the Service may contact us using the relevant contact information set forth above and at communications@metamask.io.\n\n## 15 Related Links ##\n\n**[Terms of Use](https://metamask.io/terms.html)**\n\n**[Privacy](https://metamask.io/privacy.html)**\n\n**[Attributions](https://metamask.io/attributions.html)**\n\n","id":0}] \ No newline at end of file From 2920faf63cea9951d4c26aa37bf589c225ee6af0 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Mon, 17 Apr 2017 13:45:49 -0700 Subject: [PATCH 149/372] Add more detailed instructions on generating and deleting notices. --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index aa79f4564..496b5423f 100644 --- a/README.md +++ b/README.md @@ -160,7 +160,11 @@ To add a notice: ``` npm run generateNotice ``` +Enter the body of your notice into the text editor that pops up, without including the body. Be sure to save the file before closing the window! +Afterwards, enter the title of the notice in the command line and press enter. Afterwards, add and commit the new changes made. + To delete a notice: ``` npm run deleteNotice ``` +A list of active notices will pop up. Enter the corresponding id in the command line prompt and add and commit the new changes afterwards. From 83811910c84342094be4ac94dca829e8f5ff630f Mon Sep 17 00:00:00 2001 From: frankiebee Date: Tue, 18 Apr 2017 18:20:31 +0200 Subject: [PATCH 150/372] Create a custom radio list component --- ui/app/components/custom-radio-list.js | 61 ++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 ui/app/components/custom-radio-list.js diff --git a/ui/app/components/custom-radio-list.js b/ui/app/components/custom-radio-list.js new file mode 100644 index 000000000..201ec11dc --- /dev/null +++ b/ui/app/components/custom-radio-list.js @@ -0,0 +1,61 @@ +const Component = require('react').Component +const h = require('react-hyperscript') +const inherits = require('util').inherits + +module.exports = RadioList + +inherits(RadioList, Component) +function RadioList () { + Component.call(this) +} + +RadioList.prototype.render = function () { + const props = this.props + let activeClass = '.custom-radio-selected' + let inactiveClass = '.custom-radio-inactive' + let { + lables, + defaultFocus, + onClick, + } = props + + + return ( + h('.flex-row', { + style: { + fontSize: '12px', + } + }, [ + h('.flex-column.custom-radios', { + style: { + marginRight: '5px', + } + }, + lables.map((lable, i) => { + let isSelcted = (this.state !== null ) + isSelcted = isSelcted ? (this.state.selected === lable) : (this.props.defaultFocus === lable) + return h(isSelcted ? activeClass : inactiveClass, { + title: lable, + onClick: (event) => { + this.setState({selected: event.target.title}) + props.onClick(event) + } + }) + }), + ), + h('.text', {}, + lables.map((lable) => { + if (props.subtext) { + return h('.flex-row', {}, [ + h('.radio-titles', lable), + h('.radio-titles-subtext', `- ${props.subtext[lable]}`) + ]) + } else { + return h('.radio-titles', lable) + } + }) + ) + ]) + ) +} + From ce03b7db51570295c7868382cf997dbb1bc5725a Mon Sep 17 00:00:00 2001 From: frankiebee Date: Tue, 18 Apr 2017 18:21:24 +0200 Subject: [PATCH 151/372] Initial redo attempt of the buy view to look like vladt's desighn --- ui/app/components/buy-button-subview.js | 139 +++++++++++++++--------- ui/app/components/coinbase-form.js | 101 +---------------- ui/app/components/custom-radio-list.js | 49 ++++----- ui/app/components/shapeshift-form.js | 24 ++-- ui/app/css/index.css | 41 +++++++ ui/app/reducers/app.js | 14 ++- 6 files changed, 173 insertions(+), 195 deletions(-) diff --git a/ui/app/components/buy-button-subview.js b/ui/app/components/buy-button-subview.js index 6810303e1..8d3e9aa21 100644 --- a/ui/app/components/buy-button-subview.js +++ b/ui/app/components/buy-button-subview.js @@ -6,12 +6,15 @@ const actions = require('../actions') const CoinbaseForm = require('./coinbase-form') const ShapeshiftForm = require('./shapeshift-form') const Loading = require('./loading') -const TabBar = require('./tab-bar') +const AccountPanel = require('./account-panel') +const RadioList = require('./custom-radio-list') module.exports = connect(mapStateToProps)(BuyButtonSubview) function mapStateToProps (state) { return { + identity: state.appState.identity, + account: state.metamask.accounts[state.appState.buyView.buyAddress], warning: state.appState.warning, buyView: state.appState.buyView, network: state.metamask.network, @@ -31,7 +34,11 @@ BuyButtonSubview.prototype.render = function () { const isLoading = props.isSubLoading return ( - h('.buy-eth-section', [ + h('.buy-eth-section.flex-column', { + style: { + alignItems: 'center', + }, + }, [ // back button h('.flex-row', { style: { @@ -46,58 +53,79 @@ BuyButtonSubview.prototype.render = function () { left: '10px', }, }), - h('h2.page-subtitle', 'Buy Eth'), - ]), - - h(Loading, { isLoading }), - - h(TabBar, { - tabs: [ - { - content: [ - 'Coinbase', - h('a', { - onClick: (event) => this.navigateTo('https://github.com/MetaMask/faq/blob/master/COINBASE.md'), - }, [ - h('i.fa.fa-question-circle', { - style: { - margin: '0px 5px', - }, - }), - ]), - ], - key: 'coinbase', + h('h2.text-transform-uppercase.flex-center', { + style: { + width: '100vw', + background: 'rgb(235, 235, 235)', + color: 'rgb(174, 174, 174)', + paddingTop: '4px', + paddingBottom: '4px', }, - { - content: [ - 'Shapeshift', - h('a', { - href: 'https://github.com/MetaMask/faq/blob/master/COINBASE.md', - onClick: (event) => this.navigateTo('https://info.shapeshift.io/about'), - }, [ - h('i.fa.fa-question-circle', { - style: { - margin: '0px 5px', - }, - }), - ]), - ], - key: 'shapeshift', + }, 'Buy Eth'), + ]), + h('div', { + style: { + position: 'absolute', + top: '57vh', + left: '49vw', + }, + }, [ + h(Loading, {isLoading}), + ]), + h('div', { + style: { + width: '80%', + }, + }, [ + h(AccountPanel, { + showFullAddress: true, + identity: props.identity, + account: props.account, + }), + ]), + h('h3.text-transform-uppercase', { + style: { + paddingLeft: '15px', + fontFamily: 'Montserrat Light', + width: '100vw', + background: 'rgb(235, 235, 235)', + color: 'rgb(174, 174, 174)', + paddingTop: '4px', + paddingBottom: '4px', + }, + }, 'Select Service'), + h('.flex-row.selected-exchange', { + style: { + position: 'relative', + right: '35px', + marginTop: '20px', + marginBottom: '20px', + }, + }, [ + h(RadioList, { + defaultFocus: props.buyView.subview, + lables: [ + 'Coinbase', + 'ShapeShift', + ], + subtext: { + 'Coinbase': 'Crypto/FIAT (USA only)', + 'ShapeShift': 'Crypto', }, - ], - defaultTab: 'coinbase', - tabSelected: (key) => { - switch (key) { - case 'coinbase': - props.dispatch(actions.coinBaseSubview()) - break - case 'shapeshift': - props.dispatch(actions.shapeShiftSubview(props.provider.type)) - break - } + onClick: this.radioHandler.bind(this), + }), + ]), + h('h3.text-transform-uppercase', { + style: { + paddingLeft: '15px', + fontFamily: 'Montserrat Light', + width: '100vw', + background: 'rgb(235, 235, 235)', + color: 'rgb(174, 174, 174)', + paddingTop: '4px', + paddingBottom: '4px', }, - }), - + }, props.buyView.subview), this.formVersionSubview(), ]) ) @@ -152,3 +180,12 @@ BuyButtonSubview.prototype.backButtonContext = function () { this.props.dispatch(actions.goHome()) } } + +BuyButtonSubview.prototype.radioHandler = function (event) { + switch (event.target.title) { + case 'Coinbase': + return this.props.dispatch(actions.coinBaseSubview()) + case 'ShapeShift': + return this.props.dispatch(actions.shapeShiftSubview(this.props.provider.type)) + } +} diff --git a/ui/app/components/coinbase-form.js b/ui/app/components/coinbase-form.js index fd5816a21..b92799375 100644 --- a/ui/app/components/coinbase-form.js +++ b/ui/app/components/coinbase-form.js @@ -4,7 +4,6 @@ const inherits = require('util').inherits const connect = require('react-redux').connect const actions = require('../actions') -const isValidAddress = require('../util').isValidAddress module.exports = connect(mapStateToProps)(CoinbaseForm) function mapStateToProps (state) { @@ -21,72 +20,19 @@ function CoinbaseForm () { CoinbaseForm.prototype.render = function () { var props = this.props - var amount = props.buyView.amount - var address = props.buyView.buyAddress return h('.flex-column', { style: { // margin: '10px', padding: '25px', + width: '100%', }, }, [ - h('.flex-column', { - style: { - alignItems: 'flex-start', - }, - }, [ - h('.flex-row', [ - h('div', 'Address:'), - h('.ellip-address', address), - ]), - h('.flex-row', [ - h('div', 'Amount: $'), - h('.input-container', [ - h('input.buy-inputs', { - style: { - width: '3em', - boxSizing: 'border-box', - }, - defaultValue: amount, - onChange: this.handleAmount.bind(this), - }), - h('i.fa.fa-pencil-square-o.edit-text', { - style: { - fontSize: '12px', - color: '#F7861C', - position: 'relative', - bottom: '5px', - right: '11px', - }, - }), - ]), - ]), - ]), - - h('.info-gray', { - style: { - fontSize: '10px', - fontFamily: 'Montserrat Light', - margin: '15px', - lineHeight: '13px', - }, - }, - `there is a USD$ 15 a day max and a USD$ 50 - dollar limit per the life time of an account without a - coinbase account. A fee of 3.75% will be aplied to debit/credit cards.`), - - !props.warning ? h('div', { - style: { - width: '340px', - height: '22px', - }, - }) : props.warning && h('span.error.flex-center', props.warning), - - h('.flex-row', { style: { justifyContent: 'space-around', margin: '33px', + marginTop: '0px', }, }, [ h('button', { @@ -106,20 +52,9 @@ CoinbaseForm.prototype.handleAddress = function (event) { this.props.dispatch(actions.updateBuyAddress(event.target.value)) } CoinbaseForm.prototype.toCoinbase = function () { - var props = this.props - var amount = props.buyView.amount - var address = props.buyView.buyAddress - var message - - if (isValidAddress(address) && isValidAmountforCoinBase(amount).valid) { - props.dispatch(actions.buyEth({ network: '1', address, amount: props.buyView.amount })) - } else if (!isValidAmountforCoinBase(amount).valid) { - message = isValidAmountforCoinBase(amount).message - return props.dispatch(actions.displayWarning(message)) - } else { - message = 'Receiving address is invalid.' - return props.dispatch(actions.displayWarning(message)) - } + const props = this.props + const address = props.buyView.buyAddress + props.dispatch(actions.buyEth({ network: '1', address, amount: 0 })) } CoinbaseForm.prototype.renderLoading = function () { @@ -131,29 +66,3 @@ CoinbaseForm.prototype.renderLoading = function () { src: 'images/loading.svg', }) } - -function isValidAmountforCoinBase (amount) { - amount = parseFloat(amount) - if (amount) { - if (amount <= 15 && amount > 0) { - return { - valid: true, - } - } else if (amount > 15) { - return { - valid: false, - message: 'The amount can not be greater then $15', - } - } else { - return { - valid: false, - message: 'Can not buy amounts less then $0', - } - } - } else { - return { - valid: false, - message: 'The amount entered is not a number', - } - } -} diff --git a/ui/app/components/custom-radio-list.js b/ui/app/components/custom-radio-list.js index 201ec11dc..a19287630 100644 --- a/ui/app/components/custom-radio-list.js +++ b/ui/app/components/custom-radio-list.js @@ -11,12 +11,11 @@ function RadioList () { RadioList.prototype.render = function () { const props = this.props - let activeClass = '.custom-radio-selected' - let inactiveClass = '.custom-radio-inactive' - let { + const activeClass = '.custom-radio-selected' + const inactiveClass = '.custom-radio-inactive' + const { lables, defaultFocus, - onClick, } = props @@ -24,37 +23,37 @@ RadioList.prototype.render = function () { h('.flex-row', { style: { fontSize: '12px', - } + }, }, [ - h('.flex-column.custom-radios', { - style: { - marginRight: '5px', - } + h('.flex-column.custom-radios', { + style: { + marginRight: '5px', }, - lables.map((lable, i) => { - let isSelcted = (this.state !== null ) - isSelcted = isSelcted ? (this.state.selected === lable) : (this.props.defaultFocus === lable) - return h(isSelcted ? activeClass : inactiveClass, { - title: lable, - onClick: (event) => { - this.setState({selected: event.target.title}) - props.onClick(event) - } - }) - }), - ), - h('.text', {}, - lables.map((lable) => { + }, + lables.map((lable, i) => { + let isSelcted = (this.state !== null) + isSelcted = isSelcted ? (this.state.selected === lable) : (defaultFocus === lable) + return h(isSelcted ? activeClass : inactiveClass, { + title: lable, + onClick: (event) => { + this.setState({selected: event.target.title}) + props.onClick(event) + }, + }) + }) + ), + h('.text', {}, + lables.map((lable) => { if (props.subtext) { return h('.flex-row', {}, [ h('.radio-titles', lable), - h('.radio-titles-subtext', `- ${props.subtext[lable]}`) + h('.radio-titles-subtext', `- ${props.subtext[lable]}`), ]) } else { return h('.radio-titles', lable) } }) - ) + ), ]) ) } diff --git a/ui/app/components/shapeshift-form.js b/ui/app/components/shapeshift-form.js index 8c9686035..2745b1b11 100644 --- a/ui/app/components/shapeshift-form.js +++ b/ui/app/components/shapeshift-form.js @@ -43,14 +43,18 @@ ShapeshiftForm.prototype.renderMain = function () { style: { // marginTop: '10px', padding: '25px', + paddingTop: '5px', width: '100%', + minHeight: '215px', alignItems: 'center', + overflowY: 'auto', }, }, [ h('.flex-row', { style: { justifyContent: 'center', alignItems: 'baseline', + height: '42px', }, }, [ h('img', { @@ -82,7 +86,6 @@ ShapeshiftForm.prototype.renderMain = function () { style: { fontSize: '12px', color: '#F7861C', - position: 'relative', bottom: '48px', left: '106px', }, @@ -92,7 +95,6 @@ ShapeshiftForm.prototype.renderMain = function () { h('.icon-control', [ h('i.fa.fa-refresh.fa-4.orange', { style: { - position: 'relative', bottom: '5px', left: '5px', color: '#F7861C', @@ -121,8 +123,6 @@ ShapeshiftForm.prototype.renderMain = function () { }, }), ]), - - this.props.isSubLoading ? this.renderLoading() : null, h('.flex-column', { style: { alignItems: 'flex-start', @@ -138,17 +138,6 @@ ShapeshiftForm.prototype.renderMain = function () { this.props.warning) : this.renderInfo(), ]), - h('.flex-row', { - style: { - padding: '10px', - paddingBottom: '2px', - width: '100%', - }, - }, [ - h('div', 'Receiving address:'), - h('.ellip-address', this.props.buyView.buyAddress), - ]), - h(this.activeToggle('.input-container'), { style: { padding: '10px', @@ -156,6 +145,7 @@ ShapeshiftForm.prototype.renderMain = function () { width: '100%', }, }, [ + h('div', `${coin} Address:`), h('input#fromCoinAddress.buy-inputs', { @@ -190,6 +180,8 @@ ShapeshiftForm.prototype.renderMain = function () { onClick: this.shift.bind(this), style: { marginTop: '10px', + position: 'relative', + bottom: '33px', }, }, 'Submit'), @@ -266,8 +258,6 @@ ShapeshiftForm.prototype.renderInfo = function () { return h('span', { style: { - marginTop: '10px', - marginBottom: '15px', }, }, [ h('h3.flex-row.text-transform-uppercase', { diff --git a/ui/app/css/index.css b/ui/app/css/index.css index 033502f5a..808aafb4c 100644 --- a/ui/app/css/index.css +++ b/ui/app/css/index.css @@ -489,6 +489,47 @@ input.large-input { } /* buy eth warning screen */ +.custom-radios { + justify-content: space-around; + align-items: center; +} + + +.custom-radio-selected { + width: 17px; + height: 17px; + border: solid; + border-style: double; + border-radius: 15px; + border-width: 5px; + background: rgba(247, 134, 28, 1); + border-color: #F7F7F7; +} + +.custom-radio-inactive { + width: 14px; + height: 14px; + border: solid; + border-width: 1px; + border-radius: 24px; + border-color: #AEAEAE; +} + +.radio-titles { + color: rgba(247, 134, 28, 1); +} + +.radio-titles-subtext { + +} + +.selected-exchange { + +} + +.buy-radio { + +} .eth-warning{ transition: opacity 400ms ease-in, transform 400ms ease-in; diff --git a/ui/app/reducers/app.js b/ui/app/reducers/app.js index 7ad1229e5..6b040e988 100644 --- a/ui/app/reducers/app.js +++ b/ui/app/reducers/app.js @@ -469,8 +469,10 @@ function reduceApp (state, action) { name: 'buyEth', context: appState.currentView.name, }, + identity: state.metamask.identities[action.value], + buyAddress: action.value, buyView: { - subview: 'buyForm', + subview: 'Coinbase', amount: '15.00', buyAddress: action.value, formView: { @@ -483,7 +485,7 @@ function reduceApp (state, action) { case actions.UPDATE_BUY_ADDRESS: return extend(appState, { buyView: { - subview: 'buyForm', + subview: appState.subview, formView: { coinbase: appState.buyView.formView.coinbase, shapeshift: appState.buyView.formView.shapeshift, @@ -496,7 +498,7 @@ function reduceApp (state, action) { case actions.UPDATE_COINBASE_AMOUNT: return extend(appState, { buyView: { - subview: 'buyForm', + subview: 'Coinbase', formView: { coinbase: true, shapeshift: false, @@ -509,7 +511,7 @@ function reduceApp (state, action) { case actions.COINBASE_SUBVIEW: return extend(appState, { buyView: { - subview: 'buyForm', + subview: 'Coinbase', formView: { coinbase: true, shapeshift: false, @@ -522,7 +524,7 @@ function reduceApp (state, action) { case actions.SHAPESHIFT_SUBVIEW: return extend(appState, { buyView: { - subview: 'buyForm', + subview: 'ShapeShift', formView: { coinbase: false, shapeshift: true, @@ -537,7 +539,7 @@ function reduceApp (state, action) { case actions.PAIR_UPDATE: return extend(appState, { buyView: { - subview: 'buyForm', + subview: 'ShapeShift', formView: { coinbase: false, shapeshift: true, From 4a4a7373607518066eb334dfae8b428832a31369 Mon Sep 17 00:00:00 2001 From: frankiebee Date: Fri, 21 Apr 2017 17:56:14 +0200 Subject: [PATCH 152/372] Fix for firefox --- mascara/src/lib/index-db-controller.js | 94 +++++++++++--------------- 1 file changed, 39 insertions(+), 55 deletions(-) diff --git a/mascara/src/lib/index-db-controller.js b/mascara/src/lib/index-db-controller.js index 8db1d5d21..1e4148b16 100644 --- a/mascara/src/lib/index-db-controller.js +++ b/mascara/src/lib/index-db-controller.js @@ -1,39 +1,20 @@ +// module.exports = const EventEmitter = require('events') module.exports = class IndexDbController extends EventEmitter { constructor (opts) { super() + global.IDBTransaction = global.IDBTransaction || global.webkitIDBTransaction || global.msIDBTransaction || {READ_WRITE: "readwrite"}; // This line should only be needed if it is needed to support the object's constants for older browsers + global.IDBKeyRange = global.IDBKeyRange || global.webkitIDBKeyRange || global.msIDBKeyRange this.migrations = opts.migrations this.key = opts.key - this.dbObject = global.indexedDB - this.IDBTransaction = global.IDBTransaction || global.webkitIDBTransaction || global.msIDBTransaction || {READ_WRITE: "readwrite"}; // This line should only be needed if it is needed to support the object's constants for older browsers - this.IDBKeyRange = global.IDBKeyRange || global.webkitIDBKeyRange || global.msIDBKeyRange; this.version = opts.version - this.logging = opts.logging this.initialState = opts.initialState - if (this.logging) this.on('log', logger) } // Opens the database connection and returns a promise open (version = this.version) { - return new Promise((resolve, reject) => { - const dbOpenRequest = this.dbObject.open(this.key, version) - dbOpenRequest.onerror = (event) => { - return reject(event) - } - dbOpenRequest.onsuccess = (event) => { - this.db = dbOpenRequest.result - this.emit('success') - resolve(this.db) - } - dbOpenRequest.onupgradeneeded = (event) => { - this.db = event.target.result - this.db.createObjectStore('dataStore') - } - }) - .then((openRequest) => { - return this.get('dataStore') - }) + return this.get('dataStore') .then((data) => { if (!data) { return this._add('dataStore', this.initialState) @@ -44,45 +25,48 @@ module.exports = class IndexDbController extends EventEmitter { }) } - requestObjectStore (key, type = 'readonly') { - return new Promise((resolve, reject) => { - const dbReadWrite = this.db.transaction(key, type) - const dataStore = dbReadWrite.objectStore(key) - resolve(dataStore) - }) - } get (key = 'dataStore') { - return this.requestObjectStore(key) - .then((dataObject)=> { - return new Promise((resolve, reject) => { - const getRequest = dataObject.get(key) - getRequest.onsuccess = (event) => resolve(event.currentTarget.result) - getRequest.onerror = (event) => reject(event) - }) - }) + return this._request('get', key) } - put (state) { - return this.requestObjectStore('dataStore', 'readwrite') - .then((dataObject)=> { - const putRequest = dataObject.put(state, 'dataStore') - putRequest.onsuccess = (event) => Promise.resolve(event.currentTarget.result) - putRequest.onerror = (event) => Promise.reject(event) - }) + return this._request('put', state, 'dataStore') } - _add (key, objStore, cb = logger) { - return this.requestObjectStore(key, 'readwrite') - .then((dataObject)=> { - const addRequest = dataObject.add(objStore, key) - addRequest.onsuccess = (event) => Promise.resolve(event.currentTarget.result) - addRequest.onerror = (event) => Promise.reject(event) - }) + _add (key = 'dataStore', objStore) { + return this._request('add', objStore, key) } -} + _request (call, ...args) { + return new Promise((resolve, reject) => { + const self = this + const dbOpenRequest = global.indexedDB.open(this.key, this.version) + + dbOpenRequest.onupgradeneeded = (event) => { + this.db = event.target.result + this.db.createObjectStore('dataStore') + } -function logger (err, ress) { - err ? console.error(`Logger says: ${err}`) : console.dir(`Logger says: ${ress}`) + dbOpenRequest.onsuccess = (event) => { + this.db = dbOpenRequest.result + this.emit('success') + const dbTransaction = this.db.transaction('dataStore', 'readwrite') + const request = dbTransaction.objectStore('dataStore') + const objRequest = request[call](...args) + objRequest.onsuccess = (event) => { + return resolve(objRequest.result) + } + objRequest.onerror = (err) => { + return reject(err.message) + } + dbTransaction.oncomplete = (event) => { + this.emit('complete') + } + } + + dbOpenRequest.onerror = (event) => { + return reject(event) + } + }) + } } From 437c4acc9f6738ff5b07682860a72c270f2bfad6 Mon Sep 17 00:00:00 2001 From: frankiebee Date: Fri, 21 Apr 2017 17:58:18 +0200 Subject: [PATCH 153/372] Reduce wakeup time for firefox --- mascara/src/ui.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mascara/src/ui.js b/mascara/src/ui.js index 2873017cb..cdf985ebd 100644 --- a/mascara/src/ui.js +++ b/mascara/src/ui.js @@ -24,7 +24,7 @@ const background = new SWcontroller({ fileName: '/background.js', letBeIdle: false, intervalDelay, - wakeUpInterval: 30000 + wakeUpInterval: 20000 }) // Setup listener for when the service worker is read background.on('ready', (readSw) => { From 1b19b51e0823726a01eab49ef9416f852f365500 Mon Sep 17 00:00:00 2001 From: frankiebee Date: Fri, 21 Apr 2017 23:00:32 +0200 Subject: [PATCH 154/372] Clean up code --- mascara/src/lib/index-db-controller.js | 27 +++++++++++++------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/mascara/src/lib/index-db-controller.js b/mascara/src/lib/index-db-controller.js index 1e4148b16..5aded1cbe 100644 --- a/mascara/src/lib/index-db-controller.js +++ b/mascara/src/lib/index-db-controller.js @@ -1,4 +1,3 @@ -// module.exports = const EventEmitter = require('events') module.exports = class IndexDbController extends EventEmitter { @@ -13,7 +12,7 @@ module.exports = class IndexDbController extends EventEmitter { } // Opens the database connection and returns a promise - open (version = this.version) { + open () { return this.get('dataStore') .then((data) => { if (!data) { @@ -42,10 +41,10 @@ module.exports = class IndexDbController extends EventEmitter { const self = this const dbOpenRequest = global.indexedDB.open(this.key, this.version) - dbOpenRequest.onupgradeneeded = (event) => { + dbOpenRequest.addEventListener('upgradeneeded', (event) => { this.db = event.target.result this.db.createObjectStore('dataStore') - } + }) dbOpenRequest.onsuccess = (event) => { this.db = dbOpenRequest.result @@ -53,20 +52,20 @@ module.exports = class IndexDbController extends EventEmitter { const dbTransaction = this.db.transaction('dataStore', 'readwrite') const request = dbTransaction.objectStore('dataStore') const objRequest = request[call](...args) - objRequest.onsuccess = (event) => { + objRequest.addEventListener('success', (event) => { return resolve(objRequest.result) - } - objRequest.onerror = (err) => { - return reject(err.message) - } - dbTransaction.oncomplete = (event) => { + }) + objRequest.addEventListener('error', (err) => { + return reject(`IndexDBController - ${call} failed to excute on indexedDB`) + }) + dbTransaction.addEventListener('complete', (event) => { this.emit('complete') - } + }) } - dbOpenRequest.onerror = (event) => { - return reject(event) - } + dbOpenRequest.addEventListener('error', (event) => { + return reject({message: `IndexDBController - open:@${call} failed to excute on indexedDB`, errorEvent: event}) + }) }) } } From e543050868b58ea1a1b0cad363d763eab2ade25d Mon Sep 17 00:00:00 2001 From: Jared Pereira Date: Sun, 23 Apr 2017 15:27:17 +0400 Subject: [PATCH 155/372] remove extra buyAddress in state --- ui/app/reducers/app.js | 1 - 1 file changed, 1 deletion(-) diff --git a/ui/app/reducers/app.js b/ui/app/reducers/app.js index 6b040e988..036286a8b 100644 --- a/ui/app/reducers/app.js +++ b/ui/app/reducers/app.js @@ -470,7 +470,6 @@ function reduceApp (state, action) { context: appState.currentView.name, }, identity: state.metamask.identities[action.value], - buyAddress: action.value, buyView: { subview: 'Coinbase', amount: '15.00', From 7a8496f9da894bf8821e91746b33f53fe23cf150 Mon Sep 17 00:00:00 2001 From: Jared Pereira Date: Sun, 23 Apr 2017 15:28:45 +0400 Subject: [PATCH 156/372] remove buyButtonDeligator function --- ui/app/account-detail.js | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/ui/app/account-detail.js b/ui/app/account-detail.js index 018e74893..d4b371947 100644 --- a/ui/app/account-detail.js +++ b/ui/app/account-detail.js @@ -262,15 +262,3 @@ AccountDetailScreen.prototype.transactionList = function () { AccountDetailScreen.prototype.requestAccountExport = function () { this.props.dispatch(actions.requestExportAccount()) } - - -AccountDetailScreen.prototype.buyButtonDeligator = function () { - var props = this.props - var selected = props.address || Object.keys(props.accounts)[0] - - if (this.props.accountDetail.subview === 'buyForm') { - props.dispatch(actions.backToAccountDetail(props.address)) - } else { - props.dispatch(actions.buyEthView(selected)) - } -} From c1df7dedd9065c8fe600642638e0e0a5d71e6f6e Mon Sep 17 00:00:00 2001 From: Jared Pereira Date: Sun, 23 Apr 2017 15:36:48 +0400 Subject: [PATCH 157/372] remove case buyForm --- ui/app/account-detail.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/ui/app/account-detail.js b/ui/app/account-detail.js index d4b371947..d592a5ad6 100644 --- a/ui/app/account-detail.js +++ b/ui/app/account-detail.js @@ -16,7 +16,6 @@ const ExportAccountView = require('./components/account-export') const ethUtil = require('ethereumjs-util') const EditableLabel = require('./components/editable-label') const Tooltip = require('./components/tooltip') -const BuyButtonSubview = require('./components/buy-button-subview') module.exports = connect(mapStateToProps)(AccountDetailScreen) function mapStateToProps (state) { @@ -238,8 +237,6 @@ AccountDetailScreen.prototype.subview = function () { case 'export': var state = extend({key: 'export'}, this.props) return h(ExportAccountView, state) - case 'buyForm': - return h(BuyButtonSubview, extend({key: 'buyForm'}, this.props)) default: return this.transactionList() } From 5cabd3e02d0eeef8bd7c65db193b0bdb8cc9cc04 Mon Sep 17 00:00:00 2001 From: Jared Pereira Date: Sun, 23 Apr 2017 21:18:14 +0400 Subject: [PATCH 158/372] remove updateBuyAddress action --- ui/app/actions.js | 9 --------- ui/app/components/coinbase-form.js | 4 +--- ui/app/components/shapeshift-form.js | 4 ---- ui/app/reducers/app.js | 13 ------------- 4 files changed, 1 insertion(+), 29 deletions(-) diff --git a/ui/app/actions.js b/ui/app/actions.js index 8934299e7..f08a93ff4 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -136,8 +136,6 @@ var actions = { BUY_ETH_VIEW: 'BUY_ETH_VIEW', UPDATE_COINBASE_AMOUNT: 'UPDATE_COIBASE_AMOUNT', updateCoinBaseAmount: updateCoinBaseAmount, - UPDATE_BUY_ADDRESS: 'UPDATE_BUY_ADDRESS', - updateBuyAddress: updateBuyAddress, COINBASE_SUBVIEW: 'COINBASE_SUBVIEW', coinBaseSubview: coinBaseSubview, SHAPESHIFT_SUBVIEW: 'SHAPESHIFT_SUBVIEW', @@ -859,13 +857,6 @@ function updateCoinBaseAmount (value) { } } -function updateBuyAddress (value) { - return { - type: actions.UPDATE_BUY_ADDRESS, - value, - } -} - function coinBaseSubview () { return { type: actions.COINBASE_SUBVIEW, diff --git a/ui/app/components/coinbase-form.js b/ui/app/components/coinbase-form.js index b92799375..82d1458bf 100644 --- a/ui/app/components/coinbase-form.js +++ b/ui/app/components/coinbase-form.js @@ -48,9 +48,7 @@ CoinbaseForm.prototype.render = function () { CoinbaseForm.prototype.handleAmount = function (event) { this.props.dispatch(actions.updateCoinBaseAmount(event.target.value)) } -CoinbaseForm.prototype.handleAddress = function (event) { - this.props.dispatch(actions.updateBuyAddress(event.target.value)) -} + CoinbaseForm.prototype.toCoinbase = function () { const props = this.props const address = props.buyView.buyAddress diff --git a/ui/app/components/shapeshift-form.js b/ui/app/components/shapeshift-form.js index 2745b1b11..f0a067c05 100644 --- a/ui/app/components/shapeshift-form.js +++ b/ui/app/components/shapeshift-form.js @@ -276,10 +276,6 @@ ShapeshiftForm.prototype.renderInfo = function () { ]) } -ShapeshiftForm.prototype.handleAddress = function (event) { - this.props.dispatch(actions.updateBuyAddress(event.target.value)) -} - ShapeshiftForm.prototype.activeToggle = function (elementType) { if (!this.props.buyView.formView.response || this.props.warning) return elementType return `${elementType}.inactive` diff --git a/ui/app/reducers/app.js b/ui/app/reducers/app.js index 036286a8b..016ddb569 100644 --- a/ui/app/reducers/app.js +++ b/ui/app/reducers/app.js @@ -481,19 +481,6 @@ function reduceApp (state, action) { }, }) - case actions.UPDATE_BUY_ADDRESS: - return extend(appState, { - buyView: { - subview: appState.subview, - formView: { - coinbase: appState.buyView.formView.coinbase, - shapeshift: appState.buyView.formView.shapeshift, - }, - buyAddress: action.value, - amount: appState.buyView.amount, - }, - }) - case actions.UPDATE_COINBASE_AMOUNT: return extend(appState, { buyView: { From 7f12be6a014286d727766174bff9391b2cc55ae9 Mon Sep 17 00:00:00 2001 From: Jared Pereira Date: Mon, 24 Apr 2017 12:18:54 +0400 Subject: [PATCH 159/372] remove updateCoinBaseAmount action --- ui/app/actions.js | 9 --------- ui/app/components/coinbase-form.js | 3 --- ui/app/reducers/app.js | 13 ------------- 3 files changed, 25 deletions(-) diff --git a/ui/app/actions.js b/ui/app/actions.js index f08a93ff4..18f341411 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -134,8 +134,6 @@ var actions = { buyEth: buyEth, buyEthView: buyEthView, BUY_ETH_VIEW: 'BUY_ETH_VIEW', - UPDATE_COINBASE_AMOUNT: 'UPDATE_COIBASE_AMOUNT', - updateCoinBaseAmount: updateCoinBaseAmount, COINBASE_SUBVIEW: 'COINBASE_SUBVIEW', coinBaseSubview: coinBaseSubview, SHAPESHIFT_SUBVIEW: 'SHAPESHIFT_SUBVIEW', @@ -850,13 +848,6 @@ function buyEthView (address) { } } -function updateCoinBaseAmount (value) { - return { - type: actions.UPDATE_COINBASE_AMOUNT, - value, - } -} - function coinBaseSubview () { return { type: actions.COINBASE_SUBVIEW, diff --git a/ui/app/components/coinbase-form.js b/ui/app/components/coinbase-form.js index 82d1458bf..7ba8ca79e 100644 --- a/ui/app/components/coinbase-form.js +++ b/ui/app/components/coinbase-form.js @@ -45,9 +45,6 @@ CoinbaseForm.prototype.render = function () { ]), ]) } -CoinbaseForm.prototype.handleAmount = function (event) { - this.props.dispatch(actions.updateCoinBaseAmount(event.target.value)) -} CoinbaseForm.prototype.toCoinbase = function () { const props = this.props diff --git a/ui/app/reducers/app.js b/ui/app/reducers/app.js index 016ddb569..324a4df35 100644 --- a/ui/app/reducers/app.js +++ b/ui/app/reducers/app.js @@ -481,19 +481,6 @@ function reduceApp (state, action) { }, }) - case actions.UPDATE_COINBASE_AMOUNT: - return extend(appState, { - buyView: { - subview: 'Coinbase', - formView: { - coinbase: true, - shapeshift: false, - }, - buyAddress: appState.buyView.buyAddress, - amount: action.value, - }, - }) - case actions.COINBASE_SUBVIEW: return extend(appState, { buyView: { From 1eda55c85a958eef7814fadd29b257c5fbf884e0 Mon Sep 17 00:00:00 2001 From: frankiebee Date: Mon, 24 Apr 2017 12:35:45 +0200 Subject: [PATCH 160/372] Fix issue where stopPropagation didnt stop submitting the tx when clicking buy button --- ui/app/conf-tx.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/app/conf-tx.js b/ui/app/conf-tx.js index 3b8618992..770f79b19 100644 --- a/ui/app/conf-tx.js +++ b/ui/app/conf-tx.js @@ -141,7 +141,7 @@ function currentTxView (opts) { } ConfirmTxScreen.prototype.buyEth = function (address, event) { - this.stopPropagation(event) + event.preventDefault() this.props.dispatch(actions.buyEthView(address)) } From 9ebc5ed33cd3450262c00ccdde9f617544dfa784 Mon Sep 17 00:00:00 2001 From: frankiebee Date: Mon, 24 Apr 2017 12:36:17 +0200 Subject: [PATCH 161/372] make buy button green --- ui/app/components/pending-tx.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/app/components/pending-tx.js b/ui/app/components/pending-tx.js index 1b83f5043..4b28ae099 100644 --- a/ui/app/components/pending-tx.js +++ b/ui/app/components/pending-tx.js @@ -289,7 +289,7 @@ PendingTx.prototype.render = function () { insufficientBalance ? - h('button', { + h('button.btn-green', { onClick: props.buyEth, }, 'Buy Ether') : null, From df9e40be636738336d6fa3c775bbcc99a9a0e210 Mon Sep 17 00:00:00 2001 From: frankiebee Date: Mon, 24 Apr 2017 12:58:01 +0200 Subject: [PATCH 162/372] Css fixes --- ui/app/components/coinbase-form.js | 6 +++--- ui/app/components/shapeshift-form.js | 10 ++++++---- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/ui/app/components/coinbase-form.js b/ui/app/components/coinbase-form.js index 7ba8ca79e..f44d86045 100644 --- a/ui/app/components/coinbase-form.js +++ b/ui/app/components/coinbase-form.js @@ -23,7 +23,7 @@ CoinbaseForm.prototype.render = function () { return h('.flex-column', { style: { - // margin: '10px', + marginTop: '35px', padding: '25px', width: '100%', }, @@ -35,11 +35,11 @@ CoinbaseForm.prototype.render = function () { marginTop: '0px', }, }, [ - h('button', { + h('button.btn-green', { onClick: this.toCoinbase.bind(this), }, 'Continue to Coinbase'), - h('button', { + h('button.btn-red', { onClick: () => props.dispatch(actions.backTobuyView(props.accounts.address)), }, 'Cancel'), ]), diff --git a/ui/app/components/shapeshift-form.js b/ui/app/components/shapeshift-form.js index f0a067c05..e0a720426 100644 --- a/ui/app/components/shapeshift-form.js +++ b/ui/app/components/shapeshift-form.js @@ -70,6 +70,7 @@ ShapeshiftForm.prototype.renderMain = function () { h('input#fromCoin.buy-inputs.ex-coins', { type: 'text', list: 'coinList', + autoFocus: true, dataset: { persistentFormId: 'input-coin', }, @@ -86,6 +87,7 @@ ShapeshiftForm.prototype.renderMain = function () { style: { fontSize: '12px', color: '#F7861C', + position: 'relative', bottom: '48px', left: '106px', }, @@ -156,8 +158,8 @@ ShapeshiftForm.prototype.renderMain = function () { }, style: { boxSizing: 'border-box', - width: '278px', - height: '20px', + width: '227px', + height: '30px', padding: ' 5px ', }, }), @@ -167,7 +169,7 @@ ShapeshiftForm.prototype.renderMain = function () { fontSize: '12px', color: '#F7861C', position: 'relative', - bottom: '5px', + bottom: '10px', right: '11px', }, }), @@ -181,7 +183,7 @@ ShapeshiftForm.prototype.renderMain = function () { style: { marginTop: '10px', position: 'relative', - bottom: '33px', + bottom: '40px', }, }, 'Submit'), From 79f88398acd116980fe91d4c56a1ec6a15672745 Mon Sep 17 00:00:00 2001 From: frankiebee Date: Mon, 24 Apr 2017 20:56:31 +0200 Subject: [PATCH 163/372] fix spelling --- ui/app/components/buy-button-subview.js | 2 +- ui/app/components/custom-radio-list.js | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ui/app/components/buy-button-subview.js b/ui/app/components/buy-button-subview.js index 8d3e9aa21..191f46319 100644 --- a/ui/app/components/buy-button-subview.js +++ b/ui/app/components/buy-button-subview.js @@ -104,7 +104,7 @@ BuyButtonSubview.prototype.render = function () { }, [ h(RadioList, { defaultFocus: props.buyView.subview, - lables: [ + labels: [ 'Coinbase', 'ShapeShift', ], diff --git a/ui/app/components/custom-radio-list.js b/ui/app/components/custom-radio-list.js index a19287630..a4c525396 100644 --- a/ui/app/components/custom-radio-list.js +++ b/ui/app/components/custom-radio-list.js @@ -14,7 +14,7 @@ RadioList.prototype.render = function () { const activeClass = '.custom-radio-selected' const inactiveClass = '.custom-radio-inactive' const { - lables, + labels, defaultFocus, } = props @@ -30,7 +30,7 @@ RadioList.prototype.render = function () { marginRight: '5px', }, }, - lables.map((lable, i) => { + labels.map((lable, i) => { let isSelcted = (this.state !== null) isSelcted = isSelcted ? (this.state.selected === lable) : (defaultFocus === lable) return h(isSelcted ? activeClass : inactiveClass, { @@ -43,7 +43,7 @@ RadioList.prototype.render = function () { }) ), h('.text', {}, - lables.map((lable) => { + labels.map((lable) => { if (props.subtext) { return h('.flex-row', {}, [ h('.radio-titles', lable), From c3746e62ec50de87a85bd2af0bfaf66fb19fbc1b Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Mon, 24 Apr 2017 13:58:46 -0700 Subject: [PATCH 164/372] Version 3.5.3 --- CHANGELOG.md | 4 ++++ app/manifest.json | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fd754cadd..1a32b8138 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,12 @@ ## Current Master +## 3.5.3 2017-4-24 + - Popup new transactions in Firefox. - Fix transition issue from account detail screen. +- Revise buy screen for more modularity. +- Fixed some other small bugs. ## 3.5.2 2017-3-28 diff --git a/app/manifest.json b/app/manifest.json index a3242149b..aeb47dfe3 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -1,7 +1,7 @@ { "name": "MetaMask", "short_name": "Metamask", - "version": "3.5.2", + "version": "3.5.3", "manifest_version": 2, "author": "https://metamask.io", "description": "Ethereum Browser Extension", From 3ae2a829956941d7703be714650d85a989c1b488 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Mon, 24 Apr 2017 18:49:23 -0700 Subject: [PATCH 165/372] Bump provider engine Should now pass test suite, and include several sweet recent fixes! --- CHANGELOG.md | 3 +++ package.json | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a32b8138..dbe63083c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## Current Master +- Fix occasional nonce tracking issue. +- Fix bug where some events would not be emitted by web3. + ## 3.5.3 2017-4-24 - Popup new transactions in Firefox. diff --git a/package.json b/package.json index b892653fa..e3c222734 100644 --- a/package.json +++ b/package.json @@ -113,7 +113,7 @@ "valid-url": "^1.0.9", "vreme": "^3.0.2", "web3": "0.18.2", - "web3-provider-engine": "^11.0.2", + "web3-provider-engine": "^12.0.1", "web3-stream-provider": "^2.0.6", "xtend": "^4.0.1" }, From 292e2dca83ff7b300aeb680d6470ab827668b1db Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Mon, 24 Apr 2017 20:59:59 -0700 Subject: [PATCH 166/372] Bump provider-engine --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e3c222734..8aa449c8d 100644 --- a/package.json +++ b/package.json @@ -113,7 +113,7 @@ "valid-url": "^1.0.9", "vreme": "^3.0.2", "web3": "0.18.2", - "web3-provider-engine": "^12.0.1", + "web3-provider-engine": "^12.0.2", "web3-stream-provider": "^2.0.6", "xtend": "^4.0.1" }, From 04e489f4df06b13f2bdd76d815f742b98bee7a37 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Tue, 25 Apr 2017 10:19:26 -0700 Subject: [PATCH 167/372] Allow signature V values over 1 byte By bumping ethereumjs-tx. --- CHANGELOG.md | 1 + package.json | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dbe63083c..fb28dc9c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - Fix occasional nonce tracking issue. - Fix bug where some events would not be emitted by web3. +- Fix bug where an error would be thrown when composing signatures for networks with large ID values. ## 3.5.3 2017-4-24 diff --git a/package.json b/package.json index 8aa449c8d..157dbda65 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,7 @@ "eth-query": "^1.0.3", "eth-sig-util": "^1.1.1", "eth-simple-keyring": "^1.1.1", - "ethereumjs-tx": "^1.2.5", + "ethereumjs-tx": "^1.3.0", "ethereumjs-util": "ethereumjs/ethereumjs-util#ac5d0908536b447083ea422b435da27f26615de9", "ethereumjs-wallet": "^0.6.0", "ethjs-ens": "^1.0.2", From f1beb0720a0964e45a71b473f173f62c6abdac6e Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Tue, 25 Apr 2017 10:55:00 -0700 Subject: [PATCH 168/372] Version 3.5.4 --- CHANGELOG.md | 2 ++ app/manifest.json | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fb28dc9c0..b855fefe0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Current Master +## 3.5.4 2017-4-25 + - Fix occasional nonce tracking issue. - Fix bug where some events would not be emitted by web3. - Fix bug where an error would be thrown when composing signatures for networks with large ID values. diff --git a/app/manifest.json b/app/manifest.json index aeb47dfe3..6ef428d2c 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -1,7 +1,7 @@ { "name": "MetaMask", "short_name": "Metamask", - "version": "3.5.3", + "version": "3.5.4", "manifest_version": 2, "author": "https://metamask.io", "description": "Ethereum Browser Extension", From e45d5c858e10db774d40a09704d38b65128ef821 Mon Sep 17 00:00:00 2001 From: kumavis Date: Tue, 25 Apr 2017 12:49:24 -0700 Subject: [PATCH 169/372] mascara - docker - bump to node7 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index d06f5377b..be0a328fe 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM node:6 +FROM node:7 MAINTAINER kumavis # setup app dir From e9aa37b699a105019384cbde88a114965ff1e2cd Mon Sep 17 00:00:00 2001 From: Nickyg Date: Wed, 26 Apr 2017 01:40:33 +0530 Subject: [PATCH 170/372] add rinkeby network --- app/scripts/config.js | 2 ++ app/scripts/lib/config-manager.js | 5 +++++ ui/app/app.js | 9 +++++++++ ui/app/components/drop-menu-item.js | 3 +++ ui/app/components/network.js | 5 ++++- ui/app/config.js | 5 +++++ 6 files changed, 28 insertions(+), 1 deletion(-) diff --git a/app/scripts/config.js b/app/scripts/config.js index ec421744d..4f62268e1 100644 --- a/app/scripts/config.js +++ b/app/scripts/config.js @@ -1,6 +1,7 @@ const MAINET_RPC_URL = 'https://mainnet.infura.io/metamask' const TESTNET_RPC_URL = 'https://ropsten.infura.io/metamask' const KOVAN_RPC_URL = 'https://kovan.infura.io/metamask' +const RINKEBY_RPC_URL = 'https://rinkeby.infura.io' const DEFAULT_RPC_URL = TESTNET_RPC_URL global.METAMASK_DEBUG = 'GULP_METAMASK_DEBUG' @@ -12,5 +13,6 @@ module.exports = { testnet: TESTNET_RPC_URL, morden: TESTNET_RPC_URL, kovan: KOVAN_RPC_URL, + rinkeby: RINKEBY_RPC_URL, }, } diff --git a/app/scripts/lib/config-manager.js b/app/scripts/lib/config-manager.js index e31cb45ed..340ad4292 100644 --- a/app/scripts/lib/config-manager.js +++ b/app/scripts/lib/config-manager.js @@ -6,6 +6,8 @@ const TESTNET_RPC = MetamaskConfig.network.testnet const MAINNET_RPC = MetamaskConfig.network.mainnet const MORDEN_RPC = MetamaskConfig.network.morden const KOVAN_RPC = MetamaskConfig.network.kovan +const RINKEBY_RPC = MetamaskConfig.network.rinkeby + /* The config-manager is a convenience object * wrapping a pojo-migrator. @@ -153,6 +155,9 @@ ConfigManager.prototype.getCurrentRpcAddress = function () { case 'kovan': return KOVAN_RPC + + case 'rinkeby': + return RINKEBY_RPC default: return provider && provider.rpcTarget ? provider.rpcTarget : TESTNET_RPC diff --git a/ui/app/app.js b/ui/app/app.js index 5a7596aca..4ae40b659 100644 --- a/ui/app/app.js +++ b/ui/app/app.js @@ -264,6 +264,15 @@ App.prototype.renderNetworkDropdown = function () { provider: props.provider, }), + h(DropMenuItem, { + label: 'Rinkeby Test Network', + closeMenu: () => this.setState({ isNetworkMenuOpen: false}), + action: () => props.dispatch(actions.setProviderType('rinkeby')), + icon: h('.menu-icon.hollow-diamond'), + activeNetworkRender: props.network, + provider: props.provider, + }), + h(DropMenuItem, { label: 'Localhost 8545', closeMenu: () => this.setState({ isNetworkMenuOpen: false }), diff --git a/ui/app/components/drop-menu-item.js b/ui/app/components/drop-menu-item.js index 3eb6ec876..bd9d8f597 100644 --- a/ui/app/components/drop-menu-item.js +++ b/ui/app/components/drop-menu-item.js @@ -47,6 +47,9 @@ DropMenuItem.prototype.activeNetworkRender = function () { case 'Kovan Test Network': if (providerType === 'kovan') return h('.check', '✓') break + case 'Rinkeby Test Network': + if (providerType === 'rinkeby') return h('.check', '✓') + break case 'Localhost 8545': if (activeNetwork === 'http://localhost:8545') return h('.check', '✓') break diff --git a/ui/app/components/network.js b/ui/app/components/network.js index d9045167f..f0fc14454 100644 --- a/ui/app/components/network.js +++ b/ui/app/components/network.js @@ -43,7 +43,10 @@ Network.prototype.render = function () { } else if (providerName === 'kovan') { hoverText = 'Kovan Test Network' iconName = 'kovan-test-network' - } else { + } else if (providerName === 'rinkeby') { + hoverText = 'Rinkeby Test Network' + iconName = 'unknown-private-network' + }else { hoverText = 'Unknown Private Network' iconName = 'unknown-private-network' } diff --git a/ui/app/config.js b/ui/app/config.js index 444365de2..26cfe663f 100644 --- a/ui/app/config.js +++ b/ui/app/config.js @@ -166,6 +166,11 @@ function currentProviderDisplay (metamaskState) { value = 'Kovan Test Network' break + case 'rinkeby': + title = 'Current Network' + value = 'Rinkeby Test Network' + break + default: title = 'Current RPC' value = metamaskState.provider.rpcTarget From d764e46a500d0dc156a1da86498b751d23c94747 Mon Sep 17 00:00:00 2001 From: Nickyg Date: Wed, 26 Apr 2017 02:15:15 +0530 Subject: [PATCH 171/372] change network name to rinkeby when selected --- ui/app/components/network.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/ui/app/components/network.js b/ui/app/components/network.js index f0fc14454..94704b1bc 100644 --- a/ui/app/components/network.js +++ b/ui/app/components/network.js @@ -45,7 +45,7 @@ Network.prototype.render = function () { iconName = 'kovan-test-network' } else if (providerName === 'rinkeby') { hoverText = 'Rinkeby Test Network' - iconName = 'unknown-private-network' + iconName = 'rinkeby-test-network' }else { hoverText = 'Unknown Private Network' iconName = 'unknown-private-network' @@ -85,6 +85,15 @@ Network.prototype.render = function () { }}, 'Kovan Test Net'), ]) + case 'rinkeby-test-network': + return h('.network-indicator', [ + h('.menu-icon.hollow-diamond'), + h('.network-name', { + style: { + color: '#550077', + }}, + 'Rinkeby Test Net'), + ]) default: return h('.network-indicator', [ h('i.fa.fa-question-circle.fa-lg', { From 4d42cfaa25366ec4f0f30018a8d5213d70205fb8 Mon Sep 17 00:00:00 2001 From: kumavis Date: Tue, 25 Apr 2017 14:04:51 -0700 Subject: [PATCH 172/372] build - make gulp return non-zero error code on bundle fail --- gulpfile.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/gulpfile.js b/gulpfile.js index fe223adf1..21b925780 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -296,8 +296,6 @@ function bundleTask(opts) { return ( bundler.bundle() - // log errors if they happen - .on('error', gutil.log.bind(gutil, 'Browserify Error')) // convert bundle stream to gulp vinyl stream .pipe(source(opts.filename)) // inject variables into bundle From 242dc1e99f1dd53e2bec9deefb5da0c8329b5f00 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Tue, 25 Apr 2017 14:39:01 -0700 Subject: [PATCH 173/372] Add missing changes. Create unique style for rinkeby icon. --- app/scripts/config.js | 2 +- app/scripts/lib/buy-eth-url.js | 6 +++++- ui/app/app.js | 2 +- ui/app/components/buy-button-subview.js | 8 +++++++- ui/app/components/network.js | 2 +- ui/app/components/transaction-list-item.js | 2 +- ui/app/css/lib.css | 4 ++++ ui/lib/account-link.js | 3 +++ ui/lib/explorer-link.js | 3 +++ 9 files changed, 26 insertions(+), 6 deletions(-) diff --git a/app/scripts/config.js b/app/scripts/config.js index 4f62268e1..391c67230 100644 --- a/app/scripts/config.js +++ b/app/scripts/config.js @@ -1,7 +1,7 @@ const MAINET_RPC_URL = 'https://mainnet.infura.io/metamask' const TESTNET_RPC_URL = 'https://ropsten.infura.io/metamask' const KOVAN_RPC_URL = 'https://kovan.infura.io/metamask' -const RINKEBY_RPC_URL = 'https://rinkeby.infura.io' +const RINKEBY_RPC_URL = 'https://rinkeby.infura.io/metamask' const DEFAULT_RPC_URL = TESTNET_RPC_URL global.METAMASK_DEBUG = 'GULP_METAMASK_DEBUG' diff --git a/app/scripts/lib/buy-eth-url.js b/app/scripts/lib/buy-eth-url.js index 91a1ec322..957a00211 100644 --- a/app/scripts/lib/buy-eth-url.js +++ b/app/scripts/lib/buy-eth-url.js @@ -11,9 +11,13 @@ function getBuyEthUrl({ network, amount, address }){ url = 'https://faucet.metamask.io/' break + case '4': + url = 'https://www.rinkeby.io/' + break + case '42': url = 'https://github.com/kovan-testnet/faucet' break } return url -} \ No newline at end of file +} diff --git a/ui/app/app.js b/ui/app/app.js index 4ae40b659..156490914 100644 --- a/ui/app/app.js +++ b/ui/app/app.js @@ -268,7 +268,7 @@ App.prototype.renderNetworkDropdown = function () { label: 'Rinkeby Test Network', closeMenu: () => this.setState({ isNetworkMenuOpen: false}), action: () => props.dispatch(actions.setProviderType('rinkeby')), - icon: h('.menu-icon.hollow-diamond'), + icon: h('.menu-icon.golden-square'), activeNetworkRender: props.network, provider: props.provider, }), diff --git a/ui/app/components/buy-button-subview.js b/ui/app/components/buy-button-subview.js index 191f46319..87084f92d 100644 --- a/ui/app/components/buy-button-subview.js +++ b/ui/app/components/buy-button-subview.js @@ -152,13 +152,19 @@ BuyButtonSubview.prototype.formVersionSubview = function () { marginBottom: '15px', }, }, 'In order to access this feature, please switch to the Main Network'), - ((network === '3') || (network === '42')) ? h('h3.text-transform-uppercase', 'or go to the') : null, + ((network === '3') || (network === '4') || (network === '42')) ? h('h3.text-transform-uppercase', 'or go to the') : null, (network === '3') ? h('button.text-transform-uppercase', { onClick: () => this.props.dispatch(actions.buyEth({ network })), style: { marginTop: '15px', }, }, 'Ropsten Test Faucet') : null, + (network === '4') ? h('button.text-transform-uppercase', { + onClick: () => this.props.dispatch(actions.buyEth({ network })), + style: { + marginTop: '15px', + }, + }, 'Rinkeby Test Faucet') : null, (network === '42') ? h('button.text-transform-uppercase', { onClick: () => this.props.dispatch(actions.buyEth({ network })), style: { diff --git a/ui/app/components/network.js b/ui/app/components/network.js index 94704b1bc..e8065cf00 100644 --- a/ui/app/components/network.js +++ b/ui/app/components/network.js @@ -87,7 +87,7 @@ Network.prototype.render = function () { ]) case 'rinkeby-test-network': return h('.network-indicator', [ - h('.menu-icon.hollow-diamond'), + h('.menu-icon.golden-square'), h('.network-name', { style: { color: '#550077', diff --git a/ui/app/components/transaction-list-item.js b/ui/app/components/transaction-list-item.js index 9fef52355..6f4dfae2d 100644 --- a/ui/app/components/transaction-list-item.js +++ b/ui/app/components/transaction-list-item.js @@ -27,7 +27,7 @@ TransactionListItem.prototype.render = function () { let isLinkable = false const numericNet = parseInt(network) - isLinkable = numericNet === 1 || numericNet === 3 || numericNet === 42 + isLinkable = numericNet === 1 || numericNet === 3 || numericNet === 4 || numericNet === 42 var isMsg = ('msgParams' in transaction) var isTx = ('txParams' in transaction) diff --git a/ui/app/css/lib.css b/ui/app/css/lib.css index 670dc9fd0..910a24ee2 100644 --- a/ui/app/css/lib.css +++ b/ui/app/css/lib.css @@ -191,6 +191,10 @@ hr.horizontal-line { border: 3px solid #690496; } +.golden-square { + background: #EBB33F; +} + .pending-dot { background: red; left: 14px; diff --git a/ui/lib/account-link.js b/ui/lib/account-link.js index 4f27b35c0..d061d0ad1 100644 --- a/ui/lib/account-link.js +++ b/ui/lib/account-link.js @@ -11,6 +11,9 @@ module.exports = function (address, network) { case 3: // ropsten test net link = `http://ropsten.etherscan.io/address/${address}` break + case 4: // rinkeby test net + link = `http://rinkeby.etherscan.io/address/${address}` + break case 42: // kovan test net link = `http://kovan.etherscan.io/address/${address}` break diff --git a/ui/lib/explorer-link.js b/ui/lib/explorer-link.js index ca89f8b25..e11249551 100644 --- a/ui/lib/explorer-link.js +++ b/ui/lib/explorer-link.js @@ -8,6 +8,9 @@ module.exports = function (hash, network) { case 3: // ropsten test net prefix = 'ropsten.' break + case 4: // rinkeby test net + prefix = 'rinkeby.' + break case 42: // kovan test net prefix = 'kovan.' break From f2bf7326ccc87fa944307f0b1ffd7ca51621e53c Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Tue, 25 Apr 2017 14:44:25 -0700 Subject: [PATCH 174/372] Linting. --- ui/app/app.js | 2 +- ui/app/components/network.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/app/app.js b/ui/app/app.js index 156490914..f3661568f 100644 --- a/ui/app/app.js +++ b/ui/app/app.js @@ -264,7 +264,7 @@ App.prototype.renderNetworkDropdown = function () { provider: props.provider, }), - h(DropMenuItem, { + h(DropMenuItem, { label: 'Rinkeby Test Network', closeMenu: () => this.setState({ isNetworkMenuOpen: false}), action: () => props.dispatch(actions.setProviderType('rinkeby')), diff --git a/ui/app/components/network.js b/ui/app/components/network.js index e8065cf00..f7ea8c49e 100644 --- a/ui/app/components/network.js +++ b/ui/app/components/network.js @@ -46,7 +46,7 @@ Network.prototype.render = function () { } else if (providerName === 'rinkeby') { hoverText = 'Rinkeby Test Network' iconName = 'rinkeby-test-network' - }else { + } else { hoverText = 'Unknown Private Network' iconName = 'unknown-private-network' } From 09034772d0536b6dd0a2c591986059b3eaba221d Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Tue, 25 Apr 2017 14:57:07 -0700 Subject: [PATCH 175/372] Add (vague) instructions to adding a new network to the dropdown. --- README.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/README.md b/README.md index 496b5423f..821e1cdfd 100644 --- a/README.md +++ b/README.md @@ -168,3 +168,28 @@ To delete a notice: npm run deleteNotice ``` A list of active notices will pop up. Enter the corresponding id in the command line prompt and add and commit the new changes afterwards. + +## Adding Custom Networks + +To add another network to our dropdown menu, make sure the following files are adjusted properly: + +``` +app/scripts/config.js +app/scripts/lib/buy-eth-url.js +app/scripts/lib/config-manager.js +ui/app/app.js +ui/app/components/buy-button-subview.js +ui/app/components/drop-menu-item.js +ui/app/components/network.js +ui/app/components/transaction-list-item.js +ui/app/config.js +ui/app/css/lib.css +ui/lib/account-link.js +ui/lib/explorer-link.js +``` + +You will need: ++ The network ID ++ An RPC Endpoint url ++ An explorer link ++ CSS for the display icon From b0919ba7296553200161913ab413f214aa58e741 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Tue, 25 Apr 2017 14:57:35 -0700 Subject: [PATCH 176/372] Add to changelog. --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fb28dc9c0..8a093a9e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - Fix occasional nonce tracking issue. - Fix bug where some events would not be emitted by web3. - Fix bug where an error would be thrown when composing signatures for networks with large ID values. +- Add Rinkeby Test Network to our network list. ## 3.5.3 2017-4-24 From 6bdb4c87288a522d9ea2e984bc1f6436d6c7369a Mon Sep 17 00:00:00 2001 From: Thomas Huang Date: Wed, 26 Apr 2017 21:05:45 -0700 Subject: [PATCH 177/372] Fix linting warnings --- .../account-import-strategies/index.js | 2 +- app/scripts/background.js | 12 ++- app/scripts/contentscript.js | 2 +- app/scripts/controllers/address-book.js | 8 +- app/scripts/controllers/currency.js | 8 +- app/scripts/controllers/preferences.js | 8 +- app/scripts/first-time-state.js | 2 +- app/scripts/keyring-controller.js | 4 +- app/scripts/lib/buy-eth-url.js | 4 +- app/scripts/lib/eth-store.js | 2 +- app/scripts/lib/inpage-provider.js | 4 +- app/scripts/lib/message-manager.js | 4 +- app/scripts/lib/migrator/index.js | 12 +-- app/scripts/lib/notification-manager.js | 2 +- app/scripts/lib/personal-message-manager.js | 4 +- app/scripts/lib/tx-utils.js | 12 +-- app/scripts/metamask-controller.js | 77 +++++++++---------- app/scripts/migrations/002.js | 2 +- app/scripts/migrations/003.js | 2 +- app/scripts/migrations/004.js | 2 +- app/scripts/migrations/005.js | 2 +- app/scripts/migrations/006.js | 2 +- app/scripts/migrations/007.js | 2 +- app/scripts/migrations/008.js | 2 +- app/scripts/migrations/009.js | 2 +- app/scripts/migrations/010.js | 2 +- app/scripts/migrations/011.js | 2 +- app/scripts/migrations/012.js | 2 +- app/scripts/migrations/_multi-keyring.js | 11 ++- app/scripts/popup-core.js | 1 - app/scripts/popup.js | 2 +- app/scripts/transaction-manager.js | 28 +++---- ui/app/accounts/import/index.js | 2 +- ui/app/actions.js | 6 +- ui/app/app.js | 1 - ui/app/components/ens-input.js | 6 +- ui/app/components/notice.js | 5 +- .../components/transaction-list-item-icon.js | 2 +- ui/app/components/transaction-list-item.js | 2 - ui/app/conf-tx.js | 2 - ui/app/reducers/app.js | 2 +- 41 files changed, 124 insertions(+), 135 deletions(-) diff --git a/app/scripts/account-import-strategies/index.js b/app/scripts/account-import-strategies/index.js index d5124eb7f..96e2b5912 100644 --- a/app/scripts/account-import-strategies/index.js +++ b/app/scripts/account-import-strategies/index.js @@ -4,7 +4,7 @@ const ethUtil = require('ethereumjs-util') const accountImporter = { - importAccount(strategy, args) { + importAccount (strategy, args) { try { const importer = this.strategies[strategy] const privateKeyHex = importer.apply(null, args) diff --git a/app/scripts/background.js b/app/scripts/background.js index 7211f1e0c..58f8e7556 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -41,10 +41,10 @@ asyncQ.waterfall([ // State and Persistence // -function loadStateFromPersistence() { +function loadStateFromPersistence () { // migrations - let migrator = new Migrator({ migrations }) - let initialState = migrator.generateInitialState(firstTimeState) + const migrator = new Migrator({ migrations }) + const initialState = migrator.generateInitialState(firstTimeState) return asyncQ.waterfall([ // read from disk () => Promise.resolve(diskStore.getState() || initialState), @@ -61,7 +61,6 @@ function loadStateFromPersistence() { } function setupController (initState) { - // // MetaMask Controller // @@ -85,8 +84,8 @@ function setupController (initState) { diskStore ) - function versionifyData(state) { - let versionedData = diskStore.getState() + function versionifyData (state) { + const versionedData = diskStore.getState() versionedData.data = state return versionedData } @@ -138,7 +137,6 @@ function setupController (initState) { } return Promise.resolve() - } // diff --git a/app/scripts/contentscript.js b/app/scripts/contentscript.js index 4d7e682d3..f7237b32e 100644 --- a/app/scripts/contentscript.js +++ b/app/scripts/contentscript.js @@ -77,7 +77,7 @@ function doctypeCheck () { } } -function suffixCheck() { +function suffixCheck () { var prohibitedTypes = ['xml', 'pdf'] var currentUrl = window.location.href var currentRegex diff --git a/app/scripts/controllers/address-book.js b/app/scripts/controllers/address-book.js index c66eb2bd4..6fb4ee114 100644 --- a/app/scripts/controllers/address-book.js +++ b/app/scripts/controllers/address-book.js @@ -39,11 +39,11 @@ class AddressBookController { // pushed object is an object of two fields. Current behavior does not set an // upper limit to the number of addresses. _addToAddressBook (address, name) { - let addressBook = this._getAddressBook() - let identities = this._getIdentities() + const addressBook = this._getAddressBook() + const identities = this._getIdentities() - let addressBookIndex = addressBook.findIndex((element) => { return element.address.toLowerCase() === address.toLowerCase() || element.name === name }) - let identitiesIndex = Object.keys(identities).findIndex((element) => { return element.toLowerCase() === address.toLowerCase() }) + const addressBookIndex = addressBook.findIndex((element) => { return element.address.toLowerCase() === address.toLowerCase() || element.name === name }) + const identitiesIndex = Object.keys(identities).findIndex((element) => { return element.toLowerCase() === address.toLowerCase() }) // trigger this condition if we own this address--no need to overwrite. if (identitiesIndex !== -1) { return Promise.resolve(addressBook) diff --git a/app/scripts/controllers/currency.js b/app/scripts/controllers/currency.js index c4904f8ac..fb130ed76 100644 --- a/app/scripts/controllers/currency.js +++ b/app/scripts/controllers/currency.js @@ -51,9 +51,11 @@ class CurrencyController { this.setConversionRate(Number(parsedResponse.ticker.price)) this.setConversionDate(Number(parsedResponse.timestamp)) }).catch((err) => { - console.warn('MetaMask - Failed to query currency conversion.') - this.setConversionRate(0) - this.setConversionDate('N/A') + if (err) { + console.warn('MetaMask - Failed to query currency conversion.') + this.setConversionRate(0) + this.setConversionDate('N/A') + } }) } diff --git a/app/scripts/controllers/preferences.js b/app/scripts/controllers/preferences.js index c7f675a41..7212c7c43 100644 --- a/app/scripts/controllers/preferences.js +++ b/app/scripts/controllers/preferences.js @@ -36,8 +36,8 @@ class PreferencesController { } addToFrequentRpcList (_url) { - let rpcList = this.getFrequentRpcList() - let index = rpcList.findIndex((element) => { return element === _url }) + const rpcList = this.getFrequentRpcList() + const index = rpcList.findIndex((element) => { return element === _url }) if (index !== -1) { rpcList.splice(index, 1) } @@ -53,13 +53,9 @@ class PreferencesController { getFrequentRpcList () { return this.store.getState().frequentRpcList } - // // PRIVATE METHODS // - - - } module.exports = PreferencesController diff --git a/app/scripts/first-time-state.js b/app/scripts/first-time-state.js index 3196981ba..87a7bb7b5 100644 --- a/app/scripts/first-time-state.js +++ b/app/scripts/first-time-state.js @@ -8,4 +8,4 @@ module.exports = { type: 'testnet', }, }, -} \ No newline at end of file +} diff --git a/app/scripts/keyring-controller.js b/app/scripts/keyring-controller.js index 16df6efa6..5b3c80e40 100644 --- a/app/scripts/keyring-controller.js +++ b/app/scripts/keyring-controller.js @@ -187,7 +187,7 @@ class KeyringController extends EventEmitter { .then((accounts) => { switch (type) { case 'Simple Key Pair': - let isNotIncluded = !accounts.find((key) => key === newAccount[0] || key === ethUtil.stripHexPrefix(newAccount[0])) + const isNotIncluded = !accounts.find((key) => key === newAccount[0] || key === ethUtil.stripHexPrefix(newAccount[0])) return (isNotIncluded) ? Promise.resolve(newAccount) : Promise.reject(new Error('The account you\'re are trying to import is a duplicate')) default: return Promise.resolve(newAccount) @@ -582,7 +582,7 @@ class KeyringController extends EventEmitter { }) } - _updateMemStoreKeyrings() { + _updateMemStoreKeyrings () { Promise.all(this.keyrings.map(this.displayForKeyring)) .then((keyrings) => { this.memStore.updateState({ keyrings }) diff --git a/app/scripts/lib/buy-eth-url.js b/app/scripts/lib/buy-eth-url.js index 91a1ec322..30db78a6c 100644 --- a/app/scripts/lib/buy-eth-url.js +++ b/app/scripts/lib/buy-eth-url.js @@ -1,6 +1,6 @@ module.exports = getBuyEthUrl -function getBuyEthUrl({ network, amount, address }){ +function getBuyEthUrl ({ network, amount, address }) { let url switch (network) { case '1': @@ -16,4 +16,4 @@ function getBuyEthUrl({ network, amount, address }){ break } return url -} \ No newline at end of file +} diff --git a/app/scripts/lib/eth-store.js b/app/scripts/lib/eth-store.js index 243253df2..6f04a9dd6 100644 --- a/app/scripts/lib/eth-store.js +++ b/app/scripts/lib/eth-store.js @@ -10,7 +10,7 @@ const async = require('async') const EthQuery = require('eth-query') const ObservableStore = require('obs-store') -function noop() {} +function noop () {} class EthereumStore extends ObservableStore { diff --git a/app/scripts/lib/inpage-provider.js b/app/scripts/lib/inpage-provider.js index 92936de2f..e5e398e24 100644 --- a/app/scripts/lib/inpage-provider.js +++ b/app/scripts/lib/inpage-provider.js @@ -85,7 +85,7 @@ MetamaskInpageProvider.prototype.send = function (payload) { break case 'net_version': - let networkVersion = self.publicConfigStore.getState().networkVersion + const networkVersion = self.publicConfigStore.getState().networkVersion result = networkVersion break @@ -125,7 +125,7 @@ function eachJsonMessage (payload, transformFn) { } } -function logStreamDisconnectWarning(remoteLabel, err){ +function logStreamDisconnectWarning (remoteLabel, err) { let warningMsg = `MetamaskInpageProvider - lost connection to ${remoteLabel}` if (err) warningMsg += '\n' + err.stack console.warn(warningMsg) diff --git a/app/scripts/lib/message-manager.js b/app/scripts/lib/message-manager.js index 711d5f159..f52e048e0 100644 --- a/app/scripts/lib/message-manager.js +++ b/app/scripts/lib/message-manager.js @@ -4,7 +4,7 @@ const ethUtil = require('ethereumjs-util') const createId = require('./random-id') -module.exports = class MessageManager extends EventEmitter{ +module.exports = class MessageManager extends EventEmitter { constructor (opts) { super() this.memStore = new ObservableStore({ @@ -108,7 +108,7 @@ module.exports = class MessageManager extends EventEmitter{ } -function normalizeMsgData(data) { +function normalizeMsgData (data) { if (data.slice(0, 2) === '0x') { // data is already hex return data diff --git a/app/scripts/lib/migrator/index.js b/app/scripts/lib/migrator/index.js index 312345263..c40c347b5 100644 --- a/app/scripts/lib/migrator/index.js +++ b/app/scripts/lib/migrator/index.js @@ -3,17 +3,17 @@ const asyncQ = require('async-q') class Migrator { constructor (opts = {}) { - let migrations = opts.migrations || [] + const migrations = opts.migrations || [] this.migrations = migrations.sort((a, b) => a.version - b.version) - let lastMigration = this.migrations.slice(-1)[0] + const lastMigration = this.migrations.slice(-1)[0] // use specified defaultVersion or highest migration version this.defaultVersion = opts.defaultVersion || (lastMigration && lastMigration.version) || 0 } // run all pending migrations on meta in place migrateData (versionedData = this.generateInitialState()) { - let remaining = this.migrations.filter(migrationIsPending) - + const remaining = this.migrations.filter(migrationIsPending) + return ( asyncQ.eachSeries(remaining, (migration) => this.runMigration(versionedData, migration)) .then(() => versionedData) @@ -21,12 +21,12 @@ class Migrator { // migration is "pending" if hit has a higher // version number than currentVersion - function migrationIsPending(migration) { + function migrationIsPending (migration) { return migration.version > versionedData.meta.version } } - runMigration(versionedData, migration) { + runMigration (versionedData, migration) { return ( migration.migrate(versionedData) .then((versionedData) => { diff --git a/app/scripts/lib/notification-manager.js b/app/scripts/lib/notification-manager.js index 55e5b8dd2..799282f6d 100644 --- a/app/scripts/lib/notification-manager.js +++ b/app/scripts/lib/notification-manager.js @@ -71,4 +71,4 @@ class NotificationManager { } -module.exports = NotificationManager \ No newline at end of file +module.exports = NotificationManager diff --git a/app/scripts/lib/personal-message-manager.js b/app/scripts/lib/personal-message-manager.js index bbc978446..6602f5aa8 100644 --- a/app/scripts/lib/personal-message-manager.js +++ b/app/scripts/lib/personal-message-manager.js @@ -5,7 +5,7 @@ const createId = require('./random-id') const hexRe = /^[0-9A-Fa-f]+$/g -module.exports = class PersonalMessageManager extends EventEmitter{ +module.exports = class PersonalMessageManager extends EventEmitter { constructor (opts) { super() this.memStore = new ObservableStore({ @@ -108,7 +108,7 @@ module.exports = class PersonalMessageManager extends EventEmitter{ this.emit('updateBadge') } - normalizeMsgData(data) { + normalizeMsgData (data) { try { const stripped = ethUtil.stripHexPrefix(data) if (stripped.match(hexRe)) { diff --git a/app/scripts/lib/tx-utils.js b/app/scripts/lib/tx-utils.js index e8e23f8b5..084ca3721 100644 --- a/app/scripts/lib/tx-utils.js +++ b/app/scripts/lib/tx-utils.js @@ -75,14 +75,14 @@ module.exports = class txProviderUtils { } fillInTxParams (txParams, cb) { - let fromAddress = txParams.from - let reqs = {} + const fromAddress = txParams.from + const reqs = {} if (isUndef(txParams.gas)) reqs.gas = (cb) => this.query.estimateGas(txParams, cb) if (isUndef(txParams.gasPrice)) reqs.gasPrice = (cb) => this.query.gasPrice(cb) if (isUndef(txParams.nonce)) reqs.nonce = (cb) => this.query.getTransactionCount(fromAddress, 'pending', cb) - async.parallel(reqs, function(err, result) { + async.parallel(reqs, function (err, result) { if (err) return cb(err) // write results to txParams obj Object.assign(txParams, result) @@ -123,14 +123,14 @@ module.exports = class txProviderUtils { // util -function isUndef(value) { +function isUndef (value) { return value === undefined } -function bnToHex(inputBn) { +function bnToHex (inputBn) { return ethUtil.addHexPrefix(inputBn.toString(16)) } -function hexToBn(inputHex) { +function hexToBn (inputHex) { return new BN(ethUtil.stripHexPrefix(inputHex), 16) } diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 2b8fc9cb8..b91b5efe8 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -32,7 +32,7 @@ module.exports = class MetamaskController extends EventEmitter { constructor (opts) { super() this.opts = opts - let initState = opts.initState || {} + const initState = opts.initState || {} // platform-specific api this.platform = opts.platform @@ -161,8 +161,7 @@ module.exports = class MetamaskController extends EventEmitter { // initializeProvider () { - - let provider = MetaMaskProvider({ + const provider = MetaMaskProvider({ static: { eth_syncing: false, web3_clientVersion: `MetaMask/v${version}`, @@ -170,8 +169,8 @@ module.exports = class MetamaskController extends EventEmitter { rpcUrl: this.configManager.getCurrentRpcAddress(), // account mgmt getAccounts: (cb) => { - let selectedAddress = this.preferencesController.getSelectedAddress() - let result = selectedAddress ? [selectedAddress] : [] + const selectedAddress = this.preferencesController.getSelectedAddress() + const result = selectedAddress ? [selectedAddress] : [] cb(null, result) }, // tx signing @@ -196,7 +195,7 @@ module.exports = class MetamaskController extends EventEmitter { publicConfigStore ) - function selectPublicState(state) { + function selectPublicState (state) { const result = { selectedAddress: undefined } try { result.selectedAddress = state.PreferencesController.selectedAddress @@ -253,56 +252,56 @@ module.exports = class MetamaskController extends EventEmitter { return { // etc - getState: (cb) => cb(null, this.getState()), - setProviderType: this.setProviderType.bind(this), - useEtherscanProvider: this.useEtherscanProvider.bind(this), - setCurrentCurrency: this.setCurrentCurrency.bind(this), - markAccountsFound: this.markAccountsFound.bind(this), + getState: (cb) => cb(null, this.getState()), + setProviderType: this.setProviderType.bind(this), + useEtherscanProvider: this.useEtherscanProvider.bind(this), + setCurrentCurrency: this.setCurrentCurrency.bind(this), + markAccountsFound: this.markAccountsFound.bind(this), // coinbase buyEth: this.buyEth.bind(this), // shapeshift createShapeShiftTx: this.createShapeShiftTx.bind(this), // primary HD keyring management - addNewAccount: this.addNewAccount.bind(this), - placeSeedWords: this.placeSeedWords.bind(this), - clearSeedWordCache: this.clearSeedWordCache.bind(this), - importAccountWithStrategy: this.importAccountWithStrategy.bind(this), + addNewAccount: this.addNewAccount.bind(this), + placeSeedWords: this.placeSeedWords.bind(this), + clearSeedWordCache: this.clearSeedWordCache.bind(this), + importAccountWithStrategy: this.importAccountWithStrategy.bind(this), // vault management submitPassword: this.submitPassword.bind(this), // PreferencesController - setSelectedAddress: nodeify(preferencesController.setSelectedAddress).bind(preferencesController), - setDefaultRpc: nodeify(this.setDefaultRpc).bind(this), - setCustomRpc: nodeify(this.setCustomRpc).bind(this), + setSelectedAddress: nodeify(preferencesController.setSelectedAddress).bind(preferencesController), + setDefaultRpc: nodeify(this.setDefaultRpc).bind(this), + setCustomRpc: nodeify(this.setCustomRpc).bind(this), // AddressController - setAddressBook: nodeify(addressBookController.setAddressBook).bind(addressBookController), + setAddressBook: nodeify(addressBookController.setAddressBook).bind(addressBookController), // KeyringController - setLocked: nodeify(keyringController.setLocked).bind(keyringController), + setLocked: nodeify(keyringController.setLocked).bind(keyringController), createNewVaultAndKeychain: nodeify(keyringController.createNewVaultAndKeychain).bind(keyringController), - createNewVaultAndRestore: nodeify(keyringController.createNewVaultAndRestore).bind(keyringController), - addNewKeyring: nodeify(keyringController.addNewKeyring).bind(keyringController), - saveAccountLabel: nodeify(keyringController.saveAccountLabel).bind(keyringController), - exportAccount: nodeify(keyringController.exportAccount).bind(keyringController), + createNewVaultAndRestore: nodeify(keyringController.createNewVaultAndRestore).bind(keyringController), + addNewKeyring: nodeify(keyringController.addNewKeyring).bind(keyringController), + saveAccountLabel: nodeify(keyringController.saveAccountLabel).bind(keyringController), + exportAccount: nodeify(keyringController.exportAccount).bind(keyringController), // txManager - approveTransaction: txManager.approveTransaction.bind(txManager), - cancelTransaction: txManager.cancelTransaction.bind(txManager), + approveTransaction: txManager.approveTransaction.bind(txManager), + cancelTransaction: txManager.cancelTransaction.bind(txManager), updateAndApproveTransaction: this.updateAndApproveTx.bind(this), // messageManager - signMessage: nodeify(this.signMessage).bind(this), - cancelMessage: this.cancelMessage.bind(this), + signMessage: nodeify(this.signMessage).bind(this), + cancelMessage: this.cancelMessage.bind(this), // personalMessageManager - signPersonalMessage: nodeify(this.signPersonalMessage).bind(this), - cancelPersonalMessage: this.cancelPersonalMessage.bind(this), + signPersonalMessage: nodeify(this.signPersonalMessage).bind(this), + cancelPersonalMessage: this.cancelPersonalMessage.bind(this), // notices - checkNotices: noticeController.updateNoticesList.bind(noticeController), + checkNotices: noticeController.updateNoticesList.bind(noticeController), markNoticeRead: noticeController.markNoticeRead.bind(noticeController), } } @@ -441,7 +440,7 @@ module.exports = class MetamaskController extends EventEmitter { } newUnsignedMessage (msgParams, cb) { - let msgId = this.messageManager.addUnapprovedMessage(msgParams) + const msgId = this.messageManager.addUnapprovedMessage(msgParams) this.sendUpdate() this.opts.showUnconfirmedMessage() this.messageManager.once(`${msgId}:finished`, (data) => { @@ -461,7 +460,7 @@ module.exports = class MetamaskController extends EventEmitter { return cb(new Error('MetaMask Message Signature: from field is required.')) } - let msgId = this.personalMessageManager.addUnapprovedMessage(msgParams) + const msgId = this.personalMessageManager.addUnapprovedMessage(msgParams) this.sendUpdate() this.opts.showUnconfirmedMessage() this.personalMessageManager.once(`${msgId}:finished`, (data) => { @@ -476,7 +475,7 @@ module.exports = class MetamaskController extends EventEmitter { }) } - updateAndApproveTx(txMeta, cb) { + updateAndApproveTx (txMeta, cb) { log.debug(`MetaMaskController - updateAndApproveTx: ${JSON.stringify(txMeta)}`) const txManager = this.txManager txManager.updateTx(txMeta) @@ -502,7 +501,7 @@ module.exports = class MetamaskController extends EventEmitter { }) } - cancelMessage(msgId, cb) { + cancelMessage (msgId, cb) { const messageManager = this.messageManager messageManager.rejectMsg(msgId) if (cb && typeof cb === 'function') { @@ -512,7 +511,7 @@ module.exports = class MetamaskController extends EventEmitter { // Prefixed Style Message Signing Methods: approvePersonalMessage (msgParams, cb) { - let msgId = this.personalMessageManager.addUnapprovedMessage(msgParams) + const msgId = this.personalMessageManager.addUnapprovedMessage(msgParams) this.sendUpdate() this.opts.showUnconfirmedMessage() this.personalMessageManager.once(`${msgId}:finished`, (data) => { @@ -545,7 +544,7 @@ module.exports = class MetamaskController extends EventEmitter { }) } - cancelPersonalMessage(msgId, cb) { + cancelPersonalMessage (msgId, cb) { const messageManager = this.personalMessageManager messageManager.rejectMsg(msgId) if (cb && typeof cb === 'function') { @@ -559,13 +558,13 @@ module.exports = class MetamaskController extends EventEmitter { cb(null, this.getState()) } - restoreOldVaultAccounts(migratorOutput) { + restoreOldVaultAccounts (migratorOutput) { const { serialized } = migratorOutput return this.keyringController.restoreKeyring(serialized) .then(() => migratorOutput) } - restoreOldLostAccounts(migratorOutput) { + restoreOldLostAccounts (migratorOutput) { const { lostAccounts } = migratorOutput if (lostAccounts) { this.configManager.setLostAccounts(lostAccounts.map(acct => acct.address)) diff --git a/app/scripts/migrations/002.js b/app/scripts/migrations/002.js index 36a870342..b1d88f2ef 100644 --- a/app/scripts/migrations/002.js +++ b/app/scripts/migrations/002.js @@ -7,7 +7,7 @@ module.exports = { version, migrate: function (originalVersionedData) { - let versionedData = clone(originalVersionedData) + const versionedData = clone(originalVersionedData) versionedData.meta.version = version try { if (versionedData.data.config.provider.type === 'etherscan') { diff --git a/app/scripts/migrations/003.js b/app/scripts/migrations/003.js index 1893576ad..140f81d40 100644 --- a/app/scripts/migrations/003.js +++ b/app/scripts/migrations/003.js @@ -8,7 +8,7 @@ module.exports = { version, migrate: function (originalVersionedData) { - let versionedData = clone(originalVersionedData) + const versionedData = clone(originalVersionedData) versionedData.meta.version = version try { if (versionedData.data.config.provider.rpcTarget === oldTestRpc) { diff --git a/app/scripts/migrations/004.js b/app/scripts/migrations/004.js index 405d932f8..cd558300c 100644 --- a/app/scripts/migrations/004.js +++ b/app/scripts/migrations/004.js @@ -6,7 +6,7 @@ module.exports = { version, migrate: function (versionedData) { - let safeVersionedData = clone(versionedData) + const safeVersionedData = clone(versionedData) safeVersionedData.meta.version = version try { if (safeVersionedData.data.config.provider.type !== 'rpc') return Promise.resolve(safeVersionedData) diff --git a/app/scripts/migrations/005.js b/app/scripts/migrations/005.js index e4b84f460..f7b68dfe4 100644 --- a/app/scripts/migrations/005.js +++ b/app/scripts/migrations/005.js @@ -14,7 +14,7 @@ module.exports = { version, migrate: function (originalVersionedData) { - let versionedData = clone(originalVersionedData) + const versionedData = clone(originalVersionedData) versionedData.meta.version = version try { const state = versionedData.data diff --git a/app/scripts/migrations/006.js b/app/scripts/migrations/006.js index 94d1b6ecd..51ea6e3e7 100644 --- a/app/scripts/migrations/006.js +++ b/app/scripts/migrations/006.js @@ -13,7 +13,7 @@ module.exports = { version, migrate: function (originalVersionedData) { - let versionedData = clone(originalVersionedData) + const versionedData = clone(originalVersionedData) versionedData.meta.version = version try { const state = versionedData.data diff --git a/app/scripts/migrations/007.js b/app/scripts/migrations/007.js index 236e35224..d9887b9c8 100644 --- a/app/scripts/migrations/007.js +++ b/app/scripts/migrations/007.js @@ -13,7 +13,7 @@ module.exports = { version, migrate: function (originalVersionedData) { - let versionedData = clone(originalVersionedData) + const versionedData = clone(originalVersionedData) versionedData.meta.version = version try { const state = versionedData.data diff --git a/app/scripts/migrations/008.js b/app/scripts/migrations/008.js index cd5e95d22..da7cb2e60 100644 --- a/app/scripts/migrations/008.js +++ b/app/scripts/migrations/008.js @@ -13,7 +13,7 @@ module.exports = { version, migrate: function (originalVersionedData) { - let versionedData = clone(originalVersionedData) + const versionedData = clone(originalVersionedData) versionedData.meta.version = version try { const state = versionedData.data diff --git a/app/scripts/migrations/009.js b/app/scripts/migrations/009.js index 4612fefdc..f47db55ac 100644 --- a/app/scripts/migrations/009.js +++ b/app/scripts/migrations/009.js @@ -13,7 +13,7 @@ module.exports = { version, migrate: function (originalVersionedData) { - let versionedData = clone(originalVersionedData) + const versionedData = clone(originalVersionedData) versionedData.meta.version = version try { const state = versionedData.data diff --git a/app/scripts/migrations/010.js b/app/scripts/migrations/010.js index c0cc56ae4..e4b9ac07e 100644 --- a/app/scripts/migrations/010.js +++ b/app/scripts/migrations/010.js @@ -13,7 +13,7 @@ module.exports = { version, migrate: function (originalVersionedData) { - let versionedData = clone(originalVersionedData) + const versionedData = clone(originalVersionedData) versionedData.meta.version = version try { const state = versionedData.data diff --git a/app/scripts/migrations/011.js b/app/scripts/migrations/011.js index 0d5d6d307..782ec809d 100644 --- a/app/scripts/migrations/011.js +++ b/app/scripts/migrations/011.js @@ -12,7 +12,7 @@ module.exports = { version, migrate: function (originalVersionedData) { - let versionedData = clone(originalVersionedData) + const versionedData = clone(originalVersionedData) versionedData.meta.version = version try { const state = versionedData.data diff --git a/app/scripts/migrations/012.js b/app/scripts/migrations/012.js index 8361b3793..f69ccbb02 100644 --- a/app/scripts/migrations/012.js +++ b/app/scripts/migrations/012.js @@ -12,7 +12,7 @@ module.exports = { version, migrate: function (originalVersionedData) { - let versionedData = clone(originalVersionedData) + const versionedData = clone(originalVersionedData) versionedData.meta.version = version try { const state = versionedData.data diff --git a/app/scripts/migrations/_multi-keyring.js b/app/scripts/migrations/_multi-keyring.js index 04c966d4d..253aa3d9d 100644 --- a/app/scripts/migrations/_multi-keyring.js +++ b/app/scripts/migrations/_multi-keyring.js @@ -15,15 +15,15 @@ const KeyringController = require('../../app/scripts/lib/keyring-controller') const password = 'obviously not correct' module.exports = { - version, + version, migrate: function (versionedData) { versionedData.meta.version = version - let store = new ObservableStore(versionedData.data) - let configManager = new ConfigManager({ store }) - let idStoreMigrator = new IdentityStoreMigrator({ configManager }) - let keyringController = new KeyringController({ + const store = new ObservableStore(versionedData.data) + const configManager = new ConfigManager({ store }) + const idStoreMigrator = new IdentityStoreMigrator({ configManager }) + const keyringController = new KeyringController({ configManager: configManager, }) @@ -46,6 +46,5 @@ module.exports = { return Promise.resolve(versionedData) }) }) - }, } diff --git a/app/scripts/popup-core.js b/app/scripts/popup-core.js index 1e5d70e8b..f9ac4d052 100644 --- a/app/scripts/popup-core.js +++ b/app/scripts/popup-core.js @@ -16,7 +16,6 @@ function initializePopup ({ container, connectionStream }, cb) { (cb) => connectToAccountManager(connectionStream, cb), (accountManager, cb) => launchMetamaskUi({ container, accountManager }, cb), ], cb) - } function connectToAccountManager (connectionStream, cb) { diff --git a/app/scripts/popup.js b/app/scripts/popup.js index 0fbde54b3..5f17f0651 100644 --- a/app/scripts/popup.js +++ b/app/scripts/popup.js @@ -41,7 +41,7 @@ function closePopupIfOpen (windowType) { } } -function displayCriticalError(err) { +function displayCriticalError (err) { container.innerHTML = '
The MetaMask app failed to load: please open and close MetaMask again to restart.
' container.style.height = '80px' log.error(err.stack) diff --git a/app/scripts/transaction-manager.js b/app/scripts/transaction-manager.js index d7051b2cb..9f267160f 100644 --- a/app/scripts/transaction-manager.js +++ b/app/scripts/transaction-manager.js @@ -28,9 +28,9 @@ module.exports = class TransactionManager extends EventEmitter { // memstore is computed from a few different stores this._updateMemstore() - this.store.subscribe(() => this._updateMemstore() ) - this.networkStore.subscribe(() => this._updateMemstore() ) - this.preferencesStore.subscribe(() => this._updateMemstore() ) + this.store.subscribe(() => this._updateMemstore()) + this.networkStore.subscribe(() => this._updateMemstore()) + this.preferencesStore.subscribe(() => this._updateMemstore()) } getState () { @@ -47,8 +47,8 @@ module.exports = class TransactionManager extends EventEmitter { // Returns the tx list getTxList () { - let network = this.getNetwork() - let fullTxList = this.getFullTxList() + const network = this.getNetwork() + const fullTxList = this.getFullTxList() return fullTxList.filter(txMeta => txMeta.metamaskNetworkId === network) } @@ -64,10 +64,10 @@ module.exports = class TransactionManager extends EventEmitter { // Adds a tx to the txlist addTx (txMeta) { - let txCount = this.getTxCount() - let network = this.getNetwork() - let fullTxList = this.getFullTxList() - let txHistoryLimit = this.txHistoryLimit + const txCount = this.getTxCount() + const network = this.getNetwork() + const fullTxList = this.getFullTxList() + const txHistoryLimit = this.txHistoryLimit // checks if the length of the tx history is // longer then desired persistence limit @@ -197,7 +197,7 @@ module.exports = class TransactionManager extends EventEmitter { } fillInTxParams (txId, cb) { - let txMeta = this.getTx(txId) + const txMeta = this.getTx(txId) this.txProviderUtils.fillInTxParams(txMeta.txParams, (err) => { if (err) return cb(err) this.updateTx(txMeta) @@ -205,7 +205,7 @@ module.exports = class TransactionManager extends EventEmitter { }) } - getChainId() { + getChainId () { const networkState = this.networkStore.getState() const getChainId = parseInt(networkState.network) if (Number.isNaN(getChainId)) { @@ -242,7 +242,7 @@ module.exports = class TransactionManager extends EventEmitter { // receives a txHash records the tx as signed setTxHash (txId, txHash) { // Add the tx hash to the persisted meta-tx object - let txMeta = this.getTx(txId) + const txMeta = this.getTx(txId) txMeta.hash = txHash this.updateTx(txMeta) } @@ -315,7 +315,7 @@ module.exports = class TransactionManager extends EventEmitter { } setTxStatusFailed (txId, reason) { - let txMeta = this.getTx(txId) + const txMeta = this.getTx(txId) txMeta.err = reason this.updateTx(txMeta) this._setTxStatus(txId, 'failed') @@ -338,7 +338,7 @@ module.exports = class TransactionManager extends EventEmitter { var txHash = txMeta.hash var txId = txMeta.id if (!txHash) { - let errReason = { + const errReason = { errCode: 'No hash was provided', message: 'We had an error while submitting this transaction, please try again.', } diff --git a/ui/app/accounts/import/index.js b/ui/app/accounts/import/index.js index 96350852a..a0f0f9bdb 100644 --- a/ui/app/accounts/import/index.js +++ b/ui/app/accounts/import/index.js @@ -73,7 +73,7 @@ AccountImportSubview.prototype.render = function () { ) } -AccountImportSubview.prototype.renderImportView = function() { +AccountImportSubview.prototype.renderImportView = function () { const props = this.props const state = this.state || {} const { type } = state diff --git a/ui/app/actions.js b/ui/app/actions.js index 18f341411..c15c9be7e 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -314,7 +314,7 @@ function importNewAccount (strategy, args) { } } -function navigateToNewAccountScreen() { +function navigateToNewAccountScreen () { return { type: this.NEW_ACCOUNT_SCREEN, } @@ -665,7 +665,7 @@ function clearNotices () { } } -function markAccountsFound() { +function markAccountsFound () { log.debug(`background.markAccountsFound`) return callBackgroundThenUpdate(background.markAccountsFound) } @@ -978,7 +978,7 @@ function callBackgroundThenUpdate (method, ...args) { } } -function forceUpdateMetamaskState(dispatch){ +function forceUpdateMetamaskState (dispatch) { log.debug(`background.getState`) background.getState((err, newState) => { if (err) { diff --git a/ui/app/app.js b/ui/app/app.js index 5a7596aca..521f453cc 100644 --- a/ui/app/app.js +++ b/ui/app/app.js @@ -552,5 +552,4 @@ App.prototype.renderCommonRpc = function (rpcList, provider) { }) } }) - } diff --git a/ui/app/components/ens-input.js b/ui/app/components/ens-input.js index facf29d97..f1cf49998 100644 --- a/ui/app/components/ens-input.js +++ b/ui/app/components/ens-input.js @@ -24,7 +24,7 @@ EnsInput.prototype.render = function () { list: 'addresses', onChange: () => { const network = this.props.network - let resolverAddress = networkResolvers[network] + const resolverAddress = networkResolvers[network] if (!resolverAddress) return const recipient = document.querySelector('input[name="address"]').value @@ -52,7 +52,7 @@ EnsInput.prototype.render = function () { [ // Corresponds to the addresses owned. Object.keys(props.identities).map((key) => { - let identity = props.identities[key] + const identity = props.identities[key] return h('option', { value: identity.address, label: identity.name, @@ -72,7 +72,7 @@ EnsInput.prototype.render = function () { EnsInput.prototype.componentDidMount = function () { const network = this.props.network - let resolverAddress = networkResolvers[network] + const resolverAddress = networkResolvers[network] if (resolverAddress) { const provider = web3.currentProvider diff --git a/ui/app/components/notice.js b/ui/app/components/notice.js index b85787033..3c8523daf 100644 --- a/ui/app/components/notice.js +++ b/ui/app/components/notice.js @@ -115,8 +115,9 @@ Notice.prototype.render = function () { Notice.prototype.componentDidMount = function () { var node = findDOMNode(this) linker.setupListener(node) - if (document.getElementsByClassName('notice-box')[0].clientHeight < 310) { this.setState({disclaimerDisabled: false}) } - + if (document.getElementsByClassName('notice-box')[0].clientHeight < 310) { + this.setState({disclaimerDisabled: false}) + } } Notice.prototype.componentWillUnmount = function () { diff --git a/ui/app/components/transaction-list-item-icon.js b/ui/app/components/transaction-list-item-icon.js index ca2781451..d63cae259 100644 --- a/ui/app/components/transaction-list-item-icon.js +++ b/ui/app/components/transaction-list-item-icon.js @@ -15,7 +15,7 @@ TransactionIcon.prototype.render = function () { const { transaction, txParams, isMsg } = this.props switch (transaction.status) { case 'unapproved': - return h( !isMsg ? '.unapproved-tx-icon' : 'i.fa.fa-certificate.fa-lg') + return h(!isMsg ? '.unapproved-tx-icon' : 'i.fa.fa-certificate.fa-lg') case 'rejected': return h('i.fa.fa-exclamation-triangle.fa-lg.warning', { diff --git a/ui/app/components/transaction-list-item.js b/ui/app/components/transaction-list-item.js index 9fef52355..ec1b0d66c 100644 --- a/ui/app/components/transaction-list-item.js +++ b/ui/app/components/transaction-list-item.js @@ -134,7 +134,6 @@ function failIfFailed (transaction) { return h('span.error', ' (Rejected)') } if (transaction.err) { - return h(Tooltip, { title: transaction.err.message, position: 'bottom', @@ -142,5 +141,4 @@ function failIfFailed (transaction) { h('span.error', ' (Failed)'), ]) } - } diff --git a/ui/app/conf-tx.js b/ui/app/conf-tx.js index 770f79b19..83ac5a4fd 100644 --- a/ui/app/conf-tx.js +++ b/ui/app/conf-tx.js @@ -125,14 +125,12 @@ function currentTxView (opts) { if (txParams) { log.debug('txParams detected, rendering pending tx') return h(PendingTx, opts) - } else if (msgParams) { log.debug('msgParams detected, rendering pending msg') if (type === 'eth_sign') { log.debug('rendering eth_sign message') return h(PendingMsg, opts) - } else if (type === 'personal_sign') { log.debug('rendering personal_sign message') return h(PendingPersonalMsg, opts) diff --git a/ui/app/reducers/app.js b/ui/app/reducers/app.js index 324a4df35..deacad0a7 100644 --- a/ui/app/reducers/app.js +++ b/ui/app/reducers/app.js @@ -315,7 +315,7 @@ function reduceApp (state, action) { case actions.COMPLETED_TX: log.debug('reducing COMPLETED_TX for tx ' + action.value) const otherUnconfActions = getUnconfActionList(state) - .filter(tx => tx.id !== action.value ) + .filter(tx => tx.id !== action.value) const hasOtherUnconfActions = otherUnconfActions.length > 0 if (hasOtherUnconfActions) { From e665dd7e1caa19d3df7c7854ff12d520398e3b34 Mon Sep 17 00:00:00 2001 From: frankiebee Date: Thu, 27 Apr 2017 13:31:54 +0200 Subject: [PATCH 178/372] bump client-sw-ready-event --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 157dbda65..73c285d06 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "bluebird": "^3.5.0", "browser-passworder": "^2.0.3", "browserify-derequire": "^0.9.4", - "client-sw-ready-event": "^3.0.1", + "client-sw-ready-event": "^3.0.3", "clone": "^1.0.2", "copy-to-clipboard": "^2.0.0", "debounce": "^1.0.0", From e7c7c85791377bdd55042e6a4b026f4424230408 Mon Sep 17 00:00:00 2001 From: frankiebee Date: Thu, 27 Apr 2017 14:26:29 +0200 Subject: [PATCH 179/372] Update README for mascara --- mascara/README.md | 14 +++----------- package.json | 3 ++- 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/mascara/README.md b/mascara/README.md index cdeb4795c..75d3cabad 100644 --- a/mascara/README.md +++ b/mascara/README.md @@ -1,20 +1,12 @@ start the dual servers (dapp + mascara) ``` -node server.js +npm run mascara ``` ## First time use: -- navigate to: http://localhost:9001/popup/popup.html +- navigate to: http://localhost:9001 - Create an Account -- go back to http://localhost:9002/ +- go back to http://localhost:9002 - open devTools - click Sync Tx - -### Todos - - - [ ] Figure out user flows and UI redesign - - [ ] Figure out FireFox - Standing problems: - - [ ] IndexDb - diff --git a/package.json b/package.json index 73c285d06..2d6d52765 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,8 @@ "testem": "npm run buildMock && testem", "announce": "node development/announcer.js", "generateNotice": "node notices/notice-generator.js", - "deleteNotice": "node notices/notice-delete.js" + "deleteNotice": "node notices/notice-delete.js", + "mascara": "node ./mascara/example/server" }, "browserify": { "transform": [ From 6d1fe7845c3e74858bbc9c7181348153dfcd55e9 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Thu, 27 Apr 2017 15:11:01 -0700 Subject: [PATCH 180/372] Version 3.6.0 t # Explicit paths specified without -i or -o; assuming --only paths... --- CHANGELOG.md | 5 ++++- app/manifest.json | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 63346fa5b..e629e0bc1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,12 +2,15 @@ ## Current Master +## 3.6.0 2017-4-25 + +- Add Rinkeby Test Network to our network list. + ## 3.5.4 2017-4-25 - Fix occasional nonce tracking issue. - Fix bug where some events would not be emitted by web3. - Fix bug where an error would be thrown when composing signatures for networks with large ID values. -- Add Rinkeby Test Network to our network list. ## 3.5.3 2017-4-24 diff --git a/app/manifest.json b/app/manifest.json index 6ef428d2c..d5f66173c 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -1,7 +1,7 @@ { "name": "MetaMask", "short_name": "Metamask", - "version": "3.5.4", + "version": "3.6.0", "manifest_version": 2, "author": "https://metamask.io", "description": "Ethereum Browser Extension", From e7e0919d7c76c818590df4435db0152298298bd9 Mon Sep 17 00:00:00 2001 From: frankiebee Date: Thu, 27 Apr 2017 15:25:00 +0200 Subject: [PATCH 181/372] Setup test enviroment for mascara --- mascara/src/background.js | 1 + mascara/test/helpers.js | 7 ++ mascara/test/index.html | 20 ++++ mascara/test/index.js | 20 ++++ mascara/test/jquery-3.1.0.min.js | 4 + mascara/test/lib/first-time.js | 119 +++++++++++++++++++++++ mascara/test/testem.yml | 13 +++ mascara/test/util/mascara-test-helper.js | 40 ++++++++ mascara/test/window-load.js | 7 ++ package.json | 6 +- 10 files changed, 236 insertions(+), 1 deletion(-) create mode 100644 mascara/test/helpers.js create mode 100644 mascara/test/index.html create mode 100644 mascara/test/index.js create mode 100644 mascara/test/jquery-3.1.0.min.js create mode 100644 mascara/test/lib/first-time.js create mode 100644 mascara/test/testem.yml create mode 100644 mascara/test/util/mascara-test-helper.js create mode 100644 mascara/test/window-load.js diff --git a/mascara/src/background.js b/mascara/src/background.js index 957570050..746c479f9 100644 --- a/mascara/src/background.js +++ b/mascara/src/background.js @@ -1,4 +1,5 @@ global.window = global +const self = global const pipe = require('pump') const SwGlobalListener = require('sw-stream/lib/sw-global-listener.js') diff --git a/mascara/test/helpers.js b/mascara/test/helpers.js new file mode 100644 index 000000000..eede103b4 --- /dev/null +++ b/mascara/test/helpers.js @@ -0,0 +1,7 @@ +function wait(time) { + return new Promise(function(resolve, reject) { + setTimeout(function() { + resolve() + }, time * 3 || 1500) + }) +} diff --git a/mascara/test/index.html b/mascara/test/index.html new file mode 100644 index 000000000..abd985121 --- /dev/null +++ b/mascara/test/index.html @@ -0,0 +1,20 @@ + + + + + + QUnit Example + + + +
+
+ + + + + + +
+ + diff --git a/mascara/test/index.js b/mascara/test/index.js new file mode 100644 index 000000000..b94ce16dd --- /dev/null +++ b/mascara/test/index.js @@ -0,0 +1,20 @@ +var fs = require('fs') +var path = require('path') +var browserify = require('browserify'); +var tests = fs.readdirSync(path.join(__dirname, 'lib')) +var bundlePath = path.join(__dirname, 'test-bundle.js') +var b = browserify(); + +// Remove old bundle +try { + fs.unlinkSync(bundlePath) +} catch (e) {} + +var writeStream = fs.createWriteStream(bundlePath) + +tests.forEach(function(fileName) { + b.add(path.join(__dirname, 'lib', fileName)) +}) + +b.bundle().pipe(writeStream); + diff --git a/mascara/test/jquery-3.1.0.min.js b/mascara/test/jquery-3.1.0.min.js new file mode 100644 index 000000000..f6a6a99e6 --- /dev/null +++ b/mascara/test/jquery-3.1.0.min.js @@ -0,0 +1,4 @@ +/*! jQuery v3.1.0 | (c) jQuery Foundation | jquery.org/license */ +!function(a,b){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){"use strict";var c=[],d=a.document,e=Object.getPrototypeOf,f=c.slice,g=c.concat,h=c.push,i=c.indexOf,j={},k=j.toString,l=j.hasOwnProperty,m=l.toString,n=m.call(Object),o={};function p(a,b){b=b||d;var c=b.createElement("script");c.text=a,b.head.appendChild(c).parentNode.removeChild(c)}var q="3.1.0",r=function(a,b){return new r.fn.init(a,b)},s=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,t=/^-ms-/,u=/-([a-z])/g,v=function(a,b){return b.toUpperCase()};r.fn=r.prototype={jquery:q,constructor:r,length:0,toArray:function(){return f.call(this)},get:function(a){return null!=a?a<0?this[a+this.length]:this[a]:f.call(this)},pushStack:function(a){var b=r.merge(this.constructor(),a);return b.prevObject=this,b},each:function(a){return r.each(this,a)},map:function(a){return this.pushStack(r.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(f.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(a<0?b:0);return this.pushStack(c>=0&&c0&&b-1 in a)}var x=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*new Date,v=a.document,w=0,x=0,y=ha(),z=ha(),A=ha(),B=function(a,b){return a===b&&(l=!0),0},C={}.hasOwnProperty,D=[],E=D.pop,F=D.push,G=D.push,H=D.slice,I=function(a,b){for(var c=0,d=a.length;c+~]|"+K+")"+K+"*"),S=new RegExp("="+K+"*([^\\]'\"]*?)"+K+"*\\]","g"),T=new RegExp(N),U=new RegExp("^"+L+"$"),V={ID:new RegExp("^#("+L+")"),CLASS:new RegExp("^\\.("+L+")"),TAG:new RegExp("^("+L+"|[*])"),ATTR:new RegExp("^"+M),PSEUDO:new RegExp("^"+N),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+K+"*(even|odd|(([+-]|)(\\d*)n|)"+K+"*(?:([+-]|)"+K+"*(\\d+)|))"+K+"*\\)|)","i"),bool:new RegExp("^(?:"+J+")$","i"),needsContext:new RegExp("^"+K+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+K+"*((?:-\\d)?\\d*)"+K+"*\\)|)(?=[^-]|$)","i")},W=/^(?:input|select|textarea|button)$/i,X=/^h\d$/i,Y=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,$=/[+~]/,_=new RegExp("\\\\([\\da-f]{1,6}"+K+"?|("+K+")|.)","ig"),aa=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:d<0?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},ba=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\x80-\uFFFF\w-]/g,ca=function(a,b){return b?"\0"===a?"\ufffd":a.slice(0,-1)+"\\"+a.charCodeAt(a.length-1).toString(16)+" ":"\\"+a},da=function(){m()},ea=ta(function(a){return a.disabled===!0},{dir:"parentNode",next:"legend"});try{G.apply(D=H.call(v.childNodes),v.childNodes),D[v.childNodes.length].nodeType}catch(fa){G={apply:D.length?function(a,b){F.apply(a,H.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function ga(a,b,d,e){var f,h,j,k,l,o,r,s=b&&b.ownerDocument,w=b?b.nodeType:9;if(d=d||[],"string"!=typeof a||!a||1!==w&&9!==w&&11!==w)return d;if(!e&&((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,p)){if(11!==w&&(l=Z.exec(a)))if(f=l[1]){if(9===w){if(!(j=b.getElementById(f)))return d;if(j.id===f)return d.push(j),d}else if(s&&(j=s.getElementById(f))&&t(b,j)&&j.id===f)return d.push(j),d}else{if(l[2])return G.apply(d,b.getElementsByTagName(a)),d;if((f=l[3])&&c.getElementsByClassName&&b.getElementsByClassName)return G.apply(d,b.getElementsByClassName(f)),d}if(c.qsa&&!A[a+" "]&&(!q||!q.test(a))){if(1!==w)s=b,r=a;else if("object"!==b.nodeName.toLowerCase()){(k=b.getAttribute("id"))?k=k.replace(ba,ca):b.setAttribute("id",k=u),o=g(a),h=o.length;while(h--)o[h]="#"+k+" "+sa(o[h]);r=o.join(","),s=$.test(a)&&qa(b.parentNode)||b}if(r)try{return G.apply(d,s.querySelectorAll(r)),d}catch(x){}finally{k===u&&b.removeAttribute("id")}}}return i(a.replace(P,"$1"),b,d,e)}function ha(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function ia(a){return a[u]=!0,a}function ja(a){var b=n.createElement("fieldset");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function ka(a,b){var c=a.split("|"),e=c.length;while(e--)d.attrHandle[c[e]]=b}function la(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&a.sourceIndex-b.sourceIndex;if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function ma(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function na(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function oa(a){return function(b){return"label"in b&&b.disabled===a||"form"in b&&b.disabled===a||"form"in b&&b.disabled===!1&&(b.isDisabled===a||b.isDisabled!==!a&&("label"in b||!ea(b))!==a)}}function pa(a){return ia(function(b){return b=+b,ia(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function qa(a){return a&&"undefined"!=typeof a.getElementsByTagName&&a}c=ga.support={},f=ga.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return!!b&&"HTML"!==b.nodeName},m=ga.setDocument=function(a){var b,e,g=a?a.ownerDocument||a:v;return g!==n&&9===g.nodeType&&g.documentElement?(n=g,o=n.documentElement,p=!f(n),v!==n&&(e=n.defaultView)&&e.top!==e&&(e.addEventListener?e.addEventListener("unload",da,!1):e.attachEvent&&e.attachEvent("onunload",da)),c.attributes=ja(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ja(function(a){return a.appendChild(n.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=Y.test(n.getElementsByClassName),c.getById=ja(function(a){return o.appendChild(a).id=u,!n.getElementsByName||!n.getElementsByName(u).length}),c.getById?(d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c?[c]:[]}},d.filter.ID=function(a){var b=a.replace(_,aa);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(_,aa);return function(a){var c="undefined"!=typeof a.getAttributeNode&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return"undefined"!=typeof b.getElementsByTagName?b.getElementsByTagName(a):c.qsa?b.querySelectorAll(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){if("undefined"!=typeof b.getElementsByClassName&&p)return b.getElementsByClassName(a)},r=[],q=[],(c.qsa=Y.test(n.querySelectorAll))&&(ja(function(a){o.appendChild(a).innerHTML="",a.querySelectorAll("[msallowcapture^='']").length&&q.push("[*^$]="+K+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+K+"*(?:value|"+J+")"),a.querySelectorAll("[id~="+u+"-]").length||q.push("~="),a.querySelectorAll(":checked").length||q.push(":checked"),a.querySelectorAll("a#"+u+"+*").length||q.push(".#.+[+~]")}),ja(function(a){a.innerHTML="";var b=n.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+K+"*[*^$|!~]?="),2!==a.querySelectorAll(":enabled").length&&q.push(":enabled",":disabled"),o.appendChild(a).disabled=!0,2!==a.querySelectorAll(":disabled").length&&q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=Y.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ja(function(a){c.disconnectedMatch=s.call(a,"*"),s.call(a,"[s!='']:x"),r.push("!=",N)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=Y.test(o.compareDocumentPosition),t=b||Y.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===n||a.ownerDocument===v&&t(v,a)?-1:b===n||b.ownerDocument===v&&t(v,b)?1:k?I(k,a)-I(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,g=[a],h=[b];if(!e||!f)return a===n?-1:b===n?1:e?-1:f?1:k?I(k,a)-I(k,b):0;if(e===f)return la(a,b);c=a;while(c=c.parentNode)g.unshift(c);c=b;while(c=c.parentNode)h.unshift(c);while(g[d]===h[d])d++;return d?la(g[d],h[d]):g[d]===v?-1:h[d]===v?1:0},n):n},ga.matches=function(a,b){return ga(a,null,null,b)},ga.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(S,"='$1']"),c.matchesSelector&&p&&!A[b+" "]&&(!r||!r.test(b))&&(!q||!q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return ga(b,n,null,[a]).length>0},ga.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},ga.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&C.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},ga.escape=function(a){return(a+"").replace(ba,ca)},ga.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},ga.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=ga.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=ga.selectors={cacheLength:50,createPseudo:ia,match:V,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(_,aa),a[3]=(a[3]||a[4]||a[5]||"").replace(_,aa),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||ga.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&ga.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return V.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&T.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(_,aa).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+K+")"+a+"("+K+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||"undefined"!=typeof a.getAttribute&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=ga.attr(d,a);return null==e?"!="===b:!b||(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e.replace(O," ")+" ").indexOf(c)>-1:"|="===b&&(e===c||e.slice(0,c.length+1)===c+"-"))}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h,t=!1;if(q){if(f){while(p){m=b;while(m=m[p])if(h?m.nodeName.toLowerCase()===r:1===m.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){m=q,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n&&j[2],m=n&&q.childNodes[n];while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if(1===m.nodeType&&++t&&m===b){k[a]=[w,n,t];break}}else if(s&&(m=b,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n),t===!1)while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if((h?m.nodeName.toLowerCase()===r:1===m.nodeType)&&++t&&(s&&(l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),k[a]=[w,t]),m===b))break;return t-=e,t===d||t%d===0&&t/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||ga.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ia(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=I(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ia(function(a){var b=[],c=[],d=h(a.replace(P,"$1"));return d[u]?ia(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),b[0]=null,!c.pop()}}),has:ia(function(a){return function(b){return ga(a,b).length>0}}),contains:ia(function(a){return a=a.replace(_,aa),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ia(function(a){return U.test(a||"")||ga.error("unsupported lang: "+a),a=a.replace(_,aa).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:oa(!1),disabled:oa(!0),checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return X.test(a.nodeName)},input:function(a){return W.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:pa(function(){return[0]}),last:pa(function(a,b){return[b-1]}),eq:pa(function(a,b,c){return[c<0?c+b:c]}),even:pa(function(a,b){for(var c=0;c=0;)a.push(d);return a}),gt:pa(function(a,b,c){for(var d=c<0?c+b:c;++d1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function va(a,b,c){for(var d=0,e=b.length;d-1&&(f[j]=!(g[j]=l))}}else r=wa(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):G.apply(g,r)})}function ya(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=ta(function(a){return a===b},h,!0),l=ta(function(a){return I(b,a)>-1},h,!0),m=[function(a,c,d){var e=!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d));return b=null,e}];i1&&ua(m),i>1&&sa(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(P,"$1"),c,i0,e=a.length>0,f=function(f,g,h,i,k){var l,o,q,r=0,s="0",t=f&&[],u=[],v=j,x=f||e&&d.find.TAG("*",k),y=w+=null==v?1:Math.random()||.1,z=x.length;for(k&&(j=g===n||g||k);s!==z&&null!=(l=x[s]);s++){if(e&&l){o=0,g||l.ownerDocument===n||(m(l),h=!p);while(q=a[o++])if(q(l,g||n,h)){i.push(l);break}k&&(w=y)}c&&((l=!q&&l)&&r--,f&&t.push(l))}if(r+=s,c&&s!==r){o=0;while(q=b[o++])q(t,u,g,h);if(f){if(r>0)while(s--)t[s]||u[s]||(u[s]=E.call(i));u=wa(u)}G.apply(i,u),k&&!f&&u.length>0&&r+b.length>1&&ga.uniqueSort(i)}return k&&(w=y,j=v),t};return c?ia(f):f}return h=ga.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=ya(b[c]),f[u]?d.push(f):e.push(f);f=A(a,za(e,d)),f.selector=a}return f},i=ga.select=function(a,b,e,f){var i,j,k,l,m,n="function"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&"ID"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(_,aa),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=V.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(_,aa),$.test(j[0].type)&&qa(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&sa(j),!a)return G.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,!b||$.test(a)&&qa(b.parentNode)||b),e},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ja(function(a){return 1&a.compareDocumentPosition(n.createElement("fieldset"))}),ja(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||ka("type|href|height|width",function(a,b,c){if(!c)return a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ja(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||ka("value",function(a,b,c){if(!c&&"input"===a.nodeName.toLowerCase())return a.defaultValue}),ja(function(a){return null==a.getAttribute("disabled")})||ka(J,function(a,b,c){var d;if(!c)return a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),ga}(a);r.find=x,r.expr=x.selectors,r.expr[":"]=r.expr.pseudos,r.uniqueSort=r.unique=x.uniqueSort,r.text=x.getText,r.isXMLDoc=x.isXML,r.contains=x.contains,r.escapeSelector=x.escape;var y=function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&r(a).is(c))break;d.push(a)}return d},z=function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c},A=r.expr.match.needsContext,B=/^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i,C=/^.[^:#\[\.,]*$/;function D(a,b,c){if(r.isFunction(b))return r.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return r.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(C.test(b))return r.filter(b,a,c);b=r.filter(b,a)}return r.grep(a,function(a){return i.call(b,a)>-1!==c&&1===a.nodeType})}r.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?r.find.matchesSelector(d,a)?[d]:[]:r.find.matches(a,r.grep(b,function(a){return 1===a.nodeType}))},r.fn.extend({find:function(a){var b,c,d=this.length,e=this;if("string"!=typeof a)return this.pushStack(r(a).filter(function(){for(b=0;b1?r.uniqueSort(c):c},filter:function(a){return this.pushStack(D(this,a||[],!1))},not:function(a){return this.pushStack(D(this,a||[],!0))},is:function(a){return!!D(this,"string"==typeof a&&A.test(a)?r(a):a||[],!1).length}});var E,F=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/,G=r.fn.init=function(a,b,c){var e,f;if(!a)return this;if(c=c||E,"string"==typeof a){if(e="<"===a[0]&&">"===a[a.length-1]&&a.length>=3?[null,a,null]:F.exec(a),!e||!e[1]&&b)return!b||b.jquery?(b||c).find(a):this.constructor(b).find(a);if(e[1]){if(b=b instanceof r?b[0]:b,r.merge(this,r.parseHTML(e[1],b&&b.nodeType?b.ownerDocument||b:d,!0)),B.test(e[1])&&r.isPlainObject(b))for(e in b)r.isFunction(this[e])?this[e](b[e]):this.attr(e,b[e]);return this}return f=d.getElementById(e[2]),f&&(this[0]=f,this.length=1),this}return a.nodeType?(this[0]=a,this.length=1,this):r.isFunction(a)?void 0!==c.ready?c.ready(a):a(r):r.makeArray(a,this)};G.prototype=r.fn,E=r(d);var H=/^(?:parents|prev(?:Until|All))/,I={children:!0,contents:!0,next:!0,prev:!0};r.fn.extend({has:function(a){var b=r(a,this),c=b.length;return this.filter(function(){for(var a=0;a-1:1===c.nodeType&&r.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?r.uniqueSort(f):f)},index:function(a){return a?"string"==typeof a?i.call(r(a),this[0]):i.call(this,a.jquery?a[0]:a):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(r.uniqueSort(r.merge(this.get(),r(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function J(a,b){while((a=a[b])&&1!==a.nodeType);return a}r.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return y(a,"parentNode")},parentsUntil:function(a,b,c){return y(a,"parentNode",c)},next:function(a){return J(a,"nextSibling")},prev:function(a){return J(a,"previousSibling")},nextAll:function(a){return y(a,"nextSibling")},prevAll:function(a){return y(a,"previousSibling")},nextUntil:function(a,b,c){return y(a,"nextSibling",c)},prevUntil:function(a,b,c){return y(a,"previousSibling",c)},siblings:function(a){return z((a.parentNode||{}).firstChild,a)},children:function(a){return z(a.firstChild)},contents:function(a){return a.contentDocument||r.merge([],a.childNodes)}},function(a,b){r.fn[a]=function(c,d){var e=r.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=r.filter(d,e)),this.length>1&&(I[a]||r.uniqueSort(e),H.test(a)&&e.reverse()),this.pushStack(e)}});var K=/\S+/g;function L(a){var b={};return r.each(a.match(K)||[],function(a,c){b[c]=!0}),b}r.Callbacks=function(a){a="string"==typeof a?L(a):r.extend({},a);var b,c,d,e,f=[],g=[],h=-1,i=function(){for(e=a.once,d=b=!0;g.length;h=-1){c=g.shift();while(++h-1)f.splice(c,1),c<=h&&h--}),this},has:function(a){return a?r.inArray(a,f)>-1:f.length>0},empty:function(){return f&&(f=[]),this},disable:function(){return e=g=[],f=c="",this},disabled:function(){return!f},lock:function(){return e=g=[],c||b||(f=c=""),this},locked:function(){return!!e},fireWith:function(a,c){return e||(c=c||[],c=[a,c.slice?c.slice():c],g.push(c),b||i()),this},fire:function(){return j.fireWith(this,arguments),this},fired:function(){return!!d}};return j};function M(a){return a}function N(a){throw a}function O(a,b,c){var d;try{a&&r.isFunction(d=a.promise)?d.call(a).done(b).fail(c):a&&r.isFunction(d=a.then)?d.call(a,b,c):b.call(void 0,a)}catch(a){c.call(void 0,a)}}r.extend({Deferred:function(b){var c=[["notify","progress",r.Callbacks("memory"),r.Callbacks("memory"),2],["resolve","done",r.Callbacks("once memory"),r.Callbacks("once memory"),0,"resolved"],["reject","fail",r.Callbacks("once memory"),r.Callbacks("once memory"),1,"rejected"]],d="pending",e={state:function(){return d},always:function(){return f.done(arguments).fail(arguments),this},"catch":function(a){return e.then(null,a)},pipe:function(){var a=arguments;return r.Deferred(function(b){r.each(c,function(c,d){var e=r.isFunction(a[d[4]])&&a[d[4]];f[d[1]](function(){var a=e&&e.apply(this,arguments);a&&r.isFunction(a.promise)?a.promise().progress(b.notify).done(b.resolve).fail(b.reject):b[d[0]+"With"](this,e?[a]:arguments)})}),a=null}).promise()},then:function(b,d,e){var f=0;function g(b,c,d,e){return function(){var h=this,i=arguments,j=function(){var a,j;if(!(b=f&&(d!==N&&(h=void 0,i=[a]),c.rejectWith(h,i))}};b?k():(r.Deferred.getStackHook&&(k.stackTrace=r.Deferred.getStackHook()),a.setTimeout(k))}}return r.Deferred(function(a){c[0][3].add(g(0,a,r.isFunction(e)?e:M,a.notifyWith)),c[1][3].add(g(0,a,r.isFunction(b)?b:M)),c[2][3].add(g(0,a,r.isFunction(d)?d:N))}).promise()},promise:function(a){return null!=a?r.extend(a,e):e}},f={};return r.each(c,function(a,b){var g=b[2],h=b[5];e[b[1]]=g.add,h&&g.add(function(){d=h},c[3-a][2].disable,c[0][2].lock),g.add(b[3].fire),f[b[0]]=function(){return f[b[0]+"With"](this===f?void 0:this,arguments),this},f[b[0]+"With"]=g.fireWith}),e.promise(f),b&&b.call(f,f),f},when:function(a){var b=arguments.length,c=b,d=Array(c),e=f.call(arguments),g=r.Deferred(),h=function(a){return function(c){d[a]=this,e[a]=arguments.length>1?f.call(arguments):c,--b||g.resolveWith(d,e)}};if(b<=1&&(O(a,g.done(h(c)).resolve,g.reject),"pending"===g.state()||r.isFunction(e[c]&&e[c].then)))return g.then();while(c--)O(e[c],h(c),g.reject);return g.promise()}});var P=/^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;r.Deferred.exceptionHook=function(b,c){a.console&&a.console.warn&&b&&P.test(b.name)&&a.console.warn("jQuery.Deferred exception: "+b.message,b.stack,c)},r.readyException=function(b){a.setTimeout(function(){throw b})};var Q=r.Deferred();r.fn.ready=function(a){return Q.then(a)["catch"](function(a){r.readyException(a)}),this},r.extend({isReady:!1,readyWait:1,holdReady:function(a){a?r.readyWait++:r.ready(!0)},ready:function(a){(a===!0?--r.readyWait:r.isReady)||(r.isReady=!0,a!==!0&&--r.readyWait>0||Q.resolveWith(d,[r]))}}),r.ready.then=Q.then;function R(){d.removeEventListener("DOMContentLoaded",R),a.removeEventListener("load",R),r.ready()}"complete"===d.readyState||"loading"!==d.readyState&&!d.documentElement.doScroll?a.setTimeout(r.ready):(d.addEventListener("DOMContentLoaded",R),a.addEventListener("load",R));var S=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===r.type(c)){e=!0;for(h in c)S(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0, +r.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(r(a),c)})),b))for(;h1,null,!0)},removeData:function(a){return this.each(function(){W.remove(this,a)})}}),r.extend({queue:function(a,b,c){var d;if(a)return b=(b||"fx")+"queue",d=V.get(a,b),c&&(!d||r.isArray(c)?d=V.access(a,b,r.makeArray(c)):d.push(c)),d||[]},dequeue:function(a,b){b=b||"fx";var c=r.queue(a,b),d=c.length,e=c.shift(),f=r._queueHooks(a,b),g=function(){r.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return V.get(a,c)||V.access(a,c,{empty:r.Callbacks("once memory").add(function(){V.remove(a,[b+"queue",c])})})}}),r.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.length\x20\t\r\n\f]+)/i,ja=/^$|\/(?:java|ecma)script/i,ka={option:[1,""],thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};ka.optgroup=ka.option,ka.tbody=ka.tfoot=ka.colgroup=ka.caption=ka.thead,ka.th=ka.td;function la(a,b){var c="undefined"!=typeof a.getElementsByTagName?a.getElementsByTagName(b||"*"):"undefined"!=typeof a.querySelectorAll?a.querySelectorAll(b||"*"):[];return void 0===b||b&&r.nodeName(a,b)?r.merge([a],c):c}function ma(a,b){for(var c=0,d=a.length;c-1)e&&e.push(f);else if(j=r.contains(f.ownerDocument,f),g=la(l.appendChild(f),"script"),j&&ma(g),c){k=0;while(f=g[k++])ja.test(f.type||"")&&c.push(f)}return l}!function(){var a=d.createDocumentFragment(),b=a.appendChild(d.createElement("div")),c=d.createElement("input");c.setAttribute("type","radio"),c.setAttribute("checked","checked"),c.setAttribute("name","t"),b.appendChild(c),o.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,b.innerHTML="",o.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue}();var pa=d.documentElement,qa=/^key/,ra=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,sa=/^([^.]*)(?:\.(.+)|)/;function ta(){return!0}function ua(){return!1}function va(){try{return d.activeElement}catch(a){}}function wa(a,b,c,d,e,f){var g,h;if("object"==typeof b){"string"!=typeof c&&(d=d||c,c=void 0);for(h in b)wa(a,h,c,d,b[h],f);return a}if(null==d&&null==e?(e=c,d=c=void 0):null==e&&("string"==typeof c?(e=d,d=void 0):(e=d,d=c,c=void 0)),e===!1)e=ua;else if(!e)return a;return 1===f&&(g=e,e=function(a){return r().off(a),g.apply(this,arguments)},e.guid=g.guid||(g.guid=r.guid++)),a.each(function(){r.event.add(this,b,e,d,c)})}r.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,n,o,p,q=V.get(a);if(q){c.handler&&(f=c,c=f.handler,e=f.selector),e&&r.find.matchesSelector(pa,e),c.guid||(c.guid=r.guid++),(i=q.events)||(i=q.events={}),(g=q.handle)||(g=q.handle=function(b){return"undefined"!=typeof r&&r.event.triggered!==b.type?r.event.dispatch.apply(a,arguments):void 0}),b=(b||"").match(K)||[""],j=b.length;while(j--)h=sa.exec(b[j])||[],n=p=h[1],o=(h[2]||"").split(".").sort(),n&&(l=r.event.special[n]||{},n=(e?l.delegateType:l.bindType)||n,l=r.event.special[n]||{},k=r.extend({type:n,origType:p,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&r.expr.match.needsContext.test(e),namespace:o.join(".")},f),(m=i[n])||(m=i[n]=[],m.delegateCount=0,l.setup&&l.setup.call(a,d,o,g)!==!1||a.addEventListener&&a.addEventListener(n,g)),l.add&&(l.add.call(a,k),k.handler.guid||(k.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,k):m.push(k),r.event.global[n]=!0)}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,n,o,p,q=V.hasData(a)&&V.get(a);if(q&&(i=q.events)){b=(b||"").match(K)||[""],j=b.length;while(j--)if(h=sa.exec(b[j])||[],n=p=h[1],o=(h[2]||"").split(".").sort(),n){l=r.event.special[n]||{},n=(d?l.delegateType:l.bindType)||n,m=i[n]||[],h=h[2]&&new RegExp("(^|\\.)"+o.join("\\.(?:.*\\.|)")+"(\\.|$)"),g=f=m.length;while(f--)k=m[f],!e&&p!==k.origType||c&&c.guid!==k.guid||h&&!h.test(k.namespace)||d&&d!==k.selector&&("**"!==d||!k.selector)||(m.splice(f,1),k.selector&&m.delegateCount--,l.remove&&l.remove.call(a,k));g&&!m.length&&(l.teardown&&l.teardown.call(a,o,q.handle)!==!1||r.removeEvent(a,n,q.handle),delete i[n])}else for(n in i)r.event.remove(a,n+b[j],c,d,!0);r.isEmptyObject(i)&&V.remove(a,"handle events")}},dispatch:function(a){var b=r.event.fix(a),c,d,e,f,g,h,i=new Array(arguments.length),j=(V.get(this,"events")||{})[b.type]||[],k=r.event.special[b.type]||{};for(i[0]=b,c=1;c-1:r.find(e,this,null,[i]).length),d[e]&&d.push(f);d.length&&g.push({elem:i,handlers:d})}return h\x20\t\r\n\f]*)[^>]*)\/>/gi,ya=/\s*$/g;function Ca(a,b){return r.nodeName(a,"table")&&r.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a:a}function Da(a){return a.type=(null!==a.getAttribute("type"))+"/"+a.type,a}function Ea(a){var b=Aa.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function Fa(a,b){var c,d,e,f,g,h,i,j;if(1===b.nodeType){if(V.hasData(a)&&(f=V.access(a),g=V.set(b,f),j=f.events)){delete g.handle,g.events={};for(e in j)for(c=0,d=j[e].length;c1&&"string"==typeof q&&!o.checkClone&&za.test(q))return a.each(function(e){var f=a.eq(e);s&&(b[0]=q.call(this,e,f.html())),Ha(f,b,c,d)});if(m&&(e=oa(b,a[0].ownerDocument,!1,a,d),f=e.firstChild,1===e.childNodes.length&&(e=f),f||d)){for(h=r.map(la(e,"script"),Da),i=h.length;l")},clone:function(a,b,c){var d,e,f,g,h=a.cloneNode(!0),i=r.contains(a.ownerDocument,a);if(!(o.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||r.isXMLDoc(a)))for(g=la(h),f=la(a),d=0,e=f.length;d0&&ma(g,!i&&la(a,"script")),h},cleanData:function(a){for(var b,c,d,e=r.event.special,f=0;void 0!==(c=a[f]);f++)if(T(c)){if(b=c[V.expando]){if(b.events)for(d in b.events)e[d]?r.event.remove(c,d):r.removeEvent(c,d,b.handle);c[V.expando]=void 0}c[W.expando]&&(c[W.expando]=void 0)}}}),r.fn.extend({detach:function(a){return Ia(this,a,!0)},remove:function(a){return Ia(this,a)},text:function(a){return S(this,function(a){return void 0===a?r.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=a)})},null,a,arguments.length)},append:function(){return Ha(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=Ca(this,a);b.appendChild(a)}})},prepend:function(){return Ha(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=Ca(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return Ha(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return Ha(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},empty:function(){for(var a,b=0;null!=(a=this[b]);b++)1===a.nodeType&&(r.cleanData(la(a,!1)),a.textContent="");return this},clone:function(a,b){return a=null!=a&&a,b=null==b?a:b,this.map(function(){return r.clone(this,a,b)})},html:function(a){return S(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a&&1===b.nodeType)return b.innerHTML;if("string"==typeof a&&!ya.test(a)&&!ka[(ia.exec(a)||["",""])[1].toLowerCase()]){a=r.htmlPrefilter(a);try{for(;c1)}});function Xa(a,b,c,d,e){return new Xa.prototype.init(a,b,c,d,e)}r.Tween=Xa,Xa.prototype={constructor:Xa,init:function(a,b,c,d,e,f){this.elem=a,this.prop=c,this.easing=e||r.easing._default,this.options=b,this.start=this.now=this.cur(),this.end=d,this.unit=f||(r.cssNumber[c]?"":"px")},cur:function(){var a=Xa.propHooks[this.prop];return a&&a.get?a.get(this):Xa.propHooks._default.get(this)},run:function(a){var b,c=Xa.propHooks[this.prop];return this.options.duration?this.pos=b=r.easing[this.easing](a,this.options.duration*a,0,1,this.options.duration):this.pos=b=a,this.now=(this.end-this.start)*b+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),c&&c.set?c.set(this):Xa.propHooks._default.set(this),this}},Xa.prototype.init.prototype=Xa.prototype,Xa.propHooks={_default:{get:function(a){var b;return 1!==a.elem.nodeType||null!=a.elem[a.prop]&&null==a.elem.style[a.prop]?a.elem[a.prop]:(b=r.css(a.elem,a.prop,""),b&&"auto"!==b?b:0)},set:function(a){r.fx.step[a.prop]?r.fx.step[a.prop](a):1!==a.elem.nodeType||null==a.elem.style[r.cssProps[a.prop]]&&!r.cssHooks[a.prop]?a.elem[a.prop]=a.now:r.style(a.elem,a.prop,a.now+a.unit)}}},Xa.propHooks.scrollTop=Xa.propHooks.scrollLeft={set:function(a){a.elem.nodeType&&a.elem.parentNode&&(a.elem[a.prop]=a.now)}},r.easing={linear:function(a){return a},swing:function(a){return.5-Math.cos(a*Math.PI)/2},_default:"swing"},r.fx=Xa.prototype.init,r.fx.step={};var Ya,Za,$a=/^(?:toggle|show|hide)$/,_a=/queueHooks$/;function ab(){Za&&(a.requestAnimationFrame(ab),r.fx.tick())}function bb(){return a.setTimeout(function(){Ya=void 0}),Ya=r.now()}function cb(a,b){var c,d=0,e={height:a};for(b=b?1:0;d<4;d+=2-b)c=aa[d],e["margin"+c]=e["padding"+c]=a;return b&&(e.opacity=e.width=a),e}function db(a,b,c){for(var d,e=(gb.tweeners[b]||[]).concat(gb.tweeners["*"]),f=0,g=e.length;f1)},removeAttr:function(a){return this.each(function(){r.removeAttr(this,a)})}}),r.extend({attr:function(a,b,c){var d,e,f=a.nodeType;if(3!==f&&8!==f&&2!==f)return"undefined"==typeof a.getAttribute?r.prop(a,b,c):(1===f&&r.isXMLDoc(a)||(e=r.attrHooks[b.toLowerCase()]||(r.expr.match.bool.test(b)?hb:void 0)),void 0!==c?null===c?void r.removeAttr(a,b):e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:(a.setAttribute(b,c+""),c):e&&"get"in e&&null!==(d=e.get(a,b))?d:(d=r.find.attr(a,b),null==d?void 0:d))},attrHooks:{type:{set:function(a,b){if(!o.radioValue&&"radio"===b&&r.nodeName(a,"input")){var c=a.value;return a.setAttribute("type",b),c&&(a.value=c),b}}}},removeAttr:function(a,b){var c,d=0,e=b&&b.match(K); +if(e&&1===a.nodeType)while(c=e[d++])a.removeAttribute(c)}}),hb={set:function(a,b,c){return b===!1?r.removeAttr(a,c):a.setAttribute(c,c),c}},r.each(r.expr.match.bool.source.match(/\w+/g),function(a,b){var c=ib[b]||r.find.attr;ib[b]=function(a,b,d){var e,f,g=b.toLowerCase();return d||(f=ib[g],ib[g]=e,e=null!=c(a,b,d)?g:null,ib[g]=f),e}});var jb=/^(?:input|select|textarea|button)$/i,kb=/^(?:a|area)$/i;r.fn.extend({prop:function(a,b){return S(this,r.prop,a,b,arguments.length>1)},removeProp:function(a){return this.each(function(){delete this[r.propFix[a]||a]})}}),r.extend({prop:function(a,b,c){var d,e,f=a.nodeType;if(3!==f&&8!==f&&2!==f)return 1===f&&r.isXMLDoc(a)||(b=r.propFix[b]||b,e=r.propHooks[b]),void 0!==c?e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:a[b]=c:e&&"get"in e&&null!==(d=e.get(a,b))?d:a[b]},propHooks:{tabIndex:{get:function(a){var b=r.find.attr(a,"tabindex");return b?parseInt(b,10):jb.test(a.nodeName)||kb.test(a.nodeName)&&a.href?0:-1}}},propFix:{"for":"htmlFor","class":"className"}}),o.optSelected||(r.propHooks.selected={get:function(a){var b=a.parentNode;return b&&b.parentNode&&b.parentNode.selectedIndex,null},set:function(a){var b=a.parentNode;b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex)}}),r.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){r.propFix[this.toLowerCase()]=this});var lb=/[\t\r\n\f]/g;function mb(a){return a.getAttribute&&a.getAttribute("class")||""}r.fn.extend({addClass:function(a){var b,c,d,e,f,g,h,i=0;if(r.isFunction(a))return this.each(function(b){r(this).addClass(a.call(this,b,mb(this)))});if("string"==typeof a&&a){b=a.match(K)||[];while(c=this[i++])if(e=mb(c),d=1===c.nodeType&&(" "+e+" ").replace(lb," ")){g=0;while(f=b[g++])d.indexOf(" "+f+" ")<0&&(d+=f+" ");h=r.trim(d),e!==h&&c.setAttribute("class",h)}}return this},removeClass:function(a){var b,c,d,e,f,g,h,i=0;if(r.isFunction(a))return this.each(function(b){r(this).removeClass(a.call(this,b,mb(this)))});if(!arguments.length)return this.attr("class","");if("string"==typeof a&&a){b=a.match(K)||[];while(c=this[i++])if(e=mb(c),d=1===c.nodeType&&(" "+e+" ").replace(lb," ")){g=0;while(f=b[g++])while(d.indexOf(" "+f+" ")>-1)d=d.replace(" "+f+" "," ");h=r.trim(d),e!==h&&c.setAttribute("class",h)}}return this},toggleClass:function(a,b){var c=typeof a;return"boolean"==typeof b&&"string"===c?b?this.addClass(a):this.removeClass(a):r.isFunction(a)?this.each(function(c){r(this).toggleClass(a.call(this,c,mb(this),b),b)}):this.each(function(){var b,d,e,f;if("string"===c){d=0,e=r(this),f=a.match(K)||[];while(b=f[d++])e.hasClass(b)?e.removeClass(b):e.addClass(b)}else void 0!==a&&"boolean"!==c||(b=mb(this),b&&V.set(this,"__className__",b),this.setAttribute&&this.setAttribute("class",b||a===!1?"":V.get(this,"__className__")||""))})},hasClass:function(a){var b,c,d=0;b=" "+a+" ";while(c=this[d++])if(1===c.nodeType&&(" "+mb(c)+" ").replace(lb," ").indexOf(b)>-1)return!0;return!1}});var nb=/\r/g,ob=/[\x20\t\r\n\f]+/g;r.fn.extend({val:function(a){var b,c,d,e=this[0];{if(arguments.length)return d=r.isFunction(a),this.each(function(c){var e;1===this.nodeType&&(e=d?a.call(this,c,r(this).val()):a,null==e?e="":"number"==typeof e?e+="":r.isArray(e)&&(e=r.map(e,function(a){return null==a?"":a+""})),b=r.valHooks[this.type]||r.valHooks[this.nodeName.toLowerCase()],b&&"set"in b&&void 0!==b.set(this,e,"value")||(this.value=e))});if(e)return b=r.valHooks[e.type]||r.valHooks[e.nodeName.toLowerCase()],b&&"get"in b&&void 0!==(c=b.get(e,"value"))?c:(c=e.value,"string"==typeof c?c.replace(nb,""):null==c?"":c)}}}),r.extend({valHooks:{option:{get:function(a){var b=r.find.attr(a,"value");return null!=b?b:r.trim(r.text(a)).replace(ob," ")}},select:{get:function(a){for(var b,c,d=a.options,e=a.selectedIndex,f="select-one"===a.type,g=f?null:[],h=f?e+1:d.length,i=e<0?h:f?e:0;i-1)&&(c=!0);return c||(a.selectedIndex=-1),f}}}}),r.each(["radio","checkbox"],function(){r.valHooks[this]={set:function(a,b){if(r.isArray(b))return a.checked=r.inArray(r(a).val(),b)>-1}},o.checkOn||(r.valHooks[this].get=function(a){return null===a.getAttribute("value")?"on":a.value})});var pb=/^(?:focusinfocus|focusoutblur)$/;r.extend(r.event,{trigger:function(b,c,e,f){var g,h,i,j,k,m,n,o=[e||d],p=l.call(b,"type")?b.type:b,q=l.call(b,"namespace")?b.namespace.split("."):[];if(h=i=e=e||d,3!==e.nodeType&&8!==e.nodeType&&!pb.test(p+r.event.triggered)&&(p.indexOf(".")>-1&&(q=p.split("."),p=q.shift(),q.sort()),k=p.indexOf(":")<0&&"on"+p,b=b[r.expando]?b:new r.Event(p,"object"==typeof b&&b),b.isTrigger=f?2:3,b.namespace=q.join("."),b.rnamespace=b.namespace?new RegExp("(^|\\.)"+q.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=e),c=null==c?[b]:r.makeArray(c,[b]),n=r.event.special[p]||{},f||!n.trigger||n.trigger.apply(e,c)!==!1)){if(!f&&!n.noBubble&&!r.isWindow(e)){for(j=n.delegateType||p,pb.test(j+p)||(h=h.parentNode);h;h=h.parentNode)o.push(h),i=h;i===(e.ownerDocument||d)&&o.push(i.defaultView||i.parentWindow||a)}g=0;while((h=o[g++])&&!b.isPropagationStopped())b.type=g>1?j:n.bindType||p,m=(V.get(h,"events")||{})[b.type]&&V.get(h,"handle"),m&&m.apply(h,c),m=k&&h[k],m&&m.apply&&T(h)&&(b.result=m.apply(h,c),b.result===!1&&b.preventDefault());return b.type=p,f||b.isDefaultPrevented()||n._default&&n._default.apply(o.pop(),c)!==!1||!T(e)||k&&r.isFunction(e[p])&&!r.isWindow(e)&&(i=e[k],i&&(e[k]=null),r.event.triggered=p,e[p](),r.event.triggered=void 0,i&&(e[k]=i)),b.result}},simulate:function(a,b,c){var d=r.extend(new r.Event,c,{type:a,isSimulated:!0});r.event.trigger(d,null,b)}}),r.fn.extend({trigger:function(a,b){return this.each(function(){r.event.trigger(a,b,this)})},triggerHandler:function(a,b){var c=this[0];if(c)return r.event.trigger(a,b,c,!0)}}),r.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(a,b){r.fn[b]=function(a,c){return arguments.length>0?this.on(b,null,a,c):this.trigger(b)}}),r.fn.extend({hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}}),o.focusin="onfocusin"in a,o.focusin||r.each({focus:"focusin",blur:"focusout"},function(a,b){var c=function(a){r.event.simulate(b,a.target,r.event.fix(a))};r.event.special[b]={setup:function(){var d=this.ownerDocument||this,e=V.access(d,b);e||d.addEventListener(a,c,!0),V.access(d,b,(e||0)+1)},teardown:function(){var d=this.ownerDocument||this,e=V.access(d,b)-1;e?V.access(d,b,e):(d.removeEventListener(a,c,!0),V.remove(d,b))}}});var qb=a.location,rb=r.now(),sb=/\?/;r.parseXML=function(b){var c;if(!b||"string"!=typeof b)return null;try{c=(new a.DOMParser).parseFromString(b,"text/xml")}catch(d){c=void 0}return c&&!c.getElementsByTagName("parsererror").length||r.error("Invalid XML: "+b),c};var tb=/\[\]$/,ub=/\r?\n/g,vb=/^(?:submit|button|image|reset|file)$/i,wb=/^(?:input|select|textarea|keygen)/i;function xb(a,b,c,d){var e;if(r.isArray(b))r.each(b,function(b,e){c||tb.test(a)?d(a,e):xb(a+"["+("object"==typeof e&&null!=e?b:"")+"]",e,c,d)});else if(c||"object"!==r.type(b))d(a,b);else for(e in b)xb(a+"["+e+"]",b[e],c,d)}r.param=function(a,b){var c,d=[],e=function(a,b){var c=r.isFunction(b)?b():b;d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(null==c?"":c)};if(r.isArray(a)||a.jquery&&!r.isPlainObject(a))r.each(a,function(){e(this.name,this.value)});else for(c in a)xb(c,a[c],b,e);return d.join("&")},r.fn.extend({serialize:function(){return r.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var a=r.prop(this,"elements");return a?r.makeArray(a):this}).filter(function(){var a=this.type;return this.name&&!r(this).is(":disabled")&&wb.test(this.nodeName)&&!vb.test(a)&&(this.checked||!ha.test(a))}).map(function(a,b){var c=r(this).val();return null==c?null:r.isArray(c)?r.map(c,function(a){return{name:b.name,value:a.replace(ub,"\r\n")}}):{name:b.name,value:c.replace(ub,"\r\n")}}).get()}});var yb=/%20/g,zb=/#.*$/,Ab=/([?&])_=[^&]*/,Bb=/^(.*?):[ \t]*([^\r\n]*)$/gm,Cb=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,Db=/^(?:GET|HEAD)$/,Eb=/^\/\//,Fb={},Gb={},Hb="*/".concat("*"),Ib=d.createElement("a");Ib.href=qb.href;function Jb(a){return function(b,c){"string"!=typeof b&&(c=b,b="*");var d,e=0,f=b.toLowerCase().match(K)||[];if(r.isFunction(c))while(d=f[e++])"+"===d[0]?(d=d.slice(1)||"*",(a[d]=a[d]||[]).unshift(c)):(a[d]=a[d]||[]).push(c)}}function Kb(a,b,c,d){var e={},f=a===Gb;function g(h){var i;return e[h]=!0,r.each(a[h]||[],function(a,h){var j=h(b,c,d);return"string"!=typeof j||f||e[j]?f?!(i=j):void 0:(b.dataTypes.unshift(j),g(j),!1)}),i}return g(b.dataTypes[0])||!e["*"]&&g("*")}function Lb(a,b){var c,d,e=r.ajaxSettings.flatOptions||{};for(c in b)void 0!==b[c]&&((e[c]?a:d||(d={}))[c]=b[c]);return d&&r.extend(!0,a,d),a}function Mb(a,b,c){var d,e,f,g,h=a.contents,i=a.dataTypes;while("*"===i[0])i.shift(),void 0===d&&(d=a.mimeType||b.getResponseHeader("Content-Type"));if(d)for(e in h)if(h[e]&&h[e].test(d)){i.unshift(e);break}if(i[0]in c)f=i[0];else{for(e in c){if(!i[0]||a.converters[e+" "+i[0]]){f=e;break}g||(g=e)}f=f||g}if(f)return f!==i[0]&&i.unshift(f),c[f]}function Nb(a,b,c,d){var e,f,g,h,i,j={},k=a.dataTypes.slice();if(k[1])for(g in a.converters)j[g.toLowerCase()]=a.converters[g];f=k.shift();while(f)if(a.responseFields[f]&&(c[a.responseFields[f]]=b),!i&&d&&a.dataFilter&&(b=a.dataFilter(b,a.dataType)),i=f,f=k.shift())if("*"===f)f=i;else if("*"!==i&&i!==f){if(g=j[i+" "+f]||j["* "+f],!g)for(e in j)if(h=e.split(" "),h[1]===f&&(g=j[i+" "+h[0]]||j["* "+h[0]])){g===!0?g=j[e]:j[e]!==!0&&(f=h[0],k.unshift(h[1]));break}if(g!==!0)if(g&&a["throws"])b=g(b);else try{b=g(b)}catch(l){return{state:"parsererror",error:g?l:"No conversion from "+i+" to "+f}}}return{state:"success",data:b}}r.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:qb.href,type:"GET",isLocal:Cb.test(qb.protocol),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":Hb,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/\bxml\b/,html:/\bhtml/,json:/\bjson\b/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":JSON.parse,"text xml":r.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(a,b){return b?Lb(Lb(a,r.ajaxSettings),b):Lb(r.ajaxSettings,a)},ajaxPrefilter:Jb(Fb),ajaxTransport:Jb(Gb),ajax:function(b,c){"object"==typeof b&&(c=b,b=void 0),c=c||{};var e,f,g,h,i,j,k,l,m,n,o=r.ajaxSetup({},c),p=o.context||o,q=o.context&&(p.nodeType||p.jquery)?r(p):r.event,s=r.Deferred(),t=r.Callbacks("once memory"),u=o.statusCode||{},v={},w={},x="canceled",y={readyState:0,getResponseHeader:function(a){var b;if(k){if(!h){h={};while(b=Bb.exec(g))h[b[1].toLowerCase()]=b[2]}b=h[a.toLowerCase()]}return null==b?null:b},getAllResponseHeaders:function(){return k?g:null},setRequestHeader:function(a,b){return null==k&&(a=w[a.toLowerCase()]=w[a.toLowerCase()]||a,v[a]=b),this},overrideMimeType:function(a){return null==k&&(o.mimeType=a),this},statusCode:function(a){var b;if(a)if(k)y.always(a[y.status]);else for(b in a)u[b]=[u[b],a[b]];return this},abort:function(a){var b=a||x;return e&&e.abort(b),A(0,b),this}};if(s.promise(y),o.url=((b||o.url||qb.href)+"").replace(Eb,qb.protocol+"//"),o.type=c.method||c.type||o.method||o.type,o.dataTypes=(o.dataType||"*").toLowerCase().match(K)||[""],null==o.crossDomain){j=d.createElement("a");try{j.href=o.url,j.href=j.href,o.crossDomain=Ib.protocol+"//"+Ib.host!=j.protocol+"//"+j.host}catch(z){o.crossDomain=!0}}if(o.data&&o.processData&&"string"!=typeof o.data&&(o.data=r.param(o.data,o.traditional)),Kb(Fb,o,c,y),k)return y;l=r.event&&o.global,l&&0===r.active++&&r.event.trigger("ajaxStart"),o.type=o.type.toUpperCase(),o.hasContent=!Db.test(o.type),f=o.url.replace(zb,""),o.hasContent?o.data&&o.processData&&0===(o.contentType||"").indexOf("application/x-www-form-urlencoded")&&(o.data=o.data.replace(yb,"+")):(n=o.url.slice(f.length),o.data&&(f+=(sb.test(f)?"&":"?")+o.data,delete o.data),o.cache===!1&&(f=f.replace(Ab,""),n=(sb.test(f)?"&":"?")+"_="+rb++ +n),o.url=f+n),o.ifModified&&(r.lastModified[f]&&y.setRequestHeader("If-Modified-Since",r.lastModified[f]),r.etag[f]&&y.setRequestHeader("If-None-Match",r.etag[f])),(o.data&&o.hasContent&&o.contentType!==!1||c.contentType)&&y.setRequestHeader("Content-Type",o.contentType),y.setRequestHeader("Accept",o.dataTypes[0]&&o.accepts[o.dataTypes[0]]?o.accepts[o.dataTypes[0]]+("*"!==o.dataTypes[0]?", "+Hb+"; q=0.01":""):o.accepts["*"]);for(m in o.headers)y.setRequestHeader(m,o.headers[m]);if(o.beforeSend&&(o.beforeSend.call(p,y,o)===!1||k))return y.abort();if(x="abort",t.add(o.complete),y.done(o.success),y.fail(o.error),e=Kb(Gb,o,c,y)){if(y.readyState=1,l&&q.trigger("ajaxSend",[y,o]),k)return y;o.async&&o.timeout>0&&(i=a.setTimeout(function(){y.abort("timeout")},o.timeout));try{k=!1,e.send(v,A)}catch(z){if(k)throw z;A(-1,z)}}else A(-1,"No Transport");function A(b,c,d,h){var j,m,n,v,w,x=c;k||(k=!0,i&&a.clearTimeout(i),e=void 0,g=h||"",y.readyState=b>0?4:0,j=b>=200&&b<300||304===b,d&&(v=Mb(o,y,d)),v=Nb(o,v,y,j),j?(o.ifModified&&(w=y.getResponseHeader("Last-Modified"),w&&(r.lastModified[f]=w),w=y.getResponseHeader("etag"),w&&(r.etag[f]=w)),204===b||"HEAD"===o.type?x="nocontent":304===b?x="notmodified":(x=v.state,m=v.data,n=v.error,j=!n)):(n=x,!b&&x||(x="error",b<0&&(b=0))),y.status=b,y.statusText=(c||x)+"",j?s.resolveWith(p,[m,x,y]):s.rejectWith(p,[y,x,n]),y.statusCode(u),u=void 0,l&&q.trigger(j?"ajaxSuccess":"ajaxError",[y,o,j?m:n]),t.fireWith(p,[y,x]),l&&(q.trigger("ajaxComplete",[y,o]),--r.active||r.event.trigger("ajaxStop")))}return y},getJSON:function(a,b,c){return r.get(a,b,c,"json")},getScript:function(a,b){return r.get(a,void 0,b,"script")}}),r.each(["get","post"],function(a,b){r[b]=function(a,c,d,e){return r.isFunction(c)&&(e=e||d,d=c,c=void 0),r.ajax(r.extend({url:a,type:b,dataType:e,data:c,success:d},r.isPlainObject(a)&&a))}}),r._evalUrl=function(a){return r.ajax({url:a,type:"GET",dataType:"script",cache:!0,async:!1,global:!1,"throws":!0})},r.fn.extend({wrapAll:function(a){var b;return this[0]&&(r.isFunction(a)&&(a=a.call(this[0])),b=r(a,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstElementChild)a=a.firstElementChild;return a}).append(this)),this},wrapInner:function(a){return r.isFunction(a)?this.each(function(b){r(this).wrapInner(a.call(this,b))}):this.each(function(){var b=r(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=r.isFunction(a);return this.each(function(c){r(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(a){return this.parent(a).not("body").each(function(){r(this).replaceWith(this.childNodes)}),this}}),r.expr.pseudos.hidden=function(a){return!r.expr.pseudos.visible(a)},r.expr.pseudos.visible=function(a){return!!(a.offsetWidth||a.offsetHeight||a.getClientRects().length)},r.ajaxSettings.xhr=function(){try{return new a.XMLHttpRequest}catch(b){}};var Ob={0:200,1223:204},Pb=r.ajaxSettings.xhr();o.cors=!!Pb&&"withCredentials"in Pb,o.ajax=Pb=!!Pb,r.ajaxTransport(function(b){var c,d;if(o.cors||Pb&&!b.crossDomain)return{send:function(e,f){var g,h=b.xhr();if(h.open(b.type,b.url,b.async,b.username,b.password),b.xhrFields)for(g in b.xhrFields)h[g]=b.xhrFields[g];b.mimeType&&h.overrideMimeType&&h.overrideMimeType(b.mimeType),b.crossDomain||e["X-Requested-With"]||(e["X-Requested-With"]="XMLHttpRequest");for(g in e)h.setRequestHeader(g,e[g]);c=function(a){return function(){c&&(c=d=h.onload=h.onerror=h.onabort=h.onreadystatechange=null,"abort"===a?h.abort():"error"===a?"number"!=typeof h.status?f(0,"error"):f(h.status,h.statusText):f(Ob[h.status]||h.status,h.statusText,"text"!==(h.responseType||"text")||"string"!=typeof h.responseText?{binary:h.response}:{text:h.responseText},h.getAllResponseHeaders()))}},h.onload=c(),d=h.onerror=c("error"),void 0!==h.onabort?h.onabort=d:h.onreadystatechange=function(){4===h.readyState&&a.setTimeout(function(){c&&d()})},c=c("abort");try{h.send(b.hasContent&&b.data||null)}catch(i){if(c)throw i}},abort:function(){c&&c()}}}),r.ajaxPrefilter(function(a){a.crossDomain&&(a.contents.script=!1)}),r.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/\b(?:java|ecma)script\b/},converters:{"text script":function(a){return r.globalEval(a),a}}}),r.ajaxPrefilter("script",function(a){void 0===a.cache&&(a.cache=!1),a.crossDomain&&(a.type="GET")}),r.ajaxTransport("script",function(a){if(a.crossDomain){var b,c;return{send:function(e,f){b=r(" - +
+ diff --git a/mascara/test/lib/first-time.js b/mascara/test/lib/first-time.js index a3b2a41a5..8e33c8a06 100644 --- a/mascara/test/lib/first-time.js +++ b/mascara/test/lib/first-time.js @@ -6,8 +6,8 @@ QUnit.test('render init screen', function (assert) { var done = assert.async() let app - wait().then(function() { - app = $('#app-content') + wait(1000).then(function() { + app = $('#app-content').contents() const recurseNotices = function () { let button = app.find('button') if (button.html() === 'Continue') { diff --git a/mascara/test/util/mascara-test-helper.js b/mascara/test/util/mascara-test-helper.js index 1ed576005..9cf4fa900 100644 --- a/mascara/test/util/mascara-test-helper.js +++ b/mascara/test/util/mascara-test-helper.js @@ -1,5 +1,5 @@ const EventEmitter = require('events') -const IDB = require('../../../mascara/src/lib/index-db-controller') +const IDB = require('idb-global') const KEY = 'metamask-test-config' module.exports = class Helper extends EventEmitter { constructor () { diff --git a/mascara/test/window-load.js b/mascara/test/window-load.js index de79faaa1..41166c466 100644 --- a/mascara/test/window-load.js +++ b/mascara/test/window-load.js @@ -1,7 +1,8 @@ const Helper = require('./util/mascara-test-helper.js') -debugger + window.addEventListener('load', () => { - const helper = new Helper() - helper.on('complete', () => require('../src/ui.js')) - helper.tryToCleanContext() + // const helper = new Helper() + // helper.on('complete', () => require('../src/ui.js')) + // helper.tryToCleanContext() + require('../src/ui.js') }) diff --git a/package.json b/package.json index 4dc42aa01..2e7887e0c 100644 --- a/package.json +++ b/package.json @@ -73,6 +73,7 @@ "extensionizer": "^1.0.0", "gulp-eslint": "^2.0.0", "hat": "0.0.3", + "idb-global": "^1.0.0", "identicon.js": "^1.2.1", "iframe": "^1.0.0", "iframe-stream": "^1.0.2", From 36bafbaebf826933262c4ad381a8953e82f8ebc3 Mon Sep 17 00:00:00 2001 From: frankiebee Date: Fri, 28 Apr 2017 14:05:10 +0200 Subject: [PATCH 183/372] General cleanup and window reload if an update is found --- mascara/README.md | 12 ++++++++++++ mascara/src/background.js | 3 +-- mascara/src/proxy.js | 1 + mascara/src/ui.js | 13 +++---------- 4 files changed, 17 insertions(+), 12 deletions(-) diff --git a/mascara/README.md b/mascara/README.md index 75d3cabad..db5b4f404 100644 --- a/mascara/README.md +++ b/mascara/README.md @@ -10,3 +10,15 @@ npm run mascara - go back to http://localhost:9002 - open devTools - click Sync Tx + +## Tests: + +``` +npm run testMascara +``` + +Test will run in browser, you will have to have these browsers installed: + +- Chrome +- Firefox +- Opera diff --git a/mascara/src/background.js b/mascara/src/background.js index a6703b291..dff5e6a7c 100644 --- a/mascara/src/background.js +++ b/mascara/src/background.js @@ -7,7 +7,7 @@ const connectionListener = new SwGlobalListener(self) const setupMultiplex = require('../../app/scripts/lib/stream-utils.js').setupMultiplex const PortStream = require('../../app/scripts/lib/port-stream.js') -const DbController = require('./lib/index-db-controller') +const DbController = require('idb-global') const SwPlatform = require('../../app/scripts/platforms/sw') const MetamaskController = require('../../app/scripts/metamask-controller') @@ -47,7 +47,6 @@ console.log('inside:open') let diskStore const dbController = new DbController({ key: STORAGE_KEY, - version: 2, }) loadStateFromPersistence() .then((initState) => setupController(initState)) diff --git a/mascara/src/proxy.js b/mascara/src/proxy.js index ec5665240..eabc547b4 100644 --- a/mascara/src/proxy.js +++ b/mascara/src/proxy.js @@ -20,6 +20,7 @@ background.on('ready', (_) => { pageStream.pipe(swStream).pipe(pageStream) }) +background.on('updatefound', () => window.location.reload()) background.on('error', console.error) background.startWorker() diff --git a/mascara/src/ui.js b/mascara/src/ui.js index 07763cc5e..65a55ccc3 100644 --- a/mascara/src/ui.js +++ b/mascara/src/ui.js @@ -46,15 +46,8 @@ background.on('ready', (sw) => { background.removeListener('updatefound', connectApp) connectApp(sw) }) -background.on('updatefound', () => background.serviceWorkerApi.ready - .then((sw) =>{ - background.removeListener('ready', connectApp) - connectApp(sw.active) - }) -) -background.on('message', (messageEvent) => { - console.log(messageEvent) -}) -window.addEventListener('load', () => background.startWorker()) +background.on('updatefound', () => window.location.reload()) + +background.startWorker() // background.startWorker() console.log('hello from MetaMascara ui!') From d4cad22fa7b2e9e05ac98a490cb446b37d6978f9 Mon Sep 17 00:00:00 2001 From: frankiebee Date: Fri, 28 Apr 2017 21:19:14 +0200 Subject: [PATCH 184/372] clean up code --- mascara/src/ui.js | 1 - mascara/test/index.js | 4 +++- mascara/test/window-load.js | 3 --- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/mascara/src/ui.js b/mascara/src/ui.js index 65a55ccc3..e798847a7 100644 --- a/mascara/src/ui.js +++ b/mascara/src/ui.js @@ -37,7 +37,6 @@ const connectApp = function (readSw) { store.subscribe(() => { const state = store.getState() if (state.appState.shouldClose) window.close() - console.log('IN the things?') }) }) } diff --git a/mascara/test/index.js b/mascara/test/index.js index b94ce16dd..0134cdf00 100644 --- a/mascara/test/index.js +++ b/mascara/test/index.js @@ -8,7 +8,9 @@ var b = browserify(); // Remove old bundle try { fs.unlinkSync(bundlePath) -} catch (e) {} +} catch (e) { + console.error(e) +} var writeStream = fs.createWriteStream(bundlePath) diff --git a/mascara/test/window-load.js b/mascara/test/window-load.js index 41166c466..d3f44f05f 100644 --- a/mascara/test/window-load.js +++ b/mascara/test/window-load.js @@ -1,8 +1,5 @@ const Helper = require('./util/mascara-test-helper.js') window.addEventListener('load', () => { - // const helper = new Helper() - // helper.on('complete', () => require('../src/ui.js')) - // helper.tryToCleanContext() require('../src/ui.js') }) From 6ace0c9afbf7d71415bcf0608977d40210651d39 Mon Sep 17 00:00:00 2001 From: kumavis Date: Fri, 28 Apr 2017 16:04:00 -0700 Subject: [PATCH 185/372] notification-manager - remove promise listener seems chrome changed their API? MDN suggests that a Promise should be returned but getting `undefined` https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/windows/create Chrome docs suggest its a callback API lolwut https://developer.chrome.com/extensions/windows#method-create --- app/scripts/lib/notification-manager.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/app/scripts/lib/notification-manager.js b/app/scripts/lib/notification-manager.js index 799282f6d..7846ef7f0 100644 --- a/app/scripts/lib/notification-manager.js +++ b/app/scripts/lib/notification-manager.js @@ -24,9 +24,6 @@ class NotificationManager { width, height, }) - .catch((reason) => { - log.error('failed to create poupup', reason) - }) } }) } From a21339dd8519d2f99e1762fa57524e6c674cee5e Mon Sep 17 00:00:00 2001 From: kumavis Date: Fri, 28 Apr 2017 16:10:26 -0700 Subject: [PATCH 186/372] Update CHANGELOG.md --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e629e0bc1..cd71a0339 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Current Master +- Fix bug where error was reported in debugger console when Chrome opened a new window. + ## 3.6.0 2017-4-25 - Add Rinkeby Test Network to our network list. From 527068b84ef17bb88a29b699c93b3f1ed7645b24 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Sun, 30 Apr 2017 12:38:22 -0700 Subject: [PATCH 187/372] Bump provider engine --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2e7887e0c..6e83b3560 100644 --- a/package.json +++ b/package.json @@ -119,7 +119,7 @@ "valid-url": "^1.0.9", "vreme": "^3.0.2", "web3": "0.18.2", - "web3-provider-engine": "^12.0.2", + "web3-provider-engine": "^12.0.3", "web3-stream-provider": "^2.0.6", "xtend": "^4.0.1" }, From a3149c17526c320f0cfdbe07a54ca1c2f01a6fad Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Sun, 30 Apr 2017 12:38:38 -0700 Subject: [PATCH 188/372] Use loglevel for more logs --- app/scripts/metamask-controller.js | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index b91b5efe8..2e4bf07e1 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -341,9 +341,7 @@ module.exports = class MetamaskController extends EventEmitter { console.error('Error in RPC response:\n', response.error) } if (request.isMetamaskInternal) return - if (global.METAMASK_DEBUG) { - console.log(`RPC (${originDomain}):`, request, '->', response) - } + log.info(`RPC (${originDomain}):`, request, '->', response) } } @@ -591,9 +589,7 @@ module.exports = class MetamaskController extends EventEmitter { // Log blocks logBlock (block) { - if (global.METAMASK_DEBUG) { - console.log(`BLOCK CHANGED: #${block.number.toString('hex')} 0x${block.hash.toString('hex')}`) - } + log.info(`BLOCK CHANGED: #${block.number.toString('hex')} 0x${block.hash.toString('hex')}`) this.verifyNetwork() } @@ -682,9 +678,7 @@ module.exports = class MetamaskController extends EventEmitter { this.setNetworkState('loading') return } - if (global.METAMASK_DEBUG) { - console.log('web3.getNetwork returned ' + network) - } + log.info('web3.getNetwork returned ' + network) this.setNetworkState(network) }) } From 7ddbd1a193ed4e6d087480cf17a6b486bcd10826 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Sun, 30 Apr 2017 12:39:17 -0700 Subject: [PATCH 189/372] Version 3.6.1 --- CHANGELOG.md | 3 +++ app/manifest.json | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cd71a0339..517a1830b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,10 @@ ## Current Master +## 3.6.1 2017-4-25 + - Fix bug where error was reported in debugger console when Chrome opened a new window. +- Fix bug where block-tracker could stop polling for new blocks. ## 3.6.0 2017-4-25 diff --git a/app/manifest.json b/app/manifest.json index d5f66173c..3a9b0b29f 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -1,7 +1,7 @@ { "name": "MetaMask", "short_name": "Metamask", - "version": "3.6.0", + "version": "3.6.1", "manifest_version": 2, "author": "https://metamask.io", "description": "Ethereum Browser Extension", From c3481f0eb1e78de69a02155d7bba68d4a33a3efb Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Sun, 30 Apr 2017 13:33:05 -0700 Subject: [PATCH 190/372] Correct change dates --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 517a1830b..2990be3d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,12 +2,12 @@ ## Current Master -## 3.6.1 2017-4-25 +## 3.6.1 2017-4-30 - Fix bug where error was reported in debugger console when Chrome opened a new window. - Fix bug where block-tracker could stop polling for new blocks. -## 3.6.0 2017-4-25 +## 3.6.0 2017-4-26 - Add Rinkeby Test Network to our network list. From 89b0d3d40318cc119195f295e39a595cac5ff540 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Sun, 30 Apr 2017 18:46:27 -0700 Subject: [PATCH 191/372] Make fox look away while typing password Inspired by this tweet: https://twitter.com/Aashay/status/858791285976481792 --- ui/app/unlock.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ui/app/unlock.js b/ui/app/unlock.js index 1aee3c5d0..de372817d 100644 --- a/ui/app/unlock.js +++ b/ui/app/unlock.js @@ -109,10 +109,10 @@ UnlockScreen.prototype.submitPassword = function (event) { UnlockScreen.prototype.inputChanged = function (event) { // tell mascot to look at page action var element = event.target - var boundingRect = element.getBoundingClientRect() - var coordinates = getCaretCoordinates(element, element.selectionEnd) - this.animationEventEmitter.emit('point', { - x: boundingRect.left + coordinates.left - element.scrollLeft, - y: boundingRect.top + coordinates.top - element.scrollTop, - }) + var viewRect = element.getBoundingClientRect() + var carat = getCaretCoordinates(element, element.selectionEnd) + var x = viewRect.right - carat.left + element.scrollLeft + var y = 300 + var pointAt = { x, y } + this.animationEventEmitter.emit('point', pointAt) } From 53cecf77a2ae5a764e31797ca41d1a19cdb86063 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Sun, 30 Apr 2017 18:54:57 -0700 Subject: [PATCH 192/372] Adjust fox look height --- ui/app/unlock.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/app/unlock.js b/ui/app/unlock.js index de372817d..5014744f5 100644 --- a/ui/app/unlock.js +++ b/ui/app/unlock.js @@ -112,7 +112,7 @@ UnlockScreen.prototype.inputChanged = function (event) { var viewRect = element.getBoundingClientRect() var carat = getCaretCoordinates(element, element.selectionEnd) var x = viewRect.right - carat.left + element.scrollLeft - var y = 300 + var y = 100 var pointAt = { x, y } this.animationEventEmitter.emit('point', pointAt) } From 791c8fccf8e4049b90f096968dc57d9dab7b3c7a Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Sun, 30 Apr 2017 18:56:11 -0700 Subject: [PATCH 193/372] Bump changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 517a1830b..ddbf0808c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Current Master +- Made fox less nosy. + ## 3.6.1 2017-4-25 - Fix bug where error was reported in debugger console when Chrome opened a new window. From 061f56b2070c4f4720a147a901e8879ca193f2ae Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Wed, 3 May 2017 07:21:42 -0700 Subject: [PATCH 194/372] Fox watches over us again. --- ui/app/unlock.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ui/app/unlock.js b/ui/app/unlock.js index 5014744f5..1aee3c5d0 100644 --- a/ui/app/unlock.js +++ b/ui/app/unlock.js @@ -109,10 +109,10 @@ UnlockScreen.prototype.submitPassword = function (event) { UnlockScreen.prototype.inputChanged = function (event) { // tell mascot to look at page action var element = event.target - var viewRect = element.getBoundingClientRect() - var carat = getCaretCoordinates(element, element.selectionEnd) - var x = viewRect.right - carat.left + element.scrollLeft - var y = 100 - var pointAt = { x, y } - this.animationEventEmitter.emit('point', pointAt) + var boundingRect = element.getBoundingClientRect() + var coordinates = getCaretCoordinates(element, element.selectionEnd) + this.animationEventEmitter.emit('point', { + x: boundingRect.left + coordinates.left - element.scrollLeft, + y: boundingRect.top + coordinates.top - element.scrollTop, + }) } From 833b9f183fca17599d4d225eeec077ddbc7bc7b0 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Wed, 3 May 2017 07:22:36 -0700 Subject: [PATCH 195/372] Minor lint --- app/scripts/lib/config-manager.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/scripts/lib/config-manager.js b/app/scripts/lib/config-manager.js index 340ad4292..ab9410842 100644 --- a/app/scripts/lib/config-manager.js +++ b/app/scripts/lib/config-manager.js @@ -155,7 +155,7 @@ ConfigManager.prototype.getCurrentRpcAddress = function () { case 'kovan': return KOVAN_RPC - + case 'rinkeby': return RINKEBY_RPC From 1895fa1489f758714d7ff5dee870eac6507492e7 Mon Sep 17 00:00:00 2001 From: Thomas Huang Date: Thu, 4 May 2017 14:32:42 -0700 Subject: [PATCH 196/372] Add mocha and chai plugins eslint --- .eslintrc | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.eslintrc b/.eslintrc index 8bbfe13c7..91c95874e 100644 --- a/.eslintrc +++ b/.eslintrc @@ -17,10 +17,13 @@ "env": { "es6": true, "node": true, - "browser": true + "browser": true, + "mocha" : true }, "plugins": [ + "mocha", + "chai" ], "globals": { From 73ee4bdec35aa2af744c2cbb61146a1cca49ebe9 Mon Sep 17 00:00:00 2001 From: Thomas Huang Date: Thu, 4 May 2017 14:33:29 -0700 Subject: [PATCH 197/372] Ignore tests bundle, jquery, abd helpers --- .eslintignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.eslintignore b/.eslintignore index df49525be..b96f79011 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1 +1,5 @@ app/scripts/lib/extension-instance.js +test/integration/bundle.js +test/integration/jquery-3.1.0.min.js +test/integration/helpers.js +test/integration/lib/first-time.js \ No newline at end of file From 8f5334e4aca8b95963f57b0fbf862256b692dbd9 Mon Sep 17 00:00:00 2001 From: Thomas Huang Date: Thu, 4 May 2017 14:34:25 -0700 Subject: [PATCH 198/372] Add Mocha/Chai eslint plugins --- package.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.json b/package.json index 6e83b3560..ee7ba7aab 100644 --- a/package.json +++ b/package.json @@ -135,6 +135,8 @@ "deep-freeze-strict": "^1.1.1", "del": "^2.2.0", "envify": "^4.0.0", + "eslint-plugin-chai": "0.0.1", + "eslint-plugin-mocha": "^4.9.0", "fs-promise": "^1.0.0", "gulp": "github:gulpjs/gulp#4.0", "gulp-if": "^2.0.1", From 0b13429daf00ddd5bdf2705c7a95d7a9d5792f54 Mon Sep 17 00:00:00 2001 From: Thomas Huang Date: Thu, 4 May 2017 14:35:10 -0700 Subject: [PATCH 199/372] Lint tests --- app/scripts/lib/config-manager.js | 2 +- test/helper.js | 8 +- test/integration/helpers.js | 4 +- test/integration/index.js | 8 +- test/lib/mock-config-manager.js | 8 +- test/lib/mock-encryptor.js | 12 +- test/lib/mock-simple-keychain.js | 14 +-- test/unit/account-link-test.js | 8 +- test/unit/actions/config_test.js | 23 ++-- test/unit/actions/save_account_label_test.js | 21 ++-- .../unit/actions/set_selected_account_test.js | 23 ++-- test/unit/actions/tx_test.js | 92 +++++++------- test/unit/actions/view_info_test.js | 15 ++- test/unit/actions/warning_test.js | 13 +- test/unit/address-book-controller.js | 17 ++- test/unit/components/binary-renderer-test.js | 10 +- test/unit/config-manager-test.js | 45 ++++--- test/unit/currency-controller-test.js | 45 ++++--- test/unit/explorer-link-test.js | 7 +- test/unit/keyring-controller-test.js | 63 +++++----- test/unit/linting_test.js | 6 +- test/unit/message-manager-test.js | 42 +++---- test/unit/metamask-controller-test.js | 16 ++- test/unit/migrations-test.js | 4 +- test/unit/nameForAccount_test.js | 17 ++- test/unit/nodeify-test.js | 8 +- test/unit/notice-controller-test.js | 75 ++++++----- test/unit/personal-message-manager-test.js | 49 ++++---- test/unit/reducers/unlock_vault_test.js | 27 ++-- test/unit/tx-manager-test.js | 94 +++++++------- test/unit/tx-utils-test.js | 26 ++-- test/unit/util_test.js | 119 +++++++++--------- 32 files changed, 439 insertions(+), 482 deletions(-) diff --git a/app/scripts/lib/config-manager.js b/app/scripts/lib/config-manager.js index 340ad4292..ab9410842 100644 --- a/app/scripts/lib/config-manager.js +++ b/app/scripts/lib/config-manager.js @@ -155,7 +155,7 @@ ConfigManager.prototype.getCurrentRpcAddress = function () { case 'kovan': return KOVAN_RPC - + case 'rinkeby': return RINKEBY_RPC diff --git a/test/helper.js b/test/helper.js index aaac7580e..1c5934a89 100644 --- a/test/helper.js +++ b/test/helper.js @@ -20,14 +20,12 @@ window.localStorage = {} if (!window.crypto) window.crypto = {} if (!window.crypto.getRandomValues) window.crypto.getRandomValues = require('polyfill-crypto.getrandomvalues') - - -function enableFailureOnUnhandledPromiseRejection() { +function enableFailureOnUnhandledPromiseRejection () { // overwrite node's promise with the stricter Bluebird promise global.Promise = require('bluebird') // modified from https://github.com/mochajs/mocha/issues/1926#issuecomment-180842722 - + // rethrow unhandledRejections if (typeof process !== 'undefined') { process.on('unhandledRejection', function (reason) { @@ -51,4 +49,4 @@ function enableFailureOnUnhandledPromiseRejection() { typeof (console.error || console.log) === 'function') { (console.error || console.log)('Unhandled rejections will be ignored!') } -} \ No newline at end of file +} diff --git a/test/integration/helpers.js b/test/integration/helpers.js index eede103b4..10cd74e64 100644 --- a/test/integration/helpers.js +++ b/test/integration/helpers.js @@ -1,6 +1,6 @@ function wait(time) { - return new Promise(function(resolve, reject) { - setTimeout(function() { + return new Promise(function (resolve, reject) { + setTimeout(function () { resolve() }, time * 3 || 1500) }) diff --git a/test/integration/index.js b/test/integration/index.js index ff6d1baf8..f2d656b0b 100644 --- a/test/integration/index.js +++ b/test/integration/index.js @@ -1,10 +1,10 @@ var fs = require('fs') var path = require('path') -var browserify = require('browserify'); +var browserify = require('browserify') var tests = fs.readdirSync(path.join(__dirname, 'lib')) var bundlePath = path.join(__dirname, 'bundle.js') -var b = browserify(); +var b = browserify() // Remove old bundle try { @@ -13,9 +13,9 @@ try { var writeStream = fs.createWriteStream(bundlePath) -tests.forEach(function(fileName) { +tests.forEach(function (fileName) { b.add(path.join(__dirname, 'lib', fileName)) }) -b.bundle().pipe(writeStream); +b.bundle().pipe(writeStream) diff --git a/test/lib/mock-config-manager.js b/test/lib/mock-config-manager.js index 72be86ed1..3238d3501 100644 --- a/test/lib/mock-config-manager.js +++ b/test/lib/mock-config-manager.js @@ -2,9 +2,9 @@ const ObservableStore = require('obs-store') const clone = require('clone') const ConfigManager = require('../../app/scripts/lib/config-manager') const firstTimeState = require('../../app/scripts/first-time-state') -const STORAGE_KEY = 'metamask-config' +// const STORAGE_KEY = 'metamask-config' -module.exports = function() { - let store = new ObservableStore(clone(firstTimeState)) +module.exports = function () { + const store = new ObservableStore(clone(firstTimeState)) return new ConfigManager({ store }) -} \ No newline at end of file +} diff --git a/test/lib/mock-encryptor.js b/test/lib/mock-encryptor.js index 09bbf7ad5..cdf13c507 100644 --- a/test/lib/mock-encryptor.js +++ b/test/lib/mock-encryptor.js @@ -4,28 +4,28 @@ let cacheVal module.exports = { - encrypt(password, dataObj) { + encrypt (password, dataObj) { cacheVal = dataObj return Promise.resolve(mockHex) }, - decrypt(password, text) { + decrypt (password, text) { return Promise.resolve(cacheVal || {}) }, - encryptWithKey(key, dataObj) { + encryptWithKey (key, dataObj) { return this.encrypt(key, dataObj) }, - decryptWithKey(key, text) { + decryptWithKey (key, text) { return this.decrypt(key, text) }, - keyFromPassword(password) { + keyFromPassword (password) { return Promise.resolve(mockKey) }, - generateSalt() { + generateSalt () { return 'WHADDASALT!' }, diff --git a/test/lib/mock-simple-keychain.js b/test/lib/mock-simple-keychain.js index 615b3e537..d3addc3e8 100644 --- a/test/lib/mock-simple-keychain.js +++ b/test/lib/mock-simple-keychain.js @@ -6,32 +6,32 @@ const type = 'Simple Key Pair' module.exports = class MockSimpleKeychain { - static type() { return type } + static type () { return type } - constructor(opts) { + constructor (opts) { this.type = type this.opts = opts || {} this.wallets = [] } - serialize() { + serialize () { return [ fakeWallet.privKey ] } - deserialize(data) { + deserialize (data) { if (!Array.isArray(data)) { throw new Error('Simple keychain deserialize requires a privKey array.') } this.wallets = [ fakeWallet ] } - addAccounts(n = 1) { - for(var i = 0; i < n; i++) { + addAccounts (n = 1) { + for (var i = 0; i < n; i++) { this.wallets.push(fakeWallet) } } - getAccounts() { + getAccounts () { return this.wallets.map(w => w.address) } diff --git a/test/unit/account-link-test.js b/test/unit/account-link-test.js index 803a70f37..47a961d1f 100644 --- a/test/unit/account-link-test.js +++ b/test/unit/account-link-test.js @@ -1,18 +1,16 @@ var assert = require('assert') var linkGen = require('../../ui/lib/account-link') -describe('account-link', function() { - - it('adds ropsten prefix to ropsten test network', function() { +describe('account-link', function () { + it('adds ropsten prefix to ropsten test network', function () { var result = linkGen('account', '3') assert.notEqual(result.indexOf('ropsten'), -1, 'ropsten included') assert.notEqual(result.indexOf('account'), -1, 'account included') }) - it('adds kovan prefix to kovan test network', function() { + it('adds kovan prefix to kovan test network', function () { var result = linkGen('account', '42') assert.notEqual(result.indexOf('kovan'), -1, 'kovan included') assert.notEqual(result.indexOf('account'), -1, 'account included') }) - }) diff --git a/test/unit/actions/config_test.js b/test/unit/actions/config_test.js index 14198fa8a..648f456fb 100644 --- a/test/unit/actions/config_test.js +++ b/test/unit/actions/config_test.js @@ -1,36 +1,34 @@ -var jsdom = require('mocha-jsdom') +// var jsdom = require('mocha-jsdom') var assert = require('assert') var freeze = require('deep-freeze-strict') var path = require('path') var actions = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'actions.js')) -var reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'reducers.js')) - -describe ('config view actions', function() { +var reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'reducers.js')) +describe('config view actions', function () { var initialState = { metamask: { rpcTarget: 'foo', - frequentRpcList: [] + frequentRpcList: [], }, appState: { currentView: { name: 'accounts', - } - } + }, + }, } freeze(initialState) - describe('SHOW_CONFIG_PAGE', function() { - it('should set appState.currentView.name to config', function() { + describe('SHOW_CONFIG_PAGE', function () { + it('should set appState.currentView.name to config', function () { var result = reducers(initialState, actions.showConfigPage()) assert.equal(result.appState.currentView.name, 'config') }) }) - describe('SET_RPC_TARGET', function() { - - it('sets the state.metamask.rpcTarget property of the state to the action.value', function() { + describe('SET_RPC_TARGET', function () { + it('sets the state.metamask.rpcTarget property of the state to the action.value', function () { const action = { type: actions.SET_RPC_TARGET, value: 'foo', @@ -41,5 +39,4 @@ describe ('config view actions', function() { assert.equal(result.metamask.provider.rpcTarget, 'foo') }) }) - }) diff --git a/test/unit/actions/save_account_label_test.js b/test/unit/actions/save_account_label_test.js index 1df428b1d..c5ffd6cbf 100644 --- a/test/unit/actions/save_account_label_test.js +++ b/test/unit/actions/save_account_label_test.js @@ -1,22 +1,21 @@ -var jsdom = require('mocha-jsdom') +// var jsdom = require('mocha-jsdom') var assert = require('assert') var freeze = require('deep-freeze-strict') var path = require('path') var actions = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'actions.js')) -var reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'reducers.js')) +var reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'reducers.js')) -describe('SAVE_ACCOUNT_LABEL', function() { - - it('updates the state.metamask.identities[:i].name property of the state to the action.value.label', function() { +describe('SAVE_ACCOUNT_LABEL', function () { + it('updates the state.metamask.identities[:i].name property of the state to the action.value.label', function () { var initialState = { metamask: { identities: { foo: { - name: 'bar' - } + name: 'bar', + }, }, - } + }, } freeze(initialState) @@ -24,13 +23,13 @@ describe('SAVE_ACCOUNT_LABEL', function() { type: actions.SAVE_ACCOUNT_LABEL, value: { account: 'foo', - label: 'baz' + label: 'baz', }, } freeze(action) var resultingState = reducers(initialState, action) assert.equal(resultingState.metamask.identities.foo.name, action.value.label) - }); -}); + }) +}) diff --git a/test/unit/actions/set_selected_account_test.js b/test/unit/actions/set_selected_account_test.js index 2dc42d2ec..28b47d09d 100644 --- a/test/unit/actions/set_selected_account_test.js +++ b/test/unit/actions/set_selected_account_test.js @@ -1,18 +1,17 @@ -var jsdom = require('mocha-jsdom') +// var jsdom = require('mocha-jsdom') var assert = require('assert') var freeze = require('deep-freeze-strict') var path = require('path') var actions = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'actions.js')) -var reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'reducers.js')) +var reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'reducers.js')) -describe('SET_SELECTED_ACCOUNT', function() { - - it('sets the state.appState.activeAddress property of the state to the action.value', function() { +describe('SET_SELECTED_ACCOUNT', function () { + it('sets the state.appState.activeAddress property of the state to the action.value', function () { var initialState = { appState: { activeAddress: 'foo', - } + }, } freeze(initialState) @@ -24,15 +23,15 @@ describe('SET_SELECTED_ACCOUNT', function() { var resultingState = reducers(initialState, action) assert.equal(resultingState.appState.activeAddress, action.value) - }); -}); + }) +}) -describe('SHOW_ACCOUNT_DETAIL', function() { - it('updates metamask state', function() { +describe('SHOW_ACCOUNT_DETAIL', function () { + it('updates metamask state', function () { var initialState = { metamask: { - selectedAddress: 'foo' - } + selectedAddress: 'foo', + }, } freeze(initialState) diff --git a/test/unit/actions/tx_test.js b/test/unit/actions/tx_test.js index bd72a666e..0ea1bfdc7 100644 --- a/test/unit/actions/tx_test.js +++ b/test/unit/actions/tx_test.js @@ -1,29 +1,27 @@ -var jsdom = require('mocha-jsdom') +// var jsdom = require('mocha-jsdom') var assert = require('assert') var freeze = require('deep-freeze-strict') var path = require('path') var sinon = require('sinon') var actions = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'actions.js')) -var reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'reducers.js')) +var reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'reducers.js')) -describe('tx confirmation screen', function() { - - beforeEach(function() { - this.sinon = sinon.sandbox.create(); - }); +describe('tx confirmation screen', function () { + beforeEach(function () { + this.sinon = sinon.sandbox.create() + }) - afterEach(function(){ - this.sinon.restore(); - }); + afterEach(function () { + this.sinon.restore() + }) var initialState, result - describe('when there is only one tx', function() { + describe('when there is only one tx', function () { var firstTxId = 1457634084250832 - beforeEach(function() { - + beforeEach(function () { initialState = { appState: { currentView: { @@ -34,70 +32,66 @@ describe('tx confirmation screen', function() { unapprovedTxs: { '1457634084250832': { id: 1457634084250832, - status: "unconfirmed", + status: 'unconfirmed', time: 1457634084250, - } + }, }, - } + }, } freeze(initialState) }) - describe('cancelTx', function() { - - before(function(done) { + describe('cancelTx', function () { + before(function (done) { actions._setBackgroundConnection({ - approveTransaction(txId, cb) { cb('An error!') }, - cancelTransaction(txId) { /* noop */ }, - clearSeedWordCache(cb) { cb() }, + approveTransaction (txId, cb) { cb('An error!') }, + cancelTransaction (txId) { /* noop */ }, + clearSeedWordCache (cb) { cb() }, }) - let action = actions.cancelTx({value: firstTxId}) + const action = actions.cancelTx({value: firstTxId}) result = reducers(initialState, action) done() }) - it('should transition to the account detail view', function() { + it('should transition to the account detail view', function () { assert.equal(result.appState.currentView.name, 'accountDetail') }) - it('should have no unconfirmed txs remaining', function() { + it('should have no unconfirmed txs remaining', function () { var count = getUnconfirmedTxCount(result) assert.equal(count, 0) }) }) - describe('sendTx', function() { + describe('sendTx', function () { var result - describe('when there is an error', function() { - - before(function(done) { - alert = () => {/* noop */} - + describe('when there is an error', function () { + before(function (done) { actions._setBackgroundConnection({ - approveTransaction(txId, cb) { cb({message: 'An error!'}) }, + approveTransaction (txId, cb) { cb({message: 'An error!'}) }, }) - actions.sendTx({id: firstTxId})(function(action) { + actions.sendTx({id: firstTxId})(function (action) { result = reducers(initialState, action) done() }) }) - it('should stay on the page', function() { + it('should stay on the page', function () { assert.equal(result.appState.currentView.name, 'confTx') }) - it('should set errorMessage on the currentView', function() { + it('should set errorMessage on the currentView', function () { assert(result.appState.currentView.errorMessage) }) }) - describe('when there is success', function() { - it('should complete tx and go home', function() { + describe('when there is success', function () { + it('should complete tx and go home', function () { actions._setBackgroundConnection({ - approveTransaction(txId, cb) { cb() }, + approveTransaction (txId, cb) { cb() }, }) var dispatchExpect = sinon.mock() @@ -108,10 +102,10 @@ describe('tx confirmation screen', function() { }) }) - describe('when there are two pending txs', function() { + describe('when there are two pending txs', function () { var firstTxId = 1457634084250832 var result, initialState - before(function(done) { + before(function (done) { initialState = { appState: { currentView: { @@ -122,42 +116,42 @@ describe('tx confirmation screen', function() { unapprovedTxs: { '1457634084250832': { id: firstTxId, - status: "unconfirmed", + status: 'unconfirmed', time: 1457634084250, }, '1457634084250833': { id: 1457634084250833, - status: "unconfirmed", + status: 'unconfirmed', time: 1457634084255, }, }, - } + }, } freeze(initialState) // Mocking a background connection: actions._setBackgroundConnection({ - approveTransaction(firstTxId, cb) { cb() }, + approveTransaction (firstTxId, cb) { cb() }, }) - let action = actions.sendTx({id: firstTxId})(function(action) { + actions.sendTx({id: firstTxId})(function (action) { result = reducers(initialState, action) }) done() }) - it('should stay on the confTx view', function() { + it('should stay on the confTx view', function () { assert.equal(result.appState.currentView.name, 'confTx') }) - it('should transition to the first tx', function() { + it('should transition to the first tx', function () { assert.equal(result.appState.currentView.context, 0) }) }) }) -}); +}) -function getUnconfirmedTxCount(state) { +function getUnconfirmedTxCount (state) { var txs = state.metamask.unapprovedTxs var count = Object.keys(txs).length return count diff --git a/test/unit/actions/view_info_test.js b/test/unit/actions/view_info_test.js index 0558c6e42..69895d801 100644 --- a/test/unit/actions/view_info_test.js +++ b/test/unit/actions/view_info_test.js @@ -1,23 +1,22 @@ -var jsdom = require('mocha-jsdom') +// var jsdom = require('mocha-jsdom') var assert = require('assert') var freeze = require('deep-freeze-strict') var path = require('path') var actions = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'actions.js')) -var reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'reducers.js')) +var reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'reducers.js')) -describe('SHOW_INFO_PAGE', function() { - - it('sets the state.appState.currentView.name property to info', function() { +describe('SHOW_INFO_PAGE', function () { + it('sets the state.appState.currentView.name property to info', function () { var initialState = { appState: { activeAddress: 'foo', - } + }, } freeze(initialState) const action = actions.showInfoPage() var resultingState = reducers(initialState, action) assert.equal(resultingState.appState.currentView.name, 'info') - }); -}); + }) +}) diff --git a/test/unit/actions/warning_test.js b/test/unit/actions/warning_test.js index 37be9ee85..28b565499 100644 --- a/test/unit/actions/warning_test.js +++ b/test/unit/actions/warning_test.js @@ -1,14 +1,13 @@ -var jsdom = require('mocha-jsdom') +// var jsdom = require('mocha-jsdom') var assert = require('assert') var freeze = require('deep-freeze-strict') var path = require('path') var actions = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'actions.js')) -var reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'reducers.js')) +var reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'reducers.js')) -describe('action DISPLAY_WARNING', function() { - - it('sets appState.warning to provided value', function() { +describe('action DISPLAY_WARNING', function () { + it('sets appState.warning to provided value', function () { var initialState = { appState: {}, } @@ -20,5 +19,5 @@ describe('action DISPLAY_WARNING', function() { const resultingState = reducers(initialState, action) assert.equal(resultingState.appState.warning, warningText, 'warning text set') - }); -}); + }) +}) diff --git a/test/unit/address-book-controller.js b/test/unit/address-book-controller.js index f345b0328..85deb8905 100644 --- a/test/unit/address-book-controller.js +++ b/test/unit/address-book-controller.js @@ -1,5 +1,5 @@ const assert = require('assert') -const extend = require('xtend') +// const extend = require('xtend') const AddressBookController = require('../../app/scripts/controllers/address-book') const mockKeyringController = { @@ -7,21 +7,20 @@ const mockKeyringController = { getState: function () { return { identities: { - '0x0aaa' : { + '0x0aaa': { address: '0x0aaa', name: 'owned', - } - } + }, + }, } - } - } + }, + }, } - -describe('address-book-controller', function() { +describe('address-book-controller', function () { var addressBookController - beforeEach(function() { + beforeEach(function () { addressBookController = new AddressBookController({}, mockKeyringController) }) diff --git a/test/unit/components/binary-renderer-test.js b/test/unit/components/binary-renderer-test.js index 3264faddc..ee2fa8b60 100644 --- a/test/unit/components/binary-renderer-test.js +++ b/test/unit/components/binary-renderer-test.js @@ -1,24 +1,22 @@ var assert = require('assert') var BinaryRenderer = require('../../../ui/app/components/binary-renderer') -describe('BinaryRenderer', function() { - +describe('BinaryRenderer', function () { let binaryRenderer const message = 'Hello, world!' const buffer = new Buffer(message, 'utf8') const hex = buffer.toString('hex') - beforeEach(function() { + beforeEach(function () { binaryRenderer = new BinaryRenderer() }) - it('recovers message', function() { + it('recovers message', function () { const result = binaryRenderer.hexToText(hex) assert.equal(result, message) }) - - it('recovers message with hex prefix', function() { + it('recovers message with hex prefix', function () { const result = binaryRenderer.hexToText('0x' + hex) assert.equal(result, message) }) diff --git a/test/unit/config-manager-test.js b/test/unit/config-manager-test.js index 05324e741..eeb193e91 100644 --- a/test/unit/config-manager-test.js +++ b/test/unit/config-manager-test.js @@ -2,26 +2,25 @@ global.fetch = global.fetch || require('isomorphic-fetch') const assert = require('assert') -const extend = require('xtend') -const rp = require('request-promise') -const nock = require('nock') +// const extend = require('xtend') +// const rp = require('request-promise') +// const nock = require('nock') const configManagerGen = require('../lib/mock-config-manager') -describe('config-manager', function() { +describe('config-manager', function () { var configManager - beforeEach(function() { + beforeEach(function () { configManager = configManagerGen() }) - describe('#setConfig', function() { - + describe('#setConfig', function () { it('should set the config key', function () { var testConfig = { provider: { type: 'rpc', - rpcTarget: 'foobar' - } + rpcTarget: 'foobar', + }, } configManager.setConfig(testConfig) var result = configManager.getData() @@ -30,17 +29,17 @@ describe('config-manager', function() { assert.equal(result.config.provider.rpcTarget, testConfig.provider.rpcTarget) }) - it('setting wallet should not overwrite config', function() { + it('setting wallet should not overwrite config', function () { var testConfig = { provider: { type: 'rpc', - rpcTarget: 'foobar' + rpcTarget: 'foobar', }, } configManager.setConfig(testConfig) var testWallet = { - name: 'this is my fake wallet' + name: 'this is my fake wallet', } configManager.setWallet(testWallet) @@ -58,13 +57,13 @@ describe('config-manager', function() { }) }) - describe('wallet nicknames', function() { - it('should return null when no nicknames are saved', function() { + describe('wallet nicknames', function () { + it('should return null when no nicknames are saved', function () { var nick = configManager.nicknameForWallet('0x0') assert.equal(nick, null, 'no nickname returned') }) - it('should persist nicknames', function() { + it('should persist nicknames', function () { var account = '0x0' var nick1 = 'foo' var nick2 = 'bar' @@ -79,8 +78,8 @@ describe('config-manager', function() { }) }) - describe('rpc manipulations', function() { - it('changing rpc should return a different rpc', function() { + describe('rpc manipulations', function () { + it('changing rpc should return a different rpc', function () { var firstRpc = 'first' var secondRpc = 'second' @@ -94,21 +93,21 @@ describe('config-manager', function() { }) }) - describe('transactions', function() { - beforeEach(function() { + describe('transactions', function () { + beforeEach(function () { configManager.setTxList([]) }) - describe('#getTxList', function() { - it('when new should return empty array', function() { + describe('#getTxList', function () { + it('when new should return empty array', function () { var result = configManager.getTxList() assert.ok(Array.isArray(result)) assert.equal(result.length, 0) }) }) - describe('#setTxList', function() { - it('saves the submitted data to the tx list', function() { + describe('#setTxList', function () { + it('saves the submitted data to the tx list', function () { var target = [{ foo: 'bar' }] configManager.setTxList(target) var result = configManager.getTxList() diff --git a/test/unit/currency-controller-test.js b/test/unit/currency-controller-test.js index 079f8b488..40868912c 100644 --- a/test/unit/currency-controller-test.js +++ b/test/unit/currency-controller-test.js @@ -2,26 +2,25 @@ global.fetch = global.fetch || require('isomorphic-fetch') const assert = require('assert') -const extend = require('xtend') -const rp = require('request-promise') +// const extend = require('xtend') +// const rp = require('request-promise') const nock = require('nock') const CurrencyController = require('../../app/scripts/controllers/currency') -describe('currency-controller', function() { +describe('currency-controller', function () { var currencyController - beforeEach(function() { + beforeEach(function () { currencyController = new CurrencyController() }) - describe('currency conversions', function() { - - describe('#setCurrentCurrency', function() { - it('should return USD as default', function() { + describe('currency conversions', function () { + describe('#setCurrentCurrency', function () { + it('should return USD as default', function () { assert.equal(currencyController.getCurrentCurrency(), 'USD') }) - it('should be able to set to other currency', function() { + it('should be able to set to other currency', function () { assert.equal(currencyController.getCurrentCurrency(), 'USD') currencyController.setCurrentCurrency('JPY') var result = currencyController.getCurrentCurrency() @@ -29,39 +28,38 @@ describe('currency-controller', function() { }) }) - describe('#getConversionRate', function() { - it('should return undefined if non-existent', function() { + describe('#getConversionRate', function () { + it('should return undefined if non-existent', function () { var result = currencyController.getConversionRate() assert.ok(!result) }) }) - describe('#updateConversionRate', function() { - it('should retrieve an update for ETH to USD and set it in memory', function(done) { + describe('#updateConversionRate', function () { + it('should retrieve an update for ETH to USD and set it in memory', function (done) { this.timeout(15000) - var usdMock = nock('https://www.cryptonator.com') + nock('https://www.cryptonator.com') .get('/api/ticker/eth-USD') .reply(200, '{"ticker":{"base":"ETH","target":"USD","price":"11.02456145","volume":"44948.91745289","change":"-0.01472534"},"timestamp":1472072136,"success":true,"error":""}') assert.equal(currencyController.getConversionRate(), 0) currencyController.setCurrentCurrency('USD') currencyController.updateConversionRate() - .then(function() { + .then(function () { var result = currencyController.getConversionRate() console.log('currencyController.getConversionRate:', result) assert.equal(typeof result, 'number') done() - }).catch(function(err) { + }).catch(function (err) { done(err) }) - }) - it('should work for JPY as well.', function() { + it('should work for JPY as well.', function () { this.timeout(15000) assert.equal(currencyController.getConversionRate(), 0) - var jpyMock = nock('https://www.cryptonator.com') + nock('https://www.cryptonator.com') .get('/api/ticker/eth-JPY') .reply(200, '{"ticker":{"base":"ETH","target":"JPY","price":"11.02456145","volume":"44948.91745289","change":"-0.01472534"},"timestamp":1472072136,"success":true,"error":""}') @@ -69,19 +67,18 @@ describe('currency-controller', function() { var promise = new Promise( function (resolve, reject) { currencyController.setCurrentCurrency('JPY') - currencyController.updateConversionRate().then(function() { + currencyController.updateConversionRate().then(function () { resolve() }) - }) + }) - promise.then(function() { + promise.then(function () { var result = currencyController.getConversionRate() assert.equal(typeof result, 'number') - }).catch(function(err) { + }).catch(function (done, err) { done(err) }) }) }) }) - }) diff --git a/test/unit/explorer-link-test.js b/test/unit/explorer-link-test.js index 4f0230c2c..e672b36ed 100644 --- a/test/unit/explorer-link-test.js +++ b/test/unit/explorer-link-test.js @@ -1,14 +1,13 @@ var assert = require('assert') var linkGen = require('../../ui/lib/explorer-link') -describe('explorer-link', function() { - - it('adds ropsten prefix to ropsten test network', function() { +describe('explorer-link', function () { + it('adds ropsten prefix to ropsten test network', function () { var result = linkGen('hash', '3') assert.notEqual(result.indexOf('ropsten'), -1, 'ropsten injected') }) - it('adds kovan prefix to kovan test network', function() { + it('adds kovan prefix to kovan test network', function () { var result = linkGen('hash', '42') assert.notEqual(result.indexOf('kovan'), -1, 'kovan injected') }) diff --git a/test/unit/keyring-controller-test.js b/test/unit/keyring-controller-test.js index efd0a3546..f7e2ec89d 100644 --- a/test/unit/keyring-controller-test.js +++ b/test/unit/keyring-controller-test.js @@ -3,21 +3,20 @@ const KeyringController = require('../../app/scripts/keyring-controller') const configManagerGen = require('../lib/mock-config-manager') const ethUtil = require('ethereumjs-util') const BN = ethUtil.BN -const async = require('async') +// const async = require('async') const mockEncryptor = require('../lib/mock-encryptor') -const MockSimpleKeychain = require('../lib/mock-simple-keychain') +// const MockSimpleKeychain = require('../lib/mock-simple-keychain') const sinon = require('sinon') -describe('KeyringController', function() { +describe('KeyringController', function () { + let keyringController + const password = 'password123' + const seedWords = 'puzzle seed penalty soldier say clay field arctic metal hen cage runway' + const addresses = ['eF35cA8EbB9669A35c31b5F6f249A9941a812AC1'.toLowerCase()] + const accounts = [] + // let originalKeystore - let keyringController, state - let password = 'password123' - let seedWords = 'puzzle seed penalty soldier say clay field arctic metal hen cage runway' - let addresses = ['eF35cA8EbB9669A35c31b5F6f249A9941a812AC1'.toLowerCase()] - let accounts = [] - let originalKeystore - - beforeEach(function(done) { + beforeEach(function (done) { this.sinon = sinon.sandbox.create() window.localStorage = {} // Hacking localStorage support into JSDom @@ -25,10 +24,10 @@ describe('KeyringController', function() { configManager: configManagerGen(), txManager: { getTxList: () => [], - getUnapprovedTxList: () => [] + getUnapprovedTxList: () => [], }, ethStore: { - addAccount(acct) { accounts.push(ethUtil.addHexPrefix(acct)) }, + addAccount (acct) { accounts.push(ethUtil.addHexPrefix(acct)) }, }, }) @@ -38,7 +37,7 @@ describe('KeyringController', function() { keyringController.createNewVaultAndKeychain(password) .then(function (newState) { - state = newState + newState done() }) .catch((err) => { @@ -46,7 +45,7 @@ describe('KeyringController', function() { }) }) - afterEach(function() { + afterEach(function () { // Cleanup mocks this.sinon.restore() }) @@ -54,7 +53,7 @@ describe('KeyringController', function() { describe('#createNewVaultAndKeychain', function () { this.timeout(10000) - it('should set a vault on the configManager', function(done) { + it('should set a vault on the configManager', function (done) { keyringController.store.updateState({ vault: null }) assert(!keyringController.store.getState().vault, 'no previous vault') keyringController.createNewVaultAndKeychain(password) @@ -69,15 +68,14 @@ describe('KeyringController', function() { }) }) - describe('#restoreKeyring', function() { - - it(`should pass a keyring's serialized data back to the correct type.`, function(done) { + describe('#restoreKeyring', function () { + it(`should pass a keyring's serialized data back to the correct type.`, function (done) { const mockSerialized = { type: 'HD Key Tree', data: { mnemonic: seedWords, numberOfAccounts: 1, - } + }, } const mock = this.sinon.mock(keyringController) @@ -100,8 +98,8 @@ describe('KeyringController', function() { }) }) - describe('#createNickname', function() { - it('should add the address to the identities hash', function() { + describe('#createNickname', function () { + it('should add the address to the identities hash', function () { const fakeAddress = '0x12345678' keyringController.createNickname(fakeAddress) const identities = keyringController.memStore.getState().identities @@ -110,8 +108,8 @@ describe('KeyringController', function() { }) }) - describe('#saveAccountLabel', function() { - it ('sets the nickname', function(done) { + describe('#saveAccountLabel', function () { + it('sets the nickname', function (done) { const account = addresses[0] var nick = 'Test nickname' const identities = keyringController.memStore.getState().identities @@ -134,31 +132,30 @@ describe('KeyringController', function() { }) }) - describe('#getAccounts', function() { - it('returns the result of getAccounts for each keyring', function(done) { + describe('#getAccounts', function () { + it('returns the result of getAccounts for each keyring', function (done) { keyringController.keyrings = [ - { getAccounts() { return Promise.resolve([1,2,3]) } }, - { getAccounts() { return Promise.resolve([4,5,6]) } }, + { getAccounts () { return Promise.resolve([1, 2, 3]) } }, + { getAccounts () { return Promise.resolve([4, 5, 6]) } }, ] keyringController.getAccounts() .then((result) => { - assert.deepEqual(result, [1,2,3,4,5,6]) + assert.deepEqual(result, [1, 2, 3, 4, 5, 6]) done() }) }) }) - describe('#addGasBuffer', function() { - it('adds 100k gas buffer to estimates', function() { - + describe('#addGasBuffer', function () { + it('adds 100k gas buffer to estimates', function () { const gas = '0x04ee59' // Actual estimated gas example const tooBigOutput = '0x80674f9' // Actual bad output const bnGas = new BN(ethUtil.stripHexPrefix(gas), 16) const correctBuffer = new BN('100000', 10) const correct = bnGas.add(correctBuffer) - const tooBig = new BN(tooBigOutput, 16) + // const tooBig = new BN(tooBigOutput, 16) const result = keyringController.addGasBuffer(gas) const bnResult = new BN(ethUtil.stripHexPrefix(result), 16) diff --git a/test/unit/linting_test.js b/test/unit/linting_test.js index 75d90652d..45578fc36 100644 --- a/test/unit/linting_test.js +++ b/test/unit/linting_test.js @@ -1,9 +1,9 @@ // LINTING: -const lint = require('mocha-eslint'); -const lintPaths = ['app/**/*.js', 'ui/**/*.js', '!node_modules/**', '!dist/**', '!docs/**', '!app/scripts/chromereload.js'] +const lint = require('mocha-eslint') +const lintPaths = ['app/**/*.js', 'ui/**/*.js', 'test/**/*.js', '!node_modules/**', '!dist/**', '!docs/**', '!app/scripts/chromereload.js'] const lintOptions = { strict: false, } -lint(lintPaths, lintOptions) \ No newline at end of file +lint(lintPaths, lintOptions) diff --git a/test/unit/message-manager-test.js b/test/unit/message-manager-test.js index faf7429d4..44190c8d0 100644 --- a/test/unit/message-manager-test.js +++ b/test/unit/message-manager-test.js @@ -1,29 +1,29 @@ const assert = require('assert') -const extend = require('xtend') -const EventEmitter = require('events') +// const extend = require('xtend') +// const EventEmitter = require('events') const MessageManger = require('../../app/scripts/lib/message-manager') -describe('Transaction Manager', function() { +describe('Transaction Manager', function () { let messageManager - beforeEach(function() { - messageManager = new MessageManger () + beforeEach(function () { + messageManager = new MessageManger() }) - describe('#getMsgList', function() { - it('when new should return empty array', function() { + describe('#getMsgList', function () { + it('when new should return empty array', function () { var result = messageManager.messages assert.ok(Array.isArray(result)) assert.equal(result.length, 0) }) - it('should also return transactions from local storage if any', function() { + it('should also return transactions from local storage if any', function () { }) }) - describe('#addMsg', function() { - it('adds a Msg returned in getMsgList', function() { + describe('#addMsg', function () { + it('adds a Msg returned in getMsgList', function () { var Msg = { id: 1, status: 'approved', metamaskNetworkId: 'unit test' } messageManager.addMsg(Msg) var result = messageManager.messages @@ -33,8 +33,8 @@ describe('Transaction Manager', function() { }) }) - describe('#setMsgStatusApproved', function() { - it('sets the Msg status to approved', function() { + describe('#setMsgStatusApproved', function () { + it('sets the Msg status to approved', function () { var Msg = { id: 1, status: 'unapproved', metamaskNetworkId: 'unit test' } messageManager.addMsg(Msg) messageManager.setMsgStatusApproved(1) @@ -45,8 +45,8 @@ describe('Transaction Manager', function() { }) }) - describe('#rejectMsg', function() { - it('sets the Msg status to rejected', function() { + describe('#rejectMsg', function () { + it('sets the Msg status to rejected', function () { var Msg = { id: 1, status: 'unapproved', metamaskNetworkId: 'unit test' } messageManager.addMsg(Msg) messageManager.rejectMsg(1) @@ -57,8 +57,8 @@ describe('Transaction Manager', function() { }) }) - describe('#_updateMsg', function() { - it('replaces the Msg with the same id', function() { + describe('#_updateMsg', function () { + it('replaces the Msg with the same id', 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' }) @@ -67,19 +67,19 @@ describe('Transaction Manager', function() { }) }) - describe('#getUnapprovedMsgs', function() { - it('returns unapproved Msgs in a hash', function() { + describe('#getUnapprovedMsgs', function () { + it('returns unapproved Msgs in a hash', function () { messageManager.addMsg({ id: '1', status: 'unapproved', metamaskNetworkId: 'unit test' }) messageManager.addMsg({ id: '2', status: 'approved', metamaskNetworkId: 'unit test' }) - let result = messageManager.getUnapprovedMsgs() + const result = messageManager.getUnapprovedMsgs() assert.equal(typeof result, 'object') assert.equal(result['1'].status, 'unapproved') assert.equal(result['2'], undefined) }) }) - describe('#getMsg', function() { - it('returns a Msg with the requested id', function() { + describe('#getMsg', function () { + it('returns a Msg with the requested id', function () { messageManager.addMsg({ id: '1', status: 'unapproved', metamaskNetworkId: 'unit test' }) messageManager.addMsg({ id: '2', status: 'approved', metamaskNetworkId: 'unit test' }) assert.equal(messageManager.getMsg('1').status, 'unapproved') diff --git a/test/unit/metamask-controller-test.js b/test/unit/metamask-controller-test.js index 78b9e9df7..ac588b313 100644 --- a/test/unit/metamask-controller-test.js +++ b/test/unit/metamask-controller-test.js @@ -4,11 +4,11 @@ const clone = require('clone') const MetaMaskController = require('../../app/scripts/metamask-controller') const firstTimeState = require('../../app/scripts/first-time-state') -const STORAGE_KEY = 'metamask-config' +// const STORAGE_KEY = 'metamask-config' -describe('MetaMaskController', function() { +describe('MetaMaskController', function () { const noop = () => {} - let controller = new MetaMaskController({ + const metamaskController = new MetaMaskController({ showUnconfirmedMessage: noop, unlockAccountMessage: noop, showUnapprovedTx: noop, @@ -16,14 +16,18 @@ describe('MetaMaskController', function() { initState: clone(firstTimeState), }) - beforeEach(function() { + beforeEach(function () { // sinon allows stubbing methods that are easily verified this.sinon = sinon.sandbox.create() }) - afterEach(function() { + afterEach(function () { // sinon requires cleanup otherwise it will overwrite context this.sinon.restore() }) -}) \ No newline at end of file + describe('Metamask Controller', function () { + assert(metamaskController) + }) +}) + diff --git a/test/unit/migrations-test.js b/test/unit/migrations-test.js index ccd1477b0..324e4d056 100644 --- a/test/unit/migrations-test.js +++ b/test/unit/migrations-test.js @@ -3,7 +3,7 @@ const path = require('path') const wallet1 = require(path.join('..', 'lib', 'migrations', '001.json')) const vault4 = require(path.join('..', 'lib', 'migrations', '004.json')) -let vault5, vault6, vault7, vault8, vault9, vault10, vault11 +let vault5, vault6, vault7, vault8, vault9 // vault10, vault11 const migration2 = require(path.join('..', '..', 'app', 'scripts', 'migrations', '002')) const migration3 = require(path.join('..', '..', 'app', 'scripts', 'migrations', '003')) @@ -23,7 +23,6 @@ const newTestRpc = 'https://testrpc.metamask.io/' describe('wallet1 is migrated successfully', () => { it('should convert providers', () => { - wallet1.data.config.provider = { type: 'etherscan', rpcTarget: null } return migration2.migrate(wallet1) @@ -99,6 +98,5 @@ describe('wallet1 is migrated successfully', () => { assert.equal(twelfthResult.data.NoticeController.noticesList[0].body, '', 'notices that have been read should have an empty body.') assert.equal(twelfthResult.data.NoticeController.noticesList[1].body, 'nonempty', 'notices that have not been read should not have an empty body.') }) - }) }) diff --git a/test/unit/nameForAccount_test.js b/test/unit/nameForAccount_test.js index 6839d40f8..e7c0b18b4 100644 --- a/test/unit/nameForAccount_test.js +++ b/test/unit/nameForAccount_test.js @@ -4,25 +4,23 @@ var sinon = require('sinon') var path = require('path') var contractNamer = require(path.join(__dirname, '..', '..', 'ui', 'lib', 'contract-namer.js')) -describe('contractNamer', function() { - - beforeEach(function() { +describe('contractNamer', function () { + beforeEach(function () { this.sinon = sinon.sandbox.create() }) - afterEach(function() { + afterEach(function () { this.sinon.restore() }) - describe('naming a contract', function() { - - it('should return nothing for an unknown random account', function() { + describe('naming a contract', function () { + it('should return nothing for an unknown random account', function () { const input = '0x2386F26FC10000' const output = contractNamer(input) assert.deepEqual(output, null) }) - it('should accept identities as an optional second parameter', function() { + it('should accept identities as an optional second parameter', function () { const input = '0x2386F26FC10000'.toLowerCase() const expected = 'bar' const identities = {} @@ -31,7 +29,7 @@ describe('contractNamer', function() { assert.deepEqual(output, expected) }) - it('should check for identities case insensitively', function() { + it('should check for identities case insensitively', function () { const input = '0x2386F26FC10000'.toLowerCase() const expected = 'bar' const identities = {} @@ -39,6 +37,5 @@ describe('contractNamer', function() { const output = contractNamer(input.toUpperCase(), identities) assert.deepEqual(output, expected) }) - }) }) diff --git a/test/unit/nodeify-test.js b/test/unit/nodeify-test.js index a14d34338..5aed758fa 100644 --- a/test/unit/nodeify-test.js +++ b/test/unit/nodeify-test.js @@ -1,22 +1,20 @@ const assert = require('assert') const nodeify = require('../../app/scripts/lib/nodeify') -describe('nodeify', function() { - +describe('nodeify', function () { var obj = { foo: 'bar', promiseFunc: function (a) { var solution = this.foo + a return Promise.resolve(solution) - } + }, } - it('should retain original context', function(done) { + it('should retain original context', function (done) { var nodified = nodeify(obj.promiseFunc).bind(obj) nodified('baz', function (err, res) { assert.equal(res, 'barbaz') done() }) }) - }) diff --git a/test/unit/notice-controller-test.js b/test/unit/notice-controller-test.js index ea37108bb..7eef615d5 100644 --- a/test/unit/notice-controller-test.js +++ b/test/unit/notice-controller-test.js @@ -1,42 +1,42 @@ const assert = require('assert') -const extend = require('xtend') -const rp = require('request-promise') -const nock = require('nock') +// const extend = require('xtend') +// const rp = require('request-promise') +// const nock = require('nock') const configManagerGen = require('../lib/mock-config-manager') const NoticeController = require('../../app/scripts/notice-controller') -const STORAGE_KEY = 'metamask-persistence-key' +// const STORAGE_KEY = 'metamask-persistence-key' -describe('notice-controller', function() { +describe('notice-controller', function () { var noticeController - beforeEach(function() { + beforeEach(function () { // simple localStorage polyfill - let configManager = configManagerGen() + const configManager = configManagerGen() noticeController = new NoticeController({ configManager: configManager, }) }) - describe('notices', function() { - describe('#getNoticesList', function() { - it('should return an empty array when new', function(done) { - var testList = [{ - id:0, - read:false, - title:"Futuristic Notice" - }] + describe('notices', function () { + describe('#getNoticesList', function () { + it('should return an empty array when new', function (done) { + // const testList = [{ + // id: 0, + // read: false, + // title: 'Futuristic Notice', + // }] var result = noticeController.getNoticesList() assert.equal(result.length, 0) done() }) }) - describe('#setNoticesList', function() { + describe('#setNoticesList', function () { it('should set data appropriately', function (done) { var testList = [{ - id:0, - read:false, - title:"Futuristic Notice" + id: 0, + read: false, + title: 'Futuristic Notice', }] noticeController.setNoticesList(testList) var testListId = noticeController.getNoticesList()[0].id @@ -45,12 +45,12 @@ describe('notice-controller', function() { }) }) - describe('#updateNoticeslist', function() { - it('should integrate the latest changes from the source', function(done) { + describe('#updateNoticeslist', function () { + it('should integrate the latest changes from the source', function (done) { var testList = [{ - id:55, - read:false, - title:"Futuristic Notice" + id: 55, + read: false, + title: 'Futuristic Notice', }] noticeController.setNoticesList(testList) noticeController.updateNoticesList().then(() => { @@ -62,14 +62,14 @@ describe('notice-controller', function() { }) it('should not overwrite any existing fields', function (done) { var testList = [{ - id:0, - read:false, - title:"Futuristic Notice" + id: 0, + read: false, + title: 'Futuristic Notice', }] noticeController.setNoticesList(testList) var newList = noticeController.getNoticesList() assert.equal(newList[0].id, 0) - assert.equal(newList[0].title, "Futuristic Notice") + assert.equal(newList[0].title, 'Futuristic Notice') assert.equal(newList.length, 1) done() }) @@ -78,9 +78,9 @@ describe('notice-controller', function() { describe('#markNoticeRead', function () { it('should mark a notice as read', function (done) { var testList = [{ - id:0, - read:false, - title:"Futuristic Notice" + id: 0, + read: false, + title: 'Futuristic Notice', }] noticeController.setNoticesList(testList) noticeController.markNoticeRead(testList[0]) @@ -93,9 +93,9 @@ describe('notice-controller', function() { describe('#getLatestUnreadNotice', function () { it('should retrieve the latest unread notice', function (done) { var testList = [ - {id:0,read:true,title:"Past Notice"}, - {id:1,read:false,title:"Current Notice"}, - {id:2,read:false,title:"Future Notice"}, + {id: 0, read: true, title: 'Past Notice'}, + {id: 1, read: false, title: 'Current Notice'}, + {id: 2, read: false, title: 'Future Notice'}, ] noticeController.setNoticesList(testList) var latestUnread = noticeController.getLatestUnreadNotice() @@ -104,9 +104,9 @@ describe('notice-controller', function() { }) it('should return undefined if no unread notices exist.', function (done) { var testList = [ - {id:0,read:true,title:"Past Notice"}, - {id:1,read:true,title:"Current Notice"}, - {id:2,read:true,title:"Future Notice"}, + {id: 0, read: true, title: 'Past Notice'}, + {id: 1, read: true, title: 'Current Notice'}, + {id: 2, read: true, title: 'Future Notice'}, ] noticeController.setNoticesList(testList) var latestUnread = noticeController.getLatestUnreadNotice() @@ -115,5 +115,4 @@ describe('notice-controller', function() { }) }) }) - }) diff --git a/test/unit/personal-message-manager-test.js b/test/unit/personal-message-manager-test.js index f2c01392c..b7a29b7b0 100644 --- a/test/unit/personal-message-manager-test.js +++ b/test/unit/personal-message-manager-test.js @@ -1,29 +1,29 @@ const assert = require('assert') -const extend = require('xtend') -const EventEmitter = require('events') +// const extend = require('xtend') +// const EventEmitter = require('events') const PersonalMessageManager = require('../../app/scripts/lib/personal-message-manager') -describe('Personal Message Manager', function() { +describe('Personal Message Manager', function () { let messageManager - beforeEach(function() { + beforeEach(function () { messageManager = new PersonalMessageManager() }) - describe('#getMsgList', function() { - it('when new should return empty array', function() { + describe('#getMsgList', function () { + it('when new should return empty array', function () { var result = messageManager.messages assert.ok(Array.isArray(result)) assert.equal(result.length, 0) }) - it('should also return transactions from local storage if any', function() { + it('should also return transactions from local storage if any', function () { }) }) - describe('#addMsg', function() { - it('adds a Msg returned in getMsgList', function() { + describe('#addMsg', function () { + it('adds a Msg returned in getMsgList', function () { var Msg = { id: 1, status: 'approved', metamaskNetworkId: 'unit test' } messageManager.addMsg(Msg) var result = messageManager.messages @@ -33,8 +33,8 @@ describe('Personal Message Manager', function() { }) }) - describe('#setMsgStatusApproved', function() { - it('sets the Msg status to approved', function() { + describe('#setMsgStatusApproved', function () { + it('sets the Msg status to approved', function () { var Msg = { id: 1, status: 'unapproved', metamaskNetworkId: 'unit test' } messageManager.addMsg(Msg) messageManager.setMsgStatusApproved(1) @@ -45,8 +45,8 @@ describe('Personal Message Manager', function() { }) }) - describe('#rejectMsg', function() { - it('sets the Msg status to rejected', function() { + describe('#rejectMsg', function () { + it('sets the Msg status to rejected', function () { var Msg = { id: 1, status: 'unapproved', metamaskNetworkId: 'unit test' } messageManager.addMsg(Msg) messageManager.rejectMsg(1) @@ -57,8 +57,8 @@ describe('Personal Message Manager', function() { }) }) - describe('#_updateMsg', function() { - it('replaces the Msg with the same id', function() { + describe('#_updateMsg', function () { + it('replaces the Msg with the same id', 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' }) @@ -67,19 +67,19 @@ describe('Personal Message Manager', function() { }) }) - describe('#getUnapprovedMsgs', function() { - it('returns unapproved Msgs in a hash', function() { + describe('#getUnapprovedMsgs', function () { + it('returns unapproved Msgs in a hash', function () { messageManager.addMsg({ id: '1', status: 'unapproved', metamaskNetworkId: 'unit test' }) messageManager.addMsg({ id: '2', status: 'approved', metamaskNetworkId: 'unit test' }) - let result = messageManager.getUnapprovedMsgs() + const result = messageManager.getUnapprovedMsgs() assert.equal(typeof result, 'object') assert.equal(result['1'].status, 'unapproved') assert.equal(result['2'], undefined) }) }) - describe('#getMsg', function() { - it('returns a Msg with the requested id', function() { + describe('#getMsg', function () { + it('returns a Msg with the requested id', function () { messageManager.addMsg({ id: '1', status: 'unapproved', metamaskNetworkId: 'unit test' }) messageManager.addMsg({ id: '2', status: 'approved', metamaskNetworkId: 'unit test' }) assert.equal(messageManager.getMsg('1').status, 'unapproved') @@ -87,24 +87,23 @@ describe('Personal Message Manager', function() { }) }) - describe('#normalizeMsgData', function() { - it('converts text to a utf8 hex string', function() { + describe('#normalizeMsgData', function () { + it('converts text to a utf8 hex string', function () { var input = 'hello' var output = messageManager.normalizeMsgData(input) assert.equal(output, '0x68656c6c6f', 'predictably hex encoded') }) - it('tolerates a hex prefix', function() { + it('tolerates a hex prefix', function () { var input = '0x12' var output = messageManager.normalizeMsgData(input) assert.equal(output, '0x12', 'un modified') }) - it('tolerates normal hex', function() { + it('tolerates normal hex', function () { var input = '12' var output = messageManager.normalizeMsgData(input) assert.equal(output, '0x12', 'adds prefix') }) }) - }) diff --git a/test/unit/reducers/unlock_vault_test.js b/test/unit/reducers/unlock_vault_test.js index b7540af08..2b7d70b2c 100644 --- a/test/unit/reducers/unlock_vault_test.js +++ b/test/unit/reducers/unlock_vault_test.js @@ -1,32 +1,31 @@ -var jsdom = require('mocha-jsdom') +// var jsdom = require('mocha-jsdom') var assert = require('assert') -var freeze = require('deep-freeze-strict') +// var freeze = require('deep-freeze-strict') var path = require('path') var sinon = require('sinon') var actions = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'actions.js')) -var reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'reducers.js')) +var reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'reducers.js')) -describe('#unlockMetamask(selectedAccount)', function() { - - beforeEach(function() { +describe('#unlockMetamask(selectedAccount)', function () { + beforeEach(function () { // sinon allows stubbing methods that are easily verified this.sinon = sinon.sandbox.create() }) - afterEach(function() { + afterEach(function () { // sinon requires cleanup otherwise it will overwrite context this.sinon.restore() }) - describe('after an error', function() { - it('clears warning', function() { + describe('after an error', function () { + it('clears warning', function () { const warning = 'this is the wrong warning' const account = 'foo_account' const initialState = { appState: { warning: warning, - } + }, } const resultState = reducers(initialState, actions.unlockMetamask(account)) @@ -34,14 +33,14 @@ describe('#unlockMetamask(selectedAccount)', function() { }) }) - describe('going home after an error', function() { - it('clears warning', function() { + describe('going home after an error', function () { + it('clears warning', function () { const warning = 'this is the wrong warning' - const account = 'foo_account' + // const account = 'foo_account' const initialState = { appState: { warning: warning, - } + }, } const resultState = reducers(initialState, actions.goHome()) diff --git a/test/unit/tx-manager-test.js b/test/unit/tx-manager-test.js index 21e94357b..ce4a2acf7 100644 --- a/test/unit/tx-manager-test.js +++ b/test/unit/tx-manager-test.js @@ -1,20 +1,20 @@ const assert = require('assert') -const extend = require('xtend') +// const extend = require('xtend') const EventEmitter = require('events') const ethUtil = require('ethereumjs-util') const EthTx = require('ethereumjs-tx') const ObservableStore = require('obs-store') -const STORAGE_KEY = 'metamask-persistance-key' +// const STORAGE_KEY = 'metamask-persistance-key' const TransactionManager = require('../../app/scripts/transaction-manager') const noop = () => true const currentNetworkId = 42 const otherNetworkId = 36 const privKey = new Buffer('8718b9618a37d1fc78c436511fc6df3c8258d3250635bba617f33003270ec03e', 'hex') -describe('Transaction Manager', function() { +describe('Transaction Manager', function () { let txManager - beforeEach(function() { + beforeEach(function () { txManager = new TransactionManager({ networkStore: new ObservableStore({ network: currentNetworkId }), txHistoryLimit: 10, @@ -22,43 +22,43 @@ describe('Transaction Manager', function() { signTransaction: (ethTx) => new Promise((resolve) => { ethTx.sign(privKey) resolve() - }) + }), }) }) describe('#validateTxParams', function () { - it('returns null for positive values', function() { + it('returns null for positive values', function () { var sample = { - value: '0x01' + value: '0x01', } - var res = txManager.txProviderUtils.validateTxParams(sample, (err) => { + txManager.txProviderUtils.validateTxParams(sample, (err) => { assert.equal(err, null, 'no error') }) }) - it('returns error for negative values', function() { + it('returns error for negative values', function () { var sample = { - value: '-0x01' + value: '-0x01', } - var res = txManager.txProviderUtils.validateTxParams(sample, (err) => { + txManager.txProviderUtils.validateTxParams(sample, (err) => { assert.ok(err, 'error') }) }) }) - describe('#getTxList', function() { - it('when new should return empty array', function() { + describe('#getTxList', function () { + it('when new should return empty array', function () { var result = txManager.getTxList() assert.ok(Array.isArray(result)) assert.equal(result.length, 0) }) - it('should also return transactions from local storage if any', function() { + it('should also return transactions from local storage if any', function () { }) }) - describe('#addTx', function() { - it('adds a tx returned in getTxList', function() { + describe('#addTx', function () { + it('adds a tx returned in getTxList', function () { var tx = { id: 1, status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} } txManager.addTx(tx, noop) var result = txManager.getTxList() @@ -67,7 +67,7 @@ describe('Transaction Manager', function() { assert.equal(result[0].id, 1) }) - it('does not override txs from other networks', function() { + it('does not override txs from other networks', function () { var tx = { id: 1, status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} } var tx2 = { id: 2, status: 'confirmed', metamaskNetworkId: otherNetworkId, txParams: {} } txManager.addTx(tx, noop) @@ -78,10 +78,10 @@ describe('Transaction Manager', function() { assert.equal(result2.length, 1, 'incorrect number of txs on network.') }) - it('cuts off early txs beyond a limit', function() { + it('cuts off early txs beyond a limit', function () { const limit = txManager.txHistoryLimit for (let i = 0; i < limit + 1; i++) { - let tx = { id: i, time: new Date(), status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} } + const tx = { id: i, time: new Date(), status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} } txManager.addTx(tx, noop) } var result = txManager.getTxList() @@ -89,10 +89,10 @@ describe('Transaction Manager', function() { assert.equal(result[0].id, 1, 'early txs truncted') }) - it('cuts off early txs beyond a limit whether or not it is confirmed or rejected', function() { + it('cuts off early txs beyond a limit whether or not it is confirmed or rejected', function () { const limit = txManager.txHistoryLimit for (let i = 0; i < limit + 1; i++) { - let tx = { id: i, time: new Date(), status: 'rejected', metamaskNetworkId: currentNetworkId, txParams: {} } + const tx = { id: i, time: new Date(), status: 'rejected', metamaskNetworkId: currentNetworkId, txParams: {} } txManager.addTx(tx, noop) } var result = txManager.getTxList() @@ -100,12 +100,12 @@ describe('Transaction Manager', function() { assert.equal(result[0].id, 1, 'early txs truncted') }) - it('cuts off early txs beyond a limit but does not cut unapproved txs', function() { + it('cuts off early txs beyond a limit but does not cut unapproved txs', function () { var unconfirmedTx = { id: 0, time: new Date(), status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} } txManager.addTx(unconfirmedTx, noop) const limit = txManager.txHistoryLimit for (let i = 1; i < limit + 1; i++) { - let tx = { id: i, time: new Date(), status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} } + const tx = { id: i, time: new Date(), status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} } txManager.addTx(tx, noop) } var result = txManager.getTxList() @@ -116,8 +116,8 @@ describe('Transaction Manager', function() { }) }) - describe('#setTxStatusSigned', function() { - it('sets the tx status to signed', function() { + describe('#setTxStatusSigned', function () { + it('sets the tx status to signed', function () { var tx = { id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} } txManager.addTx(tx, noop) txManager.setTxStatusSigned(1) @@ -130,7 +130,7 @@ describe('Transaction Manager', function() { it('should emit a signed event to signal the exciton of callback', (done) => { this.timeout(10000) var tx = { id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} } - let noop = function () { + const noop = function () { assert(true, 'event listener has been triggered and noop executed') done() } @@ -140,8 +140,8 @@ describe('Transaction Manager', function() { }) }) - describe('#setTxStatusRejected', function() { - it('sets the tx status to rejected', function() { + describe('#setTxStatusRejected', function () { + it('sets the tx status to rejected', function () { var tx = { id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} } txManager.addTx(tx) txManager.setTxStatusRejected(1) @@ -155,18 +155,17 @@ describe('Transaction Manager', function() { this.timeout(10000) var tx = { id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} } txManager.addTx(tx) - let noop = function (err, txId) { + const noop = function (err, txId) { assert(true, 'event listener has been triggered and noop executed') done() } txManager.on('1:rejected', noop) txManager.setTxStatusRejected(1) }) - }) - describe('#updateTx', function() { - it('replaces the tx with the same id', function() { + describe('#updateTx', function () { + it('replaces the tx with the same id', function () { txManager.addTx({ id: '1', status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }, noop) txManager.addTx({ id: '2', status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} }, noop) txManager.updateTx({ id: '1', status: 'blah', hash: 'foo', metamaskNetworkId: currentNetworkId, txParams: {} }) @@ -175,19 +174,19 @@ describe('Transaction Manager', function() { }) }) - describe('#getUnapprovedTxList', function() { - it('returns unapproved txs in a hash', function() { + describe('#getUnapprovedTxList', function () { + it('returns unapproved txs in a hash', function () { txManager.addTx({ id: '1', status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }, noop) txManager.addTx({ id: '2', status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} }, noop) - let result = txManager.getUnapprovedTxList() + const result = txManager.getUnapprovedTxList() assert.equal(typeof result, 'object') assert.equal(result['1'].status, 'unapproved') assert.equal(result['2'], undefined) }) }) - describe('#getTx', function() { - it('returns a tx with the requested id', function() { + describe('#getTx', function () { + it('returns a tx with the requested id', function () { txManager.addTx({ id: '1', status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }, noop) txManager.addTx({ id: '2', status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} }, noop) assert.equal(txManager.getTx('1').status, 'unapproved') @@ -195,19 +194,19 @@ describe('Transaction Manager', function() { }) }) - describe('#getFilteredTxList', function() { - it('returns a tx with the requested data', function() { - let txMetas = [ + describe('#getFilteredTxList', function () { + it('returns a tx with the requested data', function () { + const txMetas = [ { id: 0, status: 'unapproved', txParams: { from: '0xaa', to: '0xbb' }, metamaskNetworkId: currentNetworkId }, { id: 1, status: 'unapproved', txParams: { from: '0xaa', to: '0xbb' }, metamaskNetworkId: currentNetworkId }, { id: 2, status: 'unapproved', txParams: { from: '0xaa', to: '0xbb' }, metamaskNetworkId: currentNetworkId }, { id: 3, status: 'unapproved', txParams: { from: '0xbb', to: '0xaa' }, metamaskNetworkId: currentNetworkId }, { id: 4, status: 'unapproved', txParams: { from: '0xbb', to: '0xaa' }, metamaskNetworkId: currentNetworkId }, - { id: 5, status: 'confirmed', txParams: { from: '0xaa', to: '0xbb' }, metamaskNetworkId: currentNetworkId }, - { id: 6, status: 'confirmed', txParams: { from: '0xaa', to: '0xbb' }, metamaskNetworkId: currentNetworkId }, - { id: 7, status: 'confirmed', txParams: { from: '0xbb', to: '0xaa' }, metamaskNetworkId: currentNetworkId }, - { id: 8, status: 'confirmed', txParams: { from: '0xbb', to: '0xaa' }, metamaskNetworkId: currentNetworkId }, - { id: 9, status: 'confirmed', txParams: { from: '0xbb', to: '0xaa' }, metamaskNetworkId: currentNetworkId }, + { id: 5, status: 'confirmed', txParams: { from: '0xaa', to: '0xbb' }, metamaskNetworkId: currentNetworkId }, + { id: 6, status: 'confirmed', txParams: { from: '0xaa', to: '0xbb' }, metamaskNetworkId: currentNetworkId }, + { id: 7, status: 'confirmed', txParams: { from: '0xbb', to: '0xaa' }, metamaskNetworkId: currentNetworkId }, + { id: 8, status: 'confirmed', txParams: { from: '0xbb', to: '0xaa' }, metamaskNetworkId: currentNetworkId }, + { id: 9, status: 'confirmed', txParams: { from: '0xbb', to: '0xaa' }, metamaskNetworkId: currentNetworkId }, ] txMetas.forEach((txMeta) => txManager.addTx(txMeta, noop)) let filterParams @@ -227,8 +226,8 @@ describe('Transaction Manager', function() { }) }) - describe('#sign replay-protected tx', function() { - it('prepares a tx with the chainId set', function() { + describe('#sign replay-protected tx', function () { + it('prepares a tx with the chainId set', function () { txManager.addTx({ id: '1', status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }, noop) txManager.signTransaction('1', (err, rawTx) => { if (err) return assert.fail('it should not fail') @@ -237,5 +236,4 @@ describe('Transaction Manager', function() { }) }) }) - }) diff --git a/test/unit/tx-utils-test.js b/test/unit/tx-utils-test.js index 93e9e4134..57d4638a0 100644 --- a/test/unit/tx-utils-test.js +++ b/test/unit/tx-utils-test.js @@ -5,15 +5,15 @@ const BN = ethUtil.BN const TxUtils = require('../../app/scripts/lib/tx-utils') -describe('txUtils', function() { +describe('txUtils', function () { let txUtils - before(function() { + before(function () { txUtils = new TxUtils() }) - describe('chain Id', function() { - it('prepares a transaction with the provided chainId', function() { + describe('chain Id', function () { + it('prepares a transaction with the provided chainId', function () { const txParams = { to: '0x70ad465e0bab6504002ad58c744ed89c7da38524', from: '0x69ad465e0bab6504002ad58c744ed89c7da38525', @@ -29,8 +29,8 @@ describe('txUtils', function() { }) }) - describe('addGasBuffer', function() { - it('multiplies by 1.5, when within block gas limit', function() { + describe('addGasBuffer', function () { + it('multiplies by 1.5, when within block gas limit', function () { // naive estimatedGas: 0x16e360 (1.5 mil) const inputHex = '0x16e360' // dummy gas limit: 0x3d4c52 (4 mil) @@ -41,20 +41,20 @@ describe('txUtils', function() { const expectedBn = inputBn.muln(1.5) assert(outputBn.eq(expectedBn), 'returns 1.5 the input value') }) - - it('uses original estimatedGas, when above block gas limit', function() { + + it('uses original estimatedGas, when above block gas limit', function () { // naive estimatedGas: 0x16e360 (1.5 mil) const inputHex = '0x16e360' // dummy gas limit: 0x0f4240 (1 mil) const blockGasLimitHex = '0x0f4240' const output = txUtils.addGasBuffer(inputHex, blockGasLimitHex) - const inputBn = hexToBn(inputHex) + // const inputBn = hexToBn(inputHex) const outputBn = hexToBn(output) const expectedBn = hexToBn(inputHex) assert(outputBn.eq(expectedBn), 'returns the original estimatedGas value') }) - it('buffers up to reccomend gas limit reccomended ceiling', function() { + it('buffers up to reccomend gas limit reccomended ceiling', function () { // naive estimatedGas: 0x16e360 (1.5 mil) const inputHex = '0x16e360' // dummy gas limit: 0x1e8480 (2 mil) @@ -72,10 +72,10 @@ describe('txUtils', function() { // util -function hexToBn(inputHex) { +function hexToBn (inputHex) { return new BN(ethUtil.stripHexPrefix(inputHex), 16) } -function bnToHex(inputBn) { +function bnToHex (inputBn) { return ethUtil.addHexPrefix(inputBn.toString(16)) -} \ No newline at end of file +} diff --git a/test/unit/util_test.js b/test/unit/util_test.js index 00528b905..3a8b6bdfd 100644 --- a/test/unit/util_test.js +++ b/test/unit/util_test.js @@ -5,96 +5,96 @@ const ethUtil = require('ethereumjs-util') var path = require('path') var util = require(path.join(__dirname, '..', '..', 'ui', 'app', 'util.js')) -describe('util', function() { +describe('util', function () { var ethInWei = '1' - for (var i = 0; i < 18; i++ ) { ethInWei += '0' } + for (var i = 0; i < 18; i++) { ethInWei += '0' } - beforeEach(function() { + beforeEach(function () { this.sinon = sinon.sandbox.create() }) - afterEach(function() { + afterEach(function () { this.sinon.restore() }) - describe('#parseBalance', function() { - it('should render 0.01 eth correctly', function() { + describe('#parseBalance', function () { + it('should render 0.01 eth correctly', function () { const input = '0x2386F26FC10000' const output = util.parseBalance(input) assert.deepEqual(output, ['0', '01']) }) - it('should render 12.023 eth correctly', function() { + it('should render 12.023 eth correctly', function () { const input = 'A6DA46CCA6858000' const output = util.parseBalance(input) assert.deepEqual(output, ['12', '023']) }) - it('should render 0.0000000342422 eth correctly', function() { + it('should render 0.0000000342422 eth correctly', function () { const input = '0x7F8FE81C0' const output = util.parseBalance(input) assert.deepEqual(output, ['0', '0000000342422']) }) - it('should render 0 eth correctly', function() { + it('should render 0 eth correctly', function () { const input = '0x0' const output = util.parseBalance(input) assert.deepEqual(output, ['0', '0']) }) }) - describe('#addressSummary', function() { - it('should add case-sensitive checksum', function() { + describe('#addressSummary', function () { + it('should add case-sensitive checksum', function () { var address = '0xfdea65c8e26263f6d9a1b5de9555d2931a33b825' var result = util.addressSummary(address) assert.equal(result, '0xFDEa65C8...b825') }) - it('should accept arguments for firstseg, lastseg, and keepPrefix', function() { + it('should accept arguments for firstseg, lastseg, and keepPrefix', function () { var address = '0xfdea65c8e26263f6d9a1b5de9555d2931a33b825' var result = util.addressSummary(address, 4, 4, false) assert.equal(result, 'FDEa...b825') }) }) - describe('#isValidAddress', function() { - it('should allow 40-char non-prefixed hex', function() { + describe('#isValidAddress', function () { + it('should allow 40-char non-prefixed hex', function () { var address = 'fdea65c8e26263f6d9a1b5de9555d2931a33b825' var result = util.isValidAddress(address) assert.ok(result) }) - it('should allow 42-char non-prefixed hex', function() { + it('should allow 42-char non-prefixed hex', function () { var address = '0xfdea65c8e26263f6d9a1b5de9555d2931a33b825' var result = util.isValidAddress(address) assert.ok(result) }) - it('should not allow less non hex-prefixed', function() { + it('should not allow less non hex-prefixed', function () { var address = 'fdea65c8e26263f6d9a1b5de9555d2931a33b85' var result = util.isValidAddress(address) assert.ok(!result) }) - it('should not allow less hex-prefixed', function() { + it('should not allow less hex-prefixed', function () { var address = '0xfdea65ce26263f6d9a1b5de9555d2931a33b85' var result = util.isValidAddress(address) assert.ok(!result) }) - it('should recognize correct capitalized checksum', function() { + it('should recognize correct capitalized checksum', function () { var address = '0xFDEa65C8e26263F6d9A1B5de9555D2931A33b825' var result = util.isValidAddress(address) assert.ok(result) }) - it('should recognize incorrect capitalized checksum', function() { + it('should recognize incorrect capitalized checksum', function () { var address = '0xFDea65C8e26263F6d9A1B5de9555D2931A33b825' var result = util.isValidAddress(address) assert.ok(!result) }) - it('should recognize this sample hashed address', function() { + it('should recognize this sample hashed address', function () { const address = '0x5Fda30Bb72B8Dfe20e48A00dFc108d0915BE9Bb0' const result = util.isValidAddress(address) const hashed = ethUtil.toChecksumAddress(address.toLowerCase()) @@ -103,60 +103,57 @@ describe('util', function() { }) }) - describe('#numericBalance', function() { - - it('should return a BN 0 if given nothing', function() { + describe('#numericBalance', function () { + it('should return a BN 0 if given nothing', function () { var result = util.numericBalance() assert.equal(result.toString(10), 0) }) - it('should work with hex prefix', function() { + it('should work with hex prefix', function () { var result = util.numericBalance('0x012') assert.equal(result.toString(10), '18') }) - it('should work with no hex prefix', function() { + it('should work with no hex prefix', function () { var result = util.numericBalance('012') assert.equal(result.toString(10), '18') }) - }) - describe('#formatBalance', function() { - - it('when given nothing', function() { + describe('#formatBalance', function () { + it('when given nothing', function () { var result = util.formatBalance() assert.equal(result, 'None', 'should return "None"') }) - it('should return eth as string followed by ETH', function() { + it('should return eth as string followed by ETH', function () { var input = new ethUtil.BN(ethInWei, 10).toJSON() var result = util.formatBalance(input, 4) assert.equal(result, '1.0000 ETH') }) - it('should return eth as string followed by ETH', function() { + it('should return eth as string followed by ETH', function () { var input = new ethUtil.BN(ethInWei, 10).div(new ethUtil.BN('2', 10)).toJSON() var result = util.formatBalance(input, 3) assert.equal(result, '0.500 ETH') }) - it('should display specified decimal points', function() { - var input = "0x128dfa6a90b28000" + it('should display specified decimal points', function () { + var input = '0x128dfa6a90b28000' var result = util.formatBalance(input, 2) assert.equal(result, '1.33 ETH') }) - it('should default to 3 decimal points', function() { - var input = "0x128dfa6a90b28000" + it('should default to 3 decimal points', function () { + var input = '0x128dfa6a90b28000' var result = util.formatBalance(input) assert.equal(result, '1.337 ETH') }) - it('should show 2 significant digits for tiny balances', function() { - var input = "0x1230fa6a90b28" + it('should show 2 significant digits for tiny balances', function () { + var input = '0x1230fa6a90b28' var result = util.formatBalance(input) assert.equal(result, '0.00032 ETH') }) - it('should not parse the balance and return value with 2 decimal points with ETH at the end', function() { + it('should not parse the balance and return value with 2 decimal points with ETH at the end', function () { var value = '1.2456789' var needsParse = false var result = util.formatBalance(value, 2, needsParse) @@ -164,17 +161,16 @@ describe('util', function() { }) }) - describe('normalizing values', function() { - - describe('#normalizeToWei', function() { - it('should convert an eth to the appropriate equivalent values', function() { + describe('normalizing values', function () { + describe('#normalizeToWei', function () { + it('should convert an eth to the appropriate equivalent values', function () { var valueTable = { - wei: '1000000000000000000', - kwei: '1000000000000000', - mwei: '1000000000000', - gwei: '1000000000', + wei: '1000000000000000000', + kwei: '1000000000000000', + mwei: '1000000000000', + gwei: '1000000000', szabo: '1000000', - finney:'1000', + finney: '1000', ether: '1', // kether:'0.001', // mether:'0.000001', @@ -185,8 +181,7 @@ describe('util', function() { } var oneEthBn = new ethUtil.BN(ethInWei, 10) - for(var currency in valueTable) { - + for (var currency in valueTable) { var value = new ethUtil.BN(valueTable[currency], 10) var output = util.normalizeToWei(value, currency) assert.equal(output.toString(10), valueTable.wei, `value of ${output.toString(10)} ${currency} should convert to ${oneEthBn}`) @@ -194,60 +189,58 @@ describe('util', function() { }) }) - describe('#normalizeEthStringToWei', function() { - it('should convert decimal eth to pure wei BN', function() { + describe('#normalizeEthStringToWei', function () { + it('should convert decimal eth to pure wei BN', function () { var input = '1.23456789' var output = util.normalizeEthStringToWei(input) assert.equal(output.toString(10), '1234567890000000000') }) - it('should convert 1 to expected wei', function() { + it('should convert 1 to expected wei', function () { var input = '1' var output = util.normalizeEthStringToWei(input) assert.equal(output.toString(10), ethInWei) }) }) - describe('#normalizeNumberToWei', function() { - - it('should handle a simple use case', function() { + describe('#normalizeNumberToWei', function () { + it('should handle a simple use case', function () { var input = 0.0002 var output = util.normalizeNumberToWei(input, 'ether') var str = output.toString(10) assert.equal(str, '200000000000000') }) - it('should convert a kwei number to the appropriate equivalent wei', function() { + it('should convert a kwei number to the appropriate equivalent wei', function () { var result = util.normalizeNumberToWei(1.111, 'kwei') assert.equal(result.toString(10), '1111', 'accepts decimals') }) - it('should convert a ether number to the appropriate equivalent wei', function() { + it('should convert a ether number to the appropriate equivalent wei', function () { var result = util.normalizeNumberToWei(1.111, 'ether') assert.equal(result.toString(10), '1111000000000000000', 'accepts decimals') }) }) - describe('#isHex', function(){ - it('should return true when given a hex string', function() { + describe('#isHex', function () { + it('should return true when given a hex string', function () { var result = util.isHex('c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2') assert(result) }) - it('should return false when given a non-hex string', function() { + it('should return false when given a non-hex string', function () { var result = util.isHex('c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714imnotreal') assert(!result) }) - it('should return false when given a string containing a non letter/number character', function() { + it('should return false when given a string containing a non letter/number character', function () { var result = util.isHex('c3ab8ff13720!8ad9047dd39466b3c%8974e592c2fa383d4a396071imnotreal') assert(!result) }) - it('should return true when given a hex string with hex-prefix', function() { + it('should return true when given a hex string with hex-prefix', function () { var result = util.isHex('0xc3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2') assert(result) }) - }) }) }) From 9bd7d06c4f3aab94308335f2e13c01bcca88eb4b Mon Sep 17 00:00:00 2001 From: Thomas Huang Date: Thu, 4 May 2017 15:06:27 -0700 Subject: [PATCH 200/372] Remove unused modules and STORAGE_KEY --- test/lib/mock-config-manager.js | 1 - test/unit/currency-controller-test.js | 2 -- test/unit/keyring-controller-test.js | 2 -- test/unit/message-manager-test.js | 3 --- test/unit/metamask-controller-test.js | 2 -- test/unit/notice-controller-test.js | 4 ---- 6 files changed, 14 deletions(-) diff --git a/test/lib/mock-config-manager.js b/test/lib/mock-config-manager.js index 3238d3501..0cc6953bb 100644 --- a/test/lib/mock-config-manager.js +++ b/test/lib/mock-config-manager.js @@ -2,7 +2,6 @@ const ObservableStore = require('obs-store') const clone = require('clone') const ConfigManager = require('../../app/scripts/lib/config-manager') const firstTimeState = require('../../app/scripts/first-time-state') -// const STORAGE_KEY = 'metamask-config' module.exports = function () { const store = new ObservableStore(clone(firstTimeState)) diff --git a/test/unit/currency-controller-test.js b/test/unit/currency-controller-test.js index 40868912c..cfbce7fb3 100644 --- a/test/unit/currency-controller-test.js +++ b/test/unit/currency-controller-test.js @@ -2,8 +2,6 @@ global.fetch = global.fetch || require('isomorphic-fetch') const assert = require('assert') -// const extend = require('xtend') -// const rp = require('request-promise') const nock = require('nock') const CurrencyController = require('../../app/scripts/controllers/currency') diff --git a/test/unit/keyring-controller-test.js b/test/unit/keyring-controller-test.js index f7e2ec89d..2d9a53723 100644 --- a/test/unit/keyring-controller-test.js +++ b/test/unit/keyring-controller-test.js @@ -3,9 +3,7 @@ const KeyringController = require('../../app/scripts/keyring-controller') const configManagerGen = require('../lib/mock-config-manager') const ethUtil = require('ethereumjs-util') const BN = ethUtil.BN -// const async = require('async') const mockEncryptor = require('../lib/mock-encryptor') -// const MockSimpleKeychain = require('../lib/mock-simple-keychain') const sinon = require('sinon') describe('KeyringController', function () { diff --git a/test/unit/message-manager-test.js b/test/unit/message-manager-test.js index 44190c8d0..30cb4f067 100644 --- a/test/unit/message-manager-test.js +++ b/test/unit/message-manager-test.js @@ -1,7 +1,4 @@ const assert = require('assert') -// const extend = require('xtend') -// const EventEmitter = require('events') - const MessageManger = require('../../app/scripts/lib/message-manager') describe('Transaction Manager', function () { diff --git a/test/unit/metamask-controller-test.js b/test/unit/metamask-controller-test.js index ac588b313..5ee0a6c84 100644 --- a/test/unit/metamask-controller-test.js +++ b/test/unit/metamask-controller-test.js @@ -4,8 +4,6 @@ const clone = require('clone') const MetaMaskController = require('../../app/scripts/metamask-controller') const firstTimeState = require('../../app/scripts/first-time-state') -// const STORAGE_KEY = 'metamask-config' - describe('MetaMaskController', function () { const noop = () => {} const metamaskController = new MetaMaskController({ diff --git a/test/unit/notice-controller-test.js b/test/unit/notice-controller-test.js index 7eef615d5..09eeda15c 100644 --- a/test/unit/notice-controller-test.js +++ b/test/unit/notice-controller-test.js @@ -1,10 +1,6 @@ const assert = require('assert') -// const extend = require('xtend') -// const rp = require('request-promise') -// const nock = require('nock') const configManagerGen = require('../lib/mock-config-manager') const NoticeController = require('../../app/scripts/notice-controller') -// const STORAGE_KEY = 'metamask-persistence-key' describe('notice-controller', function () { var noticeController From 1e4855fc0e2e89ebe6b50ff8d34762f742433110 Mon Sep 17 00:00:00 2001 From: Thomas Huang Date: Thu, 4 May 2017 15:21:51 -0700 Subject: [PATCH 201/372] Whoops missed some modules --- test/unit/address-book-controller.js | 1 - test/unit/config-manager-test.js | 3 --- test/unit/personal-message-manager-test.js | 2 -- test/unit/tx-manager-test.js | 2 -- 4 files changed, 8 deletions(-) diff --git a/test/unit/address-book-controller.js b/test/unit/address-book-controller.js index 85deb8905..655c9022c 100644 --- a/test/unit/address-book-controller.js +++ b/test/unit/address-book-controller.js @@ -1,5 +1,4 @@ const assert = require('assert') -// const extend = require('xtend') const AddressBookController = require('../../app/scripts/controllers/address-book') const mockKeyringController = { diff --git a/test/unit/config-manager-test.js b/test/unit/config-manager-test.js index eeb193e91..b710e2dfb 100644 --- a/test/unit/config-manager-test.js +++ b/test/unit/config-manager-test.js @@ -2,9 +2,6 @@ global.fetch = global.fetch || require('isomorphic-fetch') const assert = require('assert') -// const extend = require('xtend') -// const rp = require('request-promise') -// const nock = require('nock') const configManagerGen = require('../lib/mock-config-manager') describe('config-manager', function () { diff --git a/test/unit/personal-message-manager-test.js b/test/unit/personal-message-manager-test.js index b7a29b7b0..ec2f9a4d1 100644 --- a/test/unit/personal-message-manager-test.js +++ b/test/unit/personal-message-manager-test.js @@ -1,6 +1,4 @@ const assert = require('assert') -// const extend = require('xtend') -// const EventEmitter = require('events') const PersonalMessageManager = require('../../app/scripts/lib/personal-message-manager') diff --git a/test/unit/tx-manager-test.js b/test/unit/tx-manager-test.js index ce4a2acf7..b5d148723 100644 --- a/test/unit/tx-manager-test.js +++ b/test/unit/tx-manager-test.js @@ -1,10 +1,8 @@ const assert = require('assert') -// const extend = require('xtend') const EventEmitter = require('events') const ethUtil = require('ethereumjs-util') const EthTx = require('ethereumjs-tx') const ObservableStore = require('obs-store') -// const STORAGE_KEY = 'metamask-persistance-key' const TransactionManager = require('../../app/scripts/transaction-manager') const noop = () => true const currentNetworkId = 42 From 10ba760ed32be6e23186bd9f9a025e28bd757042 Mon Sep 17 00:00:00 2001 From: kumavis Date: Thu, 4 May 2017 17:50:59 -0700 Subject: [PATCH 202/372] metamask - selected accounts - dont reveal when locked --- app/scripts/metamask-controller.js | 32 ++++++++++++++++-------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 2e4bf07e1..497b661d4 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -4,7 +4,6 @@ const promiseToCallback = require('promise-to-callback') const pipe = require('pump') const Dnode = require('dnode') const ObservableStore = require('obs-store') -const storeTransform = require('obs-store/lib/transform') const EthStore = require('./lib/eth-store') const EthQuery = require('eth-query') const streamIntoProvider = require('web3-stream-provider/handler') @@ -169,8 +168,13 @@ module.exports = class MetamaskController extends EventEmitter { rpcUrl: this.configManager.getCurrentRpcAddress(), // account mgmt getAccounts: (cb) => { + const isUnlocked = this.keyringController.memStore.getState().isUnlocked + const result = [] const selectedAddress = this.preferencesController.getSelectedAddress() - const result = selectedAddress ? [selectedAddress] : [] + // only show address if account is unlocked + if (isUnlocked && selectedAddress) { + result.push(selectedAddress) + } cb(null, result) }, // tx signing @@ -186,21 +190,19 @@ module.exports = class MetamaskController extends EventEmitter { initPublicConfigStore () { // get init state - const publicConfigStore = new ObservableStore() + const publicConfigStore = new ObservableStore(this.store.getState()) - // sync publicConfigStore with transform - pipe( - this.store, - storeTransform(selectPublicState.bind(this)), - publicConfigStore - ) + // memStore -> transform -> publicConfigStore + this.on('update', (memState) => { + const publicState = selectPublicState(memState) + publicConfigStore.putState(publicState) + }) - function selectPublicState (state) { - const result = { selectedAddress: undefined } - try { - result.selectedAddress = state.PreferencesController.selectedAddress - result.networkVersion = this.getNetworkState() - } catch (_) {} + function selectPublicState (memState) { + const result = { + selectedAddress: memState.isUnlocked ? memState.selectedAddress : undefined, + networkVersion: memState.network, + } return result } From fb08c4a1316248485710a277d397fb5d4f395231 Mon Sep 17 00:00:00 2001 From: kumavis Date: Thu, 4 May 2017 17:56:30 -0700 Subject: [PATCH 203/372] metamask - publicConfig - fix init state --- app/scripts/metamask-controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 497b661d4..175602ec1 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -190,7 +190,7 @@ module.exports = class MetamaskController extends EventEmitter { initPublicConfigStore () { // get init state - const publicConfigStore = new ObservableStore(this.store.getState()) + const publicConfigStore = new ObservableStore() // memStore -> transform -> publicConfigStore this.on('update', (memState) => { From 998128d4dfd246ecf7bc206536f6143da4c97bbd Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Sun, 7 May 2017 15:20:56 -0700 Subject: [PATCH 204/372] allow copy(logState()) to copy to clipboard --- ui/app/reducers.js | 1 + 1 file changed, 1 insertion(+) diff --git a/ui/app/reducers.js b/ui/app/reducers.js index c656af849..11efca529 100644 --- a/ui/app/reducers.js +++ b/ui/app/reducers.js @@ -44,6 +44,7 @@ function rootReducer (state, action) { window.logState = function () { var stateString = JSON.stringify(window.METAMASK_CACHED_LOG_STATE, removeSeedWords, 2) console.log(stateString) + return stateString } function removeSeedWords (key, value) { From a09dca68a456ba0406b9d88556e44f9a1e0f8c57 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Sun, 7 May 2017 16:44:23 -0700 Subject: [PATCH 205/372] Fix changelog formatting --- CHANGELOG.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d552e6ab8..627f13aea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,8 +5,6 @@ ## 3.6.1 2017-4-30 - Made fox less nosy. - - - Fix bug where error was reported in debugger console when Chrome opened a new window. - Fix bug where block-tracker could stop polling for new blocks. From f17c6b4eef8defe55e316ee782b499072e1e795a Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Sun, 7 May 2017 16:44:43 -0700 Subject: [PATCH 206/372] Fix ens iterated element without key error --- ui/app/components/ens-input.js | 1 + 1 file changed, 1 insertion(+) diff --git a/ui/app/components/ens-input.js b/ui/app/components/ens-input.js index f1cf49998..001c227c4 100644 --- a/ui/app/components/ens-input.js +++ b/ui/app/components/ens-input.js @@ -63,6 +63,7 @@ EnsInput.prototype.render = function () { return h('option', { value: identity.address, label: identity.name, + key: identity.address, }) }), ]), From 80d8a4e73ef2d55dd9024f6e4f8cf94f263703cc Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Sun, 7 May 2017 16:51:57 -0700 Subject: [PATCH 207/372] Input gas in gwei Also enforces "safe low gas" minimum recommended by this article by eth-gas-station: https://medium.com/@ethgasstation/the-safe-low-gas-price-fb44fdc85b91 Fixes #1381 --- ui/app/components/pending-tx.js | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/ui/app/components/pending-tx.js b/ui/app/components/pending-tx.js index 4b28ae099..c381066a9 100644 --- a/ui/app/components/pending-tx.js +++ b/ui/app/components/pending-tx.js @@ -15,7 +15,9 @@ const addressSummary = util.addressSummary const nameForAddress = require('../../lib/contract-namer') const HexInput = require('./hex-as-decimal-input') -const MIN_GAS_PRICE_BN = new BN(20000000) +const MIN_GAS_PRICE_GWEI_BN = new BN(2) +const GWEI_FACTOR = new BN(Math.pow(10, 9)) +const MIN_GAS_PRICE_BN = MIN_GAS_PRICE_GWEI_BN.mul(GWEI_FACTOR) const MIN_GAS_LIMIT_BN = new BN(21000) module.exports = connect(mapStateToProps)(PendingTx) @@ -39,16 +41,20 @@ PendingTx.prototype.render = function () { const txMeta = this.gatherTxMeta() const txParams = txMeta.txParams || {} + // Account Details const address = txParams.from || props.selectedAddress const identity = props.identities[address] || { address: address } const account = props.accounts[address] const balance = account ? account.balance : '0x0' + // Gas const gas = txParams.gas - const gasPrice = txParams.gasPrice - const gasBn = hexToBn(gas) + + // Gas Price + const gasPrice = txParams.gasPrice || MIN_GAS_PRICE_BN.toString(16) const gasPriceBn = hexToBn(gasPrice) + const gasPriceGweiBn = gasPriceBn.div(new BN(Math.pow(10, 9))) const txFeeBn = gasBn.mul(gasPriceBn) const valueBn = hexToBn(txParams.value) @@ -187,17 +193,18 @@ PendingTx.prototype.render = function () { }, [ h(HexInput, { name: 'Gas Price', - value: gasPrice, - suffix: 'WEI', - min: MIN_GAS_PRICE_BN.toString(10), + value: gasPriceGweiBn.toString(16), + suffix: 'GWEI', + min: MIN_GAS_PRICE_GWEI_BN.toString(10), style: { position: 'relative', top: '5px', }, onChange: (newHex) => { log.info(`Gas price changed to: ${newHex}`) + const inWei = hexToBn(newHex).mul(GWEI_FACTOR) const txMeta = this.gatherTxMeta() - txMeta.txParams.gasPrice = newHex + txMeta.txParams.gasPrice = inWei.toString(16) this.setState({ txData: txMeta }) }, ref: (hexInput) => { this.inputs.push(hexInput) }, From 17accd3a200109b286fda0920b1a73edeeedfaa2 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Sun, 7 May 2017 16:53:14 -0700 Subject: [PATCH 208/372] Bump changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 627f13aea..8a57cc217 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## Current Master +- Input gas price in Gwei. +- Enforce Safe Gas Minimum recommended by EthGasStation. + ## 3.6.1 2017-4-30 - Made fox less nosy. From 0d39de6d66aab2b87ea7415cb5451ac61c055fd8 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Mon, 8 May 2017 09:53:30 -0700 Subject: [PATCH 209/372] Run install before dist --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6e83b3560..8e63c2467 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "start": "npm run dev", "dev": "gulp dev --debug", "disc": "gulp disc --debug", - "dist": "gulp dist --disableLiveReload", + "dist": "npm install && gulp dist --disableLiveReload", "test": "npm run lint && npm run test-unit && npm run test-integration", "test-unit": "METAMASK_ENV=test mocha --require test/helper.js --recursive \"test/unit/**/*.js\"", "test-integration": "npm run buildMock && npm run buildCiUnits && testem ci -P 2", From 469648239fc94a6599a0b2483364a8a74d66e5a1 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Mon, 8 May 2017 09:55:14 -0700 Subject: [PATCH 210/372] Linted --- app/scripts/lib/config-manager.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/scripts/lib/config-manager.js b/app/scripts/lib/config-manager.js index 340ad4292..ab9410842 100644 --- a/app/scripts/lib/config-manager.js +++ b/app/scripts/lib/config-manager.js @@ -155,7 +155,7 @@ ConfigManager.prototype.getCurrentRpcAddress = function () { case 'kovan': return KOVAN_RPC - + case 'rinkeby': return RINKEBY_RPC From d7a2d29e6c6fadae5fa7f94d1bd2762560534463 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Mon, 8 May 2017 12:04:57 -0700 Subject: [PATCH 211/372] fix block polling changelog note --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a57cc217..c94799a8a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,12 +4,12 @@ - Input gas price in Gwei. - Enforce Safe Gas Minimum recommended by EthGasStation. +- Fix bug where block-tracker could stop polling for new blocks. ## 3.6.1 2017-4-30 - Made fox less nosy. - Fix bug where error was reported in debugger console when Chrome opened a new window. -- Fix bug where block-tracker could stop polling for new blocks. ## 3.6.0 2017-4-26 From 68be86abe959afd26ea50e0833c552c442bfce51 Mon Sep 17 00:00:00 2001 From: kumavis Date: Mon, 8 May 2017 12:29:08 -0700 Subject: [PATCH 212/372] ui - remove web3, use eth-query --- app/scripts/popup-core.js | 5 +++-- ui/app/actions.js | 2 +- ui/app/components/ens-input.js | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/app/scripts/popup-core.js b/app/scripts/popup-core.js index f9ac4d052..f1eb394d7 100644 --- a/app/scripts/popup-core.js +++ b/app/scripts/popup-core.js @@ -1,7 +1,7 @@ const EventEmitter = require('events').EventEmitter const async = require('async') const Dnode = require('dnode') -const Web3 = require('web3') +const EthQuery = require('eth-query') const launchMetamaskUi = require('../../ui') const StreamProvider = require('web3-stream-provider') const setupMultiplex = require('./lib/stream-utils.js').setupMultiplex @@ -32,7 +32,8 @@ function setupWeb3Connection (connectionStream) { providerStream.pipe(connectionStream).pipe(providerStream) connectionStream.on('error', console.error.bind(console)) providerStream.on('error', console.error.bind(console)) - global.web3 = new Web3(providerStream) + global.ethereumProvider = providerStream + global.ethQuery = new EthQuery(providerStream) } function setupControllerConnection (connectionStream, cb) { diff --git a/ui/app/actions.js b/ui/app/actions.js index c15c9be7e..1a3557cb4 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -393,7 +393,7 @@ function signPersonalMsg (msgData) { function signTx (txData) { return (dispatch) => { - web3.eth.sendTransaction(txData, (err, data) => { + global.ethQuery.sendTransaction(txData, (err, data) => { dispatch(actions.hideLoadingIndication()) if (err) return dispatch(actions.displayWarning(err.message)) dispatch(actions.hideWarning()) diff --git a/ui/app/components/ens-input.js b/ui/app/components/ens-input.js index f1cf49998..0457bde2e 100644 --- a/ui/app/components/ens-input.js +++ b/ui/app/components/ens-input.js @@ -75,7 +75,7 @@ EnsInput.prototype.componentDidMount = function () { const resolverAddress = networkResolvers[network] if (resolverAddress) { - const provider = web3.currentProvider + const provider = global.ethereumProvider this.ens = new ENS({ provider, network }) this.checkName = debounce(this.lookupEnsName.bind(this), 200) } From 21b6a1b478032fa51505c1be517587515fe8bd6c Mon Sep 17 00:00:00 2001 From: kumavis Date: Mon, 8 May 2017 12:29:38 -0700 Subject: [PATCH 213/372] deps - bump eth-query for smaller bundle size --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ee7ba7aab..1ec784a69 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,7 @@ "ensnare": "^1.0.0", "eth-bin-to-ops": "^1.0.1", "eth-hd-keyring": "^1.1.1", - "eth-query": "^1.0.3", + "eth-query": "^2.1.1", "eth-sig-util": "^1.1.1", "eth-simple-keyring": "^1.1.1", "ethereumjs-tx": "^1.3.0", From 05000683277cd3eb2dec9ce656fbfddb1e48f0b2 Mon Sep 17 00:00:00 2001 From: kumavis Date: Mon, 8 May 2017 12:30:47 -0700 Subject: [PATCH 214/372] build - fix disc task --- gulpfile.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gulpfile.js b/gulpfile.js index 21b925780..9f4a606be 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -182,7 +182,7 @@ gulp.task('build:js', gulp.parallel(...jsBuildStrings)) // disc bundle analyzer tasks jsFiles.forEach((jsFile) => { - gulp.task(`disc:${jsFile}`, bundleTask({ label: jsFile, filename: `${jsFile}.js` })) + gulp.task(`disc:${jsFile}`, discTask({ label: jsFile, filename: `${jsFile}.js` })) }) gulp.task('disc', gulp.parallel(jsFiles.map(jsFile => `disc:${jsFile}`))) From e921f7f13a81bbf2e10fb996a001f16daf944940 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Mon, 8 May 2017 13:32:45 -0700 Subject: [PATCH 215/372] Add changelog entry for 1390 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c94799a8a..157c4186a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - Input gas price in Gwei. - Enforce Safe Gas Minimum recommended by EthGasStation. - Fix bug where block-tracker could stop polling for new blocks. +- Reduce UI size by removing internal web3. ## 3.6.1 2017-4-30 From c7b2f2f2e986981496168dbf57cee787882ffd59 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Mon, 8 May 2017 13:34:01 -0700 Subject: [PATCH 216/372] Cleanup --- ui/app/components/pending-tx.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/app/components/pending-tx.js b/ui/app/components/pending-tx.js index c381066a9..71d4d767a 100644 --- a/ui/app/components/pending-tx.js +++ b/ui/app/components/pending-tx.js @@ -16,7 +16,7 @@ const nameForAddress = require('../../lib/contract-namer') const HexInput = require('./hex-as-decimal-input') const MIN_GAS_PRICE_GWEI_BN = new BN(2) -const GWEI_FACTOR = new BN(Math.pow(10, 9)) +const GWEI_FACTOR = new BN(1e9) const MIN_GAS_PRICE_BN = MIN_GAS_PRICE_GWEI_BN.mul(GWEI_FACTOR) const MIN_GAS_LIMIT_BN = new BN(21000) @@ -54,7 +54,7 @@ PendingTx.prototype.render = function () { // Gas Price const gasPrice = txParams.gasPrice || MIN_GAS_PRICE_BN.toString(16) const gasPriceBn = hexToBn(gasPrice) - const gasPriceGweiBn = gasPriceBn.div(new BN(Math.pow(10, 9))) + const gasPriceGweiBn = gasPriceBn.div(GWEI_FACTOR) const txFeeBn = gasBn.mul(gasPriceBn) const valueBn = hexToBn(txParams.value) From aa1139762c500428406b1ef725eb72280cf74995 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Mon, 8 May 2017 13:54:25 -0700 Subject: [PATCH 217/372] Add new beta notice. --- notices/archive/notice_2.md | 8 ++++++++ notices/notice-nonce.json | 2 +- notices/notices.json | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) create mode 100644 notices/archive/notice_2.md diff --git a/notices/archive/notice_2.md b/notices/archive/notice_2.md new file mode 100644 index 000000000..76e9bd8fb --- /dev/null +++ b/notices/archive/notice_2.md @@ -0,0 +1,8 @@ +MetaMask is beta software. + +When you log in to MetaMask, your current account is visible to every new site you visit. + +For your privacy, for now, please sign out of MetaMask when you're done using a site. + +Also, by default, you will be signed in to a test network. To use real Ether, you must connect to the main network manually in the top left network menu. + diff --git a/notices/notice-nonce.json b/notices/notice-nonce.json index d8263ee98..e440e5c84 100644 --- a/notices/notice-nonce.json +++ b/notices/notice-nonce.json @@ -1 +1 @@ -2 \ No newline at end of file +3 \ No newline at end of file diff --git a/notices/notices.json b/notices/notices.json index 9f28b32a6..e7f74c925 100644 --- a/notices/notices.json +++ b/notices/notices.json @@ -1 +1 @@ -[{"read":false,"date":"Thu Feb 09 2017","title":"Terms of Use","body":"# Terms of Use #\n\n**THIS AGREEMENT IS SUBJECT TO BINDING ARBITRATION AND A WAIVER OF CLASS ACTION RIGHTS AS DETAILED IN SECTION 13. PLEASE READ THE AGREEMENT CAREFULLY.**\n\n_Our Terms of Use have been updated as of September 5, 2016_\n\n## 1. Acceptance of Terms ##\n\nMetaMask provides a platform for managing Ethereum (or \"ETH\") accounts, and allowing ordinary websites to interact with the Ethereum blockchain, while keeping the user in control over what transactions they approve, through our website located at[ ](http://metamask.io)[https://metamask.io/](https://metamask.io/) and browser plugin (the \"Site\") — which includes text, images, audio, code and other materials (collectively, the “Content”) and all of the features, and services provided. The Site, and any other features, tools, materials, or other services offered from time to time by MetaMask are referred to here as the “Service.” Please read these Terms of Use (the “Terms” or “Terms of Use”) carefully before using the Service. By using or otherwise accessing the Services, or clicking to accept or agree to these Terms where that option is made available, you (1) accept and agree to these Terms (2) consent to the collection, use, disclosure and other handling of information as described in our Privacy Policy and (3) any additional terms, rules and conditions of participation issued by MetaMask from time to time. If you do not agree to the Terms, then you may not access or use the Content or Services.\n\n## 2. Modification of Terms of Use ##\n\nExcept for Section 13, providing for binding arbitration and waiver of class action rights, MetaMask reserves the right, at its sole discretion, to modify or replace the Terms of Use at any time. The most current version of these Terms will be posted on our Site. You shall be responsible for reviewing and becoming familiar with any such modifications. Use of the Services by you after any modification to the Terms constitutes your acceptance of the Terms of Use as modified.\n\n\n\n## 3. Eligibility ##\n\nYou hereby represent and warrant that you are fully able and competent to enter into the terms, conditions, obligations, affirmations, representations and warranties set forth in these Terms and to abide by and comply with these Terms.\n\nMetaMask is a global platform and by accessing the Content or Services, you are representing and warranting that, you are of the legal age of majority in your jurisdiction as is required to access such Services and Content and enter into arrangements as provided by the Service. You further represent that you are otherwise legally permitted to use the service in your jurisdiction including owning cryptographic tokens of value, and interacting with the Services or Content in any way. You further represent you are responsible for ensuring compliance with the laws of your jurisdiction and acknowledge that MetaMask is not liable for your compliance with such laws.\n\n## 4 Account Password and Security ##\n\nWhen setting up an account within MetaMask, you will be responsible for keeping your own account secrets, which may be a twelve-word seed phrase, an account file, or other locally stored secret information. MetaMask encrypts this information locally with a password you provide, that we never send to our servers. You agree to (a) never use the same password for MetaMask that you have ever used outside of this service; (b) keep your secret information and password confidential and do not share them with anyone else; (c) immediately notify MetaMask of any unauthorized use of your account or breach of security. MetaMask cannot and will not be liable for any loss or damage arising from your failure to comply with this section.\n\n## 5. Representations, Warranties, and Risks ##\n\n### 5.1. Warranty Disclaimer ###\n\nYou expressly understand and agree that your use of the Service is at your sole risk. The Service (including the Service and the Content) are provided on an \"AS IS\" and \"as available\" basis, without warranties of any kind, either express or implied, including, without limitation, implied warranties of merchantability, fitness for a particular purpose or non-infringement. You acknowledge that MetaMask has no control over, and no duty to take any action regarding: which users gain access to or use the Service; what effects the Content may have on you; how you may interpret or use the Content; or what actions you may take as a result of having been exposed to the Content. You release MetaMask from all liability for you having acquired or not acquired Content through the Service. MetaMask makes no representations concerning any Content contained in or accessed through the Service, and MetaMask will not be responsible or liable for the accuracy, copyright compliance, legality or decency of material contained in or accessed through the Service.\n\n### 5.2 Sophistication and Risk of Cryptographic Systems ###\n\nBy utilizing the Service or interacting with the Content or platform in any way, you represent that you understand the inherent risks associated with cryptographic systems; and warrant that you have an understanding of the usage and intricacies of native cryptographic tokens, like Ether (ETH) and Bitcoin (BTC), smart contract based tokens such as those that follow the Ethereum Token Standard (https://github.com/ethereum/EIPs/issues/20), and blockchain-based software systems.\n\n### 5.3 Risk of Regulatory Actions in One or More Jurisdictions ###\n\nMetaMask and ETH could be impacted by one or more regulatory inquiries or regulatory action, which could impede or limit the ability of MetaMask to continue to develop, or which could impede or limit your ability to access or use the Service or Ethereum blockchain.\n\n### 5.4 Risk of Weaknesses or Exploits in the Field of Cryptography ###\n\nYou acknowledge and understand that Cryptography is a progressing field. Advances in code cracking or technical advances such as the development of quantum computers may present risks to cryptocurrencies and Services of Content, which could result in the theft or loss of your cryptographic tokens or property. To the extent possible, MetaMask intends to update the protocol underlying Services to account for any advances in cryptography and to incorporate additional security measures, but does not guarantee or otherwise represent full security of the system. By using the Service or accessing Content, you acknowledge these inherent risks.\n\n### 5.5 Volatility of Crypto Currencies ###\n\nYou understand that Ethereum and other blockchain technologies and associated currencies or tokens are highly volatile due to many factors including but not limited to adoption, speculation, technology and security risks. You also acknowledge that the cost of transacting on such technologies is variable and may increase at any time causing impact to any activities taking place on the Ethereum blockchain. You acknowledge these risks and represent that MetaMask cannot be held liable for such fluctuations or increased costs.\n\n### 5.6 Application Security ###\n\nYou acknowledge that Ethereum applications are code subject to flaws and acknowledge that you are solely responsible for evaluating any code provided by the Services or Content and the trustworthiness of any third-party websites, products, smart-contracts, or Content you access or use through the Service. You further expressly acknowledge and represent that Ethereum applications can be written maliciously or negligently, that MetaMask cannot be held liable for your interaction with such applications and that such applications may cause the loss of property or even identity. This warning and others later provided by MetaMask in no way evidence or represent an on-going duty to alert you to all of the potential risks of utilizing the Service or Content.\n\n## 6. Indemnity ##\n\nYou agree to release and to indemnify, defend and hold harmless MetaMask and its parents, subsidiaries, affiliates and agencies, as well as the officers, directors, employees, shareholders and representatives of any of the foregoing entities, from and against any and all losses, liabilities, expenses, damages, costs (including attorneys’ fees and court costs) claims or actions of any kind whatsoever arising or resulting from your use of the Service, your violation of these Terms of Use, and any of your acts or omissions that implicate publicity rights, defamation or invasion of privacy. MetaMask reserves the right, at its own expense, to assume exclusive defense and control of any matter otherwise subject to indemnification by you and, in such case, you agree to cooperate with MetaMask in the defense of such matter.\n\n## 7. Limitation on liability ##\n\nYOU ACKNOWLEDGE AND AGREE THAT YOU ASSUME FULL RESPONSIBILITY FOR YOUR USE OF THE SITE AND SERVICE. YOU ACKNOWLEDGE AND AGREE THAT ANY INFORMATION YOU SEND OR RECEIVE DURING YOUR USE OF THE SITE AND SERVICE MAY NOT BE SECURE AND MAY BE INTERCEPTED OR LATER ACQUIRED BY UNAUTHORIZED PARTIES. YOU ACKNOWLEDGE AND AGREE THAT YOUR USE OF THE SITE AND SERVICE IS AT YOUR OWN RISK. RECOGNIZING SUCH, YOU UNDERSTAND AND AGREE THAT, TO THE FULLEST EXTENT PERMITTED BY APPLICABLE LAW, NEITHER METAMASK NOR ITS SUPPLIERS OR LICENSORS WILL BE LIABLE TO YOU FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY OR OTHER DAMAGES OF ANY KIND, INCLUDING WITHOUT LIMITATION DAMAGES FOR LOSS OF PROFITS, GOODWILL, USE, DATA OR OTHER TANGIBLE OR INTANGIBLE LOSSES OR ANY OTHER DAMAGES BASED ON CONTRACT, TORT, STRICT LIABILITY OR ANY OTHER THEORY (EVEN IF METAMASK HAD BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES), RESULTING FROM THE SITE OR SERVICE; THE USE OR THE INABILITY TO USE THE SITE OR SERVICE; UNAUTHORIZED ACCESS TO OR ALTERATION OF YOUR TRANSMISSIONS OR DATA; STATEMENTS OR CONDUCT OF ANY THIRD PARTY ON THE SITE OR SERVICE; ANY ACTIONS WE TAKE OR FAIL TO TAKE AS A RESULT OF COMMUNICATIONS YOU SEND TO US; HUMAN ERRORS; TECHNICAL MALFUNCTIONS; FAILURES, INCLUDING PUBLIC UTILITY OR TELEPHONE OUTAGES; OMISSIONS, INTERRUPTIONS, LATENCY, DELETIONS OR DEFECTS OF ANY DEVICE OR NETWORK, PROVIDERS, OR SOFTWARE (INCLUDING, BUT NOT LIMITED TO, THOSE THAT DO NOT PERMIT PARTICIPATION IN THE SERVICE); ANY INJURY OR DAMAGE TO COMPUTER EQUIPMENT; INABILITY TO FULLY ACCESS THE SITE OR SERVICE OR ANY OTHER WEBSITE; THEFT, TAMPERING, DESTRUCTION, OR UNAUTHORIZED ACCESS TO, IMAGES OR OTHER CONTENT OF ANY KIND; DATA THAT IS PROCESSED LATE OR INCORRECTLY OR IS INCOMPLETE OR LOST; TYPOGRAPHICAL, PRINTING OR OTHER ERRORS, OR ANY COMBINATION THEREOF; OR ANY OTHER MATTER RELATING TO THE SITE OR SERVICE.\n\nSOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF CERTAIN WARRANTIES OR THE LIMITATION OR EXCLUSION OF LIABILITY FOR INCIDENTAL OR CONSEQUENTIAL DAMAGES. ACCORDINGLY, SOME OF THE ABOVE LIMITATIONS MAY NOT APPLY TO YOU.\n\n## 8. Our Proprietary Rights ##\n\nAll title, ownership and intellectual property rights in and to the Service are owned by MetaMask or its licensors. You acknowledge and agree that the Service contains proprietary and confidential information that is protected by applicable intellectual property and other laws. Except as expressly authorized by MetaMask, you agree not to copy, modify, rent, lease, loan, sell, distribute, perform, display or create derivative works based on the Service, in whole or in part. MetaMask issues a license for MetaMask, found [here](https://github.com/MetaMask/metamask-plugin/blob/master/LICENSE). For information on other licenses utilized in the development of MetaMask, please see our attribution page at: [https://metamask.io/attributions.html](https://metamask.io/attributions.html)\n\n## 9. Links ##\n\nThe Service provides, or third parties may provide, links to other World Wide Web or accessible sites, applications or resources. Because MetaMask has no control over such sites, applications and resources, you acknowledge and agree that MetaMask is not responsible for the availability of such external sites, applications or resources, and does not endorse and is not responsible or liable for any content, advertising, products or other materials on or available from such sites or resources. You further acknowledge and agree that MetaMask shall not be responsible or liable, directly or indirectly, for any damage or loss caused or alleged to be caused by or in connection with use of or reliance on any such content, goods or services available on or through any such site or resource.\n\n## 10. Termination and Suspension ##\n\nMetaMask may terminate or suspend all or part of the Service and your MetaMask access immediately, without prior notice or liability, if you breach any of the terms or conditions of the Terms. Upon termination of your access, your right to use the Service will immediately cease.\n\nThe following provisions of the Terms survive any termination of these Terms: INDEMNITY; WARRANTY DISCLAIMERS; LIMITATION ON LIABILITY; OUR PROPRIETARY RIGHTS; LINKS; TERMINATION; NO THIRD PARTY BENEFICIARIES; BINDING ARBITRATION AND CLASS ACTION WAIVER; GENERAL INFORMATION.\n\n## 11. No Third Party Beneficiaries ##\n\nYou agree that, except as otherwise expressly provided in these Terms, there shall be no third party beneficiaries to the Terms.\n\n## 12. Notice and Procedure For Making Claims of Copyright Infringement ##\n\nIf you believe that your copyright or the copyright of a person on whose behalf you are authorized to act has been infringed, please provide MetaMask’s Copyright Agent a written Notice containing the following information:\n\n· an electronic or physical signature of the person authorized to act on behalf of the owner of the copyright or other intellectual property interest;\n\n· a description of the copyrighted work or other intellectual property that you claim has been infringed;\n\n· a description of where the material that you claim is infringing is located on the Service;\n\n· your address, telephone number, and email address;\n\n· a statement by you that you have a good faith belief that the disputed use is not authorized by the copyright owner, its agent, or the law;\n\n· a statement by you, made under penalty of perjury, that the above information in your Notice is accurate and that you are the copyright or intellectual property owner or authorized to act on the copyright or intellectual property owner's behalf.\n\nMetaMask’s Copyright Agent can be reached at:\n\nEmail: copyright [at] metamask [dot] io\n\nMail:\n\nAttention:\n\nMetaMask Copyright ℅ ConsenSys\n\n49 Bogart Street\n\nBrooklyn, NY 11206\n\n## 13. Binding Arbitration and Class Action Waiver ##\n\nPLEASE READ THIS SECTION CAREFULLY – IT MAY SIGNIFICANTLY AFFECT YOUR LEGAL RIGHTS, INCLUDING YOUR RIGHT TO FILE A LAWSUIT IN COURT\n\n### 13.1 Initial Dispute Resolution ###\n\nThe parties shall use their best efforts to engage directly to settle any dispute, claim, question, or disagreement and engage in good faith negotiations which shall be a condition to either party initiating a lawsuit or arbitration.\n\n### 13.2 Binding Arbitration ###\n\nIf the parties do not reach an agreed upon solution within a period of 30 days from the time informal dispute resolution under the Initial Dispute Resolution provision begins, then either party may initiate binding arbitration as the sole means to resolve claims, subject to the terms set forth below. Specifically, all claims arising out of or relating to these Terms (including their formation, performance and breach), the parties’ relationship with each other and/or your use of the Service shall be finally settled by binding arbitration administered by the American Arbitration Association in accordance with the provisions of its Commercial Arbitration Rules and the supplementary procedures for consumer related disputes of the American Arbitration Association (the \"AAA\"), excluding any rules or procedures governing or permitting class actions.\n\nThe arbitrator, and not any federal, state or local court or agency, shall have exclusive authority to resolve all disputes arising out of or relating to the interpretation, applicability, enforceability or formation of these Terms, including, but not limited to any claim that all or any part of these Terms are void or voidable, or whether a claim is subject to arbitration. The arbitrator shall be empowered to grant whatever relief would be available in a court under law or in equity. The arbitrator’s award shall be written, and binding on the parties and may be entered as a judgment in any court of competent jurisdiction.\n\nThe parties understand that, absent this mandatory provision, they would have the right to sue in court and have a jury trial. They further understand that, in some instances, the costs of arbitration could exceed the costs of litigation and the right to discovery may be more limited in arbitration than in court.\n\n### 13.3 Location ###\n\nBinding arbitration shall take place in New York. You agree to submit to the personal jurisdiction of any federal or state court in New York County, New York, in order to compel arbitration, to stay proceedings pending arbitration, or to confirm, modify, vacate or enter judgment on the award entered by the arbitrator.\n\n### 13.4 Class Action Waiver ###\n\nThe parties further agree that any arbitration shall be conducted in their individual capacities only and not as a class action or other representative action, and the parties expressly waive their right to file a class action or seek relief on a class basis. YOU AND METAMASK AGREE THAT EACH MAY BRING CLAIMS AGAINST THE OTHER ONLY IN YOUR OR ITS INDIVIDUAL CAPACITY, AND NOT AS A PLAINTIFF OR CLASS MEMBER IN ANY PURPORTED CLASS OR REPRESENTATIVE PROCEEDING. If any court or arbitrator determines that the class action waiver set forth in this paragraph is void or unenforceable for any reason or that an arbitration can proceed on a class basis, then the arbitration provision set forth above shall be deemed null and void in its entirety and the parties shall be deemed to have not agreed to arbitrate disputes.\n\n### 13.5 Exception - Litigation of Intellectual Property and Small Claims Court Claims ###\n\nNotwithstanding the parties' decision to resolve all disputes through arbitration, either party may bring an action in state or federal court to protect its intellectual property rights (\"intellectual property rights\" means patents, copyrights, moral rights, trademarks, and trade secrets, but not privacy or publicity rights). Either party may also seek relief in a small claims court for disputes or claims within the scope of that court’s jurisdiction.\n\n### 13.6 30-Day Right to Opt Out ###\n\nYou have the right to opt-out and not be bound by the arbitration and class action waiver provisions set forth above by sending written notice of your decision to opt-out to the following address: MetaMask ℅ ConsenSys, 49 Bogart Street, Brooklyn NY 11206 and via email at legal-opt@metamask.io. The notice must be sent within 30 days of September 6, 2016 or your first use of the Service, whichever is later, otherwise you shall be bound to arbitrate disputes in accordance with the terms of those paragraphs. If you opt-out of these arbitration provisions, MetaMask also will not be bound by them.\n\n### 13.7 Changes to This Section ###\n\nMetaMask will provide 60-days’ notice of any changes to this section. Changes will become effective on the 60th day, and will apply prospectively only to any claims arising after the 60th day.\n\nFor any dispute not subject to arbitration you and MetaMask agree to submit to the personal and exclusive jurisdiction of and venue in the federal and state courts located in New York, New York. You further agree to accept service of process by mail, and hereby waive any and all jurisdictional and venue defenses otherwise available.\n\nThe Terms and the relationship between you and MetaMask shall be governed by the laws of the State of New York without regard to conflict of law provisions.\n\n## 14. General Information ##\n\n### 14.1 Entire Agreement ###\n\nThese Terms (and any additional terms, rules and conditions of participation that MetaMask may post on the Service) constitute the entire agreement between you and MetaMask with respect to the Service and supersedes any prior agreements, oral or written, between you and MetaMask. In the event of a conflict between these Terms and the additional terms, rules and conditions of participation, the latter will prevail over the Terms to the extent of the conflict.\n\n### 14.2 Waiver and Severability of Terms ###\n\nThe failure of MetaMask to exercise or enforce any right or provision of the Terms shall not constitute a waiver of such right or provision. If any provision of the Terms is found by an arbitrator or court of competent jurisdiction to be invalid, the parties nevertheless agree that the arbitrator or court should endeavor to give effect to the parties' intentions as reflected in the provision, and the other provisions of the Terms remain in full force and effect.\n\n### 14.3 Statute of Limitations ###\n\nYou agree that regardless of any statute or law to the contrary, any claim or cause of action arising out of or related to the use of the Service or the Terms must be filed within one (1) year after such claim or cause of action arose or be forever barred.\n\n### 14.4 Section Titles ###\n\nThe section titles in the Terms are for convenience only and have no legal or contractual effect.\n\n### 14.5 Communications ###\n\nUsers with questions, complaints or claims with respect to the Service may contact us using the relevant contact information set forth above and at communications@metamask.io.\n\n## 15 Related Links ##\n\n**[Terms of Use](https://metamask.io/terms.html)**\n\n**[Privacy](https://metamask.io/privacy.html)**\n\n**[Attributions](https://metamask.io/attributions.html)**\n\n","id":0}] \ No newline at end of file +[{"read":false,"date":"Thu Feb 09 2017","title":"Terms of Use","body":"# Terms of Use #\n\n**THIS AGREEMENT IS SUBJECT TO BINDING ARBITRATION AND A WAIVER OF CLASS ACTION RIGHTS AS DETAILED IN SECTION 13. PLEASE READ THE AGREEMENT CAREFULLY.**\n\n_Our Terms of Use have been updated as of September 5, 2016_\n\n## 1. Acceptance of Terms ##\n\nMetaMask provides a platform for managing Ethereum (or \"ETH\") accounts, and allowing ordinary websites to interact with the Ethereum blockchain, while keeping the user in control over what transactions they approve, through our website located at[ ](http://metamask.io)[https://metamask.io/](https://metamask.io/) and browser plugin (the \"Site\") — which includes text, images, audio, code and other materials (collectively, the “Content”) and all of the features, and services provided. The Site, and any other features, tools, materials, or other services offered from time to time by MetaMask are referred to here as the “Service.” Please read these Terms of Use (the “Terms” or “Terms of Use”) carefully before using the Service. By using or otherwise accessing the Services, or clicking to accept or agree to these Terms where that option is made available, you (1) accept and agree to these Terms (2) consent to the collection, use, disclosure and other handling of information as described in our Privacy Policy and (3) any additional terms, rules and conditions of participation issued by MetaMask from time to time. If you do not agree to the Terms, then you may not access or use the Content or Services.\n\n## 2. Modification of Terms of Use ##\n\nExcept for Section 13, providing for binding arbitration and waiver of class action rights, MetaMask reserves the right, at its sole discretion, to modify or replace the Terms of Use at any time. The most current version of these Terms will be posted on our Site. You shall be responsible for reviewing and becoming familiar with any such modifications. Use of the Services by you after any modification to the Terms constitutes your acceptance of the Terms of Use as modified.\n\n\n\n## 3. Eligibility ##\n\nYou hereby represent and warrant that you are fully able and competent to enter into the terms, conditions, obligations, affirmations, representations and warranties set forth in these Terms and to abide by and comply with these Terms.\n\nMetaMask is a global platform and by accessing the Content or Services, you are representing and warranting that, you are of the legal age of majority in your jurisdiction as is required to access such Services and Content and enter into arrangements as provided by the Service. You further represent that you are otherwise legally permitted to use the service in your jurisdiction including owning cryptographic tokens of value, and interacting with the Services or Content in any way. You further represent you are responsible for ensuring compliance with the laws of your jurisdiction and acknowledge that MetaMask is not liable for your compliance with such laws.\n\n## 4 Account Password and Security ##\n\nWhen setting up an account within MetaMask, you will be responsible for keeping your own account secrets, which may be a twelve-word seed phrase, an account file, or other locally stored secret information. MetaMask encrypts this information locally with a password you provide, that we never send to our servers. You agree to (a) never use the same password for MetaMask that you have ever used outside of this service; (b) keep your secret information and password confidential and do not share them with anyone else; (c) immediately notify MetaMask of any unauthorized use of your account or breach of security. MetaMask cannot and will not be liable for any loss or damage arising from your failure to comply with this section.\n\n## 5. Representations, Warranties, and Risks ##\n\n### 5.1. Warranty Disclaimer ###\n\nYou expressly understand and agree that your use of the Service is at your sole risk. The Service (including the Service and the Content) are provided on an \"AS IS\" and \"as available\" basis, without warranties of any kind, either express or implied, including, without limitation, implied warranties of merchantability, fitness for a particular purpose or non-infringement. You acknowledge that MetaMask has no control over, and no duty to take any action regarding: which users gain access to or use the Service; what effects the Content may have on you; how you may interpret or use the Content; or what actions you may take as a result of having been exposed to the Content. You release MetaMask from all liability for you having acquired or not acquired Content through the Service. MetaMask makes no representations concerning any Content contained in or accessed through the Service, and MetaMask will not be responsible or liable for the accuracy, copyright compliance, legality or decency of material contained in or accessed through the Service.\n\n### 5.2 Sophistication and Risk of Cryptographic Systems ###\n\nBy utilizing the Service or interacting with the Content or platform in any way, you represent that you understand the inherent risks associated with cryptographic systems; and warrant that you have an understanding of the usage and intricacies of native cryptographic tokens, like Ether (ETH) and Bitcoin (BTC), smart contract based tokens such as those that follow the Ethereum Token Standard (https://github.com/ethereum/EIPs/issues/20), and blockchain-based software systems.\n\n### 5.3 Risk of Regulatory Actions in One or More Jurisdictions ###\n\nMetaMask and ETH could be impacted by one or more regulatory inquiries or regulatory action, which could impede or limit the ability of MetaMask to continue to develop, or which could impede or limit your ability to access or use the Service or Ethereum blockchain.\n\n### 5.4 Risk of Weaknesses or Exploits in the Field of Cryptography ###\n\nYou acknowledge and understand that Cryptography is a progressing field. Advances in code cracking or technical advances such as the development of quantum computers may present risks to cryptocurrencies and Services of Content, which could result in the theft or loss of your cryptographic tokens or property. To the extent possible, MetaMask intends to update the protocol underlying Services to account for any advances in cryptography and to incorporate additional security measures, but does not guarantee or otherwise represent full security of the system. By using the Service or accessing Content, you acknowledge these inherent risks.\n\n### 5.5 Volatility of Crypto Currencies ###\n\nYou understand that Ethereum and other blockchain technologies and associated currencies or tokens are highly volatile due to many factors including but not limited to adoption, speculation, technology and security risks. You also acknowledge that the cost of transacting on such technologies is variable and may increase at any time causing impact to any activities taking place on the Ethereum blockchain. You acknowledge these risks and represent that MetaMask cannot be held liable for such fluctuations or increased costs.\n\n### 5.6 Application Security ###\n\nYou acknowledge that Ethereum applications are code subject to flaws and acknowledge that you are solely responsible for evaluating any code provided by the Services or Content and the trustworthiness of any third-party websites, products, smart-contracts, or Content you access or use through the Service. You further expressly acknowledge and represent that Ethereum applications can be written maliciously or negligently, that MetaMask cannot be held liable for your interaction with such applications and that such applications may cause the loss of property or even identity. This warning and others later provided by MetaMask in no way evidence or represent an on-going duty to alert you to all of the potential risks of utilizing the Service or Content.\n\n## 6. Indemnity ##\n\nYou agree to release and to indemnify, defend and hold harmless MetaMask and its parents, subsidiaries, affiliates and agencies, as well as the officers, directors, employees, shareholders and representatives of any of the foregoing entities, from and against any and all losses, liabilities, expenses, damages, costs (including attorneys’ fees and court costs) claims or actions of any kind whatsoever arising or resulting from your use of the Service, your violation of these Terms of Use, and any of your acts or omissions that implicate publicity rights, defamation or invasion of privacy. MetaMask reserves the right, at its own expense, to assume exclusive defense and control of any matter otherwise subject to indemnification by you and, in such case, you agree to cooperate with MetaMask in the defense of such matter.\n\n## 7. Limitation on liability ##\n\nYOU ACKNOWLEDGE AND AGREE THAT YOU ASSUME FULL RESPONSIBILITY FOR YOUR USE OF THE SITE AND SERVICE. YOU ACKNOWLEDGE AND AGREE THAT ANY INFORMATION YOU SEND OR RECEIVE DURING YOUR USE OF THE SITE AND SERVICE MAY NOT BE SECURE AND MAY BE INTERCEPTED OR LATER ACQUIRED BY UNAUTHORIZED PARTIES. YOU ACKNOWLEDGE AND AGREE THAT YOUR USE OF THE SITE AND SERVICE IS AT YOUR OWN RISK. RECOGNIZING SUCH, YOU UNDERSTAND AND AGREE THAT, TO THE FULLEST EXTENT PERMITTED BY APPLICABLE LAW, NEITHER METAMASK NOR ITS SUPPLIERS OR LICENSORS WILL BE LIABLE TO YOU FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY OR OTHER DAMAGES OF ANY KIND, INCLUDING WITHOUT LIMITATION DAMAGES FOR LOSS OF PROFITS, GOODWILL, USE, DATA OR OTHER TANGIBLE OR INTANGIBLE LOSSES OR ANY OTHER DAMAGES BASED ON CONTRACT, TORT, STRICT LIABILITY OR ANY OTHER THEORY (EVEN IF METAMASK HAD BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES), RESULTING FROM THE SITE OR SERVICE; THE USE OR THE INABILITY TO USE THE SITE OR SERVICE; UNAUTHORIZED ACCESS TO OR ALTERATION OF YOUR TRANSMISSIONS OR DATA; STATEMENTS OR CONDUCT OF ANY THIRD PARTY ON THE SITE OR SERVICE; ANY ACTIONS WE TAKE OR FAIL TO TAKE AS A RESULT OF COMMUNICATIONS YOU SEND TO US; HUMAN ERRORS; TECHNICAL MALFUNCTIONS; FAILURES, INCLUDING PUBLIC UTILITY OR TELEPHONE OUTAGES; OMISSIONS, INTERRUPTIONS, LATENCY, DELETIONS OR DEFECTS OF ANY DEVICE OR NETWORK, PROVIDERS, OR SOFTWARE (INCLUDING, BUT NOT LIMITED TO, THOSE THAT DO NOT PERMIT PARTICIPATION IN THE SERVICE); ANY INJURY OR DAMAGE TO COMPUTER EQUIPMENT; INABILITY TO FULLY ACCESS THE SITE OR SERVICE OR ANY OTHER WEBSITE; THEFT, TAMPERING, DESTRUCTION, OR UNAUTHORIZED ACCESS TO, IMAGES OR OTHER CONTENT OF ANY KIND; DATA THAT IS PROCESSED LATE OR INCORRECTLY OR IS INCOMPLETE OR LOST; TYPOGRAPHICAL, PRINTING OR OTHER ERRORS, OR ANY COMBINATION THEREOF; OR ANY OTHER MATTER RELATING TO THE SITE OR SERVICE.\n\nSOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF CERTAIN WARRANTIES OR THE LIMITATION OR EXCLUSION OF LIABILITY FOR INCIDENTAL OR CONSEQUENTIAL DAMAGES. ACCORDINGLY, SOME OF THE ABOVE LIMITATIONS MAY NOT APPLY TO YOU.\n\n## 8. Our Proprietary Rights ##\n\nAll title, ownership and intellectual property rights in and to the Service are owned by MetaMask or its licensors. You acknowledge and agree that the Service contains proprietary and confidential information that is protected by applicable intellectual property and other laws. Except as expressly authorized by MetaMask, you agree not to copy, modify, rent, lease, loan, sell, distribute, perform, display or create derivative works based on the Service, in whole or in part. MetaMask issues a license for MetaMask, found [here](https://github.com/MetaMask/metamask-plugin/blob/master/LICENSE). For information on other licenses utilized in the development of MetaMask, please see our attribution page at: [https://metamask.io/attributions.html](https://metamask.io/attributions.html)\n\n## 9. Links ##\n\nThe Service provides, or third parties may provide, links to other World Wide Web or accessible sites, applications or resources. Because MetaMask has no control over such sites, applications and resources, you acknowledge and agree that MetaMask is not responsible for the availability of such external sites, applications or resources, and does not endorse and is not responsible or liable for any content, advertising, products or other materials on or available from such sites or resources. You further acknowledge and agree that MetaMask shall not be responsible or liable, directly or indirectly, for any damage or loss caused or alleged to be caused by or in connection with use of or reliance on any such content, goods or services available on or through any such site or resource.\n\n## 10. Termination and Suspension ##\n\nMetaMask may terminate or suspend all or part of the Service and your MetaMask access immediately, without prior notice or liability, if you breach any of the terms or conditions of the Terms. Upon termination of your access, your right to use the Service will immediately cease.\n\nThe following provisions of the Terms survive any termination of these Terms: INDEMNITY; WARRANTY DISCLAIMERS; LIMITATION ON LIABILITY; OUR PROPRIETARY RIGHTS; LINKS; TERMINATION; NO THIRD PARTY BENEFICIARIES; BINDING ARBITRATION AND CLASS ACTION WAIVER; GENERAL INFORMATION.\n\n## 11. No Third Party Beneficiaries ##\n\nYou agree that, except as otherwise expressly provided in these Terms, there shall be no third party beneficiaries to the Terms.\n\n## 12. Notice and Procedure For Making Claims of Copyright Infringement ##\n\nIf you believe that your copyright or the copyright of a person on whose behalf you are authorized to act has been infringed, please provide MetaMask’s Copyright Agent a written Notice containing the following information:\n\n· an electronic or physical signature of the person authorized to act on behalf of the owner of the copyright or other intellectual property interest;\n\n· a description of the copyrighted work or other intellectual property that you claim has been infringed;\n\n· a description of where the material that you claim is infringing is located on the Service;\n\n· your address, telephone number, and email address;\n\n· a statement by you that you have a good faith belief that the disputed use is not authorized by the copyright owner, its agent, or the law;\n\n· a statement by you, made under penalty of perjury, that the above information in your Notice is accurate and that you are the copyright or intellectual property owner or authorized to act on the copyright or intellectual property owner's behalf.\n\nMetaMask’s Copyright Agent can be reached at:\n\nEmail: copyright [at] metamask [dot] io\n\nMail:\n\nAttention:\n\nMetaMask Copyright ℅ ConsenSys\n\n49 Bogart Street\n\nBrooklyn, NY 11206\n\n## 13. Binding Arbitration and Class Action Waiver ##\n\nPLEASE READ THIS SECTION CAREFULLY – IT MAY SIGNIFICANTLY AFFECT YOUR LEGAL RIGHTS, INCLUDING YOUR RIGHT TO FILE A LAWSUIT IN COURT\n\n### 13.1 Initial Dispute Resolution ###\n\nThe parties shall use their best efforts to engage directly to settle any dispute, claim, question, or disagreement and engage in good faith negotiations which shall be a condition to either party initiating a lawsuit or arbitration.\n\n### 13.2 Binding Arbitration ###\n\nIf the parties do not reach an agreed upon solution within a period of 30 days from the time informal dispute resolution under the Initial Dispute Resolution provision begins, then either party may initiate binding arbitration as the sole means to resolve claims, subject to the terms set forth below. Specifically, all claims arising out of or relating to these Terms (including their formation, performance and breach), the parties’ relationship with each other and/or your use of the Service shall be finally settled by binding arbitration administered by the American Arbitration Association in accordance with the provisions of its Commercial Arbitration Rules and the supplementary procedures for consumer related disputes of the American Arbitration Association (the \"AAA\"), excluding any rules or procedures governing or permitting class actions.\n\nThe arbitrator, and not any federal, state or local court or agency, shall have exclusive authority to resolve all disputes arising out of or relating to the interpretation, applicability, enforceability or formation of these Terms, including, but not limited to any claim that all or any part of these Terms are void or voidable, or whether a claim is subject to arbitration. The arbitrator shall be empowered to grant whatever relief would be available in a court under law or in equity. The arbitrator’s award shall be written, and binding on the parties and may be entered as a judgment in any court of competent jurisdiction.\n\nThe parties understand that, absent this mandatory provision, they would have the right to sue in court and have a jury trial. They further understand that, in some instances, the costs of arbitration could exceed the costs of litigation and the right to discovery may be more limited in arbitration than in court.\n\n### 13.3 Location ###\n\nBinding arbitration shall take place in New York. You agree to submit to the personal jurisdiction of any federal or state court in New York County, New York, in order to compel arbitration, to stay proceedings pending arbitration, or to confirm, modify, vacate or enter judgment on the award entered by the arbitrator.\n\n### 13.4 Class Action Waiver ###\n\nThe parties further agree that any arbitration shall be conducted in their individual capacities only and not as a class action or other representative action, and the parties expressly waive their right to file a class action or seek relief on a class basis. YOU AND METAMASK AGREE THAT EACH MAY BRING CLAIMS AGAINST THE OTHER ONLY IN YOUR OR ITS INDIVIDUAL CAPACITY, AND NOT AS A PLAINTIFF OR CLASS MEMBER IN ANY PURPORTED CLASS OR REPRESENTATIVE PROCEEDING. If any court or arbitrator determines that the class action waiver set forth in this paragraph is void or unenforceable for any reason or that an arbitration can proceed on a class basis, then the arbitration provision set forth above shall be deemed null and void in its entirety and the parties shall be deemed to have not agreed to arbitrate disputes.\n\n### 13.5 Exception - Litigation of Intellectual Property and Small Claims Court Claims ###\n\nNotwithstanding the parties' decision to resolve all disputes through arbitration, either party may bring an action in state or federal court to protect its intellectual property rights (\"intellectual property rights\" means patents, copyrights, moral rights, trademarks, and trade secrets, but not privacy or publicity rights). Either party may also seek relief in a small claims court for disputes or claims within the scope of that court’s jurisdiction.\n\n### 13.6 30-Day Right to Opt Out ###\n\nYou have the right to opt-out and not be bound by the arbitration and class action waiver provisions set forth above by sending written notice of your decision to opt-out to the following address: MetaMask ℅ ConsenSys, 49 Bogart Street, Brooklyn NY 11206 and via email at legal-opt@metamask.io. The notice must be sent within 30 days of September 6, 2016 or your first use of the Service, whichever is later, otherwise you shall be bound to arbitrate disputes in accordance with the terms of those paragraphs. If you opt-out of these arbitration provisions, MetaMask also will not be bound by them.\n\n### 13.7 Changes to This Section ###\n\nMetaMask will provide 60-days’ notice of any changes to this section. Changes will become effective on the 60th day, and will apply prospectively only to any claims arising after the 60th day.\n\nFor any dispute not subject to arbitration you and MetaMask agree to submit to the personal and exclusive jurisdiction of and venue in the federal and state courts located in New York, New York. You further agree to accept service of process by mail, and hereby waive any and all jurisdictional and venue defenses otherwise available.\n\nThe Terms and the relationship between you and MetaMask shall be governed by the laws of the State of New York without regard to conflict of law provisions.\n\n## 14. General Information ##\n\n### 14.1 Entire Agreement ###\n\nThese Terms (and any additional terms, rules and conditions of participation that MetaMask may post on the Service) constitute the entire agreement between you and MetaMask with respect to the Service and supersedes any prior agreements, oral or written, between you and MetaMask. In the event of a conflict between these Terms and the additional terms, rules and conditions of participation, the latter will prevail over the Terms to the extent of the conflict.\n\n### 14.2 Waiver and Severability of Terms ###\n\nThe failure of MetaMask to exercise or enforce any right or provision of the Terms shall not constitute a waiver of such right or provision. If any provision of the Terms is found by an arbitrator or court of competent jurisdiction to be invalid, the parties nevertheless agree that the arbitrator or court should endeavor to give effect to the parties' intentions as reflected in the provision, and the other provisions of the Terms remain in full force and effect.\n\n### 14.3 Statute of Limitations ###\n\nYou agree that regardless of any statute or law to the contrary, any claim or cause of action arising out of or related to the use of the Service or the Terms must be filed within one (1) year after such claim or cause of action arose or be forever barred.\n\n### 14.4 Section Titles ###\n\nThe section titles in the Terms are for convenience only and have no legal or contractual effect.\n\n### 14.5 Communications ###\n\nUsers with questions, complaints or claims with respect to the Service may contact us using the relevant contact information set forth above and at communications@metamask.io.\n\n## 15 Related Links ##\n\n**[Terms of Use](https://metamask.io/terms.html)**\n\n**[Privacy](https://metamask.io/privacy.html)**\n\n**[Attributions](https://metamask.io/attributions.html)**\n\n","id":0},{"read":false,"date":"Mon May 08 2017","title":"Privacy Notice","body":"MetaMask is beta software. \n\nWhen you log in to MetaMask, your current account is visible to every new site you visit.\n\nFor your privacy, for now, please sign out of MetaMask when you're done using a site.\n\nAlso, by default, you will be signed in to a test network. To use real Ether, you must connect to the main network manually in the top left network menu.\n\n","id":2}] \ No newline at end of file From d61b587f3014823fcd23b1e5becce7949e88d952 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Mon, 8 May 2017 16:02:41 -0700 Subject: [PATCH 218/372] Redefine txmeta when submitting. --- ui/app/components/pending-tx.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/app/components/pending-tx.js b/ui/app/components/pending-tx.js index 71d4d767a..5ea885195 100644 --- a/ui/app/components/pending-tx.js +++ b/ui/app/components/pending-tx.js @@ -76,6 +76,7 @@ PendingTx.prototype.render = function () { h('form#pending-tx-form', { onSubmit: (event) => { + const txMeta = this.gatherTxMeta() event.preventDefault() const form = document.querySelector('form#pending-tx-form') const valid = form.checkValidity() @@ -418,4 +419,3 @@ function forwardCarrat () { ) } - From f131f59c82ed7446d98c4d2301ce83aa25877d49 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Mon, 8 May 2017 16:03:28 -0700 Subject: [PATCH 219/372] Changelog bump --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 157c4186a..2d3c1513a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - Enforce Safe Gas Minimum recommended by EthGasStation. - Fix bug where block-tracker could stop polling for new blocks. - Reduce UI size by removing internal web3. +- Fix bug where gas parameters would not properly update on adjustment. ## 3.6.1 2017-4-30 From 662a646fa92479e30b492520bd0edd753179bbdd Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Mon, 8 May 2017 16:20:37 -0700 Subject: [PATCH 220/372] Version 3.6.2 --- CHANGELOG.md | 2 ++ app/manifest.json | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d3c1513a..1abab9c7a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Current Master +## 3.6.2 2017-5-8 + - Input gas price in Gwei. - Enforce Safe Gas Minimum recommended by EthGasStation. - Fix bug where block-tracker could stop polling for new blocks. diff --git a/app/manifest.json b/app/manifest.json index 3a9b0b29f..faceea60b 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -1,7 +1,7 @@ { "name": "MetaMask", "short_name": "Metamask", - "version": "3.6.1", + "version": "3.6.2", "manifest_version": 2, "author": "https://metamask.io", "description": "Ethereum Browser Extension", From ff1a1284cc768a0e7d179083d360a5579373e256 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Mon, 8 May 2017 22:05:38 -0700 Subject: [PATCH 221/372] Version 3.6.3 --- CHANGELOG.md | 4 ++++ app/manifest.json | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1abab9c7a..40ba4d34d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Current Master +## 3.6.3 2017-5-8 + +- Fix bug that could stop newer versions of Geth from working with MetaMask. + ## 3.6.2 2017-5-8 - Input gas price in Gwei. diff --git a/app/manifest.json b/app/manifest.json index faceea60b..cef44446e 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -1,7 +1,7 @@ { "name": "MetaMask", "short_name": "Metamask", - "version": "3.6.2", + "version": "3.6.3", "manifest_version": 2, "author": "https://metamask.io", "description": "Ethereum Browser Extension", From ac54c7d96b503e8d79fae4a67289ae95d09c3c75 Mon Sep 17 00:00:00 2001 From: kumavis Date: Tue, 9 May 2017 11:28:39 -0700 Subject: [PATCH 222/372] ens - add mainnet ens support --- ui/app/components/ens-input.js | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/ui/app/components/ens-input.js b/ui/app/components/ens-input.js index ec3cd60ed..04c6222c2 100644 --- a/ui/app/components/ens-input.js +++ b/ui/app/components/ens-input.js @@ -5,11 +5,9 @@ const extend = require('xtend') const debounce = require('debounce') const copyToClipboard = require('copy-to-clipboard') const ENS = require('ethjs-ens') +const networkMap = require('ethjs-ens/lib/network-map.json') const ensRE = /.+\.eth$/ -const networkResolvers = { - '3': '112234455c3a32fd11230c42e7bccd4a84e02010', -} module.exports = EnsInput @@ -24,8 +22,8 @@ EnsInput.prototype.render = function () { list: 'addresses', onChange: () => { const network = this.props.network - const resolverAddress = networkResolvers[network] - if (!resolverAddress) return + const networkHasEnsSupport = getNetworkEnsSupport(network) + if (!networkHasEnsSupport) return const recipient = document.querySelector('input[name="address"]').value if (recipient.match(ensRE) === null) { @@ -73,9 +71,9 @@ EnsInput.prototype.render = function () { EnsInput.prototype.componentDidMount = function () { const network = this.props.network - const resolverAddress = networkResolvers[network] + const networkHasEnsSupport = getNetworkEnsSupport(network) - if (resolverAddress) { + if (networkHasEnsSupport) { const provider = global.ethereumProvider this.ens = new ENS({ provider, network }) this.checkName = debounce(this.lookupEnsName.bind(this), 200) @@ -169,3 +167,7 @@ EnsInput.prototype.ensIconContents = function (recipient) { }) } } + +function getNetworkEnsSupport(network) { + return Boolean(networkMap[network]) +} \ No newline at end of file From 3ed7205b7505133a1dd6a278665070eb83bd4a32 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Tue, 9 May 2017 17:08:33 -0700 Subject: [PATCH 223/372] Version 3.6.4 --- CHANGELOG.md | 4 ++++ app/manifest.json | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 40ba4d34d..532abcca9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Current Master +## 3.6.4 2017-5-8 + +- Fix main-net ENS resolution. + ## 3.6.3 2017-5-8 - Fix bug that could stop newer versions of Geth from working with MetaMask. diff --git a/app/manifest.json b/app/manifest.json index cef44446e..a1f6d7855 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -1,7 +1,7 @@ { "name": "MetaMask", "short_name": "Metamask", - "version": "3.6.3", + "version": "3.6.4", "manifest_version": 2, "author": "https://metamask.io", "description": "Ethereum Browser Extension", From d737bd1633977174ddf3d3248ee6873cc3adca8e Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Wed, 10 May 2017 17:26:09 -0700 Subject: [PATCH 224/372] Break up pending-tx component for better unit testability --- ui/app/components/pending-tx.js | 77 +++++++++++++++++---------------- 1 file changed, 39 insertions(+), 38 deletions(-) diff --git a/ui/app/components/pending-tx.js b/ui/app/components/pending-tx.js index 5ea885195..6b8f16dae 100644 --- a/ui/app/components/pending-tx.js +++ b/ui/app/components/pending-tx.js @@ -1,5 +1,4 @@ const Component = require('react').Component -const connect = require('react-redux').connect const h = require('react-hyperscript') const inherits = require('util').inherits const actions = require('../actions') @@ -20,12 +19,7 @@ const GWEI_FACTOR = new BN(1e9) const MIN_GAS_PRICE_BN = MIN_GAS_PRICE_GWEI_BN.mul(GWEI_FACTOR) const MIN_GAS_LIMIT_BN = new BN(21000) -module.exports = connect(mapStateToProps)(PendingTx) - -function mapStateToProps (state) { - return {} -} - +module.exports = PendingTx inherits(PendingTx, Component) function PendingTx () { Component.call(this) @@ -37,7 +31,6 @@ function PendingTx () { PendingTx.prototype.render = function () { const props = this.props - const txMeta = this.gatherTxMeta() const txParams = txMeta.txParams || {} @@ -61,7 +54,6 @@ PendingTx.prototype.render = function () { const maxCost = txFeeBn.add(valueBn) const dataLength = txParams.data ? (txParams.data.length - 2) / 2 : 0 - const imageify = props.imageifyIdenticons === undefined ? true : props.imageifyIdenticons const balanceBn = hexToBn(balance) const insufficientBalance = balanceBn.lt(maxCost) @@ -75,18 +67,8 @@ PendingTx.prototype.render = function () { }, [ h('form#pending-tx-form', { - onSubmit: (event) => { - const txMeta = this.gatherTxMeta() - event.preventDefault() - const form = document.querySelector('form#pending-tx-form') - const valid = form.checkValidity() - this.setState({ valid }) - if (valid && this.verifyGasParams()) { - props.sendTransaction(txMeta, event) - } else { - this.props.dispatch(actions.displayWarning('Invalid Gas Parameters')) - } - }, + onSubmit: this.onSubmit.bind(this), + }, [ // tx info @@ -100,7 +82,6 @@ PendingTx.prototype.render = function () { h(MiniAccountPanel, { imageSeed: address, - imageifyIdenticons: imageify, picOrder: 'right', }, [ h('span.font-small', { @@ -176,12 +157,8 @@ PendingTx.prototype.render = function () { position: 'relative', top: '5px', }, - onChange: (newHex) => { - log.info(`Gas limit changed to ${newHex}`) - const txMeta = this.gatherTxMeta() - txMeta.txParams.gas = newHex - this.setState({ txData: txMeta }) - }, + onChange: this.gasLimitChanged.bind(this), + ref: (hexInput) => { this.inputs.push(hexInput) }, }), ]), @@ -201,13 +178,7 @@ PendingTx.prototype.render = function () { position: 'relative', top: '5px', }, - onChange: (newHex) => { - log.info(`Gas price changed to: ${newHex}`) - const inWei = hexToBn(newHex).mul(GWEI_FACTOR) - const txMeta = this.gatherTxMeta() - txMeta.txParams.gasPrice = inWei.toString(16) - this.setState({ txData: txMeta }) - }, + onChange: this.gasPriceChanged.bind(this), ref: (hexInput) => { this.inputs.push(hexInput) }, }), ]), @@ -331,13 +302,11 @@ PendingTx.prototype.miniAccountPanelForRecipient = function () { const txData = props.txData const txParams = txData.txParams || {} const isContractDeploy = !('to' in txParams) - const imageify = props.imageifyIdenticons === undefined ? true : props.imageifyIdenticons // If it's not a contract deploy, send to the account if (!isContractDeploy) { return h(MiniAccountPanel, { imageSeed: txParams.to, - imageifyIdenticons: imageify, picOrder: 'left', }, [ h('span.font-small', { @@ -353,7 +322,6 @@ PendingTx.prototype.miniAccountPanelForRecipient = function () { ]) } else { return h(MiniAccountPanel, { - imageifyIdenticons: imageify, picOrder: 'left', }, [ @@ -367,6 +335,21 @@ PendingTx.prototype.miniAccountPanelForRecipient = function () { } } +PendingTx.prototype.gasPriceChanged = function (newHex) { + log.info(`Gas price changed to: ${newHex}`) + const inWei = hexToBn(newHex).mul(GWEI_FACTOR) + const txMeta = this.gatherTxMeta() + txMeta.txParams.gasPrice = inWei.toString(16) + this.setState({ txData: txMeta }) +} + +PendingTx.prototype.gasLimitChanged = function (newHex) { + log.info(`Gas limit changed to ${newHex}`) + const txMeta = this.gatherTxMeta() + txMeta.txParams.gas = newHex + this.setState({ txData: txMeta }) +} + PendingTx.prototype.resetGasFields = function () { log.debug(`pending-tx resetGasFields`) @@ -382,6 +365,24 @@ PendingTx.prototype.resetGasFields = function () { }) } +PendingTx.prototype.onSubmit = function (event) { + event.preventDefault() + const txMeta = this.gatherTxMeta() + const valid = this.checkValidity() + this.setState({ valid }) + if (valid && this.verifyGasParams()) { + this.props.sendTransaction(txMeta, event) + } else { + this.props.dispatch(actions.displayWarning('Invalid Gas Parameters')) + } +} + +PendingTx.prototype.checkValidity = function() { + const form = document.querySelector('form#pending-tx-form') + const valid = form.checkValidity() + return valid +} + // After a customizable state value has been updated, PendingTx.prototype.gatherTxMeta = function () { log.debug(`pending-tx gatherTxMeta`) From e9b11a430b8f447e9c6f21c1b639d150976f98cf Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Wed, 10 May 2017 17:26:51 -0700 Subject: [PATCH 225/372] Add an attempt at a unit test for reproducing #1407 --- test/unit/components/pending-tx-test.js | 61 +++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 test/unit/components/pending-tx-test.js diff --git a/test/unit/components/pending-tx-test.js b/test/unit/components/pending-tx-test.js new file mode 100644 index 000000000..e0f02a5bb --- /dev/null +++ b/test/unit/components/pending-tx-test.js @@ -0,0 +1,61 @@ +var assert = require('assert') +var PendingTx = require('../../../ui/app/components/pending-tx') + +describe('PendingTx', function () { + let pendingTxComponent + + const identities = { + '0xfdea65c8e26263f6d9a1b5de9555d2931a33b826': { + name: 'Main Account 1', + balance: '0x00000000000000056bc75e2d63100000', + }, + } + + const gasPrice = '0x4A817C800' // 20 Gwei + const txData = { + 'id':5021615666270214, + 'time':1494458763011, + 'status':'unapproved', + 'metamaskNetworkId':'1494442339676', + 'txParams':{ + 'from':'0xfdea65c8e26263f6d9a1b5de9555d2931a33b826', + 'to':'0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb', + 'value':'0xde0b6b3a7640000', + gasPrice, + 'gas':'0x7b0c'}, + 'gasLimitSpecified':false, + 'estimatedGas':'0x5208', + } + + + it('should use updated values when edited.', function (done) { + + const props = { + identities, + accounts: identities, + txData, + sendTransaction: (txMeta, event) => { + assert.notEqual(txMeta.txParams.gasPrice, gasPrice, 'gas price should change') + done() + }, + } + + pendingTxComponent = new PendingTx(props) + + const noop = () => {} + + pendingTxComponent.componentDidMount = () => { + + const newGasPrice = '0x451456' + pendingTxComponent.gasPriceChanged(newGasPrice) + + setTimeout(() => { + pendingTxComponent.onSubmit({ preventDefault: noop }) + }, 20) + } + + pendingTxComponent.props = props + pendingTxComponent.render() + }) + +}) From 1772d34e947ec5e940cc99f53ff0a102e048d69c Mon Sep 17 00:00:00 2001 From: frankiebee Date: Thu, 11 May 2017 10:10:50 +0200 Subject: [PATCH 226/372] fix migrator --- app/scripts/lib/migrator/index.js | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/app/scripts/lib/migrator/index.js b/app/scripts/lib/migrator/index.js index c40c347b5..caa0ef318 100644 --- a/app/scripts/lib/migrator/index.js +++ b/app/scripts/lib/migrator/index.js @@ -13,10 +13,10 @@ class Migrator { // run all pending migrations on meta in place migrateData (versionedData = this.generateInitialState()) { const remaining = this.migrations.filter(migrationIsPending) - + if (remaining.length === 0) return versionedData return ( asyncQ.eachSeries(remaining, (migration) => this.runMigration(versionedData, migration)) - .then(() => versionedData) + .then((migratedData) => migratedData.pop()) ) // migration is "pending" if hit has a higher @@ -27,14 +27,13 @@ class Migrator { } runMigration (versionedData, migration) { - return ( - migration.migrate(versionedData) - .then((versionedData) => { - if (!versionedData.data) return Promise.reject(new Error('Migrator - Migration returned empty data')) - if (migration.version !== undefined && versionedData.meta.version !== migration.version) return Promise.reject(new Error('Migrator - Migration did not update version number correctly')) - return Promise.resolve(versionedData) + return migration.migrate(versionedData) + .then((migratedData) => { + if (!migratedData.data) return Promise.reject(new Error('Migrator - Migration returned empty data')) + if (migration.version !== undefined && migratedData.meta.version !== migration.version) return Promise.reject(new Error('Migrator - Migration did not update version number correctly')) + + return Promise.resolve(migratedData) }) - ) } generateInitialState (initState) { From 73e1cd2317db4366a6e29aa9f8119cc871747a1b Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Thu, 11 May 2017 12:30:39 -0700 Subject: [PATCH 227/372] Trim currency list. --- app/currencies.json | 1 - ui/app/conversion.json | 5731 +--------------------------------------- 2 files changed, 1 insertion(+), 5731 deletions(-) delete mode 100644 app/currencies.json diff --git a/app/currencies.json b/app/currencies.json deleted file mode 100644 index 07889798b..000000000 --- a/app/currencies.json +++ /dev/null @@ -1 +0,0 @@ -{"rows":[{"code":"007","name":"007","statuses":["primary"]},{"code":"1337","name":"1337","statuses":["primary"]},{"code":"1CR","name":"1CR","statuses":["primary"]},{"code":"256","name":"256","statuses":["primary"]},{"code":"2FLAV","name":"2FLAV","statuses":["primary"]},{"code":"2GIVE","name":"2GIVE","statuses":["primary"]},{"code":"404","name":"404","statuses":["primary"]},{"code":"611","name":"611","statuses":["primary"]},{"code":"888","name":"888","statuses":["primary"]},{"code":"8BIT","name":"8Bit","statuses":["primary"]},{"code":"ACLR","name":"ACLR","statuses":["primary"]},{"code":"ACOIN","name":"ACOIN","statuses":["primary"]},{"code":"ACP","name":"ACP","statuses":["primary"]},{"code":"ADC","name":"ADC","statuses":["primary"]},{"code":"ADZ","name":"Adzcoin","statuses":["primary"]},{"code":"AEC","name":"AEC","statuses":["primary"]},{"code":"AEON","name":"Aeon","statuses":["primary"]},{"code":"AGRS","name":"Agoras Tokens","statuses":["primary"]},{"code":"AIB","name":"AIB","statuses":["primary"]},{"code":"ADN","name":"Aiden","statuses":["primary"]},{"code":"AIR","name":"AIR","statuses":["primary"]},{"code":"ALC","name":"ALC","statuses":["primary"]},{"code":"ALTC","name":"ALTC","statuses":["primary"]},{"code":"AM","name":"AM","statuses":["primary"]},{"code":"AMBER","name":"AMBER","statuses":["primary"]},{"code":"AMS","name":"AMS","statuses":["primary"]},{"code":"ANAL","name":"ANAL","statuses":["primary"]},{"code":"AND","name":"AND","statuses":["primary"]},{"code":"ANI","name":"ANI","statuses":["primary"]},{"code":"ANC","name":"Anoncoin","statuses":["primary"]},{"code":"ANTI","name":"AntiBitcoin","statuses":["primary"]},{"code":"APEX","name":"APEX","statuses":["primary"]},{"code":"APC","name":"Applecoin","statuses":["primary"]},{"code":"APT","name":"APT","statuses":["primary"]},{"code":"AR2","name":"AR2","statuses":["primary"]},{"code":"ARB","name":"ARB","statuses":["primary"]},{"code":"ARC","name":"ARC","statuses":["primary"]},{"code":"ARCH","name":"ARCH","statuses":["primary"]},{"code":"ABY","name":"ArtByte","statuses":["primary"]},{"code":"ARTC","name":"ARTC","statuses":["primary"]},{"code":"ADCN","name":"Asiadigicoin","statuses":["primary"]},{"code":"ATEN","name":"ATEN","statuses":["primary"]},{"code":"REP","name":"Augur","statuses":["primary"]},{"code":"AUR","name":"Auroracoin","statuses":["primary"]},{"code":"AUD","name":"Australian Dollar","statuses":["secondary"]},{"code":"AV","name":"AV","statuses":["primary"]},{"code":"BA","name":"BA","statuses":["primary"]},{"code":"BAC","name":"BAC","statuses":["primary"]},{"code":"BTA","name":"Bata","statuses":["primary"]},{"code":"BAY","name":"BAY","statuses":["primary"]},{"code":"BBCC","name":"BBCC","statuses":["primary"]},{"code":"BQC","name":"BBQCoin","statuses":["primary"]},{"code":"BDC","name":"BDC","statuses":["primary"]},{"code":"BEC","name":"BEC","statuses":["primary"]},{"code":"BEEZ","name":"BEEZ","statuses":["primary"]},{"code":"BELA","name":"BellaCoin","statuses":["primary"]},{"code":"BERN","name":"BERNcash","statuses":["primary"]},{"code":"BILL","name":"BILL","statuses":["primary"]},{"code":"BILS","name":"BILS","statuses":["primary"]},{"code":"BIOS","name":"BiosCrypto","statuses":["primary"]},{"code":"BIT","name":"BIT","statuses":["primary"]},{"code":"BIT16","name":"BIT16","statuses":["primary"]},{"code":"BITB","name":"BitBean","statuses":["primary"]},{"code":"BTC","name":"Bitcoin","statuses":["primary","secondary"]},{"code":"XBC","name":"Bitcoin Plus","statuses":["primary"]},{"code":"BTCD","name":"BitcoinDark","statuses":["primary"]},{"code":"BCY","name":"Bitcrystals","statuses":["primary"]},{"code":"BTM","name":"Bitmark","statuses":["primary"]},{"code":"BTQ","name":"BitQuark","statuses":["primary"]},{"code":"BITS","name":"BITS","statuses":["primary"]},{"code":"BSD","name":"BitSend","statuses":["primary"]},{"code":"BTS","name":"BitShares","statuses":["primary"]},{"code":"PTS","name":"BitShares PTS","statuses":["primary"]},{"code":"SWIFT","name":"BitSwift","statuses":["primary"]},{"code":"BITZ","name":"Bitz","statuses":["primary"]},{"code":"BLK","name":"Blackcoin","statuses":["primary"]},{"code":"JACK","name":"BlackJack","statuses":["primary"]},{"code":"BLC","name":"Blakecoin","statuses":["primary"]},{"code":"BLEU","name":"BLEU","statuses":["primary"]},{"code":"BLITZ","name":"Blitzcoin","statuses":["primary"]},{"code":"BLOCK","name":"Blocknet","statuses":["primary"]},{"code":"BLRY","name":"BLRY","statuses":["primary"]},{"code":"BLU","name":"BLU","statuses":["primary"]},{"code":"BM","name":"BM","statuses":["primary"]},{"code":"BNT","name":"BNT","statuses":["primary"]},{"code":"BOB","name":"BOB","statuses":["primary"]},{"code":"BON","name":"BON","statuses":["primary"]},{"code":"BBR","name":"Boolberry","statuses":["primary"]},{"code":"BOST","name":"BoostCoin","statuses":["primary"]},{"code":"BOSS","name":"BOSS","statuses":["primary"]},{"code":"BPOK","name":"BPOK","statuses":["primary"]},{"code":"BRAIN","name":"BRAIN","statuses":["primary"]},{"code":"BRC","name":"BRC","statuses":["primary"]},{"code":"BRDD","name":"BRDD","statuses":["primary"]},{"code":"BRIT","name":"BRIT","statuses":["primary"]},{"code":"GBP","name":"British Pound Sterling","statuses":["secondary"]},{"code":"BRK","name":"BRK","statuses":["primary"]},{"code":"BRX","name":"BRX","statuses":["primary"]},{"code":"BSC","name":"BSC","statuses":["primary"]},{"code":"BST","name":"BST","statuses":["primary"]},{"code":"BTCHC","name":"BTCHC","statuses":["primary"]},{"code":"BTCR","name":"BTCR","statuses":["primary"]},{"code":"BTCS","name":"BTCS","statuses":["primary"]},{"code":"BTCU","name":"BTCU","statuses":["primary"]},{"code":"BTTF","name":"BTTF","statuses":["primary"]},{"code":"BTX","name":"BTX","statuses":["primary"]},{"code":"BUCKS","name":"BUCKS","statuses":["primary"]},{"code":"BUN","name":"BUN","statuses":["primary"]},{"code":"BURST","name":"Burst","statuses":["primary"]},{"code":"BUZZ","name":"BUZZ","statuses":["primary"]},{"code":"BVC","name":"BVC","statuses":["primary"]},{"code":"BYC","name":"Bytecent","statuses":["primary"]},{"code":"BCN","name":"Bytecoin","statuses":["primary"]},{"code":"XCT","name":"C-Bit","statuses":["primary"]},{"code":"C0C0","name":"C0C0","statuses":["primary"]},{"code":"CAB","name":"Cabbage Unit","statuses":["primary"]},{"code":"CAD","name":"CAD","statuses":["primary","secondary"]},{"code":"CAGE","name":"CAGE","statuses":["primary"]},{"code":"CANN","name":"CannabisCoin","statuses":["primary"]},{"code":"CCN","name":"Cannacoin","statuses":["primary"]},{"code":"CPC","name":"Capricoin","statuses":["primary"]},{"code":"DIEM","name":"CarpeDiemCoin","statuses":["primary"]},{"code":"CASH","name":"CASH","statuses":["primary"]},{"code":"CBIT","name":"CBIT","statuses":["primary"]},{"code":"CC","name":"CC","statuses":["primary"]},{"code":"CCB","name":"CCB","statuses":["primary"]},{"code":"CD","name":"CD","statuses":["primary"]},{"code":"CDN","name":"CDN","statuses":["primary"]},{"code":"CF","name":"CF","statuses":["primary"]},{"code":"CFC","name":"CFC","statuses":["primary"]},{"code":"CGA","name":"CGA","statuses":["primary"]},{"code":"CHC","name":"CHC","statuses":["primary"]},{"code":"CKC","name":"Checkcoin","statuses":["primary"]},{"code":"CHEMX","name":"CHEMX","statuses":["primary"]},{"code":"CHESS","name":"CHESS","statuses":["primary"]},{"code":"CHF","name":"CHF","statuses":["primary","secondary"]},{"code":"CNY","name":"Chinese Yuan","statuses":["secondary"]},{"code":"CHRG","name":"CHRG","statuses":["primary"]},{"code":"CJ","name":"CJ","statuses":["primary"]},{"code":"CLAM","name":"Clams","statuses":["primary"]},{"code":"CLICK","name":"CLICK","statuses":["primary"]},{"code":"CLINT","name":"CLINT","statuses":["primary"]},{"code":"CLOAK","name":"Cloakcoin","statuses":["primary"]},{"code":"CLR","name":"CLR","statuses":["primary"]},{"code":"CLUB","name":"CLUB","statuses":["primary"]},{"code":"CLUD","name":"CLUD","statuses":["primary"]},{"code":"CMT","name":"CMT","statuses":["primary"]},{"code":"CNC","name":"CNC","statuses":["primary"]},{"code":"COXST","name":"CoExistCoin","statuses":["primary"]},{"code":"COIN","name":"COIN","statuses":["primary"]},{"code":"C2","name":"Coin2.1","statuses":["primary"]},{"code":"CNMT","name":"Coinomat","statuses":["primary"]},{"code":"CV2","name":"Colossuscoin2.0","statuses":["primary"]},{"code":"CON","name":"CON","statuses":["primary"]},{"code":"XCP","name":"Counterparty","statuses":["primary"]},{"code":"COV","name":"COV","statuses":["primary"]},{"code":"CRAFT","name":"CRAFT","statuses":["primary"]},{"code":"CRAVE","name":"CRAVE","statuses":["primary"]},{"code":"CRC","name":"CRC","statuses":["primary"]},{"code":"CRE","name":"CRE","statuses":["primary"]},{"code":"CRBIT","name":"Creditbit","statuses":["primary"]},{"code":"CREVA","name":"CrevaCoin","statuses":["primary"]},{"code":"CRIME","name":"CRIME","statuses":["primary"]},{"code":"CRT","name":"CRT","statuses":["primary"]},{"code":"CRW","name":"CRW","statuses":["primary"]},{"code":"CRY","name":"CRY","statuses":["primary"]},{"code":"XCR","name":"Crypti","statuses":["primary"]},{"code":"CBX","name":"Crypto Bullion","statuses":["primary"]},{"code":"CESC","name":"CryptoEscudo","statuses":["primary"]},{"code":"XCN","name":"Cryptonite","statuses":["primary"]},{"code":"CSMIC","name":"CSMIC","statuses":["primary"]},{"code":"CST","name":"CST","statuses":["primary"]},{"code":"CTC","name":"CTC","statuses":["primary"]},{"code":"CTO","name":"CTO","statuses":["primary"]},{"code":"CURE","name":"Curecoin","statuses":["primary"]},{"code":"CYP","name":"Cypher","statuses":["primary"]},{"code":"CZC","name":"CZC","statuses":["primary"]},{"code":"CZECO","name":"CZECO","statuses":["primary"]},{"code":"CZR","name":"CZR","statuses":["primary"]},{"code":"DAO","name":"DAO","statuses":["primary"]},{"code":"DGD","name":"DarkGoldCoin","statuses":["primary"]},{"code":"DNET","name":"Darknet","statuses":["primary"]},{"code":"DASH","name":"Dash","statuses":["primary"]},{"code":"DTC","name":"Datacoin","statuses":["primary"]},{"code":"DBG","name":"DBG","statuses":["primary"]},{"code":"DBLK","name":"DBLK","statuses":["primary"]},{"code":"DBTC","name":"DBTC","statuses":["primary"]},{"code":"DCK","name":"DCK","statuses":["primary"]},{"code":"DCR","name":"Decred","statuses":["primary"]},{"code":"DES","name":"Destiny","statuses":["primary"]},{"code":"DETH","name":"DETH","statuses":["primary"]},{"code":"DEUR","name":"DEUR","statuses":["primary"]},{"code":"DEM","name":"Deutsche eMark","statuses":["primary"]},{"code":"DVC","name":"Devcoin","statuses":["primary"]},{"code":"DGCS","name":"DGCS","statuses":["primary"]},{"code":"DGMS","name":"DGMS","statuses":["primary"]},{"code":"DGORE","name":"DGORE","statuses":["primary"]},{"code":"DMD","name":"Diamond","statuses":["primary"]},{"code":"DGB","name":"Digibyte","statuses":["primary"]},{"code":"CUBE","name":"DigiCube","statuses":["primary"]},{"code":"DGC","name":"Digitalcoin","statuses":["primary"]},{"code":"XDN","name":"DigitalNote","statuses":["primary"]},{"code":"DP","name":"DigitalPrice","statuses":["primary"]},{"code":"DIGS","name":"DIGS","statuses":["primary"]},{"code":"DIME","name":"Dimecoin","statuses":["primary"]},{"code":"DISK","name":"DISK","statuses":["primary"]},{"code":"DLISK","name":"DLISK","statuses":["primary"]},{"code":"NOTE","name":"DNotes","statuses":["primary"]},{"code":"DOGE","name":"DOGE","statuses":["primary","secondary"]},{"code":"DOGE","name":"Dogecoin","statuses":["primary","secondary"]},{"code":"DON","name":"DON","statuses":["primary"]},{"code":"DOPE","name":"DopeCoin","statuses":["primary"]},{"code":"DOX","name":"DOX","statuses":["primary"]},{"code":"DRACO","name":"DRACO","statuses":["primary"]},{"code":"DRM","name":"DRM","statuses":["primary"]},{"code":"DROP","name":"DROP","statuses":["primary"]},{"code":"DRZ","name":"DRZ","statuses":["primary"]},{"code":"DSH","name":"DSH","statuses":["primary"]},{"code":"DBIC","name":"DubaiCoin","statuses":["primary"]},{"code":"DUO","name":"DUO","statuses":["primary"]},{"code":"DUST","name":"DUST","statuses":["primary"]},{"code":"EAC","name":"Earthcoin","statuses":["primary"]},{"code":"ECCHI","name":"ECCHI","statuses":["primary"]},{"code":"ECC","name":"ECCoin","statuses":["primary"]},{"code":"ECOS","name":"ECOS","statuses":["primary"]},{"code":"EDC","name":"EDC","statuses":["primary"]},{"code":"EDRC","name":"EDRC","statuses":["primary"]},{"code":"EGG","name":"EGG","statuses":["primary"]},{"code":"EMC2","name":"Einsteinium","statuses":["primary"]},{"code":"EKO","name":"EKO","statuses":["primary"]},{"code":"EL","name":"EL","statuses":["primary"]},{"code":"ELCO","name":"ELcoin","statuses":["primary"]},{"code":"ELE","name":"ELE","statuses":["primary"]},{"code":"EFL","name":"Electronic Gulden","statuses":["primary"]},{"code":"EMC","name":"Emercoin","statuses":["primary"]},{"code":"EMIRG","name":"EMIRG","statuses":["primary"]},{"code":"ENE","name":"ENE","statuses":["primary"]},{"code":"ENRG","name":"Energycoin","statuses":["primary"]},{"code":"EPC","name":"EPC","statuses":["primary"]},{"code":"EPY","name":"EPY","statuses":["primary"]},{"code":"ERC","name":"ERC","statuses":["primary"]},{"code":"ERC3","name":"ERC3","statuses":["primary"]},{"code":"ESC","name":"ESC","statuses":["primary"]},{"code":"ETH","name":"Ethereum","statuses":["primary","secondary"]},{"code":"ETC","name":"Ethereum Classic","statuses":["primary"]},{"code":"ETHS","name":"ETHS","statuses":["primary"]},{"code":"EURC","name":"EURC","statuses":["primary"]},{"code":"EUR","name":"Euro","statuses":["primary","secondary"]},{"code":"EGC","name":"EvergreenCoin","statuses":["primary"]},{"code":"EVIL","name":"EVIL","statuses":["primary"]},{"code":"EVO","name":"EVO","statuses":["primary"]},{"code":"EXCL","name":"EXCL","statuses":["primary"]},{"code":"EXIT","name":"EXIT","statuses":["primary"]},{"code":"EXP","name":"Expanse","statuses":["primary"]},{"code":"FCT","name":"Factom","statuses":["primary"]},{"code":"FAIR","name":"Faircoin","statuses":["primary"]},{"code":"FC2","name":"FC2","statuses":["primary"]},{"code":"FCN","name":"FCN","statuses":["primary"]},{"code":"FTC","name":"Feathercoin","statuses":["primary"]},{"code":"TIPS","name":"Fedoracoin","statuses":["primary"]},{"code":"FFC","name":"FFC","statuses":["primary"]},{"code":"FIBRE","name":"Fibre","statuses":["primary"]},{"code":"FIT","name":"FIT","statuses":["primary"]},{"code":"FJC","name":"FJC","statuses":["primary"]},{"code":"FLO","name":"Florincoin","statuses":["primary"]},{"code":"FLOZ","name":"FLOZ","statuses":["primary"]},{"code":"FLT","name":"FlutterCoin","statuses":["primary"]},{"code":"FLX","name":"FLX","statuses":["primary"]},{"code":"FLY","name":"Flycoin","statuses":["primary"]},{"code":"FLDC","name":"FoldingCoin","statuses":["primary"]},{"code":"FONZ","name":"FONZ","statuses":["primary"]},{"code":"FRK","name":"Franko","statuses":["primary"]},{"code":"FRC","name":"Freicoin","statuses":["primary"]},{"code":"FRN","name":"FRN","statuses":["primary"]},{"code":"FRWC","name":"FRWC","statuses":["primary"]},{"code":"FSC2","name":"FSC2","statuses":["primary"]},{"code":"FST","name":"FST","statuses":["primary"]},{"code":"FTP","name":"FTP","statuses":["primary"]},{"code":"FUN","name":"FUN","statuses":["primary"]},{"code":"FUTC","name":"FUTC","statuses":["primary"]},{"code":"FUZZ","name":"FUZZ","statuses":["primary"]},{"code":"GAIA","name":"GAIA","statuses":["primary"]},{"code":"GAIN","name":"GAIN","statuses":["primary"]},{"code":"GAKH","name":"GAKH","statuses":["primary"]},{"code":"GAM","name":"GAM","statuses":["primary"]},{"code":"GBT","name":"GameBet Coin","statuses":["primary"]},{"code":"GAME","name":"GameCredits","statuses":["primary"]},{"code":"GAP","name":"Gapcoin","statuses":["primary"]},{"code":"GARY","name":"GARY","statuses":["primary"]},{"code":"GB","name":"GB","statuses":["primary"]},{"code":"GBC","name":"GBC","statuses":["primary"]},{"code":"GBIT","name":"GBIT","statuses":["primary"]},{"code":"GCC","name":"GCC","statuses":["primary"]},{"code":"GCN","name":"GCN","statuses":["primary"]},{"code":"GEO","name":"GeoCoin","statuses":["primary"]},{"code":"GEMZ","name":"GetGems","statuses":["primary"]},{"code":"GHOST","name":"GHOST","statuses":["primary"]},{"code":"GHS","name":"GHS","statuses":["primary"]},{"code":"GIFT","name":"GIFT","statuses":["primary"]},{"code":"GIG","name":"GIG","statuses":["primary"]},{"code":"GLC","name":"GLC","statuses":["primary"]},{"code":"BSTY","name":"GlobalBoost-Y","statuses":["primary"]},{"code":"GML","name":"GML","statuses":["primary"]},{"code":"GMX","name":"GMX","statuses":["primary"]},{"code":"GCR","name":"GoCoineR","statuses":["primary"]},{"code":"GLD","name":"GoldCoin","statuses":["primary"]},{"code":"GOON","name":"GOON","statuses":["primary"]},{"code":"GP","name":"GP","statuses":["primary"]},{"code":"GPU","name":"GPU","statuses":["primary"]},{"code":"GRAM","name":"GRAM","statuses":["primary"]},{"code":"GRT","name":"Grantcoin","statuses":["primary"]},{"code":"GRE","name":"GRE","statuses":["primary"]},{"code":"GRC","name":"Gridcoin","statuses":["primary"]},{"code":"GRN","name":"GRN","statuses":["primary"]},{"code":"GRS","name":"Groestlcoin","statuses":["primary"]},{"code":"GRW","name":"GRW","statuses":["primary"]},{"code":"GSM","name":"GSM","statuses":["primary"]},{"code":"GSX","name":"GSX","statuses":["primary"]},{"code":"GUA","name":"GUA","statuses":["primary"]},{"code":"NLG","name":"Gulden","statuses":["primary"]},{"code":"GUN","name":"GUN","statuses":["primary"]},{"code":"HAM","name":"HAM","statuses":["primary"]},{"code":"HAWK","name":"HAWK","statuses":["primary"]},{"code":"HCC","name":"HCC","statuses":["primary"]},{"code":"HEAT","name":"HEAT","statuses":["primary"]},{"code":"HMP","name":"HempCoin","statuses":["primary"]},{"code":"XHI","name":"HiCoin","statuses":["primary"]},{"code":"HIFUN","name":"HIFUN","statuses":["primary"]},{"code":"HILL","name":"HILL","statuses":["primary"]},{"code":"HIRE","name":"HIRE","statuses":["primary"]},{"code":"HNC","name":"HNC","statuses":["primary"]},{"code":"HODL","name":"HOdlcoin","statuses":["primary"]},{"code":"HKD","name":"Hong Kong Dollar","statuses":["secondary"]},{"code":"HZ","name":"Horizon","statuses":["primary"]},{"code":"HTC","name":"HTC","statuses":["primary"]},{"code":"HTML5","name":"HTMLCOIN","statuses":["primary"]},{"code":"HUC","name":"HUC","statuses":["primary"]},{"code":"HVCO","name":"HVCO","statuses":["primary"]},{"code":"HYPER","name":"Hyper","statuses":["primary"]},{"code":"HYP","name":"HyperStake","statuses":["primary"]},{"code":"I0C","name":"I0C","statuses":["primary"]},{"code":"IBANK","name":"IBANK","statuses":["primary"]},{"code":"ICASH","name":"iCash","statuses":["primary"]},{"code":"ICN","name":"ICN","statuses":["primary"]},{"code":"IEC","name":"IEC","statuses":["primary"]},{"code":"IFC","name":"Infinitecoin","statuses":["primary"]},{"code":"INFX","name":"Influxcoin","statuses":["primary"]},{"code":"INV","name":"INV","statuses":["primary"]},{"code":"IOC","name":"IO Coin","statuses":["primary"]},{"code":"ION","name":"ION","statuses":["primary"]},{"code":"IRL","name":"IRL","statuses":["primary"]},{"code":"ISL","name":"IslaCoin","statuses":["primary"]},{"code":"IVZ","name":"IVZ","statuses":["primary"]},{"code":"IXC","name":"IXC","statuses":["primary"]},{"code":"JIF","name":"JIF","statuses":["primary"]},{"code":"JPC","name":"JPC","statuses":["primary"]},{"code":"JPY","name":"JPY","statuses":["primary","secondary"]},{"code":"JBS","name":"Jumbucks","statuses":["primary"]},{"code":"KAT","name":"KAT","statuses":["primary"]},{"code":"KGC","name":"KGC","statuses":["primary"]},{"code":"KNC","name":"KhanCoin","statuses":["primary"]},{"code":"KLC","name":"KLC","statuses":["primary"]},{"code":"KOBO","name":"KOBO","statuses":["primary"]},{"code":"KORE","name":"KoreCoin","statuses":["primary"]},{"code":"KRAK","name":"KRAK","statuses":["primary"]},{"code":"KRYP","name":"KRYP","statuses":["primary"]},{"code":"KR","name":"Krypton","statuses":["primary"]},{"code":"KTK","name":"KTK","statuses":["primary"]},{"code":"KUBO","name":"KUBO","statuses":["primary"]},{"code":"LANA","name":"LANA","statuses":["primary"]},{"code":"LBC","name":"LBC","statuses":["primary"]},{"code":"LC","name":"LC","statuses":["primary"]},{"code":"LEA","name":"LeaCoin","statuses":["primary"]},{"code":"LEMON","name":"LEMON","statuses":["primary"]},{"code":"LEO","name":"LEO","statuses":["primary"]},{"code":"LFC","name":"LFC","statuses":["primary"]},{"code":"LFO","name":"LFO","statuses":["primary"]},{"code":"LFTC","name":"LFTC","statuses":["primary"]},{"code":"LQD","name":"LIQUID","statuses":["primary"]},{"code":"LIR","name":"LIR","statuses":["primary"]},{"code":"LSK","name":"Lisk","statuses":["primary"]},{"code":"LTC","name":"Litecoin","statuses":["primary","secondary"]},{"code":"LTCR","name":"Litecred","statuses":["primary"]},{"code":"LDOGE","name":"LiteDoge","statuses":["primary"]},{"code":"LKC","name":"LKC","statuses":["primary"]},{"code":"LOC","name":"LOC","statuses":["primary"]},{"code":"LOOT","name":"LOOT","statuses":["primary"]},{"code":"LTBC","name":"LTBcoin","statuses":["primary"]},{"code":"LTC","name":"LTC","statuses":["primary","secondary"]},{"code":"LTH","name":"LTH","statuses":["primary"]},{"code":"LTS","name":"LTS","statuses":["primary"]},{"code":"LUN","name":"LUN","statuses":["primary"]},{"code":"LXC","name":"LXC","statuses":["primary"]},{"code":"LYB","name":"LYB","statuses":["primary"]},{"code":"M1","name":"M1","statuses":["primary"]},{"code":"MAD","name":"MAD","statuses":["primary"]},{"code":"XMG","name":"Magi","statuses":["primary"]},{"code":"MAID","name":"MaidSafeCoin","statuses":["primary"]},{"code":"MXT","name":"MarteXcoin","statuses":["primary"]},{"code":"MARV","name":"MARV","statuses":["primary"]},{"code":"MARYJ","name":"MARYJ","statuses":["primary"]},{"code":"OMNI","name":"Mastercoin (Omni)","statuses":["primary"]},{"code":"MTR","name":"MasterTraderCoin","statuses":["primary"]},{"code":"MAX","name":"Maxcoin","statuses":["primary"]},{"code":"MZC","name":"Mazacoin","statuses":["primary"]},{"code":"MBL","name":"MBL","statuses":["primary"]},{"code":"MCAR","name":"MCAR","statuses":["primary"]},{"code":"MCN","name":"MCN","statuses":["primary"]},{"code":"MCZ","name":"MCZ","statuses":["primary"]},{"code":"MED","name":"MediterraneanCoin","statuses":["primary"]},{"code":"MEC","name":"Megacoin","statuses":["primary"]},{"code":"MEME","name":"Memetic","statuses":["primary"]},{"code":"METAL","name":"METAL","statuses":["primary"]},{"code":"MND","name":"MindCoin","statuses":["primary"]},{"code":"MINT","name":"Mintcoin","statuses":["primary"]},{"code":"MIS","name":"MIS","statuses":["primary"]},{"code":"MM","name":"MM","statuses":["primary"]},{"code":"MMC","name":"MMC","statuses":["primary"]},{"code":"MMNXT","name":"MMNXT","statuses":["primary"]},{"code":"MMXVI","name":"MMXVI","statuses":["primary"]},{"code":"MNM","name":"MNM","statuses":["primary"]},{"code":"MOIN","name":"MOIN","statuses":["primary"]},{"code":"MOJO","name":"MojoCoin","statuses":["primary"]},{"code":"MONA","name":"MonaCoin","statuses":["primary"]},{"code":"XMR","name":"Monero","statuses":["primary","secondary"]},{"code":"MNTA","name":"Moneta","statuses":["primary"]},{"code":"MUE","name":"MonetaryUnit","statuses":["primary"]},{"code":"MOON","name":"Mooncoin","statuses":["primary"]},{"code":"MOOND","name":"MOOND","statuses":["primary"]},{"code":"MOTO","name":"MOTO","statuses":["primary"]},{"code":"MPRO","name":"MPRO","statuses":["primary"]},{"code":"MRB","name":"MRB","statuses":["primary"]},{"code":"MRP","name":"MRP","statuses":["primary"]},{"code":"MSC","name":"MSC","statuses":["primary"]},{"code":"MYR","name":"Myriadcoin","statuses":["primary"]},{"code":"NMC","name":"Namecoin","statuses":["primary"]},{"code":"NAUT","name":"Nautiluscoin","statuses":["primary"]},{"code":"NAV","name":"NAV Coin","statuses":["primary"]},{"code":"NCS","name":"NCS","statuses":["primary"]},{"code":"XEM","name":"NEM","statuses":["primary"]},{"code":"NEOS","name":"NeosCoin","statuses":["primary"]},{"code":"NETC","name":"NETC","statuses":["primary"]},{"code":"NET","name":"NetCoin","statuses":["primary"]},{"code":"NEU","name":"NeuCoin","statuses":["primary"]},{"code":"NTRN","name":"Neutron","statuses":["primary"]},{"code":"NEVA","name":"NevaCoin","statuses":["primary"]},{"code":"NEWB","name":"NEWB","statuses":["primary"]},{"code":"NIRO","name":"Nexus","statuses":["primary"]},{"code":"NIC","name":"NIC","statuses":["primary"]},{"code":"NKA","name":"NKA","statuses":["primary"]},{"code":"NKC","name":"NKC","statuses":["primary"]},{"code":"NOBL","name":"NobleCoin","statuses":["primary"]},{"code":"NODE","name":"NODE","statuses":["primary"]},{"code":"NODES","name":"NODES","statuses":["primary"]},{"code":"NOO","name":"NOO","statuses":["primary"]},{"code":"NVC","name":"Novacoin","statuses":["primary"]},{"code":"NRC","name":"NRC","statuses":["primary"]},{"code":"NRS","name":"NRS","statuses":["primary"]},{"code":"NUBIS","name":"NUBIS","statuses":["primary"]},{"code":"NBT","name":"NuBits","statuses":["primary"]},{"code":"NUM","name":"NUM","statuses":["primary"]},{"code":"NSR","name":"NuShares","statuses":["primary"]},{"code":"NXE","name":"NXE","statuses":["primary"]},{"code":"NXT","name":"NXT","statuses":["primary"]},{"code":"NXTTY","name":"Nxttycoin","statuses":["primary"]},{"code":"NYC","name":"NYC","statuses":["primary"]},{"code":"NZC","name":"NZC","statuses":["primary"]},{"code":"NZD","name":"NZD","statuses":["primary","secondary"]},{"code":"OC","name":"OC","statuses":["primary"]},{"code":"OCOW","name":"OCOW","statuses":["primary"]},{"code":"OK","name":"OKCash","statuses":["primary"]},{"code":"OMA","name":"OMA","statuses":["primary"]},{"code":"ONE","name":"ONE","statuses":["primary"]},{"code":"ONEC","name":"ONEC","statuses":["primary"]},{"code":"OP","name":"OP","statuses":["primary"]},{"code":"OPAL","name":"OPAL","statuses":["primary"]},{"code":"OPES","name":"OPES","statuses":["primary"]},{"code":"ORB","name":"Orbitcoin","statuses":["primary"]},{"code":"ORLY","name":"Orlycoin","statuses":["primary"]},{"code":"OS76","name":"OS76","statuses":["primary"]},{"code":"OZC","name":"OZC","statuses":["primary"]},{"code":"PAC","name":"PAC","statuses":["primary"]},{"code":"PAK","name":"PAK","statuses":["primary"]},{"code":"PND","name":"Pandacoin","statuses":["primary"]},{"code":"PAPAF","name":"PAPAF","statuses":["primary"]},{"code":"XPY","name":"Paycoin","statuses":["primary"]},{"code":"PBC","name":"PBC","statuses":["primary"]},{"code":"PDC","name":"PDC","statuses":["primary"]},{"code":"XPB","name":"Pebblecoin","statuses":["primary"]},{"code":"PPC","name":"Peercoin","statuses":["primary"]},{"code":"PEN","name":"PEN","statuses":["primary"]},{"code":"PHR","name":"PHR","statuses":["primary"]},{"code":"PIGGY","name":"Piggycoin","statuses":["primary"]},{"code":"PC","name":"Pinkcoin","statuses":["primary"]},{"code":"PKB","name":"PKB","statuses":["primary"]},{"code":"PLN","name":"PLN","statuses":["primary","secondary"]},{"code":"PLNC","name":"PLNC","statuses":["primary"]},{"code":"PNC","name":"PNC","statuses":["primary"]},{"code":"PNK","name":"PNK","statuses":["primary"]},{"code":"POKE","name":"POKE","statuses":["primary"]},{"code":"PONZ2","name":"PONZ2","statuses":["primary"]},{"code":"PONZI","name":"PONZI","statuses":["primary"]},{"code":"PEX","name":"PosEx","statuses":["primary"]},{"code":"POST","name":"POST","statuses":["primary"]},{"code":"POT","name":"Potcoin","statuses":["primary"]},{"code":"PRES","name":"PRES","statuses":["primary"]},{"code":"PXI","name":"Prime-XI","statuses":["primary"]},{"code":"PRIME","name":"PrimeChain","statuses":["primary"]},{"code":"XPM","name":"Primecoin","statuses":["primary"]},{"code":"PRM","name":"PRM","statuses":["primary"]},{"code":"PRT","name":"PRT","statuses":["primary"]},{"code":"PSP","name":"PSP","statuses":["primary"]},{"code":"PTC","name":"PTC","statuses":["primary"]},{"code":"PULSE","name":"PULSE","statuses":["primary"]},{"code":"PURE","name":"PURE","statuses":["primary"]},{"code":"PUTIN","name":"PUTIN","statuses":["primary"]},{"code":"PWR","name":"PWR","statuses":["primary"]},{"code":"PXL","name":"PXL","statuses":["primary"]},{"code":"QBC","name":"QBC","statuses":["primary"]},{"code":"QBK","name":"QBK","statuses":["primary"]},{"code":"QCN","name":"QCN","statuses":["primary"]},{"code":"QORA","name":"Qora","statuses":["primary"]},{"code":"QTZ","name":"QTZ","statuses":["primary"]},{"code":"QRK","name":"Quark","statuses":["primary"]},{"code":"QTL","name":"Quatloo","statuses":["primary"]},{"code":"RADI","name":"RADI","statuses":["primary"]},{"code":"RADS","name":"Radium","statuses":["primary"]},{"code":"RED","name":"RED","statuses":["primary"]},{"code":"RDD","name":"Reddcoin","statuses":["primary"]},{"code":"REE","name":"REE","statuses":["primary"]},{"code":"REV","name":"Revenu","statuses":["primary"]},{"code":"RBR","name":"RibbitRewards","statuses":["primary"]},{"code":"RICHX","name":"RICHX","statuses":["primary"]},{"code":"RIC","name":"Riecoin","statuses":["primary"]},{"code":"RBT","name":"Rimbit","statuses":["primary"]},{"code":"RIO","name":"RIO","statuses":["primary"]},{"code":"XRP","name":"Ripple","statuses":["primary"]},{"code":"RISE","name":"RISE","statuses":["primary"]},{"code":"RMS","name":"RMS","statuses":["primary"]},{"code":"RONIN","name":"RONIN","statuses":["primary"]},{"code":"ROOT","name":"ROOT","statuses":["primary"]},{"code":"ROS","name":"RosCoin","statuses":["primary"]},{"code":"RPC","name":"RPC","statuses":["primary"]},{"code":"RBIES","name":"Rubies","statuses":["primary"]},{"code":"RUBIT","name":"RUBIT","statuses":["primary"]},{"code":"RUR","name":"Ruble","statuses":["secondary"]},{"code":"RBY","name":"Rubycoin","statuses":["primary"]},{"code":"RUST","name":"RUST","statuses":["primary"]},{"code":"SEC","name":"Safe Exchange Coin","statuses":["primary"]},{"code":"SAK","name":"SAK","statuses":["primary"]},{"code":"SAR","name":"SAR","statuses":["primary"]},{"code":"SBD","name":"SBD","statuses":["primary"]},{"code":"SBIT","name":"SBIT","statuses":["primary"]},{"code":"SCAN","name":"SCAN","statuses":["primary"]},{"code":"SCOT","name":"Scotcoin","statuses":["primary"]},{"code":"SCRPT","name":"SCRPT","statuses":["primary"]},{"code":"SCRT","name":"SCRT","statuses":["primary"]},{"code":"SRC","name":"SecureCoin","statuses":["primary"]},{"code":"SXC","name":"Sexcoin","statuses":["primary"]},{"code":"SFE","name":"SFE","statuses":["primary"]},{"code":"SFR","name":"SFR","statuses":["primary"]},{"code":"SGD","name":"SGD","statuses":["primary","secondary"]},{"code":"SDC","name":"ShadowCash","statuses":["primary"]},{"code":"SHELL","name":"SHELL","statuses":["primary"]},{"code":"SHF","name":"SHF","statuses":["primary"]},{"code":"SHI","name":"SHI","statuses":["primary"]},{"code":"SHIFT","name":"Shift","statuses":["primary"]},{"code":"SHREK","name":"SHREK","statuses":["primary"]},{"code":"SC","name":"Siacoin","statuses":["primary"]},{"code":"SIB","name":"Siberian chervonets","statuses":["primary"]},{"code":"SIC","name":"SIC","statuses":["primary"]},{"code":"SIGU","name":"SIGU","statuses":["primary"]},{"code":"SILK","name":"Silkcoin","statuses":["primary"]},{"code":"SIX","name":"SIX","statuses":["primary"]},{"code":"SLING","name":"Sling","statuses":["primary"]},{"code":"SLS","name":"SLS","statuses":["primary"]},{"code":"SMBR","name":"SMBR","statuses":["primary"]},{"code":"SMC","name":"SMC","statuses":["primary"]},{"code":"SMLY","name":"SmileyCoin","statuses":["primary"]},{"code":"SNRG","name":"SNRG","statuses":["primary"]},{"code":"SOIL","name":"SOILcoin","statuses":["primary"]},{"code":"SLR","name":"Solarcoin","statuses":["primary"]},{"code":"SOLO","name":"SOLO","statuses":["primary"]},{"code":"SONG","name":"SongCoin","statuses":["primary"]},{"code":"SOON","name":"SOON","statuses":["primary"]},{"code":"SPC","name":"SPC","statuses":["primary"]},{"code":"SPEX","name":"SPEX","statuses":["primary"]},{"code":"SPHR","name":"Sphere","statuses":["primary"]},{"code":"SPM","name":"SPM","statuses":["primary"]},{"code":"SPN","name":"SPN","statuses":["primary"]},{"code":"SPOTS","name":"SPOTS","statuses":["primary"]},{"code":"SPR","name":"SpreadCoin","statuses":["primary"]},{"code":"SPRTS","name":"Sprouts","statuses":["primary"]},{"code":"SQC","name":"SQC","statuses":["primary"]},{"code":"SSC","name":"SSC","statuses":["primary"]},{"code":"SSTC","name":"SSTC","statuses":["primary"]},{"code":"STA","name":"STA","statuses":["primary"]},{"code":"START","name":"Startcoin","statuses":["primary"]},{"code":"XST","name":"Stealthcoin","statuses":["primary"]},{"code":"STEEM","name":"Steem","statuses":["primary"]},{"code":"XLM","name":"Stellar","statuses":["primary"]},{"code":"STR","name":"Stellar","statuses":["primary"]},{"code":"STEPS","name":"Steps","statuses":["primary"]},{"code":"SLG","name":"Sterlingcoin","statuses":["primary"]},{"code":"STL","name":"STL","statuses":["primary"]},{"code":"SJCX","name":"Storjcoin X","statuses":["primary"]},{"code":"STP","name":"STP","statuses":["primary"]},{"code":"STRB","name":"STRB","statuses":["primary"]},{"code":"STS","name":"Stress","statuses":["primary"]},{"code":"STRP","name":"STRP","statuses":["primary"]},{"code":"STV","name":"STV","statuses":["primary"]},{"code":"SUB","name":"Subcriptio","statuses":["primary"]},{"code":"SUPER","name":"SUPER","statuses":["primary"]},{"code":"UNITY","name":"SuperNET","statuses":["primary"]},{"code":"SWARM","name":"Swarm","statuses":["primary"]},{"code":"SWING","name":"SWING","statuses":["primary"]},{"code":"SDP","name":"SydPak Coin","statuses":["primary"]},{"code":"SYNC","name":"SYNC","statuses":["primary"]},{"code":"AMP","name":"Synereo","statuses":["primary"]},{"code":"SYS","name":"Syscoin","statuses":["primary"]},{"code":"TAG","name":"TagCoin","statuses":["primary"]},{"code":"TAJ","name":"TAJ","statuses":["primary"]},{"code":"TAK","name":"TAK","statuses":["primary"]},{"code":"TAM","name":"TAM","statuses":["primary"]},{"code":"TAO","name":"TAO","statuses":["primary"]},{"code":"TBC","name":"TBC","statuses":["primary"]},{"code":"TBCX","name":"TBCX","statuses":["primary"]},{"code":"TCR","name":"TCR","statuses":["primary"]},{"code":"TDFB","name":"TDFB","statuses":["primary"]},{"code":"TDY","name":"TDY","statuses":["primary"]},{"code":"TEK","name":"TEKcoin","statuses":["primary"]},{"code":"TRC","name":"Terracoin","statuses":["primary"]},{"code":"TESLA","name":"TESLA","statuses":["primary"]},{"code":"TES","name":"TeslaCoin","statuses":["primary"]},{"code":"TET","name":"TET","statuses":["primary"]},{"code":"USDT","name":"Tether","statuses":["primary","secondary"]},{"code":"THC","name":"THC","statuses":["primary"]},{"code":"THS","name":"THS","statuses":["primary"]},{"code":"TIX","name":"Tickets","statuses":["primary"]},{"code":"XTC","name":"TileCoin","statuses":["primary"]},{"code":"TIT","name":"Titcoin","statuses":["primary"]},{"code":"TTC","name":"TittieCoin","statuses":["primary"]},{"code":"TMC","name":"TMC","statuses":["primary"]},{"code":"TODAY","name":"TODAY","statuses":["primary"]},{"code":"TOKEN","name":"TOKEN","statuses":["primary"]},{"code":"TP1","name":"TP1","statuses":["primary"]},{"code":"TPC","name":"TPC","statuses":["primary"]},{"code":"TPG","name":"TPG","statuses":["primary"]},{"code":"TX","name":"Transfercoin","statuses":["primary"]},{"code":"TRAP","name":"TRAP","statuses":["primary"]},{"code":"TRICK","name":"TRICK","statuses":["primary"]},{"code":"TROLL","name":"TROLL","statuses":["primary"]},{"code":"TRK","name":"Truckcoin","statuses":["primary"]},{"code":"TRUMP","name":"TrumpCoin","statuses":["primary"]},{"code":"TRUST","name":"TRUST","statuses":["primary"]},{"code":"UAE","name":"UAE","statuses":["primary"]},{"code":"UFO","name":"UFO Coin","statuses":["primary"]},{"code":"UIS","name":"UIS","statuses":["primary"]},{"code":"UTC","name":"UltraCoin","statuses":["primary"]},{"code":"UNC","name":"UNC","statuses":["primary"]},{"code":"UNIQ","name":"UNIQ","statuses":["primary"]},{"code":"UNIT","name":"Universal Currency","statuses":["primary"]},{"code":"UNO","name":"Unobtanium","statuses":["primary"]},{"code":"URO","name":"Uro","statuses":["primary"]},{"code":"USD","name":"US Dollar","statuses":["primary","secondary"]},{"code":"USDE","name":"USDE","statuses":["primary"]},{"code":"UTH","name":"UTH","statuses":["primary"]},{"code":"VAL","name":"VAL","statuses":["primary"]},{"code":"XVC","name":"Vcash","statuses":["primary"]},{"code":"VCN","name":"VCN","statuses":["primary"]},{"code":"VEG","name":"VEG","statuses":["primary"]},{"code":"VENE","name":"VENE","statuses":["primary"]},{"code":"XVG","name":"Verge","statuses":["primary"]},{"code":"VRC","name":"VeriCoin","statuses":["primary"]},{"code":"VTC","name":"Vertcoin","statuses":["primary"]},{"code":"VIA","name":"Viacoin","statuses":["primary"]},{"code":"VIOR","name":"Viorcoin","statuses":["primary"]},{"code":"VIP","name":"VIP Tokens","statuses":["primary"]},{"code":"VIRAL","name":"Viral","statuses":["primary"]},{"code":"VOOT","name":"VootCoin","statuses":["primary"]},{"code":"VOX","name":"Voxels","statuses":["primary"]},{"code":"VOYA","name":"VOYA","statuses":["primary"]},{"code":"VPN","name":"VPNCoin","statuses":["primary"]},{"code":"VPRC","name":"VPRC","statuses":["primary"]},{"code":"VTA","name":"VTA","statuses":["primary"]},{"code":"VTN","name":"VTN","statuses":["primary"]},{"code":"VTR","name":"VTR","statuses":["primary"]},{"code":"WAC","name":"WAC","statuses":["primary"]},{"code":"WARP","name":"WARP","statuses":["primary"]},{"code":"WAVES","name":"WAVES","statuses":["primary"]},{"code":"WGC","name":"WGC","statuses":["primary"]},{"code":"XWC","name":"Whitecoin","statuses":["primary"]},{"code":"WBB","name":"Wild Beast Block","statuses":["primary"]},{"code":"WLC","name":"WLC","statuses":["primary"]},{"code":"WMC","name":"WMC","statuses":["primary"]},{"code":"LOG","name":"Woodcoin","statuses":["primary"]},{"code":"WOP","name":"WOP","statuses":["primary"]},{"code":"WDC","name":"Worldcoin","statuses":["primary"]},{"code":"XAB","name":"XAB","statuses":["primary"]},{"code":"XAI","name":"XAI","statuses":["primary"]},{"code":"XAU","name":"Xaurum","statuses":["primary"]},{"code":"XBS","name":"XBS","statuses":["primary"]},{"code":"XBU","name":"XBU","statuses":["primary"]},{"code":"XCO","name":"XCO","statuses":["primary"]},{"code":"XC","name":"XCurrency","statuses":["primary"]},{"code":"XDB","name":"XDB","statuses":["primary"]},{"code":"XEMP","name":"XEMP","statuses":["primary"]},{"code":"XFC","name":"XFC","statuses":["primary"]},{"code":"MI","name":"Xiaomicoin","statuses":["primary"]},{"code":"XID","name":"XID","statuses":["primary"]},{"code":"XJO","name":"XJO","statuses":["primary"]},{"code":"XLTCG","name":"XLTCG","statuses":["primary"]},{"code":"XMS","name":"XMS","statuses":["primary"]},{"code":"XNX","name":"XNX","statuses":["primary"]},{"code":"XPD","name":"XPD","statuses":["primary"]},{"code":"XPOKE","name":"XPOKE","statuses":["primary"]},{"code":"XPRO","name":"XPRO","statuses":["primary"]},{"code":"XQN","name":"XQN","statuses":["primary"]},{"code":"XSEED","name":"XSEED","statuses":["primary"]},{"code":"XSP","name":"XSP","statuses":["primary"]},{"code":"XT","name":"XT","statuses":["primary"]},{"code":"XTP","name":"XTP","statuses":["primary"]},{"code":"XUSD","name":"XUSD","statuses":["primary"]},{"code":"YACC","name":"YACC","statuses":["primary"]},{"code":"YAC","name":"Yacoin","statuses":["primary"]},{"code":"YAY","name":"YAY","statuses":["primary"]},{"code":"YBC","name":"Ybcoin","statuses":["primary"]},{"code":"YOC","name":"YOC","statuses":["primary"]},{"code":"YOVI","name":"YOVI","statuses":["primary"]},{"code":"YUM","name":"YUM","statuses":["primary"]},{"code":"ZCC","name":"ZCC","statuses":["primary"]},{"code":"ZEIT","name":"Zeitcoin","statuses":["primary"]},{"code":"ZET","name":"Zetacoin","statuses":["primary"]},{"code":"ZRC","name":"ZiftrCOIN","statuses":["primary"]},{"code":"ZMC","name":"ZMC","statuses":["primary"]},{"code":"ZNY","name":"ZNY","statuses":["primary"]},{"code":"ZS","name":"ZS","statuses":["primary"]}]} \ No newline at end of file diff --git a/ui/app/conversion.json b/ui/app/conversion.json index eeca164ce..e0c033a76 100644 --- a/ui/app/conversion.json +++ b/ui/app/conversion.json @@ -1,5730 +1 @@ -{ - "rows":[ - { - "code":"007", - "name":"007", - "statuses":[ - "primary" - ] - }, - { - "code":"1337", - "name":"1337", - "statuses":[ - "primary" - ] - }, - { - "code":"1CR", - "name":"1CR", - "statuses":[ - "primary" - ] - }, - { - "code":"256", - "name":"256", - "statuses":[ - "primary" - ] - }, - { - "code":"2FLAV", - "name":"2FLAV", - "statuses":[ - "primary" - ] - }, - { - "code":"2GIVE", - "name":"2GIVE", - "statuses":[ - "primary" - ] - }, - { - "code":"32BIT", - "name":"32BIT", - "statuses":[ - "primary" - ] - }, - { - "code":"404", - "name":"404", - "statuses":[ - "primary" - ] - }, - { - "code":"611", - "name":"611", - "statuses":[ - "primary" - ] - }, - { - "code":"888", - "name":"888", - "statuses":[ - "primary" - ] - }, - { - "code":"8BIT", - "name":"8Bit", - "statuses":[ - "primary" - ] - }, - { - "code":"ACES", - "name":"ACES", - "statuses":[ - "primary" - ] - }, - { - "code":"ACID", - "name":"ACID", - "statuses":[ - "primary" - ] - }, - { - "code":"ACLR", - "name":"ACLR", - "statuses":[ - "primary" - ] - }, - { - "code":"ACP", - "name":"ACP", - "statuses":[ - "primary" - ] - }, - { - "code":"ADC", - "name":"ADC", - "statuses":[ - "primary" - ] - }, - { - "code":"ADZ", - "name":"Adzcoin", - "statuses":[ - "primary" - ] - }, - { - "code":"AEON", - "name":"Aeon", - "statuses":[ - "primary" - ] - }, - { - "code":"AGRS", - "name":"Agoras Tokens", - "statuses":[ - "primary" - ] - }, - { - "code":"AIB", - "name":"AIB", - "statuses":[ - "primary" - ] - }, - { - "code":"ALC", - "name":"ALC", - "statuses":[ - "primary" - ] - }, - { - "code":"ALTC", - "name":"ALTC", - "statuses":[ - "primary" - ] - }, - { - "code":"AM", - "name":"AM", - "statuses":[ - "primary" - ] - }, - { - "code":"AMBER", - "name":"AMBER", - "statuses":[ - "primary" - ] - }, - { - "code":"AMS", - "name":"AMS", - "statuses":[ - "primary" - ] - }, - { - "code":"ANAL", - "name":"ANAL", - "statuses":[ - "primary" - ] - }, - { - "code":"ANI", - "name":"ANI", - "statuses":[ - "primary" - ] - }, - { - "code":"ANC", - "name":"Anoncoin", - "statuses":[ - "primary" - ] - }, - { - "code":"ANS", - "name":"ANS", - "statuses":[ - "primary" - ] - }, - { - "code":"ANTI", - "name":"AntiBitcoin", - "statuses":[ - "primary" - ] - }, - { - "code":"APEX", - "name":"APEX", - "statuses":[ - "primary" - ] - }, - { - "code":"APC", - "name":"Applecoin", - "statuses":[ - "primary" - ] - }, - { - "code":"APT", - "name":"APT", - "statuses":[ - "primary" - ] - }, - { - "code":"AR2", - "name":"AR2", - "statuses":[ - "primary" - ] - }, - { - "code":"ARB", - "name":"ARB", - "statuses":[ - "primary" - ] - }, - { - "code":"ARC", - "name":"ARC", - "statuses":[ - "primary" - ] - }, - { - "code":"ARCH", - "name":"ARCH", - "statuses":[ - "primary" - ] - }, - { - "code":"ARD", - "name":"ARD", - "statuses":[ - "primary" - ] - }, - { - "code":"ARDR", - "name":"ARDR", - "statuses":[ - "primary" - ] - }, - { - "code":"ABY", - "name":"ArtByte", - "statuses":[ - "primary" - ] - }, - { - "code":"ARTC", - "name":"ARTC", - "statuses":[ - "primary" - ] - }, - { - "code":"ASAFE", - "name":"ASAFE", - "statuses":[ - "primary" - ] - }, - { - "code":"ADCN", - "name":"Asiadigicoin", - "statuses":[ - "primary" - ] - }, - { - "code":"ASN", - "name":"ASN", - "statuses":[ - "primary" - ] - }, - { - "code":"ATEN", - "name":"ATEN", - "statuses":[ - "primary" - ] - }, - { - "code":"ATOM", - "name":"ATOM", - "statuses":[ - "primary" - ] - }, - { - "code":"ATX", - "name":"ATX", - "statuses":[ - "primary" - ] - }, - { - "code":"REP", - "name":"Augur", - "statuses":[ - "primary" - ] - }, - { - "code":"AUR", - "name":"Auroracoin", - "statuses":[ - "primary" - ] - }, - { - "code":"AUD", - "name":"Australian Dollar", - "statuses":[ - "secondary" - ] - }, - { - "code":"AV", - "name":"AV", - "statuses":[ - "primary" - ] - }, - { - "code":"B2", - "name":"B2", - "statuses":[ - "primary" - ] - }, - { - "code":"B3", - "name":"B3", - "statuses":[ - "primary" - ] - }, - { - "code":"BA", - "name":"BA", - "statuses":[ - "primary" - ] - }, - { - "code":"BAC", - "name":"BAC", - "statuses":[ - "primary" - ] - }, - { - "code":"BASH", - "name":"BASH", - "statuses":[ - "primary" - ] - }, - { - "code":"BTA", - "name":"Bata", - "statuses":[ - "primary" - ] - }, - { - "code":"BAY", - "name":"BAY", - "statuses":[ - "primary" - ] - }, - { - "code":"BBCC", - "name":"BBCC", - "statuses":[ - "primary" - ] - }, - { - "code":"BQC", - "name":"BBQCoin", - "statuses":[ - "primary" - ] - }, - { - "code":"BEC", - "name":"BEC", - "statuses":[ - "primary" - ] - }, - { - "code":"BEEP", - "name":"BEEP", - "statuses":[ - "primary" - ] - }, - { - "code":"BELA", - "name":"BellaCoin", - "statuses":[ - "primary" - ] - }, - { - "code":"BERN", - "name":"BERNcash", - "statuses":[ - "primary" - ] - }, - { - "code":"BHC", - "name":"BHC", - "statuses":[ - "primary" - ] - }, - { - "code":"BILL", - "name":"BILL", - "statuses":[ - "primary" - ] - }, - { - "code":"BILS", - "name":"BILS", - "statuses":[ - "primary" - ] - }, - { - "code":"BIOS", - "name":"BiosCrypto", - "statuses":[ - "primary" - ] - }, - { - "code":"BIT", - "name":"BIT", - "statuses":[ - "primary" - ] - }, - { - "code":"BIT16", - "name":"BIT16", - "statuses":[ - "primary" - ] - }, - { - "code":"BITB", - "name":"BitBean", - "statuses":[ - "primary" - ] - }, - { - "code":"BTC", - "name":"Bitcoin", - "statuses":[ - "primary", - "secondary" - ] - }, - { - "code":"XBC", - "name":"Bitcoin Plus", - "statuses":[ - "primary" - ] - }, - { - "code":"BTCD", - "name":"BitcoinDark", - "statuses":[ - "primary" - ] - }, - { - "code":"BCY", - "name":"Bitcrystals", - "statuses":[ - "primary" - ] - }, - { - "code":"BFX", - "name":"Bitfinex Debt token", - "statuses":[ - "primary" - ] - }, - { - "code":"BTM", - "name":"Bitmark", - "statuses":[ - "primary" - ] - }, - { - "code":"BITON", - "name":"BITON", - "statuses":[ - "primary" - ] - }, - { - "code":"BTQ", - "name":"BitQuark", - "statuses":[ - "primary" - ] - }, - { - "code":"BITS", - "name":"BITS", - "statuses":[ - "primary" - ] - }, - { - "code":"BSD", - "name":"BitSend", - "statuses":[ - "primary" - ] - }, - { - "code":"BTS", - "name":"BitShares", - "statuses":[ - "primary" - ] - }, - { - "code":"SWIFT", - "name":"BitSwift", - "statuses":[ - "primary" - ] - }, - { - "code":"BITZ", - "name":"Bitz", - "statuses":[ - "primary" - ] - }, - { - "code":"BLK", - "name":"Blackcoin", - "statuses":[ - "primary" - ] - }, - { - "code":"BLC", - "name":"Blakecoin", - "statuses":[ - "primary" - ] - }, - { - "code":"BLEU", - "name":"BLEU", - "statuses":[ - "primary" - ] - }, - { - "code":"BLITZ", - "name":"Blitzcoin", - "statuses":[ - "primary" - ] - }, - { - "code":"BLOCK", - "name":"Blocknet", - "statuses":[ - "primary" - ] - }, - { - "code":"BLRY", - "name":"BLRY", - "statuses":[ - "primary" - ] - }, - { - "code":"BLU", - "name":"BLU", - "statuses":[ - "primary" - ] - }, - { - "code":"BLUS", - "name":"BLUS", - "statuses":[ - "primary" - ] - }, - { - "code":"BNT", - "name":"BNT", - "statuses":[ - "primary" - ] - }, - { - "code":"BOLI", - "name":"Bolivarcoin", - "statuses":[ - "primary" - ] - }, - { - "code":"BBR", - "name":"Boolberry", - "statuses":[ - "primary" - ] - }, - { - "code":"BOOM", - "name":"BOOM", - "statuses":[ - "primary" - ] - }, - { - "code":"BOST", - "name":"BoostCoin", - "statuses":[ - "primary" - ] - }, - { - "code":"BOSS", - "name":"BOSS", - "statuses":[ - "primary" - ] - }, - { - "code":"BPOK", - "name":"BPOK", - "statuses":[ - "primary" - ] - }, - { - "code":"BRAIN", - "name":"BRAIN", - "statuses":[ - "primary" - ] - }, - { - "code":"BRC", - "name":"BRC", - "statuses":[ - "primary" - ] - }, - { - "code":"BRDD", - "name":"BRDD", - "statuses":[ - "primary" - ] - }, - { - "code":"BRIT", - "name":"BRIT", - "statuses":[ - "primary" - ] - }, - { - "code":"GBP", - "name":"British Pound Sterling", - "statuses":[ - "secondary" - ] - }, - { - "code":"BRK", - "name":"BRK", - "statuses":[ - "primary" - ] - }, - { - "code":"BRX", - "name":"BRX", - "statuses":[ - "primary" - ] - }, - { - "code":"BS", - "name":"BS", - "statuses":[ - "primary" - ] - }, - { - "code":"BSC", - "name":"BSC", - "statuses":[ - "primary" - ] - }, - { - "code":"BST", - "name":"BST", - "statuses":[ - "primary" - ] - }, - { - "code":"BTCHC", - "name":"BTCHC", - "statuses":[ - "primary" - ] - }, - { - "code":"BTCR", - "name":"BTCR", - "statuses":[ - "primary" - ] - }, - { - "code":"BTCS", - "name":"BTCS", - "statuses":[ - "primary" - ] - }, - { - "code":"BTD", - "name":"BTD", - "statuses":[ - "primary" - ] - }, - { - "code":"BTLC", - "name":"BTLC", - "statuses":[ - "primary" - ] - }, - { - "code":"BTTF", - "name":"BTTF", - "statuses":[ - "primary" - ] - }, - { - "code":"BTZ", - "name":"BTZ", - "statuses":[ - "primary" - ] - }, - { - "code":"BUCKS", - "name":"BUCKS", - "statuses":[ - "primary" - ] - }, - { - "code":"BUN", - "name":"BUN", - "statuses":[ - "primary" - ] - }, - { - "code":"BURST", - "name":"Burst", - "statuses":[ - "primary" - ] - }, - { - "code":"BUZZ", - "name":"BUZZ", - "statuses":[ - "primary" - ] - }, - { - "code":"BVC", - "name":"BVC", - "statuses":[ - "primary" - ] - }, - { - "code":"BXT", - "name":"BXT", - "statuses":[ - "primary" - ] - }, - { - "code":"BYC", - "name":"Bytecent", - "statuses":[ - "primary" - ] - }, - { - "code":"BCN", - "name":"Bytecoin", - "statuses":[ - "primary" - ] - }, - { - "code":"CAB", - "name":"Cabbage Unit", - "statuses":[ - "primary" - ] - }, - { - "code":"CAGE", - "name":"CAGE", - "statuses":[ - "primary" - ] - }, - { - "code":"CAID", - "name":"CAID", - "statuses":[ - "primary" - ] - }, - { - "code":"CAD", - "name":"Canadian Dollar", - "statuses":[ - "secondary" - ] - }, - { - "code":"CANN", - "name":"CannabisCoin", - "statuses":[ - "primary" - ] - }, - { - "code":"CCN", - "name":"Cannacoin", - "statuses":[ - "primary" - ] - }, - { - "code":"CPC", - "name":"Capricoin", - "statuses":[ - "primary" - ] - }, - { - "code":"CAPT", - "name":"CAPT", - "statuses":[ - "primary" - ] - }, - { - "code":"DIEM", - "name":"CarpeDiemCoin", - "statuses":[ - "primary" - ] - }, - { - "code":"CASH", - "name":"CASH", - "statuses":[ - "primary" - ] - }, - { - "code":"CBD", - "name":"CBD", - "statuses":[ - "primary" - ] - }, - { - "code":"CBIT", - "name":"CBIT", - "statuses":[ - "primary" - ] - }, - { - "code":"CCX", - "name":"CCX", - "statuses":[ - "primary" - ] - }, - { - "code":"CD", - "name":"CD", - "statuses":[ - "primary" - ] - }, - { - "code":"CDN", - "name":"CDN", - "statuses":[ - "primary" - ] - }, - { - "code":"CF", - "name":"CF", - "statuses":[ - "primary" - ] - }, - { - "code":"CGA", - "name":"CGA", - "statuses":[ - "primary" - ] - }, - { - "code":"CKC", - "name":"Checkcoin", - "statuses":[ - "primary" - ] - }, - { - "code":"CHEMX", - "name":"CHEMX", - "statuses":[ - "primary" - ] - }, - { - "code":"CHESS", - "name":"CHESS", - "statuses":[ - "primary" - ] - }, - { - "code":"CHF", - "name":"CHF", - "statuses":[ - "primary", - "secondary" - ] - }, - { - "code":"CNY", - "name":"Chinese Yuan", - "statuses":[ - "secondary" - ] - }, - { - "code":"CHOOF", - "name":"CHOOF", - "statuses":[ - "primary" - ] - }, - { - "code":"CJ", - "name":"CJ", - "statuses":[ - "primary" - ] - }, - { - "code":"CLAM", - "name":"Clams", - "statuses":[ - "primary" - ] - }, - { - "code":"CLICK", - "name":"CLICK", - "statuses":[ - "primary" - ] - }, - { - "code":"CLINT", - "name":"CLINT", - "statuses":[ - "primary" - ] - }, - { - "code":"CLOAK", - "name":"Cloakcoin", - "statuses":[ - "primary" - ] - }, - { - "code":"CLR", - "name":"CLR", - "statuses":[ - "primary" - ] - }, - { - "code":"CLUB", - "name":"CLUB", - "statuses":[ - "primary" - ] - }, - { - "code":"CLUD", - "name":"CLUD", - "statuses":[ - "primary" - ] - }, - { - "code":"CLV", - "name":"CLV", - "statuses":[ - "primary" - ] - }, - { - "code":"CME", - "name":"CME", - "statuses":[ - "primary" - ] - }, - { - "code":"CMT", - "name":"CMT", - "statuses":[ - "primary" - ] - }, - { - "code":"CNC", - "name":"CNC", - "statuses":[ - "primary" - ] - }, - { - "code":"COC", - "name":"COC", - "statuses":[ - "primary" - ] - }, - { - "code":"COXST", - "name":"CoExistCoin", - "statuses":[ - "primary" - ] - }, - { - "code":"COIN", - "name":"COIN", - "statuses":[ - "primary" - ] - }, - { - "code":"C2", - "name":"Coin2.1", - "statuses":[ - "primary" - ] - }, - { - "code":"CV2", - "name":"Colossuscoin2.0", - "statuses":[ - "primary" - ] - }, - { - "code":"CON", - "name":"CON", - "statuses":[ - "primary" - ] - }, - { - "code":"XCP", - "name":"Counterparty", - "statuses":[ - "primary" - ] - }, - { - "code":"COVAL", - "name":"COVAL", - "statuses":[ - "primary" - ] - }, - { - "code":"COX", - "name":"COX", - "statuses":[ - "primary" - ] - }, - { - "code":"CRAB", - "name":"CRAB", - "statuses":[ - "primary" - ] - }, - { - "code":"CRC", - "name":"CRC", - "statuses":[ - "primary" - ] - }, - { - "code":"CRE", - "name":"CRE", - "statuses":[ - "primary" - ] - }, - { - "code":"CRBIT", - "name":"Creditbit", - "statuses":[ - "primary" - ] - }, - { - "code":"CREVA", - "name":"CrevaCoin", - "statuses":[ - "primary" - ] - }, - { - "code":"CRNK", - "name":"CRNK", - "statuses":[ - "primary" - ] - }, - { - "code":"CRPC", - "name":"CRPC", - "statuses":[ - "primary" - ] - }, - { - "code":"CRPS", - "name":"CRPS", - "statuses":[ - "primary" - ] - }, - { - "code":"CRT", - "name":"CRT", - "statuses":[ - "primary" - ] - }, - { - "code":"CRW", - "name":"CRW", - "statuses":[ - "primary" - ] - }, - { - "code":"CRX", - "name":"CRX", - "statuses":[ - "primary" - ] - }, - { - "code":"CRY", - "name":"CRY", - "statuses":[ - "primary" - ] - }, - { - "code":"CBX", - "name":"Crypto Bullion", - "statuses":[ - "primary" - ] - }, - { - "code":"CESC", - "name":"CryptoEscudo", - "statuses":[ - "primary" - ] - }, - { - "code":"XCN", - "name":"Cryptonite", - "statuses":[ - "primary" - ] - }, - { - "code":"CSH", - "name":"CSH", - "statuses":[ - "primary" - ] - }, - { - "code":"CST", - "name":"CST", - "statuses":[ - "primary" - ] - }, - { - "code":"CTK", - "name":"CTK", - "statuses":[ - "primary" - ] - }, - { - "code":"CTL", - "name":"CTL", - "statuses":[ - "primary" - ] - }, - { - "code":"CTO", - "name":"CTO", - "statuses":[ - "primary" - ] - }, - { - "code":"CURE", - "name":"Curecoin", - "statuses":[ - "primary" - ] - }, - { - "code":"CYC", - "name":"CYC", - "statuses":[ - "primary" - ] - }, - { - "code":"CYP", - "name":"Cypher", - "statuses":[ - "primary" - ] - }, - { - "code":"CZC", - "name":"CZC", - "statuses":[ - "primary" - ] - }, - { - "code":"CZR", - "name":"CZR", - "statuses":[ - "primary" - ] - }, - { - "code":"DGD", - "name":"DarkGoldCoin", - "statuses":[ - "primary" - ] - }, - { - "code":"DNET", - "name":"Darknet", - "statuses":[ - "primary" - ] - }, - { - "code":"DAS", - "name":"DAS", - "statuses":[ - "primary" - ] - }, - { - "code":"DASH", - "name":"Dash", - "statuses":[ - "primary" - ] - }, - { - "code":"DTC", - "name":"Datacoin", - "statuses":[ - "primary" - ] - }, - { - "code":"DB", - "name":"DB", - "statuses":[ - "primary" - ] - }, - { - "code":"DBG", - "name":"DBG", - "statuses":[ - "primary" - ] - }, - { - "code":"DBLK", - "name":"DBLK", - "statuses":[ - "primary" - ] - }, - { - "code":"DBTC", - "name":"DBTC", - "statuses":[ - "primary" - ] - }, - { - "code":"DC", - "name":"DC", - "statuses":[ - "primary" - ] - }, - { - "code":"DCK", - "name":"DCK", - "statuses":[ - "primary" - ] - }, - { - "code":"DCRE", - "name":"DCRE", - "statuses":[ - "primary" - ] - }, - { - "code":"DCT", - "name":"DCT", - "statuses":[ - "primary" - ] - }, - { - "code":"DCYP", - "name":"DCYP", - "statuses":[ - "primary" - ] - }, - { - "code":"DCR", - "name":"Decred", - "statuses":[ - "primary" - ] - }, - { - "code":"DES", - "name":"Destiny", - "statuses":[ - "primary" - ] - }, - { - "code":"DEUR", - "name":"DEUR", - "statuses":[ - "primary" - ] - }, - { - "code":"DEM", - "name":"Deutsche eMark", - "statuses":[ - "primary" - ] - }, - { - "code":"DVC", - "name":"Devcoin", - "statuses":[ - "primary" - ] - }, - { - "code":"DGMS", - "name":"DGMS", - "statuses":[ - "primary" - ] - }, - { - "code":"DGORE", - "name":"DGORE", - "statuses":[ - "primary" - ] - }, - { - "code":"DMD", - "name":"Diamond", - "statuses":[ - "primary" - ] - }, - { - "code":"DGB", - "name":"Digibyte", - "statuses":[ - "primary" - ] - }, - { - "code":"CUBE", - "name":"DigiCube", - "statuses":[ - "primary" - ] - }, - { - "code":"DGC", - "name":"Digitalcoin", - "statuses":[ - "primary" - ] - }, - { - "code":"XDN", - "name":"DigitalNote", - "statuses":[ - "primary" - ] - }, - { - "code":"DP", - "name":"DigitalPrice", - "statuses":[ - "primary" - ] - }, - { - "code":"DIME", - "name":"Dimecoin", - "statuses":[ - "primary" - ] - }, - { - "code":"DISK", - "name":"DISK", - "statuses":[ - "primary" - ] - }, - { - "code":"DKC", - "name":"DKC", - "statuses":[ - "primary" - ] - }, - { - "code":"DLC", - "name":"DLC", - "statuses":[ - "primary" - ] - }, - { - "code":"DLISK", - "name":"DLISK", - "statuses":[ - "primary" - ] - }, - { - "code":"DMC", - "name":"DMC", - "statuses":[ - "primary" - ] - }, - { - "code":"NOTE", - "name":"DNotes", - "statuses":[ - "primary" - ] - }, - { - "code":"DOGE", - "name":"Dogecoin", - "statuses":[ - "primary", - "secondary" - ] - }, - { - "code":"DOPE", - "name":"DopeCoin", - "statuses":[ - "primary" - ] - }, - { - "code":"DOV", - "name":"DOV", - "statuses":[ - "primary" - ] - }, - { - "code":"DOX", - "name":"DOX", - "statuses":[ - "primary" - ] - }, - { - "code":"DPAY", - "name":"DPAY", - "statuses":[ - "primary" - ] - }, - { - "code":"DRACO", - "name":"DRACO", - "statuses":[ - "primary" - ] - }, - { - "code":"DRM8", - "name":"DRM8", - "statuses":[ - "primary" - ] - }, - { - "code":"DROP", - "name":"DROP", - "statuses":[ - "primary" - ] - }, - { - "code":"DRZ", - "name":"DRZ", - "statuses":[ - "primary" - ] - }, - { - "code":"DSH", - "name":"DSH", - "statuses":[ - "primary" - ] - }, - { - "code":"DTT", - "name":"DTT", - "statuses":[ - "primary" - ] - }, - { - "code":"DBIC", - "name":"DubaiCoin", - "statuses":[ - "primary" - ] - }, - { - "code":"DUO", - "name":"DUO", - "statuses":[ - "primary" - ] - }, - { - "code":"DUST", - "name":"DUST", - "statuses":[ - "primary" - ] - }, - { - "code":"EAGS", - "name":"EAGS", - "statuses":[ - "primary" - ] - }, - { - "code":"EAC", - "name":"Earthcoin", - "statuses":[ - "primary" - ] - }, - { - "code":"EBST", - "name":"EBST", - "statuses":[ - "primary" - ] - }, - { - "code":"EC", - "name":"EC", - "statuses":[ - "primary" - ] - }, - { - "code":"ECC", - "name":"ECCoin", - "statuses":[ - "primary" - ] - }, - { - "code":"ECLI", - "name":"ECLI", - "statuses":[ - "primary" - ] - }, - { - "code":"EDC", - "name":"EDC", - "statuses":[ - "primary" - ] - }, - { - "code":"EDRC", - "name":"EDRC", - "statuses":[ - "primary" - ] - }, - { - "code":"EDR", - "name":"EDRCoin", - "statuses":[ - "primary" - ] - }, - { - "code":"EGG", - "name":"EGG", - "statuses":[ - "primary" - ] - }, - { - "code":"EGO", - "name":"EGO", - "statuses":[ - "primary" - ] - }, - { - "code":"EMC2", - "name":"Einsteinium", - "statuses":[ - "primary" - ] - }, - { - "code":"EL", - "name":"EL", - "statuses":[ - "primary" - ] - }, - { - "code":"ELE", - "name":"ELE", - "statuses":[ - "primary" - ] - }, - { - "code":"EFL", - "name":"Electronic Gulden", - "statuses":[ - "primary" - ] - }, - { - "code":"EMB", - "name":"EMB", - "statuses":[ - "secondary" - ] - }, - { - "code":"EME", - "name":"EME", - "statuses":[ - "secondary" - ] - }, - { - "code":"EMC", - "name":"Emercoin", - "statuses":[ - "primary" - ] - }, - { - "code":"EMIRG", - "name":"EMIRG", - "statuses":[ - "primary" - ] - }, - { - "code":"EMP", - "name":"EMP", - "statuses":[ - "primary" - ] - }, - { - "code":"EMPC", - "name":"EMPC", - "statuses":[ - "primary" - ] - }, - { - "code":"ENRG", - "name":"Energycoin", - "statuses":[ - "primary" - ] - }, - { - "code":"ENT", - "name":"ENT", - "statuses":[ - "primary" - ] - }, - { - "code":"EPC", - "name":"EPC", - "statuses":[ - "primary" - ] - }, - { - "code":"EQM", - "name":"EQM", - "statuses":[ - "primary" - ] - }, - { - "code":"EQUAL", - "name":"EQUAL", - "statuses":[ - "primary" - ] - }, - { - "code":"ERC", - "name":"ERC", - "statuses":[ - "primary" - ] - }, - { - "code":"ERC3", - "name":"ERC3", - "statuses":[ - "primary" - ] - }, - { - "code":"ESB", - "name":"ESB", - "statuses":[ - "secondary" - ] - }, - { - "code":"ESC", - "name":"ESC", - "statuses":[ - "primary" - ] - }, - { - "code":"ESP", - "name":"ESP", - "statuses":[ - "primary" - ] - }, - { - "code":"ETCO", - "name":"ETCO", - "statuses":[ - "primary" - ] - }, - { - "code":"ETH", - "name":"Ethereum", - "statuses":[ - "primary", - "secondary" - ] - }, - { - "code":"ETC", - "name":"Ethereum Classic", - "statuses":[ - "primary" - ] - }, - { - "code":"ETHS", - "name":"ETHS", - "statuses":[ - "primary" - ] - }, - { - "code":"EUC", - "name":"EUC", - "statuses":[ - "primary" - ] - }, - { - "code":"EUR", - "name":"Euro", - "statuses":[ - "primary", - "secondary" - ] - }, - { - "code":"EGC", - "name":"EvergreenCoin", - "statuses":[ - "primary" - ] - }, - { - "code":"EVIL", - "name":"EVIL", - "statuses":[ - "primary" - ] - }, - { - "code":"EXCL", - "name":"EXCL", - "statuses":[ - "primary" - ] - }, - { - "code":"EXP", - "name":"Expanse", - "statuses":[ - "primary" - ] - }, - { - "code":"FCT", - "name":"Factom", - "statuses":[ - "primary" - ] - }, - { - "code":"FAIR", - "name":"Faircoin", - "statuses":[ - "primary" - ] - }, - { - "code":"FC2", - "name":"FC2", - "statuses":[ - "primary" - ] - }, - { - "code":"FCH", - "name":"FCH", - "statuses":[ - "primary" - ] - }, - { - "code":"FCN", - "name":"FCN", - "statuses":[ - "primary" - ] - }, - { - "code":"FCP", - "name":"FCP", - "statuses":[ - "primary" - ] - }, - { - "code":"FTC", - "name":"Feathercoin", - "statuses":[ - "primary" - ] - }, - { - "code":"TIPS", - "name":"Fedoracoin", - "statuses":[ - "primary" - ] - }, - { - "code":"FIND", - "name":"FIND", - "statuses":[ - "primary" - ] - }, - { - "code":"FIT", - "name":"FIT", - "statuses":[ - "primary" - ] - }, - { - "code":"FJC", - "name":"FJC", - "statuses":[ - "primary" - ] - }, - { - "code":"FLO", - "name":"Florincoin", - "statuses":[ - "primary" - ] - }, - { - "code":"FLOZ", - "name":"FLOZ", - "statuses":[ - "primary" - ] - }, - { - "code":"FLT", - "name":"FlutterCoin", - "statuses":[ - "primary" - ] - }, - { - "code":"FLY", - "name":"Flycoin", - "statuses":[ - "primary" - ] - }, - { - "code":"FLDC", - "name":"FoldingCoin", - "statuses":[ - "primary" - ] - }, - { - "code":"FOREX", - "name":"FOREX", - "statuses":[ - "primary" - ] - }, - { - "code":"FRK", - "name":"Franko", - "statuses":[ - "primary" - ] - }, - { - "code":"FRDC", - "name":"FRDC", - "statuses":[ - "primary" - ] - }, - { - "code":"FRC", - "name":"Freicoin", - "statuses":[ - "primary" - ] - }, - { - "code":"FRN", - "name":"FRN", - "statuses":[ - "primary" - ] - }, - { - "code":"FRWC", - "name":"FRWC", - "statuses":[ - "primary" - ] - }, - { - "code":"FSN", - "name":"FSN", - "statuses":[ - "primary" - ] - }, - { - "code":"FST", - "name":"FST", - "statuses":[ - "primary" - ] - }, - { - "code":"FTP", - "name":"FTP", - "statuses":[ - "primary" - ] - }, - { - "code":"FUEL", - "name":"FUEL", - "statuses":[ - "primary" - ] - }, - { - "code":"FUN", - "name":"FUN", - "statuses":[ - "primary" - ] - }, - { - "code":"FUTC", - "name":"FUTC", - "statuses":[ - "primary" - ] - }, - { - "code":"FUZZ", - "name":"FUZZ", - "statuses":[ - "primary" - ] - }, - { - "code":"FX", - "name":"FX", - "statuses":[ - "primary" - ] - }, - { - "code":"GAIA", - "name":"GAIA", - "statuses":[ - "primary" - ] - }, - { - "code":"GAIN", - "name":"GAIN", - "statuses":[ - "primary" - ] - }, - { - "code":"GAKH", - "name":"GAKH", - "statuses":[ - "primary" - ] - }, - { - "code":"GAM", - "name":"GAM", - "statuses":[ - "primary" - ] - }, - { - "code":"GBT", - "name":"GameBet Coin", - "statuses":[ - "primary" - ] - }, - { - "code":"GAME", - "name":"GameCredits", - "statuses":[ - "primary" - ] - }, - { - "code":"GAP", - "name":"Gapcoin", - "statuses":[ - "primary" - ] - }, - { - "code":"GARY", - "name":"GARY", - "statuses":[ - "primary" - ] - }, - { - "code":"GB", - "name":"GB", - "statuses":[ - "primary" - ] - }, - { - "code":"GBC", - "name":"GBC", - "statuses":[ - "primary" - ] - }, - { - "code":"GBIT", - "name":"GBIT", - "statuses":[ - "primary" - ] - }, - { - "code":"GBRC", - "name":"GBRC", - "statuses":[ - "primary" - ] - }, - { - "code":"GCN", - "name":"GCN", - "statuses":[ - "primary" - ] - }, - { - "code":"GENE", - "name":"GENE", - "statuses":[ - "primary" - ] - }, - { - "code":"GEO", - "name":"GeoCoin", - "statuses":[ - "primary" - ] - }, - { - "code":"GEMZ", - "name":"GetGems", - "statuses":[ - "primary" - ] - }, - { - "code":"GHOST", - "name":"GHOST", - "statuses":[ - "primary" - ] - }, - { - "code":"GHS", - "name":"GHS", - "statuses":[ - "primary" - ] - }, - { - "code":"GLC", - "name":"GLC", - "statuses":[ - "primary" - ] - }, - { - "code":"BSTY", - "name":"GlobalBoost-Y", - "statuses":[ - "primary" - ] - }, - { - "code":"GMCX", - "name":"GMCX", - "statuses":[ - "primary" - ] - }, - { - "code":"GML", - "name":"GML", - "statuses":[ - "primary" - ] - }, - { - "code":"GMX", - "name":"GMX", - "statuses":[ - "primary" - ] - }, - { - "code":"GOAT", - "name":"GOAT", - "statuses":[ - "primary" - ] - }, - { - "code":"GCR", - "name":"GoCoineR", - "statuses":[ - "primary" - ] - }, - { - "code":"GLD", - "name":"GoldCoin", - "statuses":[ - "primary" - ] - }, - { - "code":"GOON", - "name":"GOON", - "statuses":[ - "primary" - ] - }, - { - "code":"GOTX", - "name":"GOTX", - "statuses":[ - "primary" - ] - }, - { - "code":"GP", - "name":"GP", - "statuses":[ - "primary" - ] - }, - { - "code":"GPU", - "name":"GPU", - "statuses":[ - "primary" - ] - }, - { - "code":"GRF", - "name":"Graffiti", - "statuses":[ - "primary" - ] - }, - { - "code":"GRAM", - "name":"GRAM", - "statuses":[ - "primary" - ] - }, - { - "code":"GRT", - "name":"Grantcoin", - "statuses":[ - "primary" - ] - }, - { - "code":"GREED", - "name":"GREED", - "statuses":[ - "primary" - ] - }, - { - "code":"GRC", - "name":"Gridcoin", - "statuses":[ - "primary" - ] - }, - { - "code":"GRN", - "name":"GRN", - "statuses":[ - "primary" - ] - }, - { - "code":"GRS", - "name":"Groestlcoin", - "statuses":[ - "primary" - ] - }, - { - "code":"GROW", - "name":"GrowCoin", - "statuses":[ - "primary" - ] - }, - { - "code":"GRW", - "name":"GRW", - "statuses":[ - "primary" - ] - }, - { - "code":"GSY", - "name":"GSY", - "statuses":[ - "primary" - ] - }, - { - "code":"GUA", - "name":"GUA", - "statuses":[ - "primary" - ] - }, - { - "code":"NLG", - "name":"Gulden", - "statuses":[ - "primary" - ] - }, - { - "code":"GUM", - "name":"GUM", - "statuses":[ - "primary" - ] - }, - { - "code":"GUN", - "name":"GUN", - "statuses":[ - "primary" - ] - }, - { - "code":"GYC", - "name":"GYC", - "statuses":[ - "primary" - ] - }, - { - "code":"HALLO", - "name":"HALLO", - "statuses":[ - "primary" - ] - }, - { - "code":"HAM", - "name":"HAM", - "statuses":[ - "primary" - ] - }, - { - "code":"HBT", - "name":"HBT", - "statuses":[ - "secondary" - ] - }, - { - "code":"HCC", - "name":"HCC", - "statuses":[ - "primary" - ] - }, - { - "code":"HEAT", - "name":"HEAT", - "statuses":[ - "primary" - ] - }, - { - "code":"HMP", - "name":"HempCoin", - "statuses":[ - "primary" - ] - }, - { - "code":"XHI", - "name":"HiCoin", - "statuses":[ - "primary" - ] - }, - { - "code":"HILL", - "name":"HILL", - "statuses":[ - "primary" - ] - }, - { - "code":"HODL", - "name":"HOdlcoin", - "statuses":[ - "primary" - ] - }, - { - "code":"HKD", - "name":"Hong Kong Dollar", - "statuses":[ - "secondary" - ] - }, - { - "code":"HZ", - "name":"Horizon", - "statuses":[ - "primary" - ] - }, - { - "code":"HSP", - "name":"HSP", - "statuses":[ - "primary" - ] - }, - { - "code":"HTC", - "name":"HTC", - "statuses":[ - "primary" - ] - }, - { - "code":"HTML5", - "name":"HTMLCOIN", - "statuses":[ - "primary" - ] - }, - { - "code":"HUC", - "name":"HUC", - "statuses":[ - "primary" - ] - }, - { - "code":"HVCO", - "name":"HVCO", - "statuses":[ - "primary" - ] - }, - { - "code":"HXX", - "name":"HXX", - "statuses":[ - "primary" - ] - }, - { - "code":"HYPER", - "name":"Hyper", - "statuses":[ - "primary" - ] - }, - { - "code":"HYP", - "name":"HyperStake", - "statuses":[ - "primary" - ] - }, - { - "code":"IBANK", - "name":"IBANK", - "statuses":[ - "primary" - ] - }, - { - "code":"ICASH", - "name":"iCash", - "statuses":[ - "primary" - ] - }, - { - "code":"ICN", - "name":"iCoin", - "statuses":[ - "primary" - ] - }, - { - "code":"IFLT", - "name":"IFLT", - "statuses":[ - "primary" - ] - }, - { - "code":"IMPS", - "name":"IMPS", - "statuses":[ - "primary" - ] - }, - { - "code":"INCP", - "name":"INCP", - "statuses":[ - "primary" - ] - }, - { - "code":"IFC", - "name":"Infinitecoin", - "statuses":[ - "primary" - ] - }, - { - "code":"INFX", - "name":"Influxcoin", - "statuses":[ - "primary" - ] - }, - { - "code":"IOC", - "name":"IO Coin", - "statuses":[ - "primary" - ] - }, - { - "code":"ION", - "name":"ION", - "statuses":[ - "primary" - ] - }, - { - "code":"ISL", - "name":"IslaCoin", - "statuses":[ - "primary" - ] - }, - { - "code":"IVZ", - "name":"IVZ", - "statuses":[ - "primary" - ] - }, - { - "code":"IXC", - "name":"IXC", - "statuses":[ - "primary" - ] - }, - { - "code":"JPY", - "name":"Japanese Yen", - "statuses":[ - "secondary" - ] - }, - { - "code":"JOBS", - "name":"JOBS", - "statuses":[ - "primary" - ] - }, - { - "code":"JPC", - "name":"JPC", - "statuses":[ - "primary" - ] - }, - { - "code":"JBS", - "name":"Jumbucks", - "statuses":[ - "primary" - ] - }, - { - "code":"JW", - "name":"JW", - "statuses":[ - "primary" - ] - }, - { - "code":"JWL", - "name":"JWL", - "statuses":[ - "primary" - ] - }, - { - "code":"KAT", - "name":"KAT", - "statuses":[ - "primary" - ] - }, - { - "code":"KC", - "name":"KC", - "statuses":[ - "primary" - ] - }, - { - "code":"KNC", - "name":"KhanCoin", - "statuses":[ - "primary" - ] - }, - { - "code":"KLC", - "name":"KLC", - "statuses":[ - "primary" - ] - }, - { - "code":"KOBO", - "name":"KOBO", - "statuses":[ - "primary" - ] - }, - { - "code":"KORE", - "name":"KoreCoin", - "statuses":[ - "primary" - ] - }, - { - "code":"KRAK", - "name":"KRAK", - "statuses":[ - "primary" - ] - }, - { - "code":"KRB", - "name":"KRB", - "statuses":[ - "primary" - ] - }, - { - "code":"KRC", - "name":"KRC", - "statuses":[ - "primary" - ] - }, - { - "code":"KRYP", - "name":"KRYP", - "statuses":[ - "primary" - ] - }, - { - "code":"KR", - "name":"Krypton", - "statuses":[ - "primary" - ] - }, - { - "code":"KTK", - "name":"KTK", - "statuses":[ - "primary" - ] - }, - { - "code":"LANA", - "name":"LANA", - "statuses":[ - "primary" - ] - }, - { - "code":"LAZ", - "name":"LAZ", - "statuses":[ - "primary" - ] - }, - { - "code":"LBC", - "name":"LBC", - "statuses":[ - "primary" - ] - }, - { - "code":"LC", - "name":"LC", - "statuses":[ - "primary" - ] - }, - { - "code":"LEA", - "name":"LeaCoin", - "statuses":[ - "primary" - ] - }, - { - "code":"LEAF", - "name":"LEAF", - "statuses":[ - "primary" - ] - }, - { - "code":"LEO", - "name":"LEO", - "statuses":[ - "primary", - "secondary" - ] - }, - { - "code":"LFC", - "name":"LFC", - "statuses":[ - "primary" - ] - }, - { - "code":"LFO", - "name":"LFO", - "statuses":[ - "primary" - ] - }, - { - "code":"LFTC", - "name":"LFTC", - "statuses":[ - "primary" - ] - }, - { - "code":"LGBTQ", - "name":"LGBTQ", - "statuses":[ - "primary" - ] - }, - { - "code":"LIR", - "name":"LIR", - "statuses":[ - "primary" - ] - }, - { - "code":"LSK", - "name":"Lisk", - "statuses":[ - "primary" - ] - }, - { - "code":"LTC", - "name":"Litecoin", - "statuses":[ - "primary", - "secondary" - ] - }, - { - "code":"LTCR", - "name":"Litecred", - "statuses":[ - "primary" - ] - }, - { - "code":"LIV", - "name":"LIV", - "statuses":[ - "primary" - ] - }, - { - "code":"LKC", - "name":"LKC", - "statuses":[ - "primary" - ] - }, - { - "code":"LOC", - "name":"LOC", - "statuses":[ - "primary" - ] - }, - { - "code":"LOOT", - "name":"LOOT", - "statuses":[ - "primary" - ] - }, - { - "code":"LTBC", - "name":"LTBcoin", - "statuses":[ - "primary" - ] - }, - { - "code":"LTH", - "name":"LTH", - "statuses":[ - "primary" - ] - }, - { - "code":"LTS", - "name":"LTS", - "statuses":[ - "primary" - ] - }, - { - "code":"LUCKY", - "name":"LUCKY", - "statuses":[ - "primary" - ] - }, - { - "code":"LUN", - "name":"LUN", - "statuses":[ - "primary" - ] - }, - { - "code":"LXC", - "name":"LXC", - "statuses":[ - "primary" - ] - }, - { - "code":"MAD", - "name":"MAD", - "statuses":[ - "primary" - ] - }, - { - "code":"XMG", - "name":"Magi", - "statuses":[ - "primary" - ] - }, - { - "code":"MAID", - "name":"MaidSafeCoin", - "statuses":[ - "primary" - ] - }, - { - "code":"MXT", - "name":"MarteXcoin", - "statuses":[ - "primary" - ] - }, - { - "code":"OMNI", - "name":"Mastercoin (Omni)", - "statuses":[ - "primary" - ] - }, - { - "code":"MTR", - "name":"MasterTraderCoin", - "statuses":[ - "primary" - ] - }, - { - "code":"MAX", - "name":"Maxcoin", - "statuses":[ - "primary" - ] - }, - { - "code":"MZC", - "name":"Mazacoin", - "statuses":[ - "primary" - ] - }, - { - "code":"MBL", - "name":"MBL", - "statuses":[ - "primary" - ] - }, - { - "code":"MCZ", - "name":"MCZ", - "statuses":[ - "primary" - ] - }, - { - "code":"MED", - "name":"MediterraneanCoin", - "statuses":[ - "primary" - ] - }, - { - "code":"MEGA", - "name":"MEGA", - "statuses":[ - "primary" - ] - }, - { - "code":"MEC", - "name":"Megacoin", - "statuses":[ - "primary" - ] - }, - { - "code":"MEME", - "name":"Memetic", - "statuses":[ - "primary" - ] - }, - { - "code":"METAL", - "name":"METAL", - "statuses":[ - "primary" - ] - }, - { - "code":"MG", - "name":"MG", - "statuses":[ - "primary" - ] - }, - { - "code":"MND", - "name":"MindCoin", - "statuses":[ - "primary" - ] - }, - { - "code":"MINT", - "name":"Mintcoin", - "statuses":[ - "primary" - ] - }, - { - "code":"MIS", - "name":"MIS", - "statuses":[ - "primary" - ] - }, - { - "code":"MMNXT", - "name":"MMNXT", - "statuses":[ - "primary" - ] - }, - { - "code":"MMXVI", - "name":"MMXVI", - "statuses":[ - "primary" - ] - }, - { - "code":"MNM", - "name":"MNM", - "statuses":[ - "primary" - ] - }, - { - "code":"MOIN", - "name":"MOIN", - "statuses":[ - "primary" - ] - }, - { - "code":"MOJO", - "name":"MojoCoin", - "statuses":[ - "primary" - ] - }, - { - "code":"MONA", - "name":"MonaCoin", - "statuses":[ - "primary" - ] - }, - { - "code":"XMR", - "name":"Monero", - "statuses":[ - "primary", - "secondary" - ] - }, - { - "code":"MUE", - "name":"MonetaryUnit", - "statuses":[ - "primary" - ] - }, - { - "code":"MOON", - "name":"Mooncoin", - "statuses":[ - "primary" - ] - }, - { - "code":"MOOND", - "name":"MOOND", - "statuses":[ - "primary" - ] - }, - { - "code":"MPRO", - "name":"MPRO", - "statuses":[ - "primary" - ] - }, - { - "code":"MRB", - "name":"MRB", - "statuses":[ - "primary" - ] - }, - { - "code":"MUDRA", - "name":"MUDRA", - "statuses":[ - "primary" - ] - }, - { - "code":"MYR", - "name":"Myriadcoin", - "statuses":[ - "primary" - ] - }, - { - "code":"N2O", - "name":"N2O", - "statuses":[ - "primary" - ] - }, - { - "code":"N7", - "name":"N7", - "statuses":[ - "primary" - ] - }, - { - "code":"NMC", - "name":"Namecoin", - "statuses":[ - "primary" - ] - }, - { - "code":"NAT", - "name":"NAT", - "statuses":[ - "primary" - ] - }, - { - "code":"NAUT", - "name":"Nautiluscoin", - "statuses":[ - "primary" - ] - }, - { - "code":"NAV", - "name":"NAV Coin", - "statuses":[ - "primary" - ] - }, - { - "code":"NBIT", - "name":"NBIT", - "statuses":[ - "primary" - ] - }, - { - "code":"NCS", - "name":"NCS", - "statuses":[ - "primary" - ] - }, - { - "code":"NDOGE", - "name":"NDOGE", - "statuses":[ - "primary" - ] - }, - { - "code":"XEM", - "name":"NEM", - "statuses":[ - "primary" - ] - }, - { - "code":"NEOS", - "name":"NeosCoin", - "statuses":[ - "primary" - ] - }, - { - "code":"NET", - "name":"NetCoin", - "statuses":[ - "primary" - ] - }, - { - "code":"NEU", - "name":"NeuCoin", - "statuses":[ - "primary" - ] - }, - { - "code":"NTRN", - "name":"Neutron", - "statuses":[ - "primary" - ] - }, - { - "code":"NEVA", - "name":"NevaCoin", - "statuses":[ - "primary" - ] - }, - { - "code":"NEWB", - "name":"NEWB", - "statuses":[ - "primary" - ] - }, - { - "code":"NXS", - "name":"Nexus", - "statuses":[ - "primary" - ] - }, - { - "code":"NIC", - "name":"NIC", - "statuses":[ - "primary" - ] - }, - { - "code":"NICE", - "name":"NICE", - "statuses":[ - "primary" - ] - }, - { - "code":"NKC", - "name":"NKC", - "statuses":[ - "primary" - ] - }, - { - "code":"NLC", - "name":"NLC", - "statuses":[ - "primary" - ] - }, - { - "code":"NOBL", - "name":"NobleCoin", - "statuses":[ - "primary" - ] - }, - { - "code":"NODES", - "name":"NODES", - "statuses":[ - "primary" - ] - }, - { - "code":"NVC", - "name":"Novacoin", - "statuses":[ - "primary" - ] - }, - { - "code":"NRS", - "name":"NRS", - "statuses":[ - "primary" - ] - }, - { - "code":"NTC", - "name":"NTC", - "statuses":[ - "primary" - ] - }, - { - "code":"NBT", - "name":"NuBits", - "statuses":[ - "primary" - ] - }, - { - "code":"NUKE", - "name":"NUKE", - "statuses":[ - "primary" - ] - }, - { - "code":"NUM", - "name":"NUM", - "statuses":[ - "primary" - ] - }, - { - "code":"NSR", - "name":"NuShares", - "statuses":[ - "primary" - ] - }, - { - "code":"NXE", - "name":"NXE", - "statuses":[ - "primary" - ] - }, - { - "code":"NXT", - "name":"NXT", - "statuses":[ - "primary" - ] - }, - { - "code":"NXTTY", - "name":"Nxttycoin", - "statuses":[ - "primary" - ] - }, - { - "code":"NYC", - "name":"NYC", - "statuses":[ - "primary" - ] - }, - { - "code":"NZC", - "name":"NZC", - "statuses":[ - "primary" - ] - }, - { - "code":"NZD", - "name":"NZD", - "statuses":[ - "primary", - "secondary" - ] - }, - { - "code":"OBS", - "name":"OBS", - "statuses":[ - "primary" - ] - }, - { - "code":"OCOW", - "name":"OCOW", - "statuses":[ - "primary" - ] - }, - { - "code":"OK", - "name":"OKCash", - "statuses":[ - "primary" - ] - }, - { - "code":"OLYMP", - "name":"OLYMP", - "statuses":[ - "primary" - ] - }, - { - "code":"OMC", - "name":"OMC", - "statuses":[ - "primary" - ] - }, - { - "code":"ONE", - "name":"ONE", - "statuses":[ - "primary" - ] - }, - { - "code":"OP", - "name":"OP", - "statuses":[ - "primary" - ] - }, - { - "code":"OPAL", - "name":"OPAL", - "statuses":[ - "primary" - ] - }, - { - "code":"ORB", - "name":"Orbitcoin", - "statuses":[ - "primary" - ] - }, - { - "code":"OZC", - "name":"OZC", - "statuses":[ - "primary" - ] - }, - { - "code":"PAC", - "name":"PAC", - "statuses":[ - "primary" - ] - }, - { - "code":"PAL", - "name":"PAL", - "statuses":[ - "primary" - ] - }, - { - "code":"PND", - "name":"Pandacoin", - "statuses":[ - "primary" - ] - }, - { - "code":"PARA", - "name":"PARA", - "statuses":[ - "primary" - ] - }, - { - "code":"PAY", - "name":"PAY", - "statuses":[ - "primary" - ] - }, - { - "code":"XPY", - "name":"Paycoin", - "statuses":[ - "primary" - ] - }, - { - "code":"PBC", - "name":"PBC", - "statuses":[ - "primary" - ] - }, - { - "code":"PCM", - "name":"PCM", - "statuses":[ - "primary" - ] - }, - { - "code":"PCS", - "name":"PCS", - "statuses":[ - "primary" - ] - }, - { - "code":"PDC", - "name":"PDC", - "statuses":[ - "primary" - ] - }, - { - "code":"PEC", - "name":"PEC", - "statuses":[ - "primary" - ] - }, - { - "code":"PPC", - "name":"Peercoin", - "statuses":[ - "primary" - ] - }, - { - "code":"PEN", - "name":"PEN", - "statuses":[ - "primary" - ] - }, - { - "code":"PHR", - "name":"PHR", - "statuses":[ - "primary" - ] - }, - { - "code":"PIN", - "name":"PIN", - "statuses":[ - "primary" - ] - }, - { - "code":"PC", - "name":"Pinkcoin", - "statuses":[ - "primary" - ] - }, - { - "code":"PIO", - "name":"PIO", - "statuses":[ - "primary" - ] - }, - { - "code":"PIZZA", - "name":"PIZZA", - "statuses":[ - "primary" - ] - }, - { - "code":"PKB", - "name":"PKB", - "statuses":[ - "primary" - ] - }, - { - "code":"PLN", - "name":"PLN", - "statuses":[ - "primary", - "secondary" - ] - }, - { - "code":"PLNC", - "name":"PLNC", - "statuses":[ - "primary" - ] - }, - { - "code":"PNK", - "name":"PNK", - "statuses":[ - "primary" - ] - }, - { - "code":"POKE", - "name":"POKE", - "statuses":[ - "primary" - ] - }, - { - "code":"PONZ2", - "name":"PONZ2", - "statuses":[ - "primary" - ] - }, - { - "code":"PONZI", - "name":"PONZI", - "statuses":[ - "primary" - ] - }, - { - "code":"PEX", - "name":"PosEx", - "statuses":[ - "primary" - ] - }, - { - "code":"POST", - "name":"POST", - "statuses":[ - "primary" - ] - }, - { - "code":"POT", - "name":"Potcoin", - "statuses":[ - "primary" - ] - }, - { - "code":"PRE", - "name":"PRE", - "statuses":[ - "primary" - ] - }, - { - "code":"PRES", - "name":"PRES", - "statuses":[ - "primary" - ] - }, - { - "code":"PXI", - "name":"Prime-XI", - "statuses":[ - "primary" - ] - }, - { - "code":"PRIME", - "name":"PrimeChain", - "statuses":[ - "primary" - ] - }, - { - "code":"XPM", - "name":"Primecoin", - "statuses":[ - "primary" - ] - }, - { - "code":"PRM", - "name":"PRM", - "statuses":[ - "primary" - ] - }, - { - "code":"PRT", - "name":"PRT", - "statuses":[ - "primary" - ] - }, - { - "code":"PSB", - "name":"PSB", - "statuses":[ - "primary" - ] - }, - { - "code":"PSP", - "name":"PSP", - "statuses":[ - "primary" - ] - }, - { - "code":"PSY", - "name":"PSY", - "statuses":[ - "primary" - ] - }, - { - "code":"PTC", - "name":"PTC", - "statuses":[ - "primary" - ] - }, - { - "code":"PURE", - "name":"PURE", - "statuses":[ - "primary" - ] - }, - { - "code":"PUTIN", - "name":"PUTIN", - "statuses":[ - "primary" - ] - }, - { - "code":"PWR", - "name":"PWR", - "statuses":[ - "primary" - ] - }, - { - "code":"PX", - "name":"PX", - "statuses":[ - "primary" - ] - }, - { - "code":"PXL", - "name":"PXL", - "statuses":[ - "primary" - ] - }, - { - "code":"QBC", - "name":"QBC", - "statuses":[ - "primary" - ] - }, - { - "code":"QBK", - "name":"QBK", - "statuses":[ - "primary" - ] - }, - { - "code":"QCN", - "name":"QCN", - "statuses":[ - "primary" - ] - }, - { - "code":"QORA", - "name":"Qora", - "statuses":[ - "primary" - ] - }, - { - "code":"QTZ", - "name":"QTZ", - "statuses":[ - "primary" - ] - }, - { - "code":"QRK", - "name":"Quark", - "statuses":[ - "primary" - ] - }, - { - "code":"QTL", - "name":"Quatloo", - "statuses":[ - "primary" - ] - }, - { - "code":"RADI", - "name":"RADI", - "statuses":[ - "primary" - ] - }, - { - "code":"RADS", - "name":"Radium", - "statuses":[ - "primary" - ] - }, - { - "code":"XRA", - "name":"RateCoin", - "statuses":[ - "primary" - ] - }, - { - "code":"RBIT", - "name":"RBIT", - "statuses":[ - "primary" - ] - }, - { - "code":"RCN", - "name":"RCN", - "statuses":[ - "primary" - ] - }, - { - "code":"RED", - "name":"RED", - "statuses":[ - "primary" - ] - }, - { - "code":"RDD", - "name":"Reddcoin", - "statuses":[ - "primary" - ] - }, - { - "code":"REE", - "name":"REE", - "statuses":[ - "primary" - ] - }, - { - "code":"REV", - "name":"Revenu", - "statuses":[ - "primary" - ] - }, - { - "code":"RICHX", - "name":"RICHX", - "statuses":[ - "primary" - ] - }, - { - "code":"RIC", - "name":"Riecoin", - "statuses":[ - "primary" - ] - }, - { - "code":"RBT", - "name":"Rimbit", - "statuses":[ - "primary", - "secondary" - ] - }, - { - "code":"RIO", - "name":"RIO", - "statuses":[ - "primary" - ] - }, - { - "code":"XRP", - "name":"Ripple", - "statuses":[ - "primary" - ] - }, - { - "code":"RISE", - "name":"RISE", - "statuses":[ - "primary" - ] - }, - { - "code":"RMS", - "name":"RMS", - "statuses":[ - "primary" - ] - }, - { - "code":"RONIN", - "name":"RONIN", - "statuses":[ - "primary" - ] - }, - { - "code":"ROYAL", - "name":"ROYAL", - "statuses":[ - "primary" - ] - }, - { - "code":"RPC", - "name":"RPC", - "statuses":[ - "primary" - ] - }, - { - "code":"RRT", - "name":"RRT", - "statuses":[ - "primary" - ] - }, - { - "code":"RBIES", - "name":"Rubies", - "statuses":[ - "primary" - ] - }, - { - "code":"RUBIT", - "name":"RUBIT", - "statuses":[ - "primary" - ] - }, - { - "code":"RUR", - "name":"Ruble", - "statuses":[ - "secondary" - ] - }, - { - "code":"RBY", - "name":"Rubycoin", - "statuses":[ - "primary" - ] - }, - { - "code":"RUST", - "name":"RUST", - "statuses":[ - "primary" - ] - }, - { - "code":"RYCN", - "name":"RYCN", - "statuses":[ - "primary" - ] - }, - { - "code":"SEC", - "name":"Safe Exchange Coin", - "statuses":[ - "primary" - ] - }, - { - "code":"SAK", - "name":"SAK", - "statuses":[ - "primary" - ] - }, - { - "code":"SAR", - "name":"SAR", - "statuses":[ - "primary" - ] - }, - { - "code":"SBD", - "name":"SBD", - "statuses":[ - "primary" - ] - }, - { - "code":"SCAN", - "name":"SCAN", - "statuses":[ - "primary" - ] - }, - { - "code":"SCB", - "name":"SCB", - "statuses":[ - "primary" - ] - }, - { - "code":"SCN", - "name":"SCN", - "statuses":[ - "primary" - ] - }, - { - "code":"SCOT", - "name":"Scotcoin", - "statuses":[ - "primary" - ] - }, - { - "code":"SCRPT", - "name":"SCRPT", - "statuses":[ - "primary" - ] - }, - { - "code":"SCRT", - "name":"SCRT", - "statuses":[ - "primary" - ] - }, - { - "code":"SCT", - "name":"SCT", - "statuses":[ - "primary" - ] - }, - { - "code":"SRC", - "name":"SecureCoin", - "statuses":[ - "primary" - ] - }, - { - "code":"SED", - "name":"SED", - "statuses":[ - "primary" - ] - }, - { - "code":"SXC", - "name":"Sexcoin", - "statuses":[ - "primary" - ] - }, - { - "code":"SGD", - "name":"SGD", - "statuses":[ - "primary", - "secondary" - ] - }, - { - "code":"SH", - "name":"SH", - "statuses":[ - "primary" - ] - }, - { - "code":"SDC", - "name":"ShadowCash", - "statuses":[ - "primary" - ] - }, - { - "code":"SHELL", - "name":"SHELL", - "statuses":[ - "primary" - ] - }, - { - "code":"SHI", - "name":"SHI", - "statuses":[ - "primary" - ] - }, - { - "code":"SHIFT", - "name":"Shift", - "statuses":[ - "primary" - ] - }, - { - "code":"SC", - "name":"Siacoin", - "statuses":[ - "primary" - ] - }, - { - "code":"SIB", - "name":"Siberian chervonets", - "statuses":[ - "primary" - ] - }, - { - "code":"SIGU", - "name":"SIGU", - "statuses":[ - "primary" - ] - }, - { - "code":"SLFI", - "name":"SLFI", - "statuses":[ - "primary" - ] - }, - { - "code":"SLING", - "name":"Sling", - "statuses":[ - "primary" - ] - }, - { - "code":"SLK", - "name":"SLK", - "statuses":[ - "primary" - ] - }, - { - "code":"SLS", - "name":"SLS", - "statuses":[ - "primary" - ] - }, - { - "code":"SMC", - "name":"SMC", - "statuses":[ - "primary" - ] - }, - { - "code":"SMLY", - "name":"SmileyCoin", - "statuses":[ - "primary" - ] - }, - { - "code":"SNGLS", - "name":"SNGLS", - "statuses":[ - "primary" - ] - }, - { - "code":"SNRG", - "name":"SNRG", - "statuses":[ - "primary" - ] - }, - { - "code":"SOIL", - "name":"SOILcoin", - "statuses":[ - "primary" - ] - }, - { - "code":"SLR", - "name":"Solarcoin", - "statuses":[ - "primary" - ] - }, - { - "code":"SONG", - "name":"SongCoin", - "statuses":[ - "primary" - ] - }, - { - "code":"SOON", - "name":"SOON", - "statuses":[ - "primary" - ] - }, - { - "code":"SP", - "name":"SP", - "statuses":[ - "primary" - ] - }, - { - "code":"SPACE", - "name":"SPACE", - "statuses":[ - "primary" - ] - }, - { - "code":"SPEX", - "name":"SPEX", - "statuses":[ - "primary" - ] - }, - { - "code":"SPHR", - "name":"Sphere", - "statuses":[ - "primary" - ] - }, - { - "code":"SPKTR", - "name":"SPKTR", - "statuses":[ - "primary" - ] - }, - { - "code":"SPN", - "name":"SPN", - "statuses":[ - "primary" - ] - }, - { - "code":"SPORT", - "name":"SPORT", - "statuses":[ - "primary" - ] - }, - { - "code":"SPR", - "name":"SpreadCoin", - "statuses":[ - "primary" - ] - }, - { - "code":"SPT", - "name":"SPT", - "statuses":[ - "primary" - ] - }, - { - "code":"SPX", - "name":"SPX", - "statuses":[ - "primary" - ] - }, - { - "code":"SSC", - "name":"SSC", - "statuses":[ - "primary" - ] - }, - { - "code":"STA", - "name":"STA", - "statuses":[ - "primary" - ] - }, - { - "code":"STAR", - "name":"STAR", - "statuses":[ - "primary" - ] - }, - { - "code":"START", - "name":"Startcoin", - "statuses":[ - "primary" - ] - }, - { - "code":"STE", - "name":"STE", - "statuses":[ - "primary" - ] - }, - { - "code":"XST", - "name":"Stealthcoin", - "statuses":[ - "primary" - ] - }, - { - "code":"STEEM", - "name":"Steem", - "statuses":[ - "primary" - ] - }, - { - "code":"XLM", - "name":"Stellar", - "statuses":[ - "primary" - ] - }, - { - "code":"STR", - "name":"Stellar", - "statuses":[ - "primary" - ] - }, - { - "code":"STEPS", - "name":"Steps", - "statuses":[ - "primary" - ] - }, - { - "code":"SLG", - "name":"Sterlingcoin", - "statuses":[ - "primary" - ] - }, - { - "code":"STHR", - "name":"STHR", - "statuses":[ - "primary" - ] - }, - { - "code":"STL", - "name":"STL", - "statuses":[ - "primary" - ] - }, - { - "code":"STO", - "name":"STO", - "statuses":[ - "primary" - ] - }, - { - "code":"SJCX", - "name":"Storjcoin X", - "statuses":[ - "primary" - ] - }, - { - "code":"STP", - "name":"STP", - "statuses":[ - "primary" - ] - }, - { - "code":"STRAT", - "name":"STRAT", - "statuses":[ - "primary" - ] - }, - { - "code":"STS", - "name":"Stress", - "statuses":[ - "primary" - ] - }, - { - "code":"STV", - "name":"STV", - "statuses":[ - "primary" - ] - }, - { - "code":"SUB", - "name":"Subcriptio", - "statuses":[ - "primary" - ] - }, - { - "code":"UNITY", - "name":"SuperNET", - "statuses":[ - "primary" - ] - }, - { - "code":"SWEET", - "name":"SWEET", - "statuses":[ - "primary" - ] - }, - { - "code":"SWING", - "name":"SWING", - "statuses":[ - "primary" - ] - }, - { - "code":"SYNC", - "name":"SYNC", - "statuses":[ - "primary" - ] - }, - { - "code":"AMP", - "name":"Synereo", - "statuses":[ - "primary" - ] - }, - { - "code":"SYNX", - "name":"SYNX", - "statuses":[ - "primary" - ] - }, - { - "code":"SYS", - "name":"Syscoin", - "statuses":[ - "primary" - ] - }, - { - "code":"TAB", - "name":"TAB", - "statuses":[ - "primary" - ] - }, - { - "code":"TAG", - "name":"TagCoin", - "statuses":[ - "primary" - ] - }, - { - "code":"TAJ", - "name":"TAJ", - "statuses":[ - "primary" - ] - }, - { - "code":"TAK", - "name":"TAK", - "statuses":[ - "primary" - ] - }, - { - "code":"TAO", - "name":"TAO", - "statuses":[ - "primary" - ] - }, - { - "code":"TBC", - "name":"TBC", - "statuses":[ - "primary" - ] - }, - { - "code":"TC", - "name":"TC", - "statuses":[ - "secondary" - ] - }, - { - "code":"TCOIN", - "name":"TCOIN", - "statuses":[ - "primary" - ] - }, - { - "code":"TCR", - "name":"TCR", - "statuses":[ - "primary" - ] - }, - { - "code":"TDFB", - "name":"TDFB", - "statuses":[ - "primary" - ] - }, - { - "code":"TDY", - "name":"TDY", - "statuses":[ - "primary" - ] - }, - { - "code":"TEAM", - "name":"TEAM", - "statuses":[ - "primary" - ] - }, - { - "code":"TEC", - "name":"TEC", - "statuses":[ - "primary" - ] - }, - { - "code":"TECH", - "name":"TECH", - "statuses":[ - "primary" - ] - }, - { - "code":"TEK", - "name":"TEKcoin", - "statuses":[ - "primary" - ] - }, - { - "code":"TRC", - "name":"Terracoin", - "statuses":[ - "primary" - ] - }, - { - "code":"TESLA", - "name":"TESLA", - "statuses":[ - "primary" - ] - }, - { - "code":"TES", - "name":"TeslaCoin", - "statuses":[ - "primary" - ] - }, - { - "code":"TET", - "name":"TET", - "statuses":[ - "primary" - ] - }, - { - "code":"THC", - "name":"THC", - "statuses":[ - "primary" - ] - }, - { - "code":"DAO", - "name":"The DAO", - "statuses":[ - "primary" - ] - }, - { - "code":"TIA", - "name":"TIA", - "statuses":[ - "primary" - ] - }, - { - "code":"TIX", - "name":"Tickets", - "statuses":[ - "primary" - ] - }, - { - "code":"XTC", - "name":"TileCoin", - "statuses":[ - "primary" - ] - }, - { - "code":"TIT", - "name":"Titcoin", - "statuses":[ - "primary" - ] - }, - { - "code":"TMC", - "name":"TMC", - "statuses":[ - "primary" - ] - }, - { - "code":"TNG", - "name":"TNG", - "statuses":[ - "primary" - ] - }, - { - "code":"TODAY", - "name":"TODAY", - "statuses":[ - "primary" - ] - }, - { - "code":"TOKEN", - "name":"TOKEN", - "statuses":[ - "primary" - ] - }, - { - "code":"TP1", - "name":"TP1", - "statuses":[ - "primary" - ] - }, - { - "code":"TPC", - "name":"TPC", - "statuses":[ - "primary" - ] - }, - { - "code":"TPG", - "name":"TPG", - "statuses":[ - "primary" - ] - }, - { - "code":"TX", - "name":"Transfercoin", - "statuses":[ - "primary" - ] - }, - { - "code":"TRAP", - "name":"TRAP", - "statuses":[ - "primary" - ] - }, - { - "code":"TRICK", - "name":"TRICK", - "statuses":[ - "primary" - ] - }, - { - "code":"TRIG", - "name":"TRIG", - "statuses":[ - "primary" - ] - }, - { - "code":"TROLL", - "name":"TROLL", - "statuses":[ - "primary" - ] - }, - { - "code":"TRK", - "name":"Truckcoin", - "statuses":[ - "primary" - ] - }, - { - "code":"TRUMP", - "name":"TrumpCoin", - "statuses":[ - "primary" - ] - }, - { - "code":"TRUST", - "name":"TRUST", - "statuses":[ - "primary" - ] - }, - { - "code":"TSC", - "name":"TSC", - "statuses":[ - "primary" - ] - }, - { - "code":"TWERK", - "name":"TWERK", - "statuses":[ - "primary" - ] - }, - { - "code":"TWIST", - "name":"TWIST", - "statuses":[ - "primary" - ] - }, - { - "code":"TWO", - "name":"TWO", - "statuses":[ - "primary" - ] - }, - { - "code":"UAE", - "name":"UAE", - "statuses":[ - "primary" - ] - }, - { - "code":"UB", - "name":"UB", - "statuses":[ - "primary" - ] - }, - { - "code":"UFO", - "name":"UFO Coin", - "statuses":[ - "primary" - ] - }, - { - "code":"UIS", - "name":"UIS", - "statuses":[ - "primary" - ] - }, - { - "code":"UAH", - "name":"Ukrainian Hryvnia", - "statuses":[ - "secondary" - ] - }, - { - "code":"UTC", - "name":"UltraCoin", - "statuses":[ - "primary" - ] - }, - { - "code":"UNB", - "name":"UNB", - "statuses":[ - "primary" - ] - }, - { - "code":"UNC", - "name":"UNC", - "statuses":[ - "primary" - ] - }, - { - "code":"UNF", - "name":"Unfed", - "statuses":[ - "primary" - ] - }, - { - "code":"UNIQ", - "name":"UNIQ", - "statuses":[ - "primary" - ] - }, - { - "code":"UNIT", - "name":"Universal Currency", - "statuses":[ - "primary" - ] - }, - { - "code":"UNO", - "name":"Unobtanium", - "statuses":[ - "primary" - ] - }, - { - "code":"URC", - "name":"URC", - "statuses":[ - "primary" - ] - }, - { - "code":"URO", - "name":"Uro", - "statuses":[ - "primary" - ] - }, - { - "code":"USD", - "name":"US Dollar", - "statuses":[ - "primary", - "secondary" - ] - }, - { - "code":"USDE", - "name":"USDE", - "statuses":[ - "primary" - ] - }, - { - "code":"XVC", - "name":"Vcash", - "statuses":[ - "primary" - ] - }, - { - "code":"VCN", - "name":"VCN", - "statuses":[ - "primary" - ] - }, - { - "code":"VCOIN", - "name":"VCOIN", - "statuses":[ - "primary" - ] - }, - { - "code":"VEC", - "name":"VEC", - "statuses":[ - "primary" - ] - }, - { - "code":"VEG", - "name":"VEG", - "statuses":[ - "primary" - ] - }, - { - "code":"XVG", - "name":"Verge", - "statuses":[ - "primary" - ] - }, - { - "code":"VRC", - "name":"VeriCoin", - "statuses":[ - "primary", - "secondary" - ] - }, - { - "code":"VTC", - "name":"Vertcoin", - "statuses":[ - "primary" - ] - }, - { - "code":"VIA", - "name":"Viacoin", - "statuses":[ - "primary" - ] - }, - { - "code":"VIP", - "name":"VIP Tokens", - "statuses":[ - "primary" - ] - }, - { - "code":"VIRAL", - "name":"Viral", - "statuses":[ - "primary" - ] - }, - { - "code":"VLT", - "name":"VLT", - "statuses":[ - "primary" - ] - }, - { - "code":"VOOT", - "name":"VootCoin", - "statuses":[ - "primary" - ] - }, - { - "code":"VOX", - "name":"Voxels", - "statuses":[ - "primary" - ] - }, - { - "code":"VOYA", - "name":"VOYA", - "statuses":[ - "primary" - ] - }, - { - "code":"VPN", - "name":"VPNCoin", - "statuses":[ - "primary" - ] - }, - { - "code":"VRM", - "name":"VRM", - "statuses":[ - "primary" - ] - }, - { - "code":"VRS", - "name":"VRS", - "statuses":[ - "primary" - ] - }, - { - "code":"VTA", - "name":"VTA", - "statuses":[ - "primary" - ] - }, - { - "code":"VTN", - "name":"VTN", - "statuses":[ - "primary" - ] - }, - { - "code":"VTR", - "name":"VTR", - "statuses":[ - "primary" - ] - }, - { - "code":"VTY", - "name":"VTY", - "statuses":[ - "primary" - ] - }, - { - "code":"WA", - "name":"WA", - "statuses":[ - "primary" - ] - }, - { - "code":"WAC", - "name":"WAC", - "statuses":[ - "primary" - ] - }, - { - "code":"WARP", - "name":"WARP", - "statuses":[ - "primary" - ] - }, - { - "code":"WASH", - "name":"WASH", - "statuses":[ - "primary" - ] - }, - { - "code":"WAV", - "name":"WAV", - "statuses":[ - "primary" - ] - }, - { - "code":"WAVES", - "name":"WAVES", - "statuses":[ - "primary" - ] - }, - { - "code":"WAY", - "name":"WAY", - "statuses":[ - "primary" - ] - }, - { - "code":"WCN", - "name":"WCN", - "statuses":[ - "primary" - ] - }, - { - "code":"WEX", - "name":"WEX", - "statuses":[ - "primary" - ] - }, - { - "code":"WGC", - "name":"WGC", - "statuses":[ - "primary" - ] - }, - { - "code":"XWC", - "name":"Whitecoin", - "statuses":[ - "primary" - ] - }, - { - "code":"WBB", - "name":"Wild Beast Block", - "statuses":[ - "primary" - ] - }, - { - "code":"WINE", - "name":"WINE", - "statuses":[ - "primary" - ] - }, - { - "code":"WLC", - "name":"WLC", - "statuses":[ - "primary" - ] - }, - { - "code":"WMC", - "name":"WMC", - "statuses":[ - "primary" - ] - }, - { - "code":"LOG", - "name":"Woodcoin", - "statuses":[ - "primary" - ] - }, - { - "code":"WDC", - "name":"Worldcoin", - "statuses":[ - "primary" - ] - }, - { - "code":"WRP", - "name":"WRP", - "statuses":[ - "primary" - ] - }, - { - "code":"X2", - "name":"X2", - "statuses":[ - "primary" - ] - }, - { - "code":"X2C", - "name":"X2C", - "statuses":[ - "primary" - ] - }, - { - "code":"XAB", - "name":"XAB", - "statuses":[ - "primary" - ] - }, - { - "code":"XAUR", - "name":"XAUR", - "statuses":[ - "primary" - ] - }, - { - "code":"XAU", - "name":"Xaurum", - "statuses":[ - "primary" - ] - }, - { - "code":"XBS", - "name":"XBS", - "statuses":[ - "primary" - ] - }, - { - "code":"XBTS", - "name":"XBTS", - "statuses":[ - "primary" - ] - }, - { - "code":"XBU", - "name":"XBU", - "statuses":[ - "primary" - ] - }, - { - "code":"XCO", - "name":"XCO", - "statuses":[ - "primary" - ] - }, - { - "code":"XC", - "name":"XCurrency", - "statuses":[ - "primary" - ] - }, - { - "code":"XDB", - "name":"XDB", - "statuses":[ - "primary" - ] - }, - { - "code":"XDE2", - "name":"XDE2", - "statuses":[ - "primary" - ] - }, - { - "code":"MI", - "name":"Xiaomicoin", - "statuses":[ - "primary" - ] - }, - { - "code":"XID", - "name":"XID", - "statuses":[ - "primary" - ] - }, - { - "code":"XJO", - "name":"XJO", - "statuses":[ - "primary" - ] - }, - { - "code":"XLTCG", - "name":"XLTCG", - "statuses":[ - "primary" - ] - }, - { - "code":"XMINE", - "name":"XMINE", - "statuses":[ - "primary" - ] - }, - { - "code":"XMS", - "name":"XMS", - "statuses":[ - "primary" - ] - }, - { - "code":"XNG", - "name":"XNG", - "statuses":[ - "primary" - ] - }, - { - "code":"XODUS", - "name":"XODUS", - "statuses":[ - "primary" - ] - }, - { - "code":"XPC", - "name":"XPC", - "statuses":[ - "primary" - ] - }, - { - "code":"XPO", - "name":"XPO", - "statuses":[ - "primary" - ] - }, - { - "code":"XPOKE", - "name":"XPOKE", - "statuses":[ - "primary" - ] - }, - { - "code":"XPRO", - "name":"XPRO", - "statuses":[ - "primary" - ] - }, - { - "code":"XPTX", - "name":"XPTX", - "statuses":[ - "primary" - ] - }, - { - "code":"XQN", - "name":"XQN", - "statuses":[ - "primary" - ] - }, - { - "code":"XRC", - "name":"XRC", - "statuses":[ - "primary" - ] - }, - { - "code":"XSEED", - "name":"XSEED", - "statuses":[ - "primary" - ] - }, - { - "code":"XSY", - "name":"XSY", - "statuses":[ - "primary" - ] - }, - { - "code":"XTP", - "name":"XTP", - "statuses":[ - "primary" - ] - }, - { - "code":"XUP", - "name":"XUP", - "statuses":[ - "primary" - ] - }, - { - "code":"YAC", - "name":"Yacoin", - "statuses":[ - "primary" - ] - }, - { - "code":"YAY", - "name":"YAY", - "statuses":[ - "primary" - ] - }, - { - "code":"YBC", - "name":"Ybcoin", - "statuses":[ - "primary" - ] - }, - { - "code":"YMC", - "name":"YMC", - "statuses":[ - "primary" - ] - }, - { - "code":"YOC", - "name":"YOC", - "statuses":[ - "primary" - ] - }, - { - "code":"YOVI", - "name":"YOVI", - "statuses":[ - "primary" - ] - }, - { - "code":"YUM", - "name":"YUM", - "statuses":[ - "primary" - ] - }, - { - "code":"ZEC", - "name":"Zcash", - "statuses":[ - "primary" - ] - }, - { - "code":"ZCL", - "name":"Zcash classic", - "statuses":[ - "primary" - ] - }, - { - "code":"ZCC", - "name":"ZCC", - "statuses":[ - "primary" - ] - }, - { - "code":"ZCOIN", - "name":"ZCOIN", - "statuses":[ - "primary" - ] - }, - { - "code":"XZC", - "name":"Zcoin", - "statuses":[ - "primary" - ] - }, - { - "code":"ZECD", - "name":"ZECD", - "statuses":[ - "primary" - ] - }, - { - "code":"ZEIT", - "name":"Zeitcoin", - "statuses":[ - "primary" - ] - }, - { - "code":"ZET2", - "name":"ZET2", - "statuses":[ - "primary" - ] - }, - { - "code":"ZET", - "name":"Zetacoin", - "statuses":[ - "primary" - ] - }, - { - "code":"ZRC", - "name":"ZiftrCOIN", - "statuses":[ - "primary" - ] - }, - { - "code":"ZLQ", - "name":"ZLQ", - "statuses":[ - "primary" - ] - }, - { - "code":"ZMC", - "name":"ZMC", - "statuses":[ - "primary" - ] - }, - { - "code":"ZNE", - "name":"ZNE", - "statuses":[ - "primary" - ] - }, - { - "code":"ZNY", - "name":"ZNY", - "statuses":[ - "primary" - ] - }, - { - "code":"ZS", - "name":"ZS", - "statuses":[ - "primary" - ] - }, - { - "code":"ZUR", - "name":"ZUR", - "statuses":[ - "primary" - ] - }, - { - "code":"ZXT", - "name":"ZXT", - "statuses":[ - "primary" - ] - }, - { - "code":"ZYD", - "name":"ZYD", - "statuses":[ - "primary" - ] - } - ] -} \ No newline at end of file +{"rows":[{"code":"REP","name":"Augur","statuses":["primary"]},{"code":"BCN","name":"Bytecoin","statuses":["primary"]},{"code":"BTC","name":"Bitcoin","statuses":["primary","secondary"]},{"code":"BTS","name":"BitShares","statuses":["primary","secondary"]},{"code":"BLK","name":"Blackcoin","statuses":["primary"]},{"code":"GBP","name":"British Pound Sterling","statuses":["secondary"]},{"code":"CAD","name":"Canadian Dollar","statuses":["secondary"]},{"code":"CNY","name":"Chinese Yuan","statuses":["secondary"]},{"code":"DSH","name":"Dashcoin","statuses":["primary"]},{"code":"DOGE","name":"Dogecoin","statuses":["primary","secondary"]},{"code":"ETC","name":"Ethereum Classic","statuses":["primary"]},{"code":"EUR","name":"Euro","statuses":["primary","secondary"]},{"code":"GNO","name":"GNO","statuses":["primary"]},{"code":"GNT","name":"GNT","statuses":["primary"]},{"code":"JPY","name":"Japanese Yen","statuses":["secondary"]},{"code":"LTC","name":"Litecoin","statuses":["primary","secondary"]},{"code":"MAID","name":"MaidSafeCoin","statuses":["primary"]},{"code":"XEM","name":"NEM","statuses":["primary"]},{"code":"XLM","name":"Stellar","statuses":["primary"]},{"code":"XMR","name":"Monero","statuses":["primary","secondary"]},{"code":"XRP","name":"Ripple","statuses":["primary"]},{"code":"RUR","name":"Ruble","statuses":["secondary"]},{"code":"STEEM","name":"Steem","statuses":["primary"]},{"code":"STRAT","name":"STRAT","statuses":["primary"]},{"code":"UAH","name":"Ukrainian Hryvnia","statuses":["secondary"]},{"code":"USD","name":"US Dollar","statuses":["primary","secondary"]},{"code":"WAVES","name":"WAVES","statuses":["primary"]},{"code":"ZEC","name":"Zcash","statuses":["primary"]}]} From d389d0efd645966c251c49673009bbc113645068 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Thu, 11 May 2017 12:31:08 -0700 Subject: [PATCH 228/372] Bump changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 532abcca9..422639f70 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Current Master +- Trim currency list. + ## 3.6.4 2017-5-8 - Fix main-net ENS resolution. From 6c01b26845cfb8e113d3016ebd595a9098623f62 Mon Sep 17 00:00:00 2001 From: frankiebee Date: Thu, 11 May 2017 10:46:17 +0200 Subject: [PATCH 229/372] use asyncQ.waterfall instead of asyncQ.eachSeries --- app/scripts/lib/migrator/index.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/app/scripts/lib/migrator/index.js b/app/scripts/lib/migrator/index.js index caa0ef318..0bf88dbec 100644 --- a/app/scripts/lib/migrator/index.js +++ b/app/scripts/lib/migrator/index.js @@ -14,9 +14,15 @@ class Migrator { migrateData (versionedData = this.generateInitialState()) { const remaining = this.migrations.filter(migrationIsPending) if (remaining.length === 0) return versionedData + + const migrations = remaining.map((migration, i) => { + if (i === 0) return this.runMigration.bind(this, migration, versionedData) + return this.runMigration.bind(this, migration) + }) + return ( - asyncQ.eachSeries(remaining, (migration) => this.runMigration(versionedData, migration)) - .then((migratedData) => migratedData.pop()) + asyncQ.waterfall(migrations) + .then((migratedData) => Promise.resolve(migratedData)) ) // migration is "pending" if hit has a higher @@ -26,10 +32,10 @@ class Migrator { } } - runMigration (versionedData, migration) { + runMigration (migration, versionedData) { return migration.migrate(versionedData) .then((migratedData) => { - if (!migratedData.data) return Promise.reject(new Error('Migrator - Migration returned empty data')) + if (!migratedData.data) return Promise.reject(new Error('Migrator - migration returned empty data')) if (migration.version !== undefined && migratedData.meta.version !== migration.version) return Promise.reject(new Error('Migrator - Migration did not update version number correctly')) return Promise.resolve(migratedData) From 8421cf9ccea80e4958a1a70d068bbffa57577390 Mon Sep 17 00:00:00 2001 From: frankiebee Date: Thu, 11 May 2017 19:47:58 +0200 Subject: [PATCH 230/372] Create test for Migrator --- test/unit/migrator-test.js | 41 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 test/unit/migrator-test.js diff --git a/test/unit/migrator-test.js b/test/unit/migrator-test.js new file mode 100644 index 000000000..ece95b9f6 --- /dev/null +++ b/test/unit/migrator-test.js @@ -0,0 +1,41 @@ +const assert = require('assert') +const clone = require('clone') +const Migrator = require('../../app/scripts/lib/migrator/') +const migrations = [ + { + version: 1, + migrate: (data) => { + // clone the data just like we do in migrations + const clonedData = clone(data) + clonedData.meta.version = 1 + return Promise.resolve(clonedData) + }, + }, + { + version: 2, + migrate: (data) => { + const clonedData = clone(data) + clonedData.meta.version = 2 + return Promise.resolve(clonedData) + }, + }, + { + version: 3, + migrate: (data) => { + const clonedData = clone(data) + clonedData.meta.version = 3 + return Promise.resolve(clonedData) + }, + }, +] +const versionedData = {meta: {version: 0}, data:{hello:'world'}} +describe('Migrator', () => { + const migrator = new Migrator({ migrations }) + it('migratedData version should be version 3', (done) => { + migrator.migrateData(versionedData) + .then((migratedData) => { + assert.equal(migratedData.meta.version, migrations[2].version) + done() + }).catch(done) + }) +}) From 113f7d67f1973d1468b95517895701af8ca16f95 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Thu, 11 May 2017 14:29:44 -0700 Subject: [PATCH 231/372] Fix tests add logs --- test/unit/components/pending-tx-test.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/test/unit/components/pending-tx-test.js b/test/unit/components/pending-tx-test.js index e0f02a5bb..3e26fc6f5 100644 --- a/test/unit/components/pending-tx-test.js +++ b/test/unit/components/pending-tx-test.js @@ -1,7 +1,7 @@ var assert = require('assert') var PendingTx = require('../../../ui/app/components/pending-tx') -describe('PendingTx', function () { +describe.only('PendingTx', function () { let pendingTxComponent const identities = { @@ -44,17 +44,21 @@ describe('PendingTx', function () { const noop = () => {} - pendingTxComponent.componentDidMount = () => { + setTimeout(() => { + console.log('component mounted') const newGasPrice = '0x451456' pendingTxComponent.gasPriceChanged(newGasPrice) setTimeout(() => { + console.log('hitting submit') pendingTxComponent.onSubmit({ preventDefault: noop }) }, 20) - } + }, 200) + console.log('calling render') pendingTxComponent.props = props + pendingTxComponent.checkValidity = () => { return true } pendingTxComponent.render() }) From 16005ebd3a7db5c48f9d81d5a1c77ace7ff92958 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Thu, 11 May 2017 15:28:33 -0700 Subject: [PATCH 232/372] Got test failing --- test/unit/components/pending-tx-test.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/unit/components/pending-tx-test.js b/test/unit/components/pending-tx-test.js index 3e26fc6f5..b798865cc 100644 --- a/test/unit/components/pending-tx-test.js +++ b/test/unit/components/pending-tx-test.js @@ -30,12 +30,15 @@ describe.only('PendingTx', function () { it('should use updated values when edited.', function (done) { + const newGasPrice = '0x451456' + const props = { identities, accounts: identities, txData, sendTransaction: (txMeta, event) => { assert.notEqual(txMeta.txParams.gasPrice, gasPrice, 'gas price should change') + assert.equal(txMeta.txParams.gasPrice, newGasPrice, 'gas price assigned.') done() }, } @@ -47,7 +50,6 @@ describe.only('PendingTx', function () { setTimeout(() => { console.log('component mounted') - const newGasPrice = '0x451456' pendingTxComponent.gasPriceChanged(newGasPrice) setTimeout(() => { From 974368fe0849e58dc24398ee814d24fd799dff3b Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Thu, 11 May 2017 16:28:11 -0700 Subject: [PATCH 233/372] Prettify JSON --- ui/app/conversion.json | 208 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 207 insertions(+), 1 deletion(-) diff --git a/ui/app/conversion.json b/ui/app/conversion.json index e0c033a76..155ffc4fc 100644 --- a/ui/app/conversion.json +++ b/ui/app/conversion.json @@ -1 +1,207 @@ -{"rows":[{"code":"REP","name":"Augur","statuses":["primary"]},{"code":"BCN","name":"Bytecoin","statuses":["primary"]},{"code":"BTC","name":"Bitcoin","statuses":["primary","secondary"]},{"code":"BTS","name":"BitShares","statuses":["primary","secondary"]},{"code":"BLK","name":"Blackcoin","statuses":["primary"]},{"code":"GBP","name":"British Pound Sterling","statuses":["secondary"]},{"code":"CAD","name":"Canadian Dollar","statuses":["secondary"]},{"code":"CNY","name":"Chinese Yuan","statuses":["secondary"]},{"code":"DSH","name":"Dashcoin","statuses":["primary"]},{"code":"DOGE","name":"Dogecoin","statuses":["primary","secondary"]},{"code":"ETC","name":"Ethereum Classic","statuses":["primary"]},{"code":"EUR","name":"Euro","statuses":["primary","secondary"]},{"code":"GNO","name":"GNO","statuses":["primary"]},{"code":"GNT","name":"GNT","statuses":["primary"]},{"code":"JPY","name":"Japanese Yen","statuses":["secondary"]},{"code":"LTC","name":"Litecoin","statuses":["primary","secondary"]},{"code":"MAID","name":"MaidSafeCoin","statuses":["primary"]},{"code":"XEM","name":"NEM","statuses":["primary"]},{"code":"XLM","name":"Stellar","statuses":["primary"]},{"code":"XMR","name":"Monero","statuses":["primary","secondary"]},{"code":"XRP","name":"Ripple","statuses":["primary"]},{"code":"RUR","name":"Ruble","statuses":["secondary"]},{"code":"STEEM","name":"Steem","statuses":["primary"]},{"code":"STRAT","name":"STRAT","statuses":["primary"]},{"code":"UAH","name":"Ukrainian Hryvnia","statuses":["secondary"]},{"code":"USD","name":"US Dollar","statuses":["primary","secondary"]},{"code":"WAVES","name":"WAVES","statuses":["primary"]},{"code":"ZEC","name":"Zcash","statuses":["primary"]}]} +{ + "rows": [ + { + "code": "REP", + "name": "Augur", + "statuses": [ + "primary" + ] + }, + { + "code": "BCN", + "name": "Bytecoin", + "statuses": [ + "primary" + ] + }, + { + "code": "BTC", + "name": "Bitcoin", + "statuses": [ + "primary", + "secondary" + ] + }, + { + "code": "BTS", + "name": "BitShares", + "statuses": [ + "primary", + "secondary" + ] + }, + { + "code": "BLK", + "name": "Blackcoin", + "statuses": [ + "primary" + ] + }, + { + "code": "GBP", + "name": "British Pound Sterling", + "statuses": [ + "secondary" + ] + }, + { + "code": "CAD", + "name": "Canadian Dollar", + "statuses": [ + "secondary" + ] + }, + { + "code": "CNY", + "name": "Chinese Yuan", + "statuses": [ + "secondary" + ] + }, + { + "code": "DSH", + "name": "Dashcoin", + "statuses": [ + "primary" + ] + }, + { + "code": "DOGE", + "name": "Dogecoin", + "statuses": [ + "primary", + "secondary" + ] + }, + { + "code": "ETC", + "name": "Ethereum Classic", + "statuses": [ + "primary" + ] + }, + { + "code": "EUR", + "name": "Euro", + "statuses": [ + "primary", + "secondary" + ] + }, + { + "code": "GNO", + "name": "GNO", + "statuses": [ + "primary" + ] + }, + { + "code": "GNT", + "name": "GNT", + "statuses": [ + "primary" + ] + }, + { + "code": "JPY", + "name": "Japanese Yen", + "statuses": [ + "secondary" + ] + }, + { + "code": "LTC", + "name": "Litecoin", + "statuses": [ + "primary", + "secondary" + ] + }, + { + "code": "MAID", + "name": "MaidSafeCoin", + "statuses": [ + "primary" + ] + }, + { + "code": "XEM", + "name": "NEM", + "statuses": [ + "primary" + ] + }, + { + "code": "XLM", + "name": "Stellar", + "statuses": [ + "primary" + ] + }, + { + "code": "XMR", + "name": "Monero", + "statuses": [ + "primary", + "secondary" + ] + }, + { + "code": "XRP", + "name": "Ripple", + "statuses": [ + "primary" + ] + }, + { + "code": "RUR", + "name": "Ruble", + "statuses": [ + "secondary" + ] + }, + { + "code": "STEEM", + "name": "Steem", + "statuses": [ + "primary" + ] + }, + { + "code": "STRAT", + "name": "STRAT", + "statuses": [ + "primary" + ] + }, + { + "code": "UAH", + "name": "Ukrainian Hryvnia", + "statuses": [ + "secondary" + ] + }, + { + "code": "USD", + "name": "US Dollar", + "statuses": [ + "primary", + "secondary" + ] + }, + { + "code": "WAVES", + "name": "WAVES", + "statuses": [ + "primary" + ] + }, + { + "code": "ZEC", + "name": "Zcash", + "statuses": [ + "primary" + ] + } + ] +} From 60746a985997693612af0c8b43aac95b2a6e56e6 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Thu, 11 May 2017 17:09:23 -0700 Subject: [PATCH 234/372] Use react test utils to start composing test --- package.json | 4 ++++ test/unit/components/pending-tx-test.js | 25 +++++++++++++++++++------ 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index e2268d20a..5f2436112 100644 --- a/package.json +++ b/package.json @@ -132,9 +132,11 @@ "browserify": "^13.0.0", "chai": "^3.5.0", "clone": "^1.0.2", + "create-react-factory": "^0.2.1", "deep-freeze-strict": "^1.1.1", "del": "^2.2.0", "envify": "^4.0.0", + "enzyme": "^2.8.2", "eslint-plugin-chai": "0.0.1", "eslint-plugin-mocha": "^4.9.0", "fs-promise": "^1.0.0", @@ -161,6 +163,8 @@ "prompt": "^1.0.0", "qs": "^6.2.0", "qunit": "^0.9.1", + "react-addons-test-utils": "^15.5.1", + "react-dom": "^15.5.4", "sinon": "^1.17.3", "tape": "^4.5.1", "testem": "^1.10.3", diff --git a/test/unit/components/pending-tx-test.js b/test/unit/components/pending-tx-test.js index b798865cc..caaf66b49 100644 --- a/test/unit/components/pending-tx-test.js +++ b/test/unit/components/pending-tx-test.js @@ -1,5 +1,13 @@ var assert = require('assert') +const h = require('react-hyperscript') var PendingTx = require('../../../ui/app/components/pending-tx') +const createReactFactory = require('create-react-factory').createReactFactory +const React = require('react') +console.dir(createReactFactory) +const shallow = require('enzyme').shallow +const Factory = createReactFactory(PendingTx) +const ReactTestUtils = require('react-addons-test-utils') +const renderer = ReactTestUtils.createRenderer(); describe.only('PendingTx', function () { let pendingTxComponent @@ -43,14 +51,21 @@ describe.only('PendingTx', function () { }, } - pendingTxComponent = new PendingTx(props) + const pendingTxComponent = h(PendingTx, props) + renderer.render(pendingTxComponent) + console.dir(pendingTxComponent) const noop = () => {} setTimeout(() => { - console.log('component mounted') + console.log('timeout finished') - pendingTxComponent.gasPriceChanged(newGasPrice) + // Get the gas price input + // Set it to the newGasPrice value + // Wait for the value to change + // Get the submit button + // Click the submit button + // Get the output of the submit event. setTimeout(() => { console.log('hitting submit') @@ -59,9 +74,7 @@ describe.only('PendingTx', function () { }, 200) console.log('calling render') - pendingTxComponent.props = props - pendingTxComponent.checkValidity = () => { return true } - pendingTxComponent.render() }) }) + From de5cf2526ca3b3791545afc32d90a3bb88a8b8e3 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Thu, 11 May 2017 17:15:45 -0700 Subject: [PATCH 235/372] Fix test up a bit --- test/unit/components/pending-tx-test.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/test/unit/components/pending-tx-test.js b/test/unit/components/pending-tx-test.js index caaf66b49..5ddea7b23 100644 --- a/test/unit/components/pending-tx-test.js +++ b/test/unit/components/pending-tx-test.js @@ -7,7 +7,6 @@ console.dir(createReactFactory) const shallow = require('enzyme').shallow const Factory = createReactFactory(PendingTx) const ReactTestUtils = require('react-addons-test-utils') -const renderer = ReactTestUtils.createRenderer(); describe.only('PendingTx', function () { let pendingTxComponent @@ -38,6 +37,7 @@ describe.only('PendingTx', function () { it('should use updated values when edited.', function (done) { + const renderer = ReactTestUtils.createRenderer(); const newGasPrice = '0x451456' const props = { @@ -53,7 +53,9 @@ describe.only('PendingTx', function () { const pendingTxComponent = h(PendingTx, props) renderer.render(pendingTxComponent) - console.dir(pendingTxComponent) + const result = renderer.getRenderOutput() + assert.equal(result.type, 'div', 'should create a div') + console.dir(result) const noop = () => {} @@ -67,10 +69,6 @@ describe.only('PendingTx', function () { // Click the submit button // Get the output of the submit event. - setTimeout(() => { - console.log('hitting submit') - pendingTxComponent.onSubmit({ preventDefault: noop }) - }, 20) }, 200) console.log('calling render') From f0eeb1e1620b9c607390505c2e443979c9b44b1a Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Thu, 11 May 2017 17:43:40 -0700 Subject: [PATCH 236/372] Got a useful error message for next step --- package.json | 1 + test/unit/components/pending-tx-test.js | 24 ++++++++++++++++++++++-- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 5f2436112..11931b32f 100644 --- a/package.json +++ b/package.json @@ -165,6 +165,7 @@ "qunit": "^0.9.1", "react-addons-test-utils": "^15.5.1", "react-dom": "^15.5.4", + "react-testutils-additions": "^15.2.0", "sinon": "^1.17.3", "tape": "^4.5.1", "testem": "^1.10.3", diff --git a/test/unit/components/pending-tx-test.js b/test/unit/components/pending-tx-test.js index 5ddea7b23..2594a1a26 100644 --- a/test/unit/components/pending-tx-test.js +++ b/test/unit/components/pending-tx-test.js @@ -1,4 +1,5 @@ -var assert = require('assert') +const assert = require('assert') +const additions = require('react-testutils-additions') const h = require('react-hyperscript') var PendingTx = require('../../../ui/app/components/pending-tx') const createReactFactory = require('create-react-factory').createReactFactory @@ -52,10 +53,28 @@ describe.only('PendingTx', function () { } const pendingTxComponent = h(PendingTx, props) + var component = additions.renderIntoDocument(pendingTxComponent); renderer.render(pendingTxComponent) const result = renderer.getRenderOutput() + const form = result.props.children + console.log('FORM children') + console.dir(form.props.children) + const children = form.props.children[form.props.children.length - 1] assert.equal(result.type, 'div', 'should create a div') - console.dir(result) + console.dir(children) + + console.log('finding input') + + try{ + + const input = additions.find(component, '.cell.row input[type="number"]') + console.log('input') + console.dir(input) + + } catch (e) { + console.log("WHAAAA") + console.error(e) + } const noop = () => {} @@ -68,6 +87,7 @@ describe.only('PendingTx', function () { // Get the submit button // Click the submit button // Get the output of the submit event. + // Assert that the value was updated. }, 200) From 70a328e028230f8bffbd88a28d961fde6a4b819f Mon Sep 17 00:00:00 2001 From: kumavis Date: Thu, 11 May 2017 18:15:59 -0700 Subject: [PATCH 237/372] migrator - cleaner migration runner with es7 --- app/scripts/lib/migrator/index.js | 38 +++++++++++-------------------- 1 file changed, 13 insertions(+), 25 deletions(-) diff --git a/app/scripts/lib/migrator/index.js b/app/scripts/lib/migrator/index.js index 0bf88dbec..ed07a0c60 100644 --- a/app/scripts/lib/migrator/index.js +++ b/app/scripts/lib/migrator/index.js @@ -1,47 +1,35 @@ -const asyncQ = require('async-q') - class Migrator { constructor (opts = {}) { const migrations = opts.migrations || [] + // sort migrations by version this.migrations = migrations.sort((a, b) => a.version - b.version) + // grab migration with highest version const lastMigration = this.migrations.slice(-1)[0] // use specified defaultVersion or highest migration version this.defaultVersion = opts.defaultVersion || (lastMigration && lastMigration.version) || 0 } // run all pending migrations on meta in place - migrateData (versionedData = this.generateInitialState()) { - const remaining = this.migrations.filter(migrationIsPending) - if (remaining.length === 0) return versionedData - - const migrations = remaining.map((migration, i) => { - if (i === 0) return this.runMigration.bind(this, migration, versionedData) - return this.runMigration.bind(this, migration) - }) + async migrateData (versionedData = this.generateInitialState()) { + const pendingMigrations = this.migrations.filter(migrationIsPending) + + for (let index in pendingMigrations) { + let migration = pendingMigrations[index] + versionedData = await migration.migrate(versionedData) + if (!versionedData.data) throw new Error('Migrator - migration returned empty data') + if (versionedData.version !== undefined && migratedData.meta.version !== migration.version) throw new Error('Migrator - Migration did not update version number correctly') + } - return ( - asyncQ.waterfall(migrations) - .then((migratedData) => Promise.resolve(migratedData)) - ) + return versionedData - // migration is "pending" if hit has a higher + // migration is "pending" if it has a higher // version number than currentVersion function migrationIsPending (migration) { return migration.version > versionedData.meta.version } } - runMigration (migration, versionedData) { - return migration.migrate(versionedData) - .then((migratedData) => { - if (!migratedData.data) return Promise.reject(new Error('Migrator - migration returned empty data')) - if (migration.version !== undefined && migratedData.meta.version !== migration.version) return Promise.reject(new Error('Migrator - Migration did not update version number correctly')) - - return Promise.resolve(migratedData) - }) - } - generateInitialState (initState) { return { meta: { From daec667c16c9a55e6357871a82ff2b863501a393 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Fri, 12 May 2017 11:31:40 -0700 Subject: [PATCH 238/372] Add support for async/await --- .babelrc | 5 ++++- .eslintrc | 2 +- package.json | 6 +++++- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/.babelrc b/.babelrc index 9d8d51656..3ca197980 100644 --- a/.babelrc +++ b/.babelrc @@ -1 +1,4 @@ -{ "presets": ["es2015"] } +{ + "presets": ["es2015"], + "plugins": ["transform-runtime"] +} diff --git a/.eslintrc b/.eslintrc index 91c95874e..a57a93cdc 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,7 +1,7 @@ { "parserOptions": { "sourceType": "module", - "ecmaVersion": 6, + "ecmaVersion": 2017, "ecmaFeatures": { "experimentalObjectRestSpread": true, "impliedStrict": true, diff --git a/package.json b/package.json index 11931b32f..2f5f8434b 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,8 @@ "babelify", { "presets": [ - "es2015" + "es2015", + "stage-3" ] } ], @@ -45,6 +46,7 @@ "dependencies": { "async": "^1.5.2", "async-q": "^0.3.1", + "babel-runtime": "^6.23.0", "bip39": "^2.2.0", "bluebird": "^3.5.0", "browser-passworder": "^2.0.3", @@ -125,6 +127,8 @@ }, "devDependencies": { "babel-eslint": "^6.0.5", + "babel-plugin-transform-runtime": "^6.23.0", + "babel-preset-stage-3": "^6.24.1", "babel-register": "^6.7.2", "babelify": "^7.2.0", "beefy": "^2.1.5", From 61f5c42a4508aa13f41f5ac85bd13bc53dfa7b2e Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Fri, 12 May 2017 11:31:40 -0700 Subject: [PATCH 239/372] Add support for async/await --- .babelrc | 5 ++++- .eslintrc | 2 +- package.json | 6 +++++- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/.babelrc b/.babelrc index 9d8d51656..3ca197980 100644 --- a/.babelrc +++ b/.babelrc @@ -1 +1,4 @@ -{ "presets": ["es2015"] } +{ + "presets": ["es2015"], + "plugins": ["transform-runtime"] +} diff --git a/.eslintrc b/.eslintrc index 91c95874e..a57a93cdc 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,7 +1,7 @@ { "parserOptions": { "sourceType": "module", - "ecmaVersion": 6, + "ecmaVersion": 2017, "ecmaFeatures": { "experimentalObjectRestSpread": true, "impliedStrict": true, diff --git a/package.json b/package.json index e2268d20a..f4bdbd998 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,8 @@ "babelify", { "presets": [ - "es2015" + "es2015", + "stage-3" ] } ], @@ -45,6 +46,7 @@ "dependencies": { "async": "^1.5.2", "async-q": "^0.3.1", + "babel-runtime": "^6.23.0", "bip39": "^2.2.0", "bluebird": "^3.5.0", "browser-passworder": "^2.0.3", @@ -125,6 +127,8 @@ }, "devDependencies": { "babel-eslint": "^6.0.5", + "babel-plugin-transform-runtime": "^6.23.0", + "babel-preset-stage-3": "^6.24.1", "babel-register": "^6.7.2", "babelify": "^7.2.0", "beefy": "^2.1.5", From 2c8bbe3b25c726b8a0cebb572c3c9d962136a693 Mon Sep 17 00:00:00 2001 From: kumavis Date: Fri, 12 May 2017 12:27:40 -0700 Subject: [PATCH 240/372] migrator - fix typo --- app/scripts/lib/migrator/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/scripts/lib/migrator/index.js b/app/scripts/lib/migrator/index.js index ed07a0c60..de6f5d5cd 100644 --- a/app/scripts/lib/migrator/index.js +++ b/app/scripts/lib/migrator/index.js @@ -18,7 +18,7 @@ class Migrator { let migration = pendingMigrations[index] versionedData = await migration.migrate(versionedData) if (!versionedData.data) throw new Error('Migrator - migration returned empty data') - if (versionedData.version !== undefined && migratedData.meta.version !== migration.version) throw new Error('Migrator - Migration did not update version number correctly') + if (versionedData.version !== undefined && versionedData.meta.version !== migration.version) throw new Error('Migrator - Migration did not update version number correctly') } return versionedData From dde3beb0446a64ab9348c75386c2e7ab0641d828 Mon Sep 17 00:00:00 2001 From: kumavis Date: Fri, 12 May 2017 12:37:31 -0700 Subject: [PATCH 241/372] ci - use node 7.6.0 --- circle.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/circle.yml b/circle.yml index c9ea787ff..4305ca3b4 100644 --- a/circle.yml +++ b/circle.yml @@ -1,6 +1,6 @@ machine: node: - version: 6.0.0 + version: 7.6.0 dependencies: pre: - "npm i -g testem" From 19db11856bef65c38d96eb1d6084d88ab6e7eebc Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Fri, 12 May 2017 12:41:31 -0700 Subject: [PATCH 242/372] Remove redux dependency from eth-balance and its dependent tree For better unit testability of the conf-tx view. --- ui/app/account-detail.js | 8 ++++++-- ui/app/accounts/account-list-item.js | 3 ++- ui/app/accounts/index.js | 4 +++- ui/app/components/eth-balance.js | 16 ++++++++-------- ui/app/components/fiat-value.js | 16 +++++----------- ui/app/components/pending-tx.js | 2 ++ ui/app/components/shift-list-item.js | 6 +++++- ui/app/components/transaction-list-item.js | 3 ++- ui/app/components/transaction-list.js | 3 ++- ui/app/conf-tx.js | 4 +++- ui/app/send.js | 19 +++++++++++-------- 11 files changed, 49 insertions(+), 35 deletions(-) diff --git a/ui/app/account-detail.js b/ui/app/account-detail.js index d592a5ad6..7cadb9d47 100644 --- a/ui/app/account-detail.js +++ b/ui/app/account-detail.js @@ -29,6 +29,7 @@ function mapStateToProps (state) { unapprovedMsgs: valuesFor(state.metamask.unapprovedMsgs), shapeShiftTxList: state.metamask.shapeShiftTxList, transactions: state.metamask.selectedAddressTxList || [], + conversionRate: state.metamask.conversionRate, } } @@ -43,7 +44,7 @@ AccountDetailScreen.prototype.render = function () { var checksumAddress = selected && ethUtil.toChecksumAddress(selected) var identity = props.identities[selected] var account = props.accounts[selected] - const { network } = props + const { network, conversionRate } = props return ( @@ -182,6 +183,7 @@ AccountDetailScreen.prototype.render = function () { h(EthBalance, { value: account && account.balance, + conversionRate, style: { lineHeight: '7px', marginTop: '10px', @@ -243,11 +245,13 @@ AccountDetailScreen.prototype.subview = function () { } AccountDetailScreen.prototype.transactionList = function () { - const {transactions, unapprovedMsgs, address, network, shapeShiftTxList } = this.props + const {transactions, unapprovedMsgs, address, + network, shapeShiftTxList, conversionRate } = this.props return h(TransactionList, { transactions: transactions.sort((a, b) => b.time - a.time), network, unapprovedMsgs, + conversionRate, address, shapeShiftTxList, viewPendingTx: (txId) => { diff --git a/ui/app/accounts/account-list-item.js b/ui/app/accounts/account-list-item.js index 2a3c13d05..0e87af612 100644 --- a/ui/app/accounts/account-list-item.js +++ b/ui/app/accounts/account-list-item.js @@ -15,7 +15,7 @@ function AccountListItem () { } AccountListItem.prototype.render = function () { - const { identity, selectedAddress, accounts, onShowDetail } = this.props + const { identity, selectedAddress, accounts, onShowDetail, conversionRate } = this.props const checksumAddress = identity && identity.address && ethUtil.toChecksumAddress(identity.address) const isSelected = selectedAddress === identity.address @@ -52,6 +52,7 @@ AccountListItem.prototype.render = function () { }, checksumAddress), h(EthBalance, { value: account && account.balance, + conversionRate, style: { lineHeight: '7px', marginTop: '10px', diff --git a/ui/app/accounts/index.js b/ui/app/accounts/index.js index 9584ebad9..5105c214b 100644 --- a/ui/app/accounts/index.js +++ b/ui/app/accounts/index.js @@ -23,6 +23,7 @@ function mapStateToProps (state) { scrollToBottom: state.appState.scrollToBottom, pending, keyrings: state.metamask.keyrings, + conversionRate: state.metamask.conversionRate, } } @@ -33,7 +34,7 @@ function AccountsScreen () { AccountsScreen.prototype.render = function () { const props = this.props - const { keyrings } = props + const { keyrings, conversionRate } = props const identityList = valuesFor(props.identities) const unapprovedTxList = valuesFor(props.unapprovedTxs) @@ -81,6 +82,7 @@ AccountsScreen.prototype.render = function () { key: `acct-panel-${identity.address}`, identity, selectedAddress: this.props.selectedAddress, + conversionRate, accounts: this.props.accounts, onShowDetail: this.onShowDetail.bind(this), pending, diff --git a/ui/app/components/eth-balance.js b/ui/app/components/eth-balance.js index 57ca84564..21906aa09 100644 --- a/ui/app/components/eth-balance.js +++ b/ui/app/components/eth-balance.js @@ -16,20 +16,19 @@ function EthBalanceComponent () { EthBalanceComponent.prototype.render = function () { var props = this.props let { value } = props - var style = props.style + const { style, width } = props var needsParse = this.props.needsParse !== undefined ? this.props.needsParse : true value = value ? formatBalance(value, 6, needsParse) : '...' - var width = props.width return ( h('.ether-balance.ether-balance-amount', { - style: style, + style, }, [ h('div', { style: { display: 'inline', - width: width, + width, }, }, this.renderBalance(value)), ]) @@ -38,16 +37,17 @@ EthBalanceComponent.prototype.render = function () { } EthBalanceComponent.prototype.renderBalance = function (value) { var props = this.props + const { conversionRate, shorten, incoming } = props if (value === 'None') return value if (value === '...') return value - var balanceObj = generateBalanceObject(value, props.shorten ? 1 : 3) + var balanceObj = generateBalanceObject(value, shorten ? 1 : 3) var balance var splitBalance = value.split(' ') var ethNumber = splitBalance[0] var ethSuffix = splitBalance[1] const showFiat = 'showFiat' in props ? props.showFiat : true - if (props.shorten) { + if (shorten) { balance = balanceObj.shortBalance } else { balance = balanceObj.balance @@ -73,7 +73,7 @@ EthBalanceComponent.prototype.renderBalance = function (value) { width: '100%', textAlign: 'right', }, - }, this.props.incoming ? `+${balance}` : balance), + }, incoming ? `+${balance}` : balance), h('div', { style: { color: ' #AEAEAE', @@ -83,7 +83,7 @@ EthBalanceComponent.prototype.renderBalance = function (value) { }, label), ]), - showFiat ? h(FiatValue, { value: props.value }) : null, + showFiat ? h(FiatValue, { value, conversionRate }) : null, ])) ) } diff --git a/ui/app/components/fiat-value.js b/ui/app/components/fiat-value.js index 298809b30..6e306c9e6 100644 --- a/ui/app/components/fiat-value.js +++ b/ui/app/components/fiat-value.js @@ -1,17 +1,9 @@ const Component = require('react').Component const h = require('react-hyperscript') const inherits = require('util').inherits -const connect = require('react-redux').connect const formatBalance = require('../util').formatBalance -module.exports = connect(mapStateToProps)(FiatValue) - -function mapStateToProps (state) { - return { - conversionRate: state.metamask.conversionRate, - currentCurrency: state.metamask.currentCurrency, - } -} +module.exports = FiatValue inherits(FiatValue, Component) function FiatValue () { @@ -20,14 +12,16 @@ function FiatValue () { FiatValue.prototype.render = function () { const props = this.props + const { conversionRate } = props + const value = formatBalance(props.value, 6) if (value === 'None') return value var fiatDisplayNumber, fiatTooltipNumber var splitBalance = value.split(' ') - if (props.conversionRate !== 0) { - fiatTooltipNumber = Number(splitBalance[0]) * props.conversionRate + if (conversionRate !== 0) { + fiatTooltipNumber = Number(splitBalance[0]) * conversionRate fiatDisplayNumber = fiatTooltipNumber.toFixed(2) } else { fiatDisplayNumber = 'N/A' diff --git a/ui/app/components/pending-tx.js b/ui/app/components/pending-tx.js index 6b8f16dae..fb555b821 100644 --- a/ui/app/components/pending-tx.js +++ b/ui/app/components/pending-tx.js @@ -31,6 +31,7 @@ function PendingTx () { PendingTx.prototype.render = function () { const props = this.props + const conversionRate = props.conversionRate const txMeta = this.gatherTxMeta() const txParams = txMeta.txParams || {} @@ -102,6 +103,7 @@ PendingTx.prototype.render = function () { }, [ h(EthBalance, { value: balance, + conversionRate, inline: true, labelColor: '#F7861C', }), diff --git a/ui/app/components/shift-list-item.js b/ui/app/components/shift-list-item.js index 96a7cba6e..db5fda5cb 100644 --- a/ui/app/components/shift-list-item.js +++ b/ui/app/components/shift-list-item.js @@ -15,7 +15,9 @@ const Tooltip = require('./tooltip') module.exports = connect(mapStateToProps)(ShiftListItem) function mapStateToProps (state) { - return {} + return { + conversionRate: state.metamask.conversionRate, + } } inherits(ShiftListItem, Component) @@ -64,6 +66,7 @@ function formatDate (date) { ShiftListItem.prototype.renderUtilComponents = function () { var props = this.props + const { conversionRate } = props switch (props.response.status) { case 'no_deposits': @@ -96,6 +99,7 @@ ShiftListItem.prototype.renderUtilComponents = function () { }), h(EtherBalance, { value: `${props.response.outgoingCoin}`, + conversionRate, width: '55px', shorten: true, needsParse: false, diff --git a/ui/app/components/transaction-list-item.js b/ui/app/components/transaction-list-item.js index 7fb2e88d9..3db4c016e 100644 --- a/ui/app/components/transaction-list-item.js +++ b/ui/app/components/transaction-list-item.js @@ -19,7 +19,7 @@ function TransactionListItem () { } TransactionListItem.prototype.render = function () { - const { transaction, network } = this.props + const { transaction, network, conversionRate } = this.props if (transaction.key === 'shapeshift') { if (network === '1') return h(ShiftListItem, transaction) } @@ -80,6 +80,7 @@ TransactionListItem.prototype.render = function () { isTx ? h(EtherBalance, { value: txParams.value, + conversionRate, width: '55px', shorten: true, showFiat: false, diff --git a/ui/app/components/transaction-list.js b/ui/app/components/transaction-list.js index 3ae953637..37a757309 100644 --- a/ui/app/components/transaction-list.js +++ b/ui/app/components/transaction-list.js @@ -13,7 +13,7 @@ function TransactionList () { } TransactionList.prototype.render = function () { - const { transactions, network, unapprovedMsgs } = this.props + const { transactions, network, unapprovedMsgs, conversionRate } = this.props var shapeShiftTxList if (network === '1') { @@ -69,6 +69,7 @@ TransactionList.prototype.render = function () { } return h(TransactionListItem, { transaction, i, network, key, + conversionRate, showTx: (txId) => { this.props.viewPendingTx(txId) }, diff --git a/ui/app/conf-tx.js b/ui/app/conf-tx.js index 83ac5a4fd..c4df66931 100644 --- a/ui/app/conf-tx.js +++ b/ui/app/conf-tx.js @@ -27,6 +27,7 @@ function mapStateToProps (state) { warning: state.appState.warning, network: state.metamask.network, provider: state.metamask.provider, + conversionRate: state.metamask.conversionRate, } } @@ -38,7 +39,7 @@ function ConfirmTxScreen () { ConfirmTxScreen.prototype.render = function () { const props = this.props const { network, provider, unapprovedTxs, - unapprovedMsgs, unapprovedPersonalMsgs } = props + unapprovedMsgs, unapprovedPersonalMsgs, conversionRate } = props var unconfTxList = txHelper(unapprovedTxs, unapprovedMsgs, unapprovedPersonalMsgs, network) @@ -102,6 +103,7 @@ ConfirmTxScreen.prototype.render = function () { selectedAddress: props.selectedAddress, accounts: props.accounts, identities: props.identities, + conversionRate, // Actions buyEth: this.buyEth.bind(this, txParams.from || props.selectedAddress), sendTransaction: this.sendTransaction.bind(this, txData), diff --git a/ui/app/send.js b/ui/app/send.js index eb32d5e06..d73744f70 100644 --- a/ui/app/send.js +++ b/ui/app/send.js @@ -21,6 +21,7 @@ function mapStateToProps (state) { warning: state.appState.warning, network: state.metamask.network, addressBook: state.metamask.addressBook, + conversionRate: state.metamask.conversionRate, } result.error = result.warning && result.warning.split('.')[0] @@ -40,13 +41,14 @@ function SendTransactionScreen () { SendTransactionScreen.prototype.render = function () { this.persistentFormParentId = 'send-tx-form' - var state = this.props - var address = state.address - var account = state.account - var identity = state.identity - var network = state.network - var identities = state.identities - var addressBook = state.addressBook + var props = this.props + var address = props.address + var account = props.account + var identity = props.identity + var network = props.network + var identities = props.identities + var addressBook = props.addressBook + var conversionRate = props.conversionRate return ( @@ -125,6 +127,7 @@ SendTransactionScreen.prototype.render = function () { h(EthBalance, { value: account && account.balance, + conversionRate, }), ]), @@ -147,7 +150,7 @@ SendTransactionScreen.prototype.render = function () { ]), // error message - state.error && h('span.error.flex-center', state.error), + props.error && h('span.error.flex-center', props.error), // 'to' field h('section.flex-row.flex-center', [ From 5c9449dec1a4662ebb3e1ce18ede018a9e874c39 Mon Sep 17 00:00:00 2001 From: kumavis Date: Fri, 12 May 2017 13:09:23 -0700 Subject: [PATCH 243/372] background - drop async-q in favor of async/await --- app/scripts/background.js | 38 ++++++++++++++++---------------------- package.json | 1 - 2 files changed, 16 insertions(+), 23 deletions(-) diff --git a/app/scripts/background.js b/app/scripts/background.js index 58f8e7556..e738a9712 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -1,6 +1,5 @@ const urlUtil = require('url') const endOfStream = require('end-of-stream') -const asyncQ = require('async-q') const pipe = require('pump') const LocalStorageStore = require('obs-store/lib/localStorage') const storeTransform = require('obs-store/lib/transform') @@ -30,34 +29,29 @@ let popupIsOpen = false const diskStore = new LocalStorageStore({ storageKey: STORAGE_KEY }) // initialization flow -asyncQ.waterfall([ - () => loadStateFromPersistence(), - (initState) => setupController(initState), -]) -.then(() => console.log('MetaMask initialization complete.')) -.catch((err) => { console.error(err) }) +initialize().catch(console.error) + +async function initialize() { + const initState = await loadStateFromPersistence() + await setupController(initState) + console.log('MetaMask initialization complete.') +} // // State and Persistence // -function loadStateFromPersistence () { +async function loadStateFromPersistence () { // migrations const migrator = new Migrator({ migrations }) - const initialState = migrator.generateInitialState(firstTimeState) - return asyncQ.waterfall([ - // read from disk - () => Promise.resolve(diskStore.getState() || initialState), - // migrate data - (versionedData) => migrator.migrateData(versionedData), - // write to disk - (versionedData) => { - diskStore.putState(versionedData) - return Promise.resolve(versionedData) - }, - // resolve to just data - (versionedData) => Promise.resolve(versionedData.data), - ]) + // read from disk + let versionedData = diskStore.getState() || migrator.generateInitialState(firstTimeState) + // migrate data + versionedData = await migrator.migrateData(versionedData) + // write to disk + diskStore.putState(versionedData) + // return just the data + return versionedData.data } function setupController (initState) { diff --git a/package.json b/package.json index f4bdbd998..423821fbe 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,6 @@ }, "dependencies": { "async": "^1.5.2", - "async-q": "^0.3.1", "babel-runtime": "^6.23.0", "bip39": "^2.2.0", "bluebird": "^3.5.0", From c4be4c7195c05936fba61783b9f8a3c96d161ce0 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Mon, 15 May 2017 14:35:24 -0700 Subject: [PATCH 244/372] Skip jazzicons in unit tests --- package.json | 2 ++ ui/app/components/identicon.js | 13 +++++++++---- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 2f5f8434b..9c0a91e3b 100644 --- a/package.json +++ b/package.json @@ -57,6 +57,7 @@ "debounce": "^1.0.0", "deep-extend": "^0.4.1", "denodeify": "^1.2.1", + "detect-node": "^2.0.3", "disc": "^1.3.2", "dnode": "^1.2.2", "end-of-stream": "^1.1.0", @@ -169,6 +170,7 @@ "qunit": "^0.9.1", "react-addons-test-utils": "^15.5.1", "react-dom": "^15.5.4", + "react-test-renderer": "^15.5.4", "react-testutils-additions": "^15.2.0", "sinon": "^1.17.3", "tape": "^4.5.1", diff --git a/ui/app/components/identicon.js b/ui/app/components/identicon.js index 6d4871d02..9de854b54 100644 --- a/ui/app/components/identicon.js +++ b/ui/app/components/identicon.js @@ -1,6 +1,7 @@ const Component = require('react').Component const h = require('react-hyperscript') const inherits = require('util').inherits +const isNode = require('detect-node') const findDOMNode = require('react-dom').findDOMNode const jazzicon = require('jazzicon') const iconFactoryGen = require('../../lib/icon-factory') @@ -40,8 +41,10 @@ IdenticonComponent.prototype.componentDidMount = function () { var container = findDOMNode(this) var diameter = props.diameter || this.defaultDiameter - var img = iconFactory.iconForAddress(address, diameter, false) - container.appendChild(img) + if (!isNode) { + var img = iconFactory.iconForAddress(address, diameter, false) + container.appendChild(img) + } } IdenticonComponent.prototype.componentDidUpdate = function () { @@ -58,6 +61,8 @@ IdenticonComponent.prototype.componentDidUpdate = function () { } var diameter = props.diameter || this.defaultDiameter - var img = iconFactory.iconForAddress(address, diameter, false) - container.appendChild(img) + if (!isNode) { + var img = iconFactory.iconForAddress(address, diameter, false) + container.appendChild(img) + } } From 4b341e6a955d1fa71decfb021a86e7da09a933b0 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Mon, 15 May 2017 15:07:38 -0700 Subject: [PATCH 245/372] Got test failing nearly correctly --- test/lib/mock-store.js | 18 +++++++++++++++ test/unit/components/pending-tx-test.js | 29 +++++++++++++------------ ui/app/components/pending-tx.js | 13 ++++++++++- 3 files changed, 45 insertions(+), 15 deletions(-) create mode 100644 test/lib/mock-store.js diff --git a/test/lib/mock-store.js b/test/lib/mock-store.js new file mode 100644 index 000000000..4714c3485 --- /dev/null +++ b/test/lib/mock-store.js @@ -0,0 +1,18 @@ +const createStore = require('redux').createStore +const applyMiddleware = require('redux').applyMiddleware +const thunkMiddleware = require('redux-thunk') +const createLogger = require('redux-logger') +const rootReducer = function() {} + +module.exports = configureStore + +const loggerMiddleware = createLogger() + +const createStoreWithMiddleware = applyMiddleware( + thunkMiddleware, + loggerMiddleware +)(createStore) + +function configureStore (initialState) { + return createStoreWithMiddleware(rootReducer, initialState) +} diff --git a/test/unit/components/pending-tx-test.js b/test/unit/components/pending-tx-test.js index 2594a1a26..d7825d40e 100644 --- a/test/unit/components/pending-tx-test.js +++ b/test/unit/components/pending-tx-test.js @@ -1,11 +1,10 @@ const assert = require('assert') const additions = require('react-testutils-additions') const h = require('react-hyperscript') -var PendingTx = require('../../../ui/app/components/pending-tx') +const PendingTx = require('../../../ui/app/components/pending-tx') const createReactFactory = require('create-react-factory').createReactFactory const React = require('react') -console.dir(createReactFactory) -const shallow = require('enzyme').shallow +const shallow = require('react-test-renderer/shallow') const Factory = createReactFactory(PendingTx) const ReactTestUtils = require('react-addons-test-utils') @@ -53,23 +52,27 @@ describe.only('PendingTx', function () { } const pendingTxComponent = h(PendingTx, props) - var component = additions.renderIntoDocument(pendingTxComponent); + const component = additions.renderIntoDocument(pendingTxComponent); renderer.render(pendingTxComponent) const result = renderer.getRenderOutput() const form = result.props.children - console.log('FORM children') - console.dir(form.props.children) const children = form.props.children[form.props.children.length - 1] assert.equal(result.type, 'div', 'should create a div') - console.dir(children) - - console.log('finding input') try{ - const input = additions.find(component, '.cell.row input[type="number"]') - console.log('input') - console.dir(input) + const input = additions.find(component, '.cell.row input[type="number"]')[1] + ReactTestUtils.Simulate.change(input, { + target: { + value: 2, + checkValidity() { return true }, + } + }) + + let form = additions.find(component, 'form')[0] + form.checkValidity = () => true + form.getFormEl = () => { return { checkValidity() { return true } } } + ReactTestUtils.Simulate.submit(form, { preventDefault() {}, target: { checkValidity() {return true} } }) } catch (e) { console.log("WHAAAA") @@ -79,7 +82,6 @@ describe.only('PendingTx', function () { const noop = () => {} setTimeout(() => { - console.log('timeout finished') // Get the gas price input // Set it to the newGasPrice value @@ -91,7 +93,6 @@ describe.only('PendingTx', function () { }, 200) - console.log('calling render') }) }) diff --git a/ui/app/components/pending-tx.js b/ui/app/components/pending-tx.js index fb555b821..d31ccf2e5 100644 --- a/ui/app/components/pending-tx.js +++ b/ui/app/components/pending-tx.js @@ -380,11 +380,22 @@ PendingTx.prototype.onSubmit = function (event) { } PendingTx.prototype.checkValidity = function() { - const form = document.querySelector('form#pending-tx-form') + const form = this.getFormEl() + console.log("check validity got form el:") + console.dir(form) const valid = form.checkValidity() return valid } +PendingTx.prototype.getFormEl = function() { + const form = document.querySelector('form#pending-tx-form') + // Stub out form for unit tests: + if (!form) { + return { checkValidity() { return true } } + } + return form +} + // After a customizable state value has been updated, PendingTx.prototype.gatherTxMeta = function () { log.debug(`pending-tx gatherTxMeta`) From 75d9b5619c1b7e0949136702e7301ed0bb648f09 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Mon, 15 May 2017 15:21:28 -0700 Subject: [PATCH 246/372] Verify updating gas value updates --- test/unit/components/pending-tx-test.js | 8 +++++--- ui/app/components/pending-tx.js | 2 -- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/test/unit/components/pending-tx-test.js b/test/unit/components/pending-tx-test.js index d7825d40e..57fccba71 100644 --- a/test/unit/components/pending-tx-test.js +++ b/test/unit/components/pending-tx-test.js @@ -7,6 +7,7 @@ const React = require('react') const shallow = require('react-test-renderer/shallow') const Factory = createReactFactory(PendingTx) const ReactTestUtils = require('react-addons-test-utils') +const ethUtil = require('ethereumjs-util') describe.only('PendingTx', function () { let pendingTxComponent @@ -38,15 +39,16 @@ describe.only('PendingTx', function () { it('should use updated values when edited.', function (done) { const renderer = ReactTestUtils.createRenderer(); - const newGasPrice = '0x451456' + const newGasPrice = '0x77359400' const props = { identities, accounts: identities, txData, sendTransaction: (txMeta, event) => { - assert.notEqual(txMeta.txParams.gasPrice, gasPrice, 'gas price should change') - assert.equal(txMeta.txParams.gasPrice, newGasPrice, 'gas price assigned.') + const result = ethUtil.addHexPrefix(txMeta.txParams.gasPrice) + assert.notEqual(result, gasPrice, 'gas price should change') + assert.equal(result, newGasPrice, 'gas price assigned.') done() }, } diff --git a/ui/app/components/pending-tx.js b/ui/app/components/pending-tx.js index d31ccf2e5..bbdac3bc4 100644 --- a/ui/app/components/pending-tx.js +++ b/ui/app/components/pending-tx.js @@ -381,8 +381,6 @@ PendingTx.prototype.onSubmit = function (event) { PendingTx.prototype.checkValidity = function() { const form = this.getFormEl() - console.log("check validity got form el:") - console.dir(form) const valid = form.checkValidity() return valid } From fc7b4cb4bc5141f920131f8d79c56ca9ff3b6d2c Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Mon, 15 May 2017 15:22:49 -0700 Subject: [PATCH 247/372] Linted --- ui/app/components/ens-input.js | 5 +++-- ui/app/components/pending-tx.js | 6 +++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/ui/app/components/ens-input.js b/ui/app/components/ens-input.js index 04c6222c2..3e44d83af 100644 --- a/ui/app/components/ens-input.js +++ b/ui/app/components/ens-input.js @@ -168,6 +168,7 @@ EnsInput.prototype.ensIconContents = function (recipient) { } } -function getNetworkEnsSupport(network) { +function getNetworkEnsSupport (network) { return Boolean(networkMap[network]) -} \ No newline at end of file +} + diff --git a/ui/app/components/pending-tx.js b/ui/app/components/pending-tx.js index bbdac3bc4..b084a1d2a 100644 --- a/ui/app/components/pending-tx.js +++ b/ui/app/components/pending-tx.js @@ -379,17 +379,17 @@ PendingTx.prototype.onSubmit = function (event) { } } -PendingTx.prototype.checkValidity = function() { +PendingTx.prototype.checkValidity = function () { const form = this.getFormEl() const valid = form.checkValidity() return valid } -PendingTx.prototype.getFormEl = function() { +PendingTx.prototype.getFormEl = function () { const form = document.querySelector('form#pending-tx-form') // Stub out form for unit tests: if (!form) { - return { checkValidity() { return true } } + return { checkValidity () { return true } } } return form } From f9c0fc0e8cb04f371ce8e99c41c74989841c2c24 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Mon, 15 May 2017 15:23:38 -0700 Subject: [PATCH 248/372] Clean up test --- test/unit/components/pending-tx-test.js | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/test/unit/components/pending-tx-test.js b/test/unit/components/pending-tx-test.js index 57fccba71..fe8290003 100644 --- a/test/unit/components/pending-tx-test.js +++ b/test/unit/components/pending-tx-test.js @@ -46,6 +46,8 @@ describe.only('PendingTx', function () { accounts: identities, txData, sendTransaction: (txMeta, event) => { + + // Assert changes: const result = ethUtil.addHexPrefix(txMeta.txParams.gasPrice) assert.notEqual(result, gasPrice, 'gas price should change') assert.equal(result, newGasPrice, 'gas price assigned.') @@ -81,20 +83,6 @@ describe.only('PendingTx', function () { console.error(e) } - const noop = () => {} - - setTimeout(() => { - - // Get the gas price input - // Set it to the newGasPrice value - // Wait for the value to change - // Get the submit button - // Click the submit button - // Get the output of the submit event. - // Assert that the value was updated. - - }, 200) - }) }) From 81122170b5e1b5853a823a9290c58e514062cb3f Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Mon, 15 May 2017 15:31:19 -0700 Subject: [PATCH 249/372] Add stage 0 support to build system --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 423821fbe..3355b4442 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ { "presets": [ "es2015", - "stage-3" + "stage-0" ] } ], @@ -127,7 +127,7 @@ "devDependencies": { "babel-eslint": "^6.0.5", "babel-plugin-transform-runtime": "^6.23.0", - "babel-preset-stage-3": "^6.24.1", + "babel-preset-stage-0": "^6.24.1", "babel-register": "^6.7.2", "babelify": "^7.2.0", "beefy": "^2.1.5", From c1b0aaa4432acf3017c2653374fc5601b563163a Mon Sep 17 00:00:00 2001 From: kumavis Date: Mon, 15 May 2017 17:11:07 -0700 Subject: [PATCH 250/372] deps - bump provider-engine 12.0.6 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f4bdbd998..6358ab850 100644 --- a/package.json +++ b/package.json @@ -121,7 +121,7 @@ "valid-url": "^1.0.9", "vreme": "^3.0.2", "web3": "0.18.2", - "web3-provider-engine": "^12.0.3", + "web3-provider-engine": "^12.0.6", "web3-stream-provider": "^2.0.6", "xtend": "^4.0.1" }, From b904fa5d86067e4d23d383f15597b1d79b833088 Mon Sep 17 00:00:00 2001 From: kumavis Date: Mon, 15 May 2017 17:13:13 -0700 Subject: [PATCH 251/372] changelog - add note on filter fix --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 422639f70..3653e5018 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Current Master - Trim currency list. +- Fix event filter bug introduced by newer versions of Geth. ## 3.6.4 2017-5-8 From 4c10e2021aa0cdc4f34a22368a37c76e0e1fea22 Mon Sep 17 00:00:00 2001 From: Thomas Huang Date: Mon, 15 May 2017 18:05:11 -0700 Subject: [PATCH 252/372] Change default network to rinkeby --- app/scripts/config.js | 6 +++--- app/scripts/lib/config-manager.js | 10 +++++----- ui/app/app.js | 4 ++-- ui/app/components/drop-menu-item.js | 4 ++-- ui/app/components/network.js | 6 +++--- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/app/scripts/config.js b/app/scripts/config.js index 391c67230..67067cfd7 100644 --- a/app/scripts/config.js +++ b/app/scripts/config.js @@ -1,7 +1,7 @@ const MAINET_RPC_URL = 'https://mainnet.infura.io/metamask' -const TESTNET_RPC_URL = 'https://ropsten.infura.io/metamask' +const TESTNET_RPC_URL = 'https://rinkeby.infura.io/metamask' const KOVAN_RPC_URL = 'https://kovan.infura.io/metamask' -const RINKEBY_RPC_URL = 'https://rinkeby.infura.io/metamask' +const ROPSTEN_RPC_URL = 'https://ropsten.infura.io/metamask' const DEFAULT_RPC_URL = TESTNET_RPC_URL global.METAMASK_DEBUG = 'GULP_METAMASK_DEBUG' @@ -13,6 +13,6 @@ module.exports = { testnet: TESTNET_RPC_URL, morden: TESTNET_RPC_URL, kovan: KOVAN_RPC_URL, - rinkeby: RINKEBY_RPC_URL, + ropsten: ROPSTEN_RPC_URL, }, } diff --git a/app/scripts/lib/config-manager.js b/app/scripts/lib/config-manager.js index ab9410842..4ca02135a 100644 --- a/app/scripts/lib/config-manager.js +++ b/app/scripts/lib/config-manager.js @@ -6,7 +6,7 @@ const TESTNET_RPC = MetamaskConfig.network.testnet const MAINNET_RPC = MetamaskConfig.network.mainnet const MORDEN_RPC = MetamaskConfig.network.morden const KOVAN_RPC = MetamaskConfig.network.kovan -const RINKEBY_RPC = MetamaskConfig.network.rinkeby +const ROPSTEN_RPC = MetamaskConfig.network.ropsten /* The config-manager is a convenience object @@ -147,8 +147,8 @@ ConfigManager.prototype.getCurrentRpcAddress = function () { case 'mainnet': return MAINNET_RPC - case 'testnet': - return TESTNET_RPC + case 'ropsten': + return ROPSTEN_RPC case 'morden': return MORDEN_RPC @@ -156,8 +156,8 @@ ConfigManager.prototype.getCurrentRpcAddress = function () { case 'kovan': return KOVAN_RPC - case 'rinkeby': - return RINKEBY_RPC + case 'testnet': + return TESTNET_RPC default: return provider && provider.rpcTarget ? provider.rpcTarget : TESTNET_RPC diff --git a/ui/app/app.js b/ui/app/app.js index bbfd58588..d096ca531 100644 --- a/ui/app/app.js +++ b/ui/app/app.js @@ -249,7 +249,7 @@ App.prototype.renderNetworkDropdown = function () { h(DropMenuItem, { label: 'Ropsten Test Network', closeMenu: () => this.setState({ isNetworkMenuOpen: false }), - action: () => props.dispatch(actions.setProviderType('testnet')), + action: () => props.dispatch(actions.setProviderType('ropsten')), icon: h('.menu-icon.red-dot'), activeNetworkRender: props.network, provider: props.provider, @@ -267,7 +267,7 @@ App.prototype.renderNetworkDropdown = function () { h(DropMenuItem, { label: 'Rinkeby Test Network', closeMenu: () => this.setState({ isNetworkMenuOpen: false}), - action: () => props.dispatch(actions.setProviderType('rinkeby')), + action: () => props.dispatch(actions.setProviderType('testnet')), icon: h('.menu-icon.golden-square'), activeNetworkRender: props.network, provider: props.provider, diff --git a/ui/app/components/drop-menu-item.js b/ui/app/components/drop-menu-item.js index bd9d8f597..27c2afde3 100644 --- a/ui/app/components/drop-menu-item.js +++ b/ui/app/components/drop-menu-item.js @@ -42,13 +42,13 @@ DropMenuItem.prototype.activeNetworkRender = function () { if (providerType === 'mainnet') return h('.check', '✓') break case 'Ropsten Test Network': - if (providerType === 'testnet') return h('.check', '✓') + if (providerType === 'ropsten') return h('.check', '✓') break case 'Kovan Test Network': if (providerType === 'kovan') return h('.check', '✓') break case 'Rinkeby Test Network': - if (providerType === 'rinkeby') return h('.check', '✓') + if (providerType === 'testnet') return h('.check', '✓') break case 'Localhost 8545': if (activeNetwork === 'http://localhost:8545') return h('.check', '✓') diff --git a/ui/app/components/network.js b/ui/app/components/network.js index f7ea8c49e..1e7c082e1 100644 --- a/ui/app/components/network.js +++ b/ui/app/components/network.js @@ -34,7 +34,7 @@ Network.prototype.render = function () { } else if (providerName === 'mainnet') { hoverText = 'Main Ethereum Network' iconName = 'ethereum-network' - } else if (providerName === 'testnet') { + } else if (providerName === 'ropsten') { hoverText = 'Ropsten Test Network' iconName = 'ropsten-test-network' } else if (parseInt(networkNumber) === 3) { @@ -43,7 +43,7 @@ Network.prototype.render = function () { } else if (providerName === 'kovan') { hoverText = 'Kovan Test Network' iconName = 'kovan-test-network' - } else if (providerName === 'rinkeby') { + } else if (providerName === 'testnet') { hoverText = 'Rinkeby Test Network' iconName = 'rinkeby-test-network' } else { @@ -90,7 +90,7 @@ Network.prototype.render = function () { h('.menu-icon.golden-square'), h('.network-name', { style: { - color: '#550077', + color: '#e7a218', }}, 'Rinkeby Test Net'), ]) From 3367363b1234a076695758762d7f1220fe4a7f8c Mon Sep 17 00:00:00 2001 From: Thomas Huang Date: Mon, 15 May 2017 19:11:16 -0700 Subject: [PATCH 253/372] Remove all traces of testnet --- app/scripts/config.js | 11 ++++------- app/scripts/first-time-state.js | 2 +- app/scripts/lib/config-manager.js | 15 +++++---------- ui/app/app.js | 2 +- ui/app/components/drop-menu-item.js | 2 +- ui/app/components/network.js | 2 +- ui/app/config.js | 2 +- 7 files changed, 14 insertions(+), 22 deletions(-) diff --git a/app/scripts/config.js b/app/scripts/config.js index 67067cfd7..8e28db80e 100644 --- a/app/scripts/config.js +++ b/app/scripts/config.js @@ -1,18 +1,15 @@ const MAINET_RPC_URL = 'https://mainnet.infura.io/metamask' -const TESTNET_RPC_URL = 'https://rinkeby.infura.io/metamask' -const KOVAN_RPC_URL = 'https://kovan.infura.io/metamask' const ROPSTEN_RPC_URL = 'https://ropsten.infura.io/metamask' -const DEFAULT_RPC_URL = TESTNET_RPC_URL +const KOVAN_RPC_URL = 'https://kovan.infura.io/metamask' +const RINKEBY_RPC_URL = 'https://rinkeby.infura.io/metamask' global.METAMASK_DEBUG = 'GULP_METAMASK_DEBUG' module.exports = { network: { - default: DEFAULT_RPC_URL, mainnet: MAINET_RPC_URL, - testnet: TESTNET_RPC_URL, - morden: TESTNET_RPC_URL, - kovan: KOVAN_RPC_URL, ropsten: ROPSTEN_RPC_URL, + kovan: KOVAN_RPC_URL, + rinkeby: RINKEBY_RPC_URL, }, } diff --git a/app/scripts/first-time-state.js b/app/scripts/first-time-state.js index 87a7bb7b5..29ec1d8d3 100644 --- a/app/scripts/first-time-state.js +++ b/app/scripts/first-time-state.js @@ -5,7 +5,7 @@ module.exports = { config: { provider: { - type: 'testnet', + type: 'rinkeby', }, }, } diff --git a/app/scripts/lib/config-manager.js b/app/scripts/lib/config-manager.js index 4ca02135a..d77cd2126 100644 --- a/app/scripts/lib/config-manager.js +++ b/app/scripts/lib/config-manager.js @@ -2,12 +2,10 @@ const MetamaskConfig = require('../config.js') const ethUtil = require('ethereumjs-util') const normalize = require('eth-sig-util').normalize -const TESTNET_RPC = MetamaskConfig.network.testnet const MAINNET_RPC = MetamaskConfig.network.mainnet -const MORDEN_RPC = MetamaskConfig.network.morden -const KOVAN_RPC = MetamaskConfig.network.kovan const ROPSTEN_RPC = MetamaskConfig.network.ropsten - +const KOVAN_RPC = MetamaskConfig.network.kovan +const RINKEBY_RPC = MetamaskConfig.network.rinkeby /* The config-manager is a convenience object * wrapping a pojo-migrator. @@ -150,17 +148,14 @@ ConfigManager.prototype.getCurrentRpcAddress = function () { case 'ropsten': return ROPSTEN_RPC - case 'morden': - return MORDEN_RPC - case 'kovan': return KOVAN_RPC - case 'testnet': - return TESTNET_RPC + case 'rinkeby': + return RINKEBY_RPC default: - return provider && provider.rpcTarget ? provider.rpcTarget : TESTNET_RPC + return provider && provider.rpcTarget ? provider.rpcTarget : RINKEBY_RPC } } diff --git a/ui/app/app.js b/ui/app/app.js index d096ca531..6e5aa57cd 100644 --- a/ui/app/app.js +++ b/ui/app/app.js @@ -267,7 +267,7 @@ App.prototype.renderNetworkDropdown = function () { h(DropMenuItem, { label: 'Rinkeby Test Network', closeMenu: () => this.setState({ isNetworkMenuOpen: false}), - action: () => props.dispatch(actions.setProviderType('testnet')), + action: () => props.dispatch(actions.setProviderType('rinkeby')), icon: h('.menu-icon.golden-square'), activeNetworkRender: props.network, provider: props.provider, diff --git a/ui/app/components/drop-menu-item.js b/ui/app/components/drop-menu-item.js index 27c2afde3..e42948209 100644 --- a/ui/app/components/drop-menu-item.js +++ b/ui/app/components/drop-menu-item.js @@ -48,7 +48,7 @@ DropMenuItem.prototype.activeNetworkRender = function () { if (providerType === 'kovan') return h('.check', '✓') break case 'Rinkeby Test Network': - if (providerType === 'testnet') return h('.check', '✓') + if (providerType === 'rinkeby') return h('.check', '✓') break case 'Localhost 8545': if (activeNetwork === 'http://localhost:8545') return h('.check', '✓') diff --git a/ui/app/components/network.js b/ui/app/components/network.js index 1e7c082e1..31a8fc17c 100644 --- a/ui/app/components/network.js +++ b/ui/app/components/network.js @@ -43,7 +43,7 @@ Network.prototype.render = function () { } else if (providerName === 'kovan') { hoverText = 'Kovan Test Network' iconName = 'kovan-test-network' - } else if (providerName === 'testnet') { + } else if (providerName === 'rinkeby') { hoverText = 'Rinkeby Test Network' iconName = 'rinkeby-test-network' } else { diff --git a/ui/app/config.js b/ui/app/config.js index 26cfe663f..d7be26757 100644 --- a/ui/app/config.js +++ b/ui/app/config.js @@ -156,7 +156,7 @@ function currentProviderDisplay (metamaskState) { value = 'Main Ethereum Network' break - case 'testnet': + case 'ropsten': title = 'Current Network' value = 'Ropsten Test Network' break From a8306f15790b3fe49017518bcc3aecd73ec51e9c Mon Sep 17 00:00:00 2001 From: kumavis Date: Mon, 15 May 2017 21:59:24 -0700 Subject: [PATCH 254/372] mascara - add deploy instructions --- mascara/README.md | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/mascara/README.md b/mascara/README.md index db5b4f404..14cf7f563 100644 --- a/mascara/README.md +++ b/mascara/README.md @@ -3,7 +3,7 @@ start the dual servers (dapp + mascara) npm run mascara ``` -## First time use: +### First time use: - navigate to: http://localhost:9001 - Create an Account @@ -11,7 +11,7 @@ npm run mascara - open devTools - click Sync Tx -## Tests: +### Tests: ``` npm run testMascara @@ -22,3 +22,12 @@ Test will run in browser, you will have to have these browsers installed: - Chrome - Firefox - Opera + + +### Deploy: + +Will build and deploy mascara via docker + +``` +docker-compose build && docker-compose stop && docker-compose up && docker-compose -t 200 -f +``` \ No newline at end of file From 05933a7acb4a3967fbef95508c4474331a98d715 Mon Sep 17 00:00:00 2001 From: kumavis Date: Mon, 15 May 2017 22:01:04 -0700 Subject: [PATCH 255/372] mascara - ui - fix scale on mobile --- mascara/ui/index.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mascara/ui/index.html b/mascara/ui/index.html index c5eeb05ef..eac8e4898 100644 --- a/mascara/ui/index.html +++ b/mascara/ui/index.html @@ -2,7 +2,8 @@ - MetaMask Plugin + MetaMascara Alpha +
From 9fbe3e53714db8d359fa122c8701355ca5e9247c Mon Sep 17 00:00:00 2001 From: kumavis Date: Mon, 15 May 2017 22:12:06 -0700 Subject: [PATCH 256/372] mascara - fix deploy instructions --- mascara/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mascara/README.md b/mascara/README.md index 14cf7f563..6e3bfe96b 100644 --- a/mascara/README.md +++ b/mascara/README.md @@ -29,5 +29,5 @@ Test will run in browser, you will have to have these browsers installed: Will build and deploy mascara via docker ``` -docker-compose build && docker-compose stop && docker-compose up && docker-compose -t 200 -f +docker-compose build && docker-compose stop && docker-compose up -d && docker-compose logs --tail 200 -f ``` \ No newline at end of file From d5636080cf48fef8381a01006d4a3156a6b62aba Mon Sep 17 00:00:00 2001 From: kumavis Date: Mon, 15 May 2017 23:12:47 -0700 Subject: [PATCH 257/372] ui - send - clean props assignment --- ui/app/send.js | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/ui/app/send.js b/ui/app/send.js index d73744f70..6cfe909e6 100644 --- a/ui/app/send.js +++ b/ui/app/send.js @@ -41,14 +41,16 @@ function SendTransactionScreen () { SendTransactionScreen.prototype.render = function () { this.persistentFormParentId = 'send-tx-form' - var props = this.props - var address = props.address - var account = props.account - var identity = props.identity - var network = props.network - var identities = props.identities - var addressBook = props.addressBook - var conversionRate = props.conversionRate + const props = this.props + const { + address, + account, + identity, + network, + identities, + addressBook, + conversionRate + } = props return ( From e28e0acaa8fb690a7fe5ac45597837f20d2079e1 Mon Sep 17 00:00:00 2001 From: kumavis Date: Mon, 15 May 2017 23:21:46 -0700 Subject: [PATCH 258/372] lint - mandatory dangle :stuck_out_tongue: --- ui/app/send.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/app/send.js b/ui/app/send.js index 6cfe909e6..a313c6bee 100644 --- a/ui/app/send.js +++ b/ui/app/send.js @@ -49,7 +49,7 @@ SendTransactionScreen.prototype.render = function () { network, identities, addressBook, - conversionRate + conversionRate, } = props return ( From 01b6d9c374476bd8c59fc0ba342639ddcea7ca8d Mon Sep 17 00:00:00 2001 From: kumavis Date: Mon, 15 May 2017 23:54:05 -0700 Subject: [PATCH 259/372] test - format test data 001 --- test/lib/migrations/001.json | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/test/lib/migrations/001.json b/test/lib/migrations/001.json index 2fe6dd836..7bd55a50e 100644 --- a/test/lib/migrations/001.json +++ b/test/lib/migrations/001.json @@ -1 +1,14 @@ -{"version":0,"data":{"wallet":"{\"encSeed\":{\"encStr\":\"rT1C1jjkFRfmrwefscFcwZohl4f+HfIFlBZ9AM4ZD8atJmfKDIQCVK11NYDKYv8ZMIY03f3t8MuoZvfzBL8IJsWnZUhpzVTNNiARQJD2WpGA19eNBzgZm4vd0GwkIUruUDeJXu0iv2j9wU8hOQUqPbOePPy2Am5ro97iuvMAroRTnEKD60qFVg==\",\"nonce\":\"YUY2mwNq2v3FV0Fi94QnSiKFOLYfDR95\"},\"ksData\":{\"m/44'/60'/0'/0\":{\"info\":{\"curve\":\"secp256k1\",\"purpose\":\"sign\"},\"encHdPathPriv\":{\"encStr\":\"Iyi7ft4JQ9UtwrSXRT6ZIHPtZqJhe99rh0uWhNc6QLan6GanY2ZQeU0tt76CBealEWJyrJReSxGQdqDmSDYjpjH3m4JO5l0DfPLPseCqzXV/W+dzM0ubJ8lztLwpwi0L+vULNMqCx4dQtoNbNBq1QZUnjtpm6O8mWpScspboww==\",\"nonce\":\"Z7RqtjNjC6FrLUj5wVW1+HkjOW6Hib6K\"},\"hdIndex\":3,\"encPrivKeys\":{\"edb81c10122f34040cc4bef719a272fbbb1cf897\":{\"key\":\"8ab81tKBd4+CLAbzvS7SBFRTd6VWXBs86uBE43lgcmBu2U7UB22xdH64Q2hUf9eB\",\"nonce\":\"aGUEqI033FY39zKjWmZSI6PQrCLvkiRP\"},\"8bd7d5c000cf05284e98356370dc5ccaa3dbfc38\":{\"key\":\"+i3wmf4b+B898QtlOBfL0Ixirjg59/LLPX61vQ2L0xRPjXzNog0O4Wn15RemM5mY\",\"nonce\":\"imKrlkuoC5uuFkzJBbuDBluGCPJXNTKm\"},\"2340695474656e3124b8eba1172fbfb00eeac8f8\":{\"key\":\"pi+H9D8LYKsdCQKrfaJtsGFjE+X9s74xN675tsoIKrbPXhtpxMLOIQVtSqYveF62\",\"nonce\":\"49g80wDTovHwbguVVYf2FsYbp7Db5OAR\"}},\"addresses\":[\"edb81c10122f34040cc4bef719a272fbbb1cf897\",\"8bd7d5c000cf05284e98356370dc5ccaa3dbfc38\",\"2340695474656e3124b8eba1172fbfb00eeac8f8\"]}},\"version\":2}","config":{"provider":{"type":"etherscan"}}},"meta":{"version":0}} \ No newline at end of file +{ + "version": 0, + "data": { + "wallet": "{\"encSeed\":{\"encStr\":\"rT1C1jjkFRfmrwefscFcwZohl4f+HfIFlBZ9AM4ZD8atJmfKDIQCVK11NYDKYv8ZMIY03f3t8MuoZvfzBL8IJsWnZUhpzVTNNiARQJD2WpGA19eNBzgZm4vd0GwkIUruUDeJXu0iv2j9wU8hOQUqPbOePPy2Am5ro97iuvMAroRTnEKD60qFVg==\",\"nonce\":\"YUY2mwNq2v3FV0Fi94QnSiKFOLYfDR95\"},\"ksData\":{\"m/44'/60'/0'/0\":{\"info\":{\"curve\":\"secp256k1\",\"purpose\":\"sign\"},\"encHdPathPriv\":{\"encStr\":\"Iyi7ft4JQ9UtwrSXRT6ZIHPtZqJhe99rh0uWhNc6QLan6GanY2ZQeU0tt76CBealEWJyrJReSxGQdqDmSDYjpjH3m4JO5l0DfPLPseCqzXV/W+dzM0ubJ8lztLwpwi0L+vULNMqCx4dQtoNbNBq1QZUnjtpm6O8mWpScspboww==\",\"nonce\":\"Z7RqtjNjC6FrLUj5wVW1+HkjOW6Hib6K\"},\"hdIndex\":3,\"encPrivKeys\":{\"edb81c10122f34040cc4bef719a272fbbb1cf897\":{\"key\":\"8ab81tKBd4+CLAbzvS7SBFRTd6VWXBs86uBE43lgcmBu2U7UB22xdH64Q2hUf9eB\",\"nonce\":\"aGUEqI033FY39zKjWmZSI6PQrCLvkiRP\"},\"8bd7d5c000cf05284e98356370dc5ccaa3dbfc38\":{\"key\":\"+i3wmf4b+B898QtlOBfL0Ixirjg59/LLPX61vQ2L0xRPjXzNog0O4Wn15RemM5mY\",\"nonce\":\"imKrlkuoC5uuFkzJBbuDBluGCPJXNTKm\"},\"2340695474656e3124b8eba1172fbfb00eeac8f8\":{\"key\":\"pi+H9D8LYKsdCQKrfaJtsGFjE+X9s74xN675tsoIKrbPXhtpxMLOIQVtSqYveF62\",\"nonce\":\"49g80wDTovHwbguVVYf2FsYbp7Db5OAR\"}},\"addresses\":[\"edb81c10122f34040cc4bef719a272fbbb1cf897\",\"8bd7d5c000cf05284e98356370dc5ccaa3dbfc38\",\"2340695474656e3124b8eba1172fbfb00eeac8f8\"]}},\"version\":2}", + "config": { + "provider": { + "type": "etherscan" + } + } + }, + "meta": { + "version": 0 + } +} \ No newline at end of file From 28aba6e9dea52b66534d6ecb9713a7d20947c57c Mon Sep 17 00:00:00 2001 From: kumavis Date: Mon, 15 May 2017 23:56:13 -0700 Subject: [PATCH 260/372] migration 13 - change provider from testnet to ropsten --- app/scripts/migrations/013.js | 34 +++++++++++++++++++++++++++++++++ app/scripts/migrations/index.js | 1 + test/unit/migrations-test.js | 6 ++++++ 3 files changed, 41 insertions(+) create mode 100644 app/scripts/migrations/013.js diff --git a/app/scripts/migrations/013.js b/app/scripts/migrations/013.js new file mode 100644 index 000000000..8f11e510e --- /dev/null +++ b/app/scripts/migrations/013.js @@ -0,0 +1,34 @@ +const version = 13 + +/* + +This migration modifies the network config from ambiguous 'testnet' to explicit 'ropsten' + +*/ + +const clone = require('clone') + +module.exports = { + version, + + migrate: function (originalVersionedData) { + const versionedData = clone(originalVersionedData) + versionedData.meta.version = version + try { + const state = versionedData.data + const newState = transformState(state) + versionedData.data = newState + } catch (err) { + console.warn(`MetaMask Migration #${version}` + err.stack) + } + return Promise.resolve(versionedData) + }, +} + +function transformState (state) { + const newState = state + if (newState.config.provider.type === 'testnet') { + newState.config.provider.type = 'ropsten' + } + return newState +} diff --git a/app/scripts/migrations/index.js b/app/scripts/migrations/index.js index 019b4d13d..3a95cf88e 100644 --- a/app/scripts/migrations/index.js +++ b/app/scripts/migrations/index.js @@ -23,4 +23,5 @@ module.exports = [ require('./010'), require('./011'), require('./012'), + require('./013'), ] diff --git a/test/unit/migrations-test.js b/test/unit/migrations-test.js index 324e4d056..5bad25a45 100644 --- a/test/unit/migrations-test.js +++ b/test/unit/migrations-test.js @@ -16,6 +16,7 @@ const migration9 = require(path.join('..', '..', 'app', 'scripts', 'migrations', const migration10 = require(path.join('..', '..', 'app', 'scripts', 'migrations', '010')) const migration11 = require(path.join('..', '..', 'app', 'scripts', 'migrations', '011')) const migration12 = require(path.join('..', '..', 'app', 'scripts', 'migrations', '012')) +const migration13 = require(path.join('..', '..', 'app', 'scripts', 'migrations', '013')) const oldTestRpc = 'https://rawtestrpc.metamask.io/' @@ -97,6 +98,11 @@ describe('wallet1 is migrated successfully', () => { }).then((twelfthResult) => { assert.equal(twelfthResult.data.NoticeController.noticesList[0].body, '', 'notices that have been read should have an empty body.') assert.equal(twelfthResult.data.NoticeController.noticesList[1].body, 'nonempty', 'notices that have not been read should not have an empty body.') + + assert.equal(twelfthResult.data.config.provider.type, 'testnet', 'network is originally testnet.') + return migration13.migrate(twelfthResult) + }).then((thirteenthResult) => { + assert.equal(thirteenthResult.data.config.provider.type, 'ropsten', 'network has been changed to ropsten.') }) }) }) From 7a3b3e0f8a7a28d20980a9f839490138a562ee29 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Tue, 16 May 2017 10:27:41 -0700 Subject: [PATCH 261/372] Rename tx manager to tx controller --- .../transactions.js} | 0 app/scripts/metamask-controller.js | 32 +++--- ...-manager-test.js => tx-controller-test.js} | 108 +++++++++--------- 3 files changed, 70 insertions(+), 70 deletions(-) rename app/scripts/{transaction-manager.js => controllers/transactions.js} (100%) rename test/unit/{tx-manager-test.js => tx-controller-test.js} (67%) diff --git a/app/scripts/transaction-manager.js b/app/scripts/controllers/transactions.js similarity index 100% rename from app/scripts/transaction-manager.js rename to app/scripts/controllers/transactions.js diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 175602ec1..2406bda0d 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -17,7 +17,7 @@ const ShapeShiftController = require('./controllers/shapeshift') const AddressBookController = require('./controllers/address-book') const MessageManager = require('./lib/message-manager') const PersonalMessageManager = require('./lib/personal-message-manager') -const TxManager = require('./transaction-manager') +const TransactionController = require('./controllers/transactions') const ConfigManager = require('./lib/config-manager') const autoFaucet = require('./lib/auto-faucet') const nodeify = require('./lib/nodeify') @@ -90,8 +90,8 @@ module.exports = class MetamaskController extends EventEmitter { }, this.keyringController) // tx mgmt - this.txManager = new TxManager({ - initState: initState.TransactionManager, + this.txController = new TransactionController({ + initState: initState.TransactionController, networkStore: this.networkStore, preferencesStore: this.preferencesController.store, txHistoryLimit: 40, @@ -119,8 +119,8 @@ module.exports = class MetamaskController extends EventEmitter { this.publicConfigStore = this.initPublicConfigStore() // manual disk state subscriptions - this.txManager.store.subscribe((state) => { - this.store.updateState({ TransactionManager: state }) + this.txController.store.subscribe((state) => { + this.store.updateState({ TransactionController: state }) }) this.keyringController.store.subscribe((state) => { this.store.updateState({ KeyringController: state }) @@ -144,7 +144,7 @@ module.exports = class MetamaskController extends EventEmitter { // manual mem state subscriptions this.networkStore.subscribe(this.sendUpdate.bind(this)) this.ethStore.subscribe(this.sendUpdate.bind(this)) - this.txManager.memStore.subscribe(this.sendUpdate.bind(this)) + this.txController.memStore.subscribe(this.sendUpdate.bind(this)) this.messageManager.memStore.subscribe(this.sendUpdate.bind(this)) this.personalMessageManager.memStore.subscribe(this.sendUpdate.bind(this)) this.keyringController.memStore.subscribe(this.sendUpdate.bind(this)) @@ -223,7 +223,7 @@ module.exports = class MetamaskController extends EventEmitter { }, this.networkStore.getState(), this.ethStore.getState(), - this.txManager.memStore.getState(), + this.txController.memStore.getState(), this.messageManager.memStore.getState(), this.personalMessageManager.memStore.getState(), this.keyringController.memStore.getState(), @@ -248,7 +248,7 @@ module.exports = class MetamaskController extends EventEmitter { getApi () { const keyringController = this.keyringController const preferencesController = this.preferencesController - const txManager = this.txManager + const txController = this.txController const noticeController = this.noticeController const addressBookController = this.addressBookController @@ -289,9 +289,9 @@ module.exports = class MetamaskController extends EventEmitter { saveAccountLabel: nodeify(keyringController.saveAccountLabel).bind(keyringController), exportAccount: nodeify(keyringController.exportAccount).bind(keyringController), - // txManager - approveTransaction: txManager.approveTransaction.bind(txManager), - cancelTransaction: txManager.cancelTransaction.bind(txManager), + // txController + approveTransaction: txController.approveTransaction.bind(txController), + cancelTransaction: txController.cancelTransaction.bind(txController), updateAndApproveTransaction: this.updateAndApproveTx.bind(this), // messageManager @@ -421,12 +421,12 @@ module.exports = class MetamaskController extends EventEmitter { newUnapprovedTransaction (txParams, cb) { log.debug(`MetaMaskController newUnapprovedTransaction ${JSON.stringify(txParams)}`) const self = this - self.txManager.addUnapprovedTransaction(txParams, (err, txMeta) => { + self.txController.addUnapprovedTransaction(txParams, (err, txMeta) => { if (err) return cb(err) self.sendUpdate() self.opts.showUnapprovedTx(txMeta) // listen for tx completion (success, fail) - self.txManager.once(`${txMeta.id}:finished`, (completedTx) => { + self.txController.once(`${txMeta.id}:finished`, (completedTx) => { switch (completedTx.status) { case 'submitted': return cb(null, completedTx.hash) @@ -477,9 +477,9 @@ module.exports = class MetamaskController extends EventEmitter { updateAndApproveTx (txMeta, cb) { log.debug(`MetaMaskController - updateAndApproveTx: ${JSON.stringify(txMeta)}`) - const txManager = this.txManager - txManager.updateTx(txMeta) - txManager.approveTransaction(txMeta.id, cb) + const txController = this.txController + txController.updateTx(txMeta) + txController.approveTransaction(txMeta.id, cb) } signMessage (msgParams, cb) { diff --git a/test/unit/tx-manager-test.js b/test/unit/tx-controller-test.js similarity index 67% rename from test/unit/tx-manager-test.js rename to test/unit/tx-controller-test.js index b5d148723..d0b32ff41 100644 --- a/test/unit/tx-manager-test.js +++ b/test/unit/tx-controller-test.js @@ -3,17 +3,17 @@ const EventEmitter = require('events') const ethUtil = require('ethereumjs-util') const EthTx = require('ethereumjs-tx') const ObservableStore = require('obs-store') -const TransactionManager = require('../../app/scripts/transaction-manager') +const TransactionController = require('../../app/scripts/controllers/transactions') const noop = () => true const currentNetworkId = 42 const otherNetworkId = 36 const privKey = new Buffer('8718b9618a37d1fc78c436511fc6df3c8258d3250635bba617f33003270ec03e', 'hex') describe('Transaction Manager', function () { - let txManager + let txController beforeEach(function () { - txManager = new TransactionManager({ + txController = new TransactionController({ networkStore: new ObservableStore({ network: currentNetworkId }), txHistoryLimit: 10, blockTracker: new EventEmitter(), @@ -29,7 +29,7 @@ describe('Transaction Manager', function () { var sample = { value: '0x01', } - txManager.txProviderUtils.validateTxParams(sample, (err) => { + txController.txProviderUtils.validateTxParams(sample, (err) => { assert.equal(err, null, 'no error') }) }) @@ -38,7 +38,7 @@ describe('Transaction Manager', function () { var sample = { value: '-0x01', } - txManager.txProviderUtils.validateTxParams(sample, (err) => { + txController.txProviderUtils.validateTxParams(sample, (err) => { assert.ok(err, 'error') }) }) @@ -46,7 +46,7 @@ describe('Transaction Manager', function () { describe('#getTxList', function () { it('when new should return empty array', function () { - var result = txManager.getTxList() + var result = txController.getTxList() assert.ok(Array.isArray(result)) assert.equal(result.length, 0) }) @@ -58,8 +58,8 @@ describe('Transaction Manager', function () { describe('#addTx', function () { it('adds a tx returned in getTxList', function () { var tx = { id: 1, status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} } - txManager.addTx(tx, noop) - var result = txManager.getTxList() + txController.addTx(tx, noop) + var result = txController.getTxList() assert.ok(Array.isArray(result)) assert.equal(result.length, 1) assert.equal(result[0].id, 1) @@ -68,45 +68,45 @@ describe('Transaction Manager', function () { it('does not override txs from other networks', function () { var tx = { id: 1, status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} } var tx2 = { id: 2, status: 'confirmed', metamaskNetworkId: otherNetworkId, txParams: {} } - txManager.addTx(tx, noop) - txManager.addTx(tx2, noop) - var result = txManager.getFullTxList() - var result2 = txManager.getTxList() + txController.addTx(tx, noop) + txController.addTx(tx2, noop) + var result = txController.getFullTxList() + var result2 = txController.getTxList() assert.equal(result.length, 2, 'txs were deleted') assert.equal(result2.length, 1, 'incorrect number of txs on network.') }) it('cuts off early txs beyond a limit', function () { - const limit = txManager.txHistoryLimit + const limit = txController.txHistoryLimit for (let i = 0; i < limit + 1; i++) { const tx = { id: i, time: new Date(), status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} } - txManager.addTx(tx, noop) + txController.addTx(tx, noop) } - var result = txManager.getTxList() + var result = txController.getTxList() assert.equal(result.length, limit, `limit of ${limit} txs enforced`) assert.equal(result[0].id, 1, 'early txs truncted') }) it('cuts off early txs beyond a limit whether or not it is confirmed or rejected', function () { - const limit = txManager.txHistoryLimit + const limit = txController.txHistoryLimit for (let i = 0; i < limit + 1; i++) { const tx = { id: i, time: new Date(), status: 'rejected', metamaskNetworkId: currentNetworkId, txParams: {} } - txManager.addTx(tx, noop) + txController.addTx(tx, noop) } - var result = txManager.getTxList() + var result = txController.getTxList() assert.equal(result.length, limit, `limit of ${limit} txs enforced`) assert.equal(result[0].id, 1, 'early txs truncted') }) it('cuts off early txs beyond a limit but does not cut unapproved txs', function () { var unconfirmedTx = { id: 0, time: new Date(), status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} } - txManager.addTx(unconfirmedTx, noop) - const limit = txManager.txHistoryLimit + txController.addTx(unconfirmedTx, noop) + const limit = txController.txHistoryLimit for (let i = 1; i < limit + 1; i++) { const tx = { id: i, time: new Date(), status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} } - txManager.addTx(tx, noop) + txController.addTx(tx, noop) } - var result = txManager.getTxList() + var result = txController.getTxList() assert.equal(result.length, limit, `limit of ${limit} txs enforced`) assert.equal(result[0].id, 0, 'first tx should still be there') assert.equal(result[0].status, 'unapproved', 'first tx should be unapproved') @@ -117,9 +117,9 @@ describe('Transaction Manager', function () { describe('#setTxStatusSigned', function () { it('sets the tx status to signed', function () { var tx = { id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} } - txManager.addTx(tx, noop) - txManager.setTxStatusSigned(1) - var result = txManager.getTxList() + txController.addTx(tx, noop) + txController.setTxStatusSigned(1) + var result = txController.getTxList() assert.ok(Array.isArray(result)) assert.equal(result.length, 1) assert.equal(result[0].status, 'signed') @@ -132,18 +132,18 @@ describe('Transaction Manager', function () { assert(true, 'event listener has been triggered and noop executed') done() } - txManager.addTx(tx) - txManager.on('1:signed', noop) - txManager.setTxStatusSigned(1) + txController.addTx(tx) + txController.on('1:signed', noop) + txController.setTxStatusSigned(1) }) }) describe('#setTxStatusRejected', function () { it('sets the tx status to rejected', function () { var tx = { id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} } - txManager.addTx(tx) - txManager.setTxStatusRejected(1) - var result = txManager.getTxList() + txController.addTx(tx) + txController.setTxStatusRejected(1) + var result = txController.getTxList() assert.ok(Array.isArray(result)) assert.equal(result.length, 1) assert.equal(result[0].status, 'rejected') @@ -152,31 +152,31 @@ describe('Transaction Manager', function () { it('should emit a rejected event to signal the exciton of callback', (done) => { this.timeout(10000) var tx = { id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} } - txManager.addTx(tx) + txController.addTx(tx) const noop = function (err, txId) { assert(true, 'event listener has been triggered and noop executed') done() } - txManager.on('1:rejected', noop) - txManager.setTxStatusRejected(1) + txController.on('1:rejected', noop) + txController.setTxStatusRejected(1) }) }) describe('#updateTx', function () { it('replaces the tx with the same id', function () { - txManager.addTx({ id: '1', status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }, noop) - txManager.addTx({ id: '2', status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} }, noop) - txManager.updateTx({ id: '1', status: 'blah', hash: 'foo', metamaskNetworkId: currentNetworkId, txParams: {} }) - var result = txManager.getTx('1') + txController.addTx({ id: '1', status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }, noop) + txController.addTx({ id: '2', status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} }, noop) + txController.updateTx({ id: '1', status: 'blah', hash: 'foo', metamaskNetworkId: currentNetworkId, txParams: {} }) + var result = txController.getTx('1') assert.equal(result.hash, 'foo') }) }) describe('#getUnapprovedTxList', function () { it('returns unapproved txs in a hash', function () { - txManager.addTx({ id: '1', status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }, noop) - txManager.addTx({ id: '2', status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} }, noop) - const result = txManager.getUnapprovedTxList() + txController.addTx({ id: '1', status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }, noop) + txController.addTx({ id: '2', status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} }, noop) + const result = txController.getUnapprovedTxList() assert.equal(typeof result, 'object') assert.equal(result['1'].status, 'unapproved') assert.equal(result['2'], undefined) @@ -185,10 +185,10 @@ describe('Transaction Manager', function () { describe('#getTx', function () { it('returns a tx with the requested id', function () { - txManager.addTx({ id: '1', status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }, noop) - txManager.addTx({ id: '2', status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} }, noop) - assert.equal(txManager.getTx('1').status, 'unapproved') - assert.equal(txManager.getTx('2').status, 'confirmed') + txController.addTx({ id: '1', status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }, noop) + txController.addTx({ id: '2', status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} }, noop) + assert.equal(txController.getTx('1').status, 'unapproved') + assert.equal(txController.getTx('2').status, 'confirmed') }) }) @@ -206,28 +206,28 @@ describe('Transaction Manager', function () { { id: 8, status: 'confirmed', txParams: { from: '0xbb', to: '0xaa' }, metamaskNetworkId: currentNetworkId }, { id: 9, status: 'confirmed', txParams: { from: '0xbb', to: '0xaa' }, metamaskNetworkId: currentNetworkId }, ] - txMetas.forEach((txMeta) => txManager.addTx(txMeta, noop)) + txMetas.forEach((txMeta) => txController.addTx(txMeta, noop)) let filterParams filterParams = { status: 'unapproved', from: '0xaa' } - assert.equal(txManager.getFilteredTxList(filterParams).length, 3, `getFilteredTxList - ${JSON.stringify(filterParams)}`) + assert.equal(txController.getFilteredTxList(filterParams).length, 3, `getFilteredTxList - ${JSON.stringify(filterParams)}`) filterParams = { status: 'unapproved', to: '0xaa' } - assert.equal(txManager.getFilteredTxList(filterParams).length, 2, `getFilteredTxList - ${JSON.stringify(filterParams)}`) + assert.equal(txController.getFilteredTxList(filterParams).length, 2, `getFilteredTxList - ${JSON.stringify(filterParams)}`) filterParams = { status: 'confirmed', from: '0xbb' } - assert.equal(txManager.getFilteredTxList(filterParams).length, 3, `getFilteredTxList - ${JSON.stringify(filterParams)}`) + assert.equal(txController.getFilteredTxList(filterParams).length, 3, `getFilteredTxList - ${JSON.stringify(filterParams)}`) filterParams = { status: 'confirmed' } - assert.equal(txManager.getFilteredTxList(filterParams).length, 5, `getFilteredTxList - ${JSON.stringify(filterParams)}`) + assert.equal(txController.getFilteredTxList(filterParams).length, 5, `getFilteredTxList - ${JSON.stringify(filterParams)}`) filterParams = { from: '0xaa' } - assert.equal(txManager.getFilteredTxList(filterParams).length, 5, `getFilteredTxList - ${JSON.stringify(filterParams)}`) + assert.equal(txController.getFilteredTxList(filterParams).length, 5, `getFilteredTxList - ${JSON.stringify(filterParams)}`) filterParams = { to: '0xaa' } - assert.equal(txManager.getFilteredTxList(filterParams).length, 5, `getFilteredTxList - ${JSON.stringify(filterParams)}`) + assert.equal(txController.getFilteredTxList(filterParams).length, 5, `getFilteredTxList - ${JSON.stringify(filterParams)}`) }) }) describe('#sign replay-protected tx', function () { it('prepares a tx with the chainId set', function () { - txManager.addTx({ id: '1', status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }, noop) - txManager.signTransaction('1', (err, rawTx) => { + txController.addTx({ id: '1', status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }, noop) + txController.signTransaction('1', (err, rawTx) => { if (err) return assert.fail('it should not fail') const ethTx = new EthTx(ethUtil.toBuffer(rawTx)) assert.equal(ethTx.getChainId(), currentNetworkId) From 2df9344be5bf1c65daae2ca7ea47982fe2a1c2fb Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Tue, 16 May 2017 10:27:41 -0700 Subject: [PATCH 262/372] Rename tx manager to tx controller --- app/scripts/background.js | 4 +- .../transactions.js} | 0 app/scripts/metamask-controller.js | 32 +++--- ...-manager-test.js => tx-controller-test.js} | 108 +++++++++--------- 4 files changed, 72 insertions(+), 72 deletions(-) rename app/scripts/{transaction-manager.js => controllers/transactions.js} (100%) rename test/unit/{tx-manager-test.js => tx-controller-test.js} (67%) diff --git a/app/scripts/background.js b/app/scripts/background.js index e738a9712..63c8a7252 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -114,13 +114,13 @@ function setupController (initState) { // updateBadge() - controller.txManager.on('updateBadge', updateBadge) + controller.txController.on('updateBadge', updateBadge) controller.messageManager.on('updateBadge', updateBadge) // plugin badge text function updateBadge () { var label = '' - var unapprovedTxCount = controller.txManager.unapprovedTxCount + var unapprovedTxCount = controller.txController.unapprovedTxCount var unapprovedMsgCount = controller.messageManager.unapprovedMsgCount var count = unapprovedTxCount + unapprovedMsgCount if (count) { diff --git a/app/scripts/transaction-manager.js b/app/scripts/controllers/transactions.js similarity index 100% rename from app/scripts/transaction-manager.js rename to app/scripts/controllers/transactions.js diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 175602ec1..f18da9033 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -17,7 +17,7 @@ const ShapeShiftController = require('./controllers/shapeshift') const AddressBookController = require('./controllers/address-book') const MessageManager = require('./lib/message-manager') const PersonalMessageManager = require('./lib/personal-message-manager') -const TxManager = require('./transaction-manager') +const TransactionController = require('./controllers/transactions') const ConfigManager = require('./lib/config-manager') const autoFaucet = require('./lib/auto-faucet') const nodeify = require('./lib/nodeify') @@ -90,8 +90,8 @@ module.exports = class MetamaskController extends EventEmitter { }, this.keyringController) // tx mgmt - this.txManager = new TxManager({ - initState: initState.TransactionManager, + this.txController = new TransactionController({ + initState: initState.TransactionController || initState.TransactionManager, networkStore: this.networkStore, preferencesStore: this.preferencesController.store, txHistoryLimit: 40, @@ -119,8 +119,8 @@ module.exports = class MetamaskController extends EventEmitter { this.publicConfigStore = this.initPublicConfigStore() // manual disk state subscriptions - this.txManager.store.subscribe((state) => { - this.store.updateState({ TransactionManager: state }) + this.txController.store.subscribe((state) => { + this.store.updateState({ TransactionController: state }) }) this.keyringController.store.subscribe((state) => { this.store.updateState({ KeyringController: state }) @@ -144,7 +144,7 @@ module.exports = class MetamaskController extends EventEmitter { // manual mem state subscriptions this.networkStore.subscribe(this.sendUpdate.bind(this)) this.ethStore.subscribe(this.sendUpdate.bind(this)) - this.txManager.memStore.subscribe(this.sendUpdate.bind(this)) + this.txController.memStore.subscribe(this.sendUpdate.bind(this)) this.messageManager.memStore.subscribe(this.sendUpdate.bind(this)) this.personalMessageManager.memStore.subscribe(this.sendUpdate.bind(this)) this.keyringController.memStore.subscribe(this.sendUpdate.bind(this)) @@ -223,7 +223,7 @@ module.exports = class MetamaskController extends EventEmitter { }, this.networkStore.getState(), this.ethStore.getState(), - this.txManager.memStore.getState(), + this.txController.memStore.getState(), this.messageManager.memStore.getState(), this.personalMessageManager.memStore.getState(), this.keyringController.memStore.getState(), @@ -248,7 +248,7 @@ module.exports = class MetamaskController extends EventEmitter { getApi () { const keyringController = this.keyringController const preferencesController = this.preferencesController - const txManager = this.txManager + const txController = this.txController const noticeController = this.noticeController const addressBookController = this.addressBookController @@ -289,9 +289,9 @@ module.exports = class MetamaskController extends EventEmitter { saveAccountLabel: nodeify(keyringController.saveAccountLabel).bind(keyringController), exportAccount: nodeify(keyringController.exportAccount).bind(keyringController), - // txManager - approveTransaction: txManager.approveTransaction.bind(txManager), - cancelTransaction: txManager.cancelTransaction.bind(txManager), + // txController + approveTransaction: txController.approveTransaction.bind(txController), + cancelTransaction: txController.cancelTransaction.bind(txController), updateAndApproveTransaction: this.updateAndApproveTx.bind(this), // messageManager @@ -421,12 +421,12 @@ module.exports = class MetamaskController extends EventEmitter { newUnapprovedTransaction (txParams, cb) { log.debug(`MetaMaskController newUnapprovedTransaction ${JSON.stringify(txParams)}`) const self = this - self.txManager.addUnapprovedTransaction(txParams, (err, txMeta) => { + self.txController.addUnapprovedTransaction(txParams, (err, txMeta) => { if (err) return cb(err) self.sendUpdate() self.opts.showUnapprovedTx(txMeta) // listen for tx completion (success, fail) - self.txManager.once(`${txMeta.id}:finished`, (completedTx) => { + self.txController.once(`${txMeta.id}:finished`, (completedTx) => { switch (completedTx.status) { case 'submitted': return cb(null, completedTx.hash) @@ -477,9 +477,9 @@ module.exports = class MetamaskController extends EventEmitter { updateAndApproveTx (txMeta, cb) { log.debug(`MetaMaskController - updateAndApproveTx: ${JSON.stringify(txMeta)}`) - const txManager = this.txManager - txManager.updateTx(txMeta) - txManager.approveTransaction(txMeta.id, cb) + const txController = this.txController + txController.updateTx(txMeta) + txController.approveTransaction(txMeta.id, cb) } signMessage (msgParams, cb) { diff --git a/test/unit/tx-manager-test.js b/test/unit/tx-controller-test.js similarity index 67% rename from test/unit/tx-manager-test.js rename to test/unit/tx-controller-test.js index b5d148723..d0b32ff41 100644 --- a/test/unit/tx-manager-test.js +++ b/test/unit/tx-controller-test.js @@ -3,17 +3,17 @@ const EventEmitter = require('events') const ethUtil = require('ethereumjs-util') const EthTx = require('ethereumjs-tx') const ObservableStore = require('obs-store') -const TransactionManager = require('../../app/scripts/transaction-manager') +const TransactionController = require('../../app/scripts/controllers/transactions') const noop = () => true const currentNetworkId = 42 const otherNetworkId = 36 const privKey = new Buffer('8718b9618a37d1fc78c436511fc6df3c8258d3250635bba617f33003270ec03e', 'hex') describe('Transaction Manager', function () { - let txManager + let txController beforeEach(function () { - txManager = new TransactionManager({ + txController = new TransactionController({ networkStore: new ObservableStore({ network: currentNetworkId }), txHistoryLimit: 10, blockTracker: new EventEmitter(), @@ -29,7 +29,7 @@ describe('Transaction Manager', function () { var sample = { value: '0x01', } - txManager.txProviderUtils.validateTxParams(sample, (err) => { + txController.txProviderUtils.validateTxParams(sample, (err) => { assert.equal(err, null, 'no error') }) }) @@ -38,7 +38,7 @@ describe('Transaction Manager', function () { var sample = { value: '-0x01', } - txManager.txProviderUtils.validateTxParams(sample, (err) => { + txController.txProviderUtils.validateTxParams(sample, (err) => { assert.ok(err, 'error') }) }) @@ -46,7 +46,7 @@ describe('Transaction Manager', function () { describe('#getTxList', function () { it('when new should return empty array', function () { - var result = txManager.getTxList() + var result = txController.getTxList() assert.ok(Array.isArray(result)) assert.equal(result.length, 0) }) @@ -58,8 +58,8 @@ describe('Transaction Manager', function () { describe('#addTx', function () { it('adds a tx returned in getTxList', function () { var tx = { id: 1, status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} } - txManager.addTx(tx, noop) - var result = txManager.getTxList() + txController.addTx(tx, noop) + var result = txController.getTxList() assert.ok(Array.isArray(result)) assert.equal(result.length, 1) assert.equal(result[0].id, 1) @@ -68,45 +68,45 @@ describe('Transaction Manager', function () { it('does not override txs from other networks', function () { var tx = { id: 1, status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} } var tx2 = { id: 2, status: 'confirmed', metamaskNetworkId: otherNetworkId, txParams: {} } - txManager.addTx(tx, noop) - txManager.addTx(tx2, noop) - var result = txManager.getFullTxList() - var result2 = txManager.getTxList() + txController.addTx(tx, noop) + txController.addTx(tx2, noop) + var result = txController.getFullTxList() + var result2 = txController.getTxList() assert.equal(result.length, 2, 'txs were deleted') assert.equal(result2.length, 1, 'incorrect number of txs on network.') }) it('cuts off early txs beyond a limit', function () { - const limit = txManager.txHistoryLimit + const limit = txController.txHistoryLimit for (let i = 0; i < limit + 1; i++) { const tx = { id: i, time: new Date(), status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} } - txManager.addTx(tx, noop) + txController.addTx(tx, noop) } - var result = txManager.getTxList() + var result = txController.getTxList() assert.equal(result.length, limit, `limit of ${limit} txs enforced`) assert.equal(result[0].id, 1, 'early txs truncted') }) it('cuts off early txs beyond a limit whether or not it is confirmed or rejected', function () { - const limit = txManager.txHistoryLimit + const limit = txController.txHistoryLimit for (let i = 0; i < limit + 1; i++) { const tx = { id: i, time: new Date(), status: 'rejected', metamaskNetworkId: currentNetworkId, txParams: {} } - txManager.addTx(tx, noop) + txController.addTx(tx, noop) } - var result = txManager.getTxList() + var result = txController.getTxList() assert.equal(result.length, limit, `limit of ${limit} txs enforced`) assert.equal(result[0].id, 1, 'early txs truncted') }) it('cuts off early txs beyond a limit but does not cut unapproved txs', function () { var unconfirmedTx = { id: 0, time: new Date(), status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} } - txManager.addTx(unconfirmedTx, noop) - const limit = txManager.txHistoryLimit + txController.addTx(unconfirmedTx, noop) + const limit = txController.txHistoryLimit for (let i = 1; i < limit + 1; i++) { const tx = { id: i, time: new Date(), status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} } - txManager.addTx(tx, noop) + txController.addTx(tx, noop) } - var result = txManager.getTxList() + var result = txController.getTxList() assert.equal(result.length, limit, `limit of ${limit} txs enforced`) assert.equal(result[0].id, 0, 'first tx should still be there') assert.equal(result[0].status, 'unapproved', 'first tx should be unapproved') @@ -117,9 +117,9 @@ describe('Transaction Manager', function () { describe('#setTxStatusSigned', function () { it('sets the tx status to signed', function () { var tx = { id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} } - txManager.addTx(tx, noop) - txManager.setTxStatusSigned(1) - var result = txManager.getTxList() + txController.addTx(tx, noop) + txController.setTxStatusSigned(1) + var result = txController.getTxList() assert.ok(Array.isArray(result)) assert.equal(result.length, 1) assert.equal(result[0].status, 'signed') @@ -132,18 +132,18 @@ describe('Transaction Manager', function () { assert(true, 'event listener has been triggered and noop executed') done() } - txManager.addTx(tx) - txManager.on('1:signed', noop) - txManager.setTxStatusSigned(1) + txController.addTx(tx) + txController.on('1:signed', noop) + txController.setTxStatusSigned(1) }) }) describe('#setTxStatusRejected', function () { it('sets the tx status to rejected', function () { var tx = { id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} } - txManager.addTx(tx) - txManager.setTxStatusRejected(1) - var result = txManager.getTxList() + txController.addTx(tx) + txController.setTxStatusRejected(1) + var result = txController.getTxList() assert.ok(Array.isArray(result)) assert.equal(result.length, 1) assert.equal(result[0].status, 'rejected') @@ -152,31 +152,31 @@ describe('Transaction Manager', function () { it('should emit a rejected event to signal the exciton of callback', (done) => { this.timeout(10000) var tx = { id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} } - txManager.addTx(tx) + txController.addTx(tx) const noop = function (err, txId) { assert(true, 'event listener has been triggered and noop executed') done() } - txManager.on('1:rejected', noop) - txManager.setTxStatusRejected(1) + txController.on('1:rejected', noop) + txController.setTxStatusRejected(1) }) }) describe('#updateTx', function () { it('replaces the tx with the same id', function () { - txManager.addTx({ id: '1', status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }, noop) - txManager.addTx({ id: '2', status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} }, noop) - txManager.updateTx({ id: '1', status: 'blah', hash: 'foo', metamaskNetworkId: currentNetworkId, txParams: {} }) - var result = txManager.getTx('1') + txController.addTx({ id: '1', status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }, noop) + txController.addTx({ id: '2', status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} }, noop) + txController.updateTx({ id: '1', status: 'blah', hash: 'foo', metamaskNetworkId: currentNetworkId, txParams: {} }) + var result = txController.getTx('1') assert.equal(result.hash, 'foo') }) }) describe('#getUnapprovedTxList', function () { it('returns unapproved txs in a hash', function () { - txManager.addTx({ id: '1', status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }, noop) - txManager.addTx({ id: '2', status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} }, noop) - const result = txManager.getUnapprovedTxList() + txController.addTx({ id: '1', status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }, noop) + txController.addTx({ id: '2', status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} }, noop) + const result = txController.getUnapprovedTxList() assert.equal(typeof result, 'object') assert.equal(result['1'].status, 'unapproved') assert.equal(result['2'], undefined) @@ -185,10 +185,10 @@ describe('Transaction Manager', function () { describe('#getTx', function () { it('returns a tx with the requested id', function () { - txManager.addTx({ id: '1', status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }, noop) - txManager.addTx({ id: '2', status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} }, noop) - assert.equal(txManager.getTx('1').status, 'unapproved') - assert.equal(txManager.getTx('2').status, 'confirmed') + txController.addTx({ id: '1', status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }, noop) + txController.addTx({ id: '2', status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} }, noop) + assert.equal(txController.getTx('1').status, 'unapproved') + assert.equal(txController.getTx('2').status, 'confirmed') }) }) @@ -206,28 +206,28 @@ describe('Transaction Manager', function () { { id: 8, status: 'confirmed', txParams: { from: '0xbb', to: '0xaa' }, metamaskNetworkId: currentNetworkId }, { id: 9, status: 'confirmed', txParams: { from: '0xbb', to: '0xaa' }, metamaskNetworkId: currentNetworkId }, ] - txMetas.forEach((txMeta) => txManager.addTx(txMeta, noop)) + txMetas.forEach((txMeta) => txController.addTx(txMeta, noop)) let filterParams filterParams = { status: 'unapproved', from: '0xaa' } - assert.equal(txManager.getFilteredTxList(filterParams).length, 3, `getFilteredTxList - ${JSON.stringify(filterParams)}`) + assert.equal(txController.getFilteredTxList(filterParams).length, 3, `getFilteredTxList - ${JSON.stringify(filterParams)}`) filterParams = { status: 'unapproved', to: '0xaa' } - assert.equal(txManager.getFilteredTxList(filterParams).length, 2, `getFilteredTxList - ${JSON.stringify(filterParams)}`) + assert.equal(txController.getFilteredTxList(filterParams).length, 2, `getFilteredTxList - ${JSON.stringify(filterParams)}`) filterParams = { status: 'confirmed', from: '0xbb' } - assert.equal(txManager.getFilteredTxList(filterParams).length, 3, `getFilteredTxList - ${JSON.stringify(filterParams)}`) + assert.equal(txController.getFilteredTxList(filterParams).length, 3, `getFilteredTxList - ${JSON.stringify(filterParams)}`) filterParams = { status: 'confirmed' } - assert.equal(txManager.getFilteredTxList(filterParams).length, 5, `getFilteredTxList - ${JSON.stringify(filterParams)}`) + assert.equal(txController.getFilteredTxList(filterParams).length, 5, `getFilteredTxList - ${JSON.stringify(filterParams)}`) filterParams = { from: '0xaa' } - assert.equal(txManager.getFilteredTxList(filterParams).length, 5, `getFilteredTxList - ${JSON.stringify(filterParams)}`) + assert.equal(txController.getFilteredTxList(filterParams).length, 5, `getFilteredTxList - ${JSON.stringify(filterParams)}`) filterParams = { to: '0xaa' } - assert.equal(txManager.getFilteredTxList(filterParams).length, 5, `getFilteredTxList - ${JSON.stringify(filterParams)}`) + assert.equal(txController.getFilteredTxList(filterParams).length, 5, `getFilteredTxList - ${JSON.stringify(filterParams)}`) }) }) describe('#sign replay-protected tx', function () { it('prepares a tx with the chainId set', function () { - txManager.addTx({ id: '1', status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }, noop) - txManager.signTransaction('1', (err, rawTx) => { + txController.addTx({ id: '1', status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }, noop) + txController.signTransaction('1', (err, rawTx) => { if (err) return assert.fail('it should not fail') const ethTx = new EthTx(ethUtil.toBuffer(rawTx)) assert.equal(ethTx.getChainId(), currentNetworkId) From b4e6ea9db787cbf7839d9caad6e1ea0385cc588e Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Tue, 16 May 2017 11:34:53 -0700 Subject: [PATCH 263/372] Fix fiat rendering Fixes #1439. When reorganizing fiat-value component to not use global state, had missed its necessary `currentCurrency` parameter. This now passes it in wherever it's used. --- ui/app/account-detail.js | 4 +++- ui/app/accounts/account-list-item.js | 4 +++- ui/app/accounts/index.js | 4 +++- ui/app/components/eth-balance.js | 4 ++-- ui/app/components/fiat-value.js | 6 ++---- ui/app/components/pending-tx.js | 9 +++++++-- ui/app/components/shift-list-item.js | 8 +++++--- ui/app/components/transaction-list-item.js | 7 ++++--- ui/app/conf-tx.js | 4 +++- ui/app/send.js | 3 +++ 10 files changed, 35 insertions(+), 18 deletions(-) diff --git a/ui/app/account-detail.js b/ui/app/account-detail.js index 7cadb9d47..7a78a360c 100644 --- a/ui/app/account-detail.js +++ b/ui/app/account-detail.js @@ -30,6 +30,7 @@ function mapStateToProps (state) { shapeShiftTxList: state.metamask.shapeShiftTxList, transactions: state.metamask.selectedAddressTxList || [], conversionRate: state.metamask.conversionRate, + currentCurrency: state.metamask.currentCurrency, } } @@ -44,7 +45,7 @@ AccountDetailScreen.prototype.render = function () { var checksumAddress = selected && ethUtil.toChecksumAddress(selected) var identity = props.identities[selected] var account = props.accounts[selected] - const { network, conversionRate } = props + const { network, conversionRate, currentCurrency } = props return ( @@ -184,6 +185,7 @@ AccountDetailScreen.prototype.render = function () { h(EthBalance, { value: account && account.balance, conversionRate, + currentCurrency, style: { lineHeight: '7px', marginTop: '10px', diff --git a/ui/app/accounts/account-list-item.js b/ui/app/accounts/account-list-item.js index 0e87af612..10a0b6cc7 100644 --- a/ui/app/accounts/account-list-item.js +++ b/ui/app/accounts/account-list-item.js @@ -15,7 +15,8 @@ function AccountListItem () { } AccountListItem.prototype.render = function () { - const { identity, selectedAddress, accounts, onShowDetail, conversionRate } = this.props + const { identity, selectedAddress, accounts, onShowDetail, + conversionRate, currentCurrency } = this.props const checksumAddress = identity && identity.address && ethUtil.toChecksumAddress(identity.address) const isSelected = selectedAddress === identity.address @@ -52,6 +53,7 @@ AccountListItem.prototype.render = function () { }, checksumAddress), h(EthBalance, { value: account && account.balance, + currentCurrency, conversionRate, style: { lineHeight: '7px', diff --git a/ui/app/accounts/index.js b/ui/app/accounts/index.js index 5105c214b..ac2615cd7 100644 --- a/ui/app/accounts/index.js +++ b/ui/app/accounts/index.js @@ -24,6 +24,7 @@ function mapStateToProps (state) { pending, keyrings: state.metamask.keyrings, conversionRate: state.metamask.conversionRate, + currentCurrency: state.metamask.currentCurrency, } } @@ -34,7 +35,7 @@ function AccountsScreen () { AccountsScreen.prototype.render = function () { const props = this.props - const { keyrings, conversionRate } = props + const { keyrings, conversionRate, currentCurrency } = props const identityList = valuesFor(props.identities) const unapprovedTxList = valuesFor(props.unapprovedTxs) @@ -83,6 +84,7 @@ AccountsScreen.prototype.render = function () { identity, selectedAddress: this.props.selectedAddress, conversionRate, + currentCurrency, accounts: this.props.accounts, onShowDetail: this.onShowDetail.bind(this), pending, diff --git a/ui/app/components/eth-balance.js b/ui/app/components/eth-balance.js index 21906aa09..4f538fd31 100644 --- a/ui/app/components/eth-balance.js +++ b/ui/app/components/eth-balance.js @@ -37,7 +37,7 @@ EthBalanceComponent.prototype.render = function () { } EthBalanceComponent.prototype.renderBalance = function (value) { var props = this.props - const { conversionRate, shorten, incoming } = props + const { conversionRate, shorten, incoming, currentCurrency } = props if (value === 'None') return value if (value === '...') return value var balanceObj = generateBalanceObject(value, shorten ? 1 : 3) @@ -83,7 +83,7 @@ EthBalanceComponent.prototype.renderBalance = function (value) { }, label), ]), - showFiat ? h(FiatValue, { value, conversionRate }) : null, + showFiat ? h(FiatValue, { value: props.value, conversionRate, currentCurrency }) : null, ])) ) } diff --git a/ui/app/components/fiat-value.js b/ui/app/components/fiat-value.js index 6e306c9e6..8a64a1cfc 100644 --- a/ui/app/components/fiat-value.js +++ b/ui/app/components/fiat-value.js @@ -12,7 +12,7 @@ function FiatValue () { FiatValue.prototype.render = function () { const props = this.props - const { conversionRate } = props + const { conversionRate, currentCurrency } = props const value = formatBalance(props.value, 6) @@ -28,9 +28,7 @@ FiatValue.prototype.render = function () { fiatTooltipNumber = 'Unknown' } - var fiatSuffix = props.currentCurrency - - return fiatDisplay(fiatDisplayNumber, fiatSuffix) + return fiatDisplay(fiatDisplayNumber, currentCurrency) } function fiatDisplay (fiatDisplayNumber, fiatSuffix) { diff --git a/ui/app/components/pending-tx.js b/ui/app/components/pending-tx.js index b084a1d2a..0d1f06ba6 100644 --- a/ui/app/components/pending-tx.js +++ b/ui/app/components/pending-tx.js @@ -31,6 +31,8 @@ function PendingTx () { PendingTx.prototype.render = function () { const props = this.props + const { currentCurrency } = props + const conversionRate = props.conversionRate const txMeta = this.gatherTxMeta() const txParams = txMeta.txParams || {} @@ -104,6 +106,7 @@ PendingTx.prototype.render = function () { h(EthBalance, { value: balance, conversionRate, + currentCurrency, inline: true, labelColor: '#F7861C', }), @@ -141,7 +144,7 @@ PendingTx.prototype.render = function () { // in the way that gas and gasLimit currently are. h('.row', [ h('.cell.label', 'Amount'), - h(EthBalance, { value: txParams.value }), + h(EthBalance, { value: txParams.value, currentCurrency, conversionRate }), ]), // Gas Limit (customizable) @@ -189,7 +192,7 @@ PendingTx.prototype.render = function () { // Max Transaction Fee (calculated) h('.cell.row', [ h('.cell.label', 'Max Transaction Fee'), - h(EthBalance, { value: txFeeBn.toString(16) }), + h(EthBalance, { value: txFeeBn.toString(16), currentCurrency, conversionRate }), ]), h('.cell.row', { @@ -208,6 +211,8 @@ PendingTx.prototype.render = function () { }, [ h(EthBalance, { value: maxCost.toString(16), + currentCurrency, + conversionRate, inline: true, labelColor: 'black', fontSize: '16px', diff --git a/ui/app/components/shift-list-item.js b/ui/app/components/shift-list-item.js index db5fda5cb..32bfbeda4 100644 --- a/ui/app/components/shift-list-item.js +++ b/ui/app/components/shift-list-item.js @@ -8,7 +8,7 @@ const actions = require('../actions') const addressSummary = require('../util').addressSummary const CopyButton = require('./copyButton') -const EtherBalance = require('./eth-balance') +const EthBalance = require('./eth-balance') const Tooltip = require('./tooltip') @@ -17,6 +17,7 @@ module.exports = connect(mapStateToProps)(ShiftListItem) function mapStateToProps (state) { return { conversionRate: state.metamask.conversionRate, + currentCurrency: state.metamask.currentCurrency, } } @@ -66,7 +67,7 @@ function formatDate (date) { ShiftListItem.prototype.renderUtilComponents = function () { var props = this.props - const { conversionRate } = props + const { conversionRate, currentCurrency } = props switch (props.response.status) { case 'no_deposits': @@ -97,9 +98,10 @@ ShiftListItem.prototype.renderUtilComponents = function () { h(CopyButton, { value: this.props.response.transaction, }), - h(EtherBalance, { + h(EthBalance, { value: `${props.response.outgoingCoin}`, conversionRate, + currentCurrency, width: '55px', shorten: true, needsParse: false, diff --git a/ui/app/components/transaction-list-item.js b/ui/app/components/transaction-list-item.js index 3db4c016e..c2a585003 100644 --- a/ui/app/components/transaction-list-item.js +++ b/ui/app/components/transaction-list-item.js @@ -2,7 +2,7 @@ const Component = require('react').Component const h = require('react-hyperscript') const inherits = require('util').inherits -const EtherBalance = require('./eth-balance') +const EthBalance = require('./eth-balance') const addressSummary = require('../util').addressSummary const explorerLink = require('../../lib/explorer-link') const CopyButton = require('./copyButton') @@ -19,7 +19,7 @@ function TransactionListItem () { } TransactionListItem.prototype.render = function () { - const { transaction, network, conversionRate } = this.props + const { transaction, network, conversionRate, currentCurrency } = this.props if (transaction.key === 'shapeshift') { if (network === '1') return h(ShiftListItem, transaction) } @@ -78,9 +78,10 @@ TransactionListItem.prototype.render = function () { // Places a copy button if tx is successful, else places a placeholder empty div. transaction.hash ? h(CopyButton, { value: transaction.hash }) : h('div', {style: { display: 'flex', alignItems: 'center', width: '26px' }}), - isTx ? h(EtherBalance, { + isTx ? h(EthBalance, { value: txParams.value, conversionRate, + currentCurrency, width: '55px', shorten: true, showFiat: false, diff --git a/ui/app/conf-tx.js b/ui/app/conf-tx.js index c4df66931..0d7c4c1bb 100644 --- a/ui/app/conf-tx.js +++ b/ui/app/conf-tx.js @@ -28,6 +28,7 @@ function mapStateToProps (state) { network: state.metamask.network, provider: state.metamask.provider, conversionRate: state.metamask.conversionRate, + currentCurrency: state.metamask.currentCurrency, } } @@ -38,7 +39,7 @@ function ConfirmTxScreen () { ConfirmTxScreen.prototype.render = function () { const props = this.props - const { network, provider, unapprovedTxs, + const { network, provider, unapprovedTxs, currentCurrency, unapprovedMsgs, unapprovedPersonalMsgs, conversionRate } = props var unconfTxList = txHelper(unapprovedTxs, unapprovedMsgs, unapprovedPersonalMsgs, network) @@ -104,6 +105,7 @@ ConfirmTxScreen.prototype.render = function () { accounts: props.accounts, identities: props.identities, conversionRate, + currentCurrency, // Actions buyEth: this.buyEth.bind(this, txParams.from || props.selectedAddress), sendTransaction: this.sendTransaction.bind(this, txData), diff --git a/ui/app/send.js b/ui/app/send.js index a313c6bee..fd6994145 100644 --- a/ui/app/send.js +++ b/ui/app/send.js @@ -22,6 +22,7 @@ function mapStateToProps (state) { network: state.metamask.network, addressBook: state.metamask.addressBook, conversionRate: state.metamask.conversionRate, + currentCurrency: state.metamask.currentCurrency, } result.error = result.warning && result.warning.split('.')[0] @@ -50,6 +51,7 @@ SendTransactionScreen.prototype.render = function () { identities, addressBook, conversionRate, + currentCurrency, } = props return ( @@ -130,6 +132,7 @@ SendTransactionScreen.prototype.render = function () { h(EthBalance, { value: account && account.balance, conversionRate, + currentCurrency, }), ]), From 68d6ea44a0c9f3d75415ccadefe182f9a0872db1 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Tue, 16 May 2017 11:39:00 -0700 Subject: [PATCH 264/372] Fix path references --- app/scripts/controllers/transactions.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/scripts/controllers/transactions.js b/app/scripts/controllers/transactions.js index 9f267160f..21dd25b30 100644 --- a/app/scripts/controllers/transactions.js +++ b/app/scripts/controllers/transactions.js @@ -5,8 +5,8 @@ const Semaphore = require('semaphore') const ObservableStore = require('obs-store') const ethUtil = require('ethereumjs-util') const EthQuery = require('eth-query') -const TxProviderUtil = require('./lib/tx-utils') -const createId = require('./lib/random-id') +const TxProviderUtil = require('../lib/tx-utils') +const createId = require('../lib/random-id') module.exports = class TransactionManager extends EventEmitter { constructor (opts) { From a00941c8894258a7534f8373405a0f8f4d27a904 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Tue, 16 May 2017 13:21:31 -0700 Subject: [PATCH 265/372] Remove only line from test --- test/unit/components/pending-tx-test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/components/pending-tx-test.js b/test/unit/components/pending-tx-test.js index fe8290003..166b471cb 100644 --- a/test/unit/components/pending-tx-test.js +++ b/test/unit/components/pending-tx-test.js @@ -9,7 +9,7 @@ const Factory = createReactFactory(PendingTx) const ReactTestUtils = require('react-addons-test-utils') const ethUtil = require('ethereumjs-util') -describe.only('PendingTx', function () { +describe('PendingTx', function () { let pendingTxComponent const identities = { From a15e753c800617879384634a7096497550588eaf Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Tue, 16 May 2017 13:22:03 -0700 Subject: [PATCH 266/372] Add gas updating test to tx controller tests --- test/unit/tx-controller-test.js | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/test/unit/tx-controller-test.js b/test/unit/tx-controller-test.js index d0b32ff41..51e0b9a17 100644 --- a/test/unit/tx-controller-test.js +++ b/test/unit/tx-controller-test.js @@ -9,7 +9,7 @@ const currentNetworkId = 42 const otherNetworkId = 36 const privKey = new Buffer('8718b9618a37d1fc78c436511fc6df3c8258d3250635bba617f33003270ec03e', 'hex') -describe('Transaction Manager', function () { +describe('Transaction Controller', function () { let txController beforeEach(function () { @@ -170,6 +170,25 @@ describe('Transaction Manager', function () { var result = txController.getTx('1') assert.equal(result.hash, 'foo') }) + + it('updates gas price', function () { + const originalGasPrice = '0x01' + const desiredGasPriced = '0x02' + + const txMeta = { + id: '1', + status: 'unapproved', + metamaskNetworkId: currentNetworkId, + txParams: { + gasPrice: originalGasPrice, + }, + } + + txController.addTx(txMeta) + txMeta.txParams.gasPrice = desiredGasPriced + var result = txController.getTx('1') + assert.equal(result.txParams.gasPrice, desiredGasPriced, 'gas price updated') + }) }) describe('#getUnapprovedTxList', function () { @@ -234,4 +253,5 @@ describe('Transaction Manager', function () { }) }) }) + }) From 5c71149a8f1503bd59038b3b31ddfff60e7e6482 Mon Sep 17 00:00:00 2001 From: Nihar Date: Tue, 16 May 2017 14:23:42 -0700 Subject: [PATCH 267/372] continue button changed to agree --- mascara/test/lib/first-time.js | 2 +- test/integration/lib/first-time.js | 2 +- ui/app/components/notice.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/mascara/test/lib/first-time.js b/mascara/test/lib/first-time.js index 8e33c8a06..76a4545bf 100644 --- a/mascara/test/lib/first-time.js +++ b/mascara/test/lib/first-time.js @@ -10,7 +10,7 @@ QUnit.test('render init screen', function (assert) { app = $('#app-content').contents() const recurseNotices = function () { let button = app.find('button') - if (button.html() === 'Continue') { + if (button.html() === 'Agree') { let termsPage = app.find('.markdown')[0] termsPage.scrollTop = termsPage.scrollHeight return wait().then(() => { diff --git a/test/integration/lib/first-time.js b/test/integration/lib/first-time.js index dbb88a3da..f0fa4ee3f 100644 --- a/test/integration/lib/first-time.js +++ b/test/integration/lib/first-time.js @@ -11,7 +11,7 @@ QUnit.test('render init screen', function (assert) { const recurseNotices = function () { let button = app.find('button') - if (button.html() === 'Continue') { + if (button.html() === 'Agree') { let termsPage = app.find('.markdown')[0] termsPage.scrollTop = termsPage.scrollHeight return wait().then(() => { diff --git a/ui/app/components/notice.js b/ui/app/components/notice.js index 3c8523daf..7fe41fa88 100644 --- a/ui/app/components/notice.js +++ b/ui/app/components/notice.js @@ -107,7 +107,7 @@ Notice.prototype.render = function () { style: { marginTop: '18px', }, - }, 'Continue'), + }, 'Agree'), ]) ) } From 53b8d18a5f649c73a58a96e36a9458903d8af6aa Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Tue, 16 May 2017 15:30:22 -0700 Subject: [PATCH 268/372] Complete transition into BN. --- ui/app/components/bn-as-decimal-input.js | 143 +++++++++++++++++++++++ ui/app/components/pending-tx.js | 27 ++--- 2 files changed, 157 insertions(+), 13 deletions(-) create mode 100644 ui/app/components/bn-as-decimal-input.js diff --git a/ui/app/components/bn-as-decimal-input.js b/ui/app/components/bn-as-decimal-input.js new file mode 100644 index 000000000..6c2132ca1 --- /dev/null +++ b/ui/app/components/bn-as-decimal-input.js @@ -0,0 +1,143 @@ +const Component = require('react').Component +const h = require('react-hyperscript') +const inherits = require('util').inherits +const ethUtil = require('ethereumjs-util') +const BN = ethUtil.BN +const extend = require('xtend') + +module.exports = BnAsDecimalInput + +inherits(BnAsDecimalInput, Component) +function BnAsDecimalInput () { + this.state = { invalid: null } + Component.call(this) +} + +/* Bn as Decimal Input + * + * A component for allowing easy, decimal editing + * of a passed in hex string value. + * + * On change, calls back its `onChange` function parameter + * and passes it an updated hex string. + */ + +BnAsDecimalInput.prototype.render = function () { + const props = this.props + const state = this.state + + const { value, precision, onChange, min, max } = props + + const suffix = props.suffix + const style = props.style + const newValue = value.toNumber(10) / scale + const scale = Math.pow(10, precision) + + return ( + h('.flex-column', [ + h('.flex-row', { + style: { + alignItems: 'flex-end', + lineHeight: '13px', + fontFamily: 'Montserrat Light', + textRendering: 'geometricPrecision', + }, + }, [ + h('input.hex-input', { + type: 'number', + step: 'any', + required: true, + min: min, + max: max, + style: extend({ + display: 'block', + textAlign: 'right', + backgroundColor: 'transparent', + border: '1px solid #bdbdbd', + + }, style), + value: newValue, + onBlur: (event) => { + this.updateValidity(event) + }, + onChange: (event) => { + this.updateValidity(event) + const value = (event.target.value === '') ? '' : event.target.value + const scaledNumber = Math.floor(scale * value) + const precisionBN = new BN(scaledNumber, 10) + onChange(precisionBN) + }, + onInvalid: (event) => { + const msg = this.constructWarning() + if (msg === state.invalid) { + return + } + this.setState({ invalid: msg }) + event.preventDefault() + return false + }, + }), + h('div', { + style: { + color: ' #AEAEAE', + fontSize: '12px', + marginLeft: '5px', + marginRight: '6px', + width: '20px', + }, + }, suffix), + ]), + + state.invalid ? h('span.error', { + style: { + position: 'absolute', + right: '0px', + textAlign: 'right', + transform: 'translateY(26px)', + padding: '3px', + background: 'rgba(255,255,255,0.85)', + zIndex: '1', + textTransform: 'capitalize', + border: '2px solid #E20202', + }, + }, state.invalid) : null, + ]) + ) +} + +BnAsDecimalInput.prototype.setValid = function (message) { + this.setState({ invalid: null }) +} + +BnAsDecimalInput.prototype.updateValidity = function (event) { + const target = event.target + const value = this.props.value + const newValue = target.value + + if (value === newValue) { + return + } + + const valid = target.checkValidity() + + if (valid) { + this.setState({ invalid: null }) + } +} + +BnAsDecimalInput.prototype.constructWarning = function () { + const { name, min, max } = this.props + let message = name ? name + ' ' : '' + + if (min && max) { + message += `must be greater than or equal to ${min} and less than or equal to ${max}.` + } else if (min) { + message += `must be greater than or equal to ${min}.` + } else if (max) { + message += `must be less than or equal to ${max}.` + } else { + message += 'Invalid input.' + } + + return message +} diff --git a/ui/app/components/pending-tx.js b/ui/app/components/pending-tx.js index 5ea885195..95d345a3d 100644 --- a/ui/app/components/pending-tx.js +++ b/ui/app/components/pending-tx.js @@ -13,7 +13,7 @@ const EthBalance = require('./eth-balance') const util = require('../util') const addressSummary = util.addressSummary const nameForAddress = require('../../lib/contract-namer') -const HexInput = require('./hex-as-decimal-input') +const BNInput = require('./bn-as-decimal-input') const MIN_GAS_PRICE_GWEI_BN = new BN(2) const GWEI_FACTOR = new BN(1e9) @@ -54,7 +54,6 @@ PendingTx.prototype.render = function () { // Gas Price const gasPrice = txParams.gasPrice || MIN_GAS_PRICE_BN.toString(16) const gasPriceBn = hexToBn(gasPrice) - const gasPriceGweiBn = gasPriceBn.div(GWEI_FACTOR) const txFeeBn = gasBn.mul(gasPriceBn) const valueBn = hexToBn(txParams.value) @@ -166,9 +165,10 @@ PendingTx.prototype.render = function () { h('.cell.label', 'Gas Limit'), h('.cell.value', { }, [ - h(HexInput, { + h(BNInput, { name: 'Gas Limit', - value: gas, + value: gasBn, + precision: 0, // The hard lower limit for gas. min: MIN_GAS_LIMIT_BN.toString(10), suffix: 'UNITS', @@ -176,10 +176,10 @@ PendingTx.prototype.render = function () { position: 'relative', top: '5px', }, - onChange: (newHex) => { - log.info(`Gas limit changed to ${newHex}`) + onChange: (newBN) => { + log.info(`Gas limit changed to ${newBN.toString(10)}`) const txMeta = this.gatherTxMeta() - txMeta.txParams.gas = newHex + txMeta.txParams.gas = '0x' + newBN.toString('hex') this.setState({ txData: txMeta }) }, ref: (hexInput) => { this.inputs.push(hexInput) }, @@ -192,20 +192,20 @@ PendingTx.prototype.render = function () { h('.cell.label', 'Gas Price'), h('.cell.value', { }, [ - h(HexInput, { + h(BNInput, { name: 'Gas Price', - value: gasPriceGweiBn.toString(16), + value: gasPriceBn, + precision: 9, suffix: 'GWEI', min: MIN_GAS_PRICE_GWEI_BN.toString(10), style: { position: 'relative', top: '5px', }, - onChange: (newHex) => { - log.info(`Gas price changed to: ${newHex}`) - const inWei = hexToBn(newHex).mul(GWEI_FACTOR) + onChange: (newBN) => { + log.info(`Gas price changed to: ${newBN.toString(10)}`) const txMeta = this.gatherTxMeta() - txMeta.txParams.gasPrice = inWei.toString(16) + txMeta.txParams.gasPrice = '0x' + newBN.toString('hex') this.setState({ txData: txMeta }) }, ref: (hexInput) => { this.inputs.push(hexInput) }, @@ -368,6 +368,7 @@ PendingTx.prototype.miniAccountPanelForRecipient = function () { } PendingTx.prototype.resetGasFields = function () { + log.debug(`pending-tx resetGasFields`) this.inputs.forEach((hexInput) => { From caeadc24072829deaabd0f6a33563bb84c10008a Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Tue, 16 May 2017 16:19:10 -0700 Subject: [PATCH 269/372] Linted and removed unused deps --- package.json | 1 - test/unit/components/pending-tx-test.js | 23 ++++++++--------------- 2 files changed, 8 insertions(+), 16 deletions(-) diff --git a/package.json b/package.json index eb5ed8a32..14ddd2886 100644 --- a/package.json +++ b/package.json @@ -136,7 +136,6 @@ "browserify": "^13.0.0", "chai": "^3.5.0", "clone": "^1.0.2", - "create-react-factory": "^0.2.1", "deep-freeze-strict": "^1.1.1", "del": "^2.2.0", "envify": "^4.0.0", diff --git a/test/unit/components/pending-tx-test.js b/test/unit/components/pending-tx-test.js index 166b471cb..36339474c 100644 --- a/test/unit/components/pending-tx-test.js +++ b/test/unit/components/pending-tx-test.js @@ -2,15 +2,10 @@ const assert = require('assert') const additions = require('react-testutils-additions') const h = require('react-hyperscript') const PendingTx = require('../../../ui/app/components/pending-tx') -const createReactFactory = require('create-react-factory').createReactFactory -const React = require('react') -const shallow = require('react-test-renderer/shallow') -const Factory = createReactFactory(PendingTx) const ReactTestUtils = require('react-addons-test-utils') const ethUtil = require('ethereumjs-util') describe('PendingTx', function () { - let pendingTxComponent const identities = { '0xfdea65c8e26263f6d9a1b5de9555d2931a33b826': { @@ -38,7 +33,7 @@ describe('PendingTx', function () { it('should use updated values when edited.', function (done) { - const renderer = ReactTestUtils.createRenderer(); + const renderer = ReactTestUtils.createRenderer() const newGasPrice = '0x77359400' const props = { @@ -56,16 +51,14 @@ describe('PendingTx', function () { } const pendingTxComponent = h(PendingTx, props) - const component = additions.renderIntoDocument(pendingTxComponent); + const component = additions.renderIntoDocument(pendingTxComponent) renderer.render(pendingTxComponent) const result = renderer.getRenderOutput() const form = result.props.children - const children = form.props.children[form.props.children.length - 1] assert.equal(result.type, 'div', 'should create a div') - try{ - - const input = additions.find(component, '.cell.row input[type="number"]')[1] + try { + const input = additions.find(component, '.cell.row input[type='number']')[1] ReactTestUtils.Simulate.change(input, { target: { value: 2, @@ -76,14 +69,14 @@ describe('PendingTx', function () { let form = additions.find(component, 'form')[0] form.checkValidity = () => true form.getFormEl = () => { return { checkValidity() { return true } } } - ReactTestUtils.Simulate.submit(form, { preventDefault() {}, target: { checkValidity() {return true} } }) + ReactTestUtils.Simulate.submit(form, { preventDefault() {}, target: { checkValidity() { + return true + } } }) } catch (e) { - console.log("WHAAAA") + console.log('WHAAAA') console.error(e) } - }) - }) From d8130f1effc31a866476e10153bd854709ae23be Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Tue, 16 May 2017 16:20:58 -0700 Subject: [PATCH 270/372] Fix reset button. --- ui/app/components/bn-as-decimal-input.js | 6 +++--- ui/app/components/pending-tx.js | 12 +++++++----- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/ui/app/components/bn-as-decimal-input.js b/ui/app/components/bn-as-decimal-input.js index 6c2132ca1..d0eebe09e 100644 --- a/ui/app/components/bn-as-decimal-input.js +++ b/ui/app/components/bn-as-decimal-input.js @@ -16,10 +16,10 @@ function BnAsDecimalInput () { /* Bn as Decimal Input * * A component for allowing easy, decimal editing - * of a passed in hex string value. + * of a passed in bn string value. * * On change, calls back its `onChange` function parameter - * and passes it an updated hex string. + * and passes it an updated bn string. */ BnAsDecimalInput.prototype.render = function () { @@ -30,8 +30,8 @@ BnAsDecimalInput.prototype.render = function () { const suffix = props.suffix const style = props.style - const newValue = value.toNumber(10) / scale const scale = Math.pow(10, precision) + const newValue = value.toNumber(10) / scale return ( h('.flex-column', [ diff --git a/ui/app/components/pending-tx.js b/ui/app/components/pending-tx.js index 95d345a3d..5c8d81d07 100644 --- a/ui/app/components/pending-tx.js +++ b/ui/app/components/pending-tx.js @@ -180,7 +180,7 @@ PendingTx.prototype.render = function () { log.info(`Gas limit changed to ${newBN.toString(10)}`) const txMeta = this.gatherTxMeta() txMeta.txParams.gas = '0x' + newBN.toString('hex') - this.setState({ txData: txMeta }) + this.setState({ txData: cloneObj(txMeta) }) }, ref: (hexInput) => { this.inputs.push(hexInput) }, }), @@ -206,7 +206,7 @@ PendingTx.prototype.render = function () { log.info(`Gas price changed to: ${newBN.toString(10)}`) const txMeta = this.gatherTxMeta() txMeta.txParams.gasPrice = '0x' + newBN.toString('hex') - this.setState({ txData: txMeta }) + this.setState({ txData: cloneObj(txMeta) }) }, ref: (hexInput) => { this.inputs.push(hexInput) }, }), @@ -388,7 +388,7 @@ PendingTx.prototype.gatherTxMeta = function () { log.debug(`pending-tx gatherTxMeta`) const props = this.props const state = this.state - const txData = state.txData || props.txData + const txData = cloneObj(state.txData) || cloneObj(props.txData) log.debug(`UI has defaulted to tx meta ${JSON.stringify(txData)}`) return txData @@ -409,7 +409,6 @@ PendingTx.prototype._notZeroOrEmptyString = function (obj) { function forwardCarrat () { return ( - h('img', { src: 'images/forward-carrat.svg', style: { @@ -417,6 +416,9 @@ function forwardCarrat () { height: '37px', }, }) - ) } + +function cloneObj (obj) { + return JSON.parse(JSON.stringify(obj)) +} From 44f25cd93c18c57acf993ace3387a4d569d8dcca Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Tue, 16 May 2017 16:21:50 -0700 Subject: [PATCH 271/372] Changelog bump --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 422639f70..08dd41a3b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ ## Current Master - Trim currency list. +- Enable decimals in our gas prices. +- Fix reset button. ## 3.6.4 2017-5-8 From cfb7bfed186a03e4e879d932501a51a9758dd3ad Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Tue, 16 May 2017 16:44:17 -0700 Subject: [PATCH 272/372] Fix quotation mark --- test/unit/components/pending-tx-test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/components/pending-tx-test.js b/test/unit/components/pending-tx-test.js index 36339474c..89e6892a3 100644 --- a/test/unit/components/pending-tx-test.js +++ b/test/unit/components/pending-tx-test.js @@ -58,7 +58,7 @@ describe('PendingTx', function () { assert.equal(result.type, 'div', 'should create a div') try { - const input = additions.find(component, '.cell.row input[type='number']')[1] + const input = additions.find(component, '.cell.row input[type="number"]')[1] ReactTestUtils.Simulate.change(input, { target: { value: 2, From c1bef31d9d3b2cf091ac94c908700c3c0081318f Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Tue, 16 May 2017 16:49:59 -0700 Subject: [PATCH 273/372] Linted --- test/unit/components/pending-tx-test.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/test/unit/components/pending-tx-test.js b/test/unit/components/pending-tx-test.js index 89e6892a3..9ff948604 100644 --- a/test/unit/components/pending-tx-test.js +++ b/test/unit/components/pending-tx-test.js @@ -54,7 +54,6 @@ describe('PendingTx', function () { const component = additions.renderIntoDocument(pendingTxComponent) renderer.render(pendingTxComponent) const result = renderer.getRenderOutput() - const form = result.props.children assert.equal(result.type, 'div', 'should create a div') try { @@ -63,10 +62,10 @@ describe('PendingTx', function () { target: { value: 2, checkValidity() { return true }, - } + }, }) - let form = additions.find(component, 'form')[0] + const form = additions.find(component, 'form')[0] form.checkValidity = () => true form.getFormEl = () => { return { checkValidity() { return true } } } ReactTestUtils.Simulate.submit(form, { preventDefault() {}, target: { checkValidity() { From c6fd5090519af64bbe3d29346484bcf45572d3c2 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Tue, 16 May 2017 17:06:19 -0700 Subject: [PATCH 274/372] Improve test --- test/unit/tx-controller-test.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/unit/tx-controller-test.js b/test/unit/tx-controller-test.js index 51e0b9a17..e6645090e 100644 --- a/test/unit/tx-controller-test.js +++ b/test/unit/tx-controller-test.js @@ -3,6 +3,7 @@ const EventEmitter = require('events') const ethUtil = require('ethereumjs-util') const EthTx = require('ethereumjs-tx') const ObservableStore = require('obs-store') +const clone = require('clone') const TransactionController = require('../../app/scripts/controllers/transactions') const noop = () => true const currentNetworkId = 42 @@ -184,8 +185,11 @@ describe('Transaction Controller', function () { }, } + const updatedMeta = clone(txMeta) + txController.addTx(txMeta) - txMeta.txParams.gasPrice = desiredGasPriced + updatedMeta.txParams.gasPrice = desiredGasPriced + txController.updateTx(updatedMeta) var result = txController.getTx('1') assert.equal(result.txParams.gasPrice, desiredGasPriced, 'gas price updated') }) From 6491b42266b2114af72e72a46a6453dc3dd59290 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Tue, 16 May 2017 18:16:18 -0700 Subject: [PATCH 275/372] Add test around txManager#approveTransaction --- test/unit/tx-controller-test.js | 67 +++++++++++++++++++++++++++++++-- 1 file changed, 64 insertions(+), 3 deletions(-) diff --git a/test/unit/tx-controller-test.js b/test/unit/tx-controller-test.js index e6645090e..d4e8d79f0 100644 --- a/test/unit/tx-controller-test.js +++ b/test/unit/tx-controller-test.js @@ -4,6 +4,7 @@ const ethUtil = require('ethereumjs-util') const EthTx = require('ethereumjs-tx') const ObservableStore = require('obs-store') const clone = require('clone') +const sinon = require('sinon') const TransactionController = require('../../app/scripts/controllers/transactions') const noop = () => true const currentNetworkId = 42 @@ -174,7 +175,7 @@ describe('Transaction Controller', function () { it('updates gas price', function () { const originalGasPrice = '0x01' - const desiredGasPriced = '0x02' + const desiredGasPrice = '0x02' const txMeta = { id: '1', @@ -188,10 +189,10 @@ describe('Transaction Controller', function () { const updatedMeta = clone(txMeta) txController.addTx(txMeta) - updatedMeta.txParams.gasPrice = desiredGasPriced + updatedMeta.txParams.gasPrice = desiredGasPrice txController.updateTx(updatedMeta) var result = txController.getTx('1') - assert.equal(result.txParams.gasPrice, desiredGasPriced, 'gas price updated') + assert.equal(result.txParams.gasPrice, desiredGasPrice, 'gas price updated') }) }) @@ -247,6 +248,66 @@ describe('Transaction Controller', function () { }) }) + describe('#approveTransaction', function () { + let txMeta, originalValue + + beforeEach(function () { + originalValue = '0x01' + txMeta = { + id: '1', + status: 'unapproved', + metamaskNetworkId: currentNetworkId, + txParams: { + nonce: originalValue, + gas: originalValue, + gasPrice: originalValue, + }, + } + }) + + + it('does not overwrite set values', function (done) { + const wrongValue = '0x05' + + txController.addTx(txMeta) + + const estimateStub = sinon.stub(txController.txProviderUtils.query, 'estimateGas') + .callsArgWith(1, null, wrongValue) + + const priceStub = sinon.stub(txController.txProviderUtils.query, 'gasPrice') + .callsArgWith(0, null, wrongValue) + + const nonceStub = sinon.stub(txController.txProviderUtils.query, 'getTransactionCount') + .callsArgWith(2, null, wrongValue) + + const signStub = sinon.stub(txController, 'signTransaction') + .callsArgWith(1, null, noop) + + const pubStub = sinon.stub(txController.txProviderUtils, 'publishTransaction') + .callsArgWith(1, null, originalValue) + + txController.approveTransaction(txMeta.id, (err) => { + assert.ifError(err, 'should not error') + + const result = txController.getTx(txMeta.id) + const params = result.txParams + + assert.equal(params.gas, originalValue, 'gas unmodified') + assert.equal(params.gasPrice, originalValue, 'gas price unmodified') + assert.equal(params.nonce, originalValue, 'nonce unmodified') + assert.equal(result.hash, originalValue, 'hash was set') + + estimateStub.restore() + priceStub.restore() + signStub.restore() + nonceStub.restore() + pubStub.restore() + + done() + }) + }) + }) + describe('#sign replay-protected tx', function () { it('prepares a tx with the chainId set', function () { txController.addTx({ id: '1', status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }, noop) From 31c7daee73fa41e356d1bd5cd92186e60b252212 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Tue, 16 May 2017 23:33:40 -0700 Subject: [PATCH 276/372] Fix bug where edited gas parameters did not take effect Fixes #1407 --- CHANGELOG.md | 1 + ui/app/conf-tx.js | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3653e5018..998219254 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Current Master +- Fix bug where edited gas parameters would not take effect. - Trim currency list. - Fix event filter bug introduced by newer versions of Geth. diff --git a/ui/app/conf-tx.js b/ui/app/conf-tx.js index 0d7c4c1bb..008627ce6 100644 --- a/ui/app/conf-tx.js +++ b/ui/app/conf-tx.js @@ -108,7 +108,7 @@ ConfirmTxScreen.prototype.render = function () { currentCurrency, // Actions buyEth: this.buyEth.bind(this, txParams.from || props.selectedAddress), - sendTransaction: this.sendTransaction.bind(this, txData), + sendTransaction: this.sendTransaction.bind(this), cancelTransaction: this.cancelTransaction.bind(this, txData), signMessage: this.signMessage.bind(this, txData), signPersonalMessage: this.signPersonalMessage.bind(this, txData), From c0516ddf333336a7784787a02183c4fe212364b9 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Wed, 17 May 2017 00:09:59 -0700 Subject: [PATCH 277/372] Add test requiring high precision --- .../components/bn-as-decimal-input-test.js | 59 +++++++++++++++++++ test/unit/components/pending-tx-test.js | 2 +- 2 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 test/unit/components/bn-as-decimal-input-test.js diff --git a/test/unit/components/bn-as-decimal-input-test.js b/test/unit/components/bn-as-decimal-input-test.js new file mode 100644 index 000000000..4ea910fb0 --- /dev/null +++ b/test/unit/components/bn-as-decimal-input-test.js @@ -0,0 +1,59 @@ +var assert = require('assert') + +const additions = require('react-testutils-additions') +const h = require('react-hyperscript') +const ReactTestUtils = require('react-addons-test-utils') +const ethUtil = require('ethereumjs-util') +const BN = ethUtil.BN + +var BnInput = require('../../../ui/app/components/bn-as-decimal-input') + +describe.only('BnInput', function () { + let bnInput + const message = 'Hello, world!' + const buffer = new Buffer(message, 'utf8') + const hex = buffer.toString('hex') + + it('can tolerate a large number at a high precision', function (done) { + + const renderer = ReactTestUtils.createRenderer(); + + let valueStr = '1' + while (valueStr.length < 18 + 7) { + valueStr += '0' + } + const value = new BN(valueStr, 10) + + let inputStr = '11' + while (inputStr.length < 7) { + inputStr += '0' + } + inputStr += '.01' + + let targetStr = inputStr.split('.').join() + while (targetStr.length < 18 + 7) { + targetStr += '0' + } + const target = new BN(targetStr, 10) + + const precision = 1e18 // ether precision + + const props = { + value, + precision, + onChange: (newBn) => { + assert.equal(newBn.toString(), targetValue.toString(), 'should tolerate increase') + done() + } + } + + const inputComponent = h(BnInput, props) + const component = additions.renderIntoDocument(inputComponent) + renderer.render(inputComponent) + const input = additions.find(component, 'input.hex-input')[0] + ReactTestUtils.Simulate.change(input, { preventDefault() {}, target: { + value: inputStr, + checkValidity() {return true} }, + }) + }) +}) diff --git a/test/unit/components/pending-tx-test.js b/test/unit/components/pending-tx-test.js index fe8290003..166b471cb 100644 --- a/test/unit/components/pending-tx-test.js +++ b/test/unit/components/pending-tx-test.js @@ -9,7 +9,7 @@ const Factory = createReactFactory(PendingTx) const ReactTestUtils = require('react-addons-test-utils') const ethUtil = require('ethereumjs-util') -describe.only('PendingTx', function () { +describe('PendingTx', function () { let pendingTxComponent const identities = { From e26501aa0192b26880a8fe63f41d76fdfa849d7b Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Wed, 17 May 2017 00:19:31 -0700 Subject: [PATCH 278/372] Simplify test to represent realistic use case --- test/unit/components/bn-as-decimal-input-test.js | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/test/unit/components/bn-as-decimal-input-test.js b/test/unit/components/bn-as-decimal-input-test.js index 4ea910fb0..1f589c210 100644 --- a/test/unit/components/bn-as-decimal-input-test.js +++ b/test/unit/components/bn-as-decimal-input-test.js @@ -14,24 +14,20 @@ describe.only('BnInput', function () { const buffer = new Buffer(message, 'utf8') const hex = buffer.toString('hex') - it('can tolerate a large number at a high precision', function (done) { + it('can tolerate a gas decimal number at a high precision', function (done) { const renderer = ReactTestUtils.createRenderer(); - let valueStr = '1' - while (valueStr.length < 18 + 7) { + let valueStr = '20' + while (valueStr.length < 20) { valueStr += '0' } const value = new BN(valueStr, 10) - let inputStr = '11' - while (inputStr.length < 7) { - inputStr += '0' - } - inputStr += '.01' + let inputStr = '2.3' - let targetStr = inputStr.split('.').join() - while (targetStr.length < 18 + 7) { + let targetStr = '23' + while (targetStr.length < 19) { targetStr += '0' } const target = new BN(targetStr, 10) From 6f02f5bc5d741810edb2c011fc3095ee50f84bf9 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Wed, 17 May 2017 00:33:19 -0700 Subject: [PATCH 279/372] Clean up test --- test/unit/components/bn-as-decimal-input-test.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/test/unit/components/bn-as-decimal-input-test.js b/test/unit/components/bn-as-decimal-input-test.js index 1f589c210..502c9a2de 100644 --- a/test/unit/components/bn-as-decimal-input-test.js +++ b/test/unit/components/bn-as-decimal-input-test.js @@ -8,11 +8,8 @@ const BN = ethUtil.BN var BnInput = require('../../../ui/app/components/bn-as-decimal-input') -describe.only('BnInput', function () { +describe('BnInput', function () { let bnInput - const message = 'Hello, world!' - const buffer = new Buffer(message, 'utf8') - const hex = buffer.toString('hex') it('can tolerate a gas decimal number at a high precision', function (done) { From bfb1c92ded2bef1a2f08e1c185721010278ca69b Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Wed, 17 May 2017 00:34:56 -0700 Subject: [PATCH 280/372] Linted test --- test/unit/components/bn-as-decimal-input-test.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/test/unit/components/bn-as-decimal-input-test.js b/test/unit/components/bn-as-decimal-input-test.js index 502c9a2de..034bc3e18 100644 --- a/test/unit/components/bn-as-decimal-input-test.js +++ b/test/unit/components/bn-as-decimal-input-test.js @@ -9,11 +9,9 @@ const BN = ethUtil.BN var BnInput = require('../../../ui/app/components/bn-as-decimal-input') describe('BnInput', function () { - let bnInput - it('can tolerate a gas decimal number at a high precision', function (done) { - const renderer = ReactTestUtils.createRenderer(); + const renderer = ReactTestUtils.createRenderer() let valueStr = '20' while (valueStr.length < 20) { @@ -35,9 +33,9 @@ describe('BnInput', function () { value, precision, onChange: (newBn) => { - assert.equal(newBn.toString(), targetValue.toString(), 'should tolerate increase') + assert.equal(newBn.toString(), target.toString(), 'should tolerate increase') done() - } + }, } const inputComponent = h(BnInput, props) @@ -46,7 +44,7 @@ describe('BnInput', function () { const input = additions.find(component, 'input.hex-input')[0] ReactTestUtils.Simulate.change(input, { preventDefault() {}, target: { value: inputStr, - checkValidity() {return true} }, + checkValidity() { return true } }, }) }) }) From 24737ded3466da61fd96015beb6931e59d8232a7 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Wed, 17 May 2017 14:13:05 -0700 Subject: [PATCH 281/372] Fix bug where decimals in gas inputs gave strange results --- CHANGELOG.md | 1 + ui/app/components/hex-as-decimal-input.js | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 998219254..24ae4e781 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - Fix bug where edited gas parameters would not take effect. - Trim currency list. - Fix event filter bug introduced by newer versions of Geth. +- Fix bug where decimals in gas inputs could result in strange values. ## 3.6.4 2017-5-8 diff --git a/ui/app/components/hex-as-decimal-input.js b/ui/app/components/hex-as-decimal-input.js index e37aaa8c3..d24fdcb7b 100644 --- a/ui/app/components/hex-as-decimal-input.js +++ b/ui/app/components/hex-as-decimal-input.js @@ -139,7 +139,7 @@ HexAsDecimalInput.prototype.constructWarning = function () { } function hexify (decimalString) { - const hexBN = new BN(decimalString, 10) + const hexBN = new BN(decimalString.split('.')[0], 10) return '0x' + hexBN.toString('hex') } From 717db41d0b7bcd7b6f88a5c460aa4dcbc5828116 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Wed, 17 May 2017 14:18:01 -0700 Subject: [PATCH 282/372] Modify test, replace clone package. --- test/unit/components/bn-as-decimal-input-test.js | 4 ++-- ui/app/components/pending-tx.js | 11 ++++------- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/test/unit/components/bn-as-decimal-input-test.js b/test/unit/components/bn-as-decimal-input-test.js index 034bc3e18..f515003bb 100644 --- a/test/unit/components/bn-as-decimal-input-test.js +++ b/test/unit/components/bn-as-decimal-input-test.js @@ -14,7 +14,7 @@ describe('BnInput', function () { const renderer = ReactTestUtils.createRenderer() let valueStr = '20' - while (valueStr.length < 20) { + while (valueStr.length < 15) { valueStr += '0' } const value = new BN(valueStr, 10) @@ -22,7 +22,7 @@ describe('BnInput', function () { let inputStr = '2.3' let targetStr = '23' - while (targetStr.length < 19) { + while (targetStr.length < 14) { targetStr += '0' } const target = new BN(targetStr, 10) diff --git a/ui/app/components/pending-tx.js b/ui/app/components/pending-tx.js index 37a3a3bf3..5b238187c 100644 --- a/ui/app/components/pending-tx.js +++ b/ui/app/components/pending-tx.js @@ -2,6 +2,7 @@ const Component = require('react').Component const h = require('react-hyperscript') const inherits = require('util').inherits const actions = require('../actions') +const clone = require('clone') const ethUtil = require('ethereumjs-util') const BN = ethUtil.BN @@ -347,14 +348,14 @@ PendingTx.prototype.gasPriceChanged = function (newBN) { log.info(`Gas price changed to: ${newBN.toString(10)}`) const txMeta = this.gatherTxMeta() txMeta.txParams.gasPrice = '0x' + newBN.toString('hex') - this.setState({ txData: cloneObj(txMeta) }) + this.setState({ txData: clone(txMeta) }) } PendingTx.prototype.gasLimitChanged = function (newBN) { log.info(`Gas limit changed to ${newBN.toString(10)}`) const txMeta = this.gatherTxMeta() txMeta.txParams.gas = '0x' + newBN.toString('hex') - this.setState({ txData: cloneObj(txMeta) }) + this.setState({ txData: clone(txMeta) }) } PendingTx.prototype.resetGasFields = function () { @@ -405,7 +406,7 @@ PendingTx.prototype.gatherTxMeta = function () { log.debug(`pending-tx gatherTxMeta`) const props = this.props const state = this.state - const txData = cloneObj(state.txData) || cloneObj(props.txData) + const txData = clone(state.txData) || clone(props.txData) log.debug(`UI has defaulted to tx meta ${JSON.stringify(txData)}`) return txData @@ -435,7 +436,3 @@ function forwardCarrat () { }) ) } - -function cloneObj (obj) { - return JSON.parse(JSON.stringify(obj)) -} From 7e7ceab95edcf27a240da478a1f5da2d97cd5e85 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Wed, 17 May 2017 14:31:06 -0700 Subject: [PATCH 283/372] Fix decimal tolerance --- ui/app/components/hex-as-decimal-input.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/app/components/hex-as-decimal-input.js b/ui/app/components/hex-as-decimal-input.js index d24fdcb7b..4a71e9585 100644 --- a/ui/app/components/hex-as-decimal-input.js +++ b/ui/app/components/hex-as-decimal-input.js @@ -139,7 +139,7 @@ HexAsDecimalInput.prototype.constructWarning = function () { } function hexify (decimalString) { - const hexBN = new BN(decimalString.split('.')[0], 10) + const hexBN = new BN(parseInt(decimalString), 10) return '0x' + hexBN.toString('hex') } From 2a25f99461c01ac9e0aec3d90f73675c58303860 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Wed, 17 May 2017 14:36:50 -0700 Subject: [PATCH 284/372] Version 3.6.5 --- CHANGELOG.md | 2 ++ app/manifest.json | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 24ae4e781..371b348ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Current Master +## 3.6.5 2017-5-17 + - Fix bug where edited gas parameters would not take effect. - Trim currency list. - Fix event filter bug introduced by newer versions of Geth. diff --git a/app/manifest.json b/app/manifest.json index a1f6d7855..31e4598c7 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -1,7 +1,7 @@ { "name": "MetaMask", "short_name": "Metamask", - "version": "3.6.4", + "version": "3.6.5", "manifest_version": 2, "author": "https://metamask.io", "description": "Ethereum Browser Extension", From f87ea49b5ac2d66d8f281f08f42e8cfd2d701ba7 Mon Sep 17 00:00:00 2001 From: frankiebee Date: Thu, 18 May 2017 23:54:02 +0200 Subject: [PATCH 285/372] Create a network controller to manage switcing networks an updating the provider --- app/scripts/controllers/network.js | 152 +++++++++++++++++++++++++++++ app/scripts/keyring-controller.js | 6 ++ app/scripts/lib/config-manager.js | 67 ------------- app/scripts/lib/tx-utils.js | 8 +- app/scripts/metamask-controller.js | 124 ++++++++++------------- app/scripts/transaction-manager.js | 25 +++-- 6 files changed, 232 insertions(+), 150 deletions(-) create mode 100644 app/scripts/controllers/network.js diff --git a/app/scripts/controllers/network.js b/app/scripts/controllers/network.js new file mode 100644 index 000000000..82eabb573 --- /dev/null +++ b/app/scripts/controllers/network.js @@ -0,0 +1,152 @@ +const EventEmitter = require('events') +const MetaMaskProvider = require('web3-provider-engine/zero.js') +const ObservableStore = require('obs-store') +const extend = require('xtend') +const EthQuery = require('eth-query') +const MetamaskConfig = require('../config.js') + +const TESTNET_RPC = MetamaskConfig.network.testnet +const MAINNET_RPC = MetamaskConfig.network.mainnet +const MORDEN_RPC = MetamaskConfig.network.morden +const KOVAN_RPC = MetamaskConfig.network.kovan +const RINKEBY_RPC = MetamaskConfig.network.rinkeby + +module.exports = class NetworkController extends EventEmitter { + constructor (providerOpts) { + super() + this.networkStore = new ObservableStore({ network: 'loading' }) + providerOpts.provider.rpcTarget = this.getRpcAddressForType(providerOpts.provider.type) + this.providerStore = new ObservableStore(providerOpts) + this._claimed = 0 + } + + getState () { + return extend({}, + this.networkStore.getState(), + this.providerStore.getState() + ) + } + + initializeProvider (opts) { + this.providerConfig = opts + this.provider = MetaMaskProvider(opts) + this.ethQuery = new EthQuery(this.provider) + this.lookupNetwork() + return Promise.resolve(this.provider) + } + switchNetwork (providerConfig) { + delete this.provider + delete this.ethQuery + const newConfig = extend(this.providerConfig, providerConfig) + this.providerConfig = newConfig + this.provider = MetaMaskProvider(newConfig) + this.ethQuery = new EthQuery(this.provider) + this.emit('networkSwitch', { + provider: this.provider, + ethQuery: this.ethQuery, + }, this.claim.bind(this)) + } + + subscribe (cb) { + this.networkStore.subscribe(cb) + this.providerStore.subscribe(cb) + } + + verifyNetwork () { + // Check network when restoring connectivity: + if (this.isNetworkLoading()) this.lookupNetwork() + } + + getNetworkState () { + return this.networkStore.getState().network + } + + setNetworkState (network) { + return this.networkStore.updateState({ network }) + } + + isNetworkLoading () { + return this.getNetworkState() === 'loading' + } + + lookupNetwork (err) { + if (err) this.setNetworkState('loading') + + this.ethQuery.sendAsync({ method: 'net_version' }, (err, network) => { + if (err) return this.setNetworkState('loading') + + log.info('web3.getNetwork returned ' + network) + this.setNetworkState(network) + }) + } + + setRpcTarget (rpcUrl) { + this.providerStore.updateState({ + provider: { + type: 'rpc', + rpcTarget: rpcUrl, + }, + }) + } + + getCurrentRpcAddress () { + var provider = this.getProvider() + if (!provider) return null + return this.getRpcAddressForType(provider.type) + } + + setProviderType (type) { + if (type === this.getProvider().type) return + const rpcTarget = this.getRpcAddressForType(type) + this.networkStore.updateState({network: 'loading'}) + this.switchNetwork({ + rpcUrl: rpcTarget, + }) + this.once('claimed', () => { + this.providerStore.updateState({provider: {type, rpcTarget}}) + console.log('CLAIMED') + this.lookupNetwork() + }) + + } + + useEtherscanProvider () { + this.setProviderType('etherscan') + } + + getProvider () { + return this.providerStore.getState().provider + } + + getRpcAddressForType (type) { + const provider = this.getProvider() + switch (type) { + + case 'mainnet': + return MAINNET_RPC + + case 'testnet': + return TESTNET_RPC + + case 'morden': + return MORDEN_RPC + + case 'kovan': + return KOVAN_RPC + + case 'rinkeby': + return RINKEBY_RPC + + default: + return provider && provider.rpcTarget ? provider.rpcTarget : TESTNET_RPC + } + } + + claim () { + this._claimed += 1 + if (this._claimed === this.listenerCount('networkSwitch')) { + this.emit('claimed') + this._claimed = 0 + } + } +} diff --git a/app/scripts/keyring-controller.js b/app/scripts/keyring-controller.js index 5b3c80e40..bb699ca8b 100644 --- a/app/scripts/keyring-controller.js +++ b/app/scripts/keyring-controller.js @@ -41,6 +41,12 @@ class KeyringController extends EventEmitter { this.getNetwork = opts.getNetwork } + setEthStore (ethStore) { + delete this.ethStore + this.ethStore = ethStore + return this.setupAccounts() + } + // Full Update // returns Promise( @object state ) // diff --git a/app/scripts/lib/config-manager.js b/app/scripts/lib/config-manager.js index ab9410842..1098cc474 100644 --- a/app/scripts/lib/config-manager.js +++ b/app/scripts/lib/config-manager.js @@ -1,14 +1,6 @@ -const MetamaskConfig = require('../config.js') const ethUtil = require('ethereumjs-util') const normalize = require('eth-sig-util').normalize -const TESTNET_RPC = MetamaskConfig.network.testnet -const MAINNET_RPC = MetamaskConfig.network.mainnet -const MORDEN_RPC = MetamaskConfig.network.morden -const KOVAN_RPC = MetamaskConfig.network.kovan -const RINKEBY_RPC = MetamaskConfig.network.rinkeby - - /* The config-manager is a convenience object * wrapping a pojo-migrator. * @@ -35,36 +27,6 @@ ConfigManager.prototype.getConfig = function () { return data.config } -ConfigManager.prototype.setRpcTarget = function (rpcUrl) { - var config = this.getConfig() - config.provider = { - type: 'rpc', - rpcTarget: rpcUrl, - } - this.setConfig(config) -} - -ConfigManager.prototype.setProviderType = function (type) { - var config = this.getConfig() - config.provider = { - type: type, - } - this.setConfig(config) -} - -ConfigManager.prototype.useEtherscanProvider = function () { - var config = this.getConfig() - config.provider = { - type: 'etherscan', - } - this.setConfig(config) -} - -ConfigManager.prototype.getProvider = function () { - var config = this.getConfig() - return config.provider -} - ConfigManager.prototype.setData = function (data) { this.store.putState(data) } @@ -139,35 +101,6 @@ ConfigManager.prototype.getSeedWords = function () { return data.seedWords } -ConfigManager.prototype.getCurrentRpcAddress = function () { - var provider = this.getProvider() - if (!provider) return null - switch (provider.type) { - - case 'mainnet': - return MAINNET_RPC - - case 'testnet': - return TESTNET_RPC - - case 'morden': - return MORDEN_RPC - - case 'kovan': - return KOVAN_RPC - - case 'rinkeby': - return RINKEBY_RPC - - default: - return provider && provider.rpcTarget ? provider.rpcTarget : TESTNET_RPC - } -} - -// -// Tx -// - ConfigManager.prototype.getTxList = function () { var data = this.getData() if (data.transactions !== undefined) { diff --git a/app/scripts/lib/tx-utils.js b/app/scripts/lib/tx-utils.js index 084ca3721..76b311653 100644 --- a/app/scripts/lib/tx-utils.js +++ b/app/scripts/lib/tx-utils.js @@ -1,5 +1,4 @@ const async = require('async') -const EthQuery = require('eth-query') const ethUtil = require('ethereumjs-util') const Transaction = require('ethereumjs-tx') const normalize = require('eth-sig-util').normalize @@ -7,15 +6,14 @@ const BN = ethUtil.BN /* tx-utils are utility methods for Transaction manager -its passed a provider and that is passed to ethquery +its passed ethquery and used to do things like calculate gas of a tx. */ module.exports = class txProviderUtils { - constructor (provider) { - this.provider = provider - this.query = new EthQuery(provider) + constructor (ethQuery) { + this.query = ethQuery } analyzeGasUsage (txMeta, cb) { diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 175602ec1..71293d05f 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -7,9 +7,9 @@ const ObservableStore = require('obs-store') const EthStore = require('./lib/eth-store') const EthQuery = require('eth-query') const streamIntoProvider = require('web3-stream-provider/handler') -const MetaMaskProvider = require('web3-provider-engine/zero.js') const setupMultiplex = require('./lib/stream-utils.js').setupMultiplex const KeyringController = require('./keyring-controller') +const NetworkController = require('./controllers/network') const PreferencesController = require('./controllers/preferences') const CurrencyController = require('./controllers/currency') const NoticeController = require('./notice-controller') @@ -40,8 +40,7 @@ module.exports = class MetamaskController extends EventEmitter { this.store = new ObservableStore(initState) // network store - this.networkStore = new ObservableStore({ network: 'loading' }) - + this.networkController = new NetworkController(initState.NetworkController) // config manager this.configManager = new ConfigManager({ store: this.store, @@ -62,7 +61,7 @@ module.exports = class MetamaskController extends EventEmitter { // rpc provider this.provider = this.initializeProvider() this.provider.on('block', this.logBlock.bind(this)) - this.provider.on('error', this.verifyNetwork.bind(this)) + this.provider.on('error', this.networkController.verifyNetwork.bind(this.networkController)) // eth data query tools this.ethQuery = new EthQuery(this.provider) @@ -75,7 +74,7 @@ module.exports = class MetamaskController extends EventEmitter { this.keyringController = new KeyringController({ initState: initState.KeyringController, ethStore: this.ethStore, - getNetwork: this.getNetworkState.bind(this), + getNetwork: this.networkController.getNetworkState.bind(this.networkController), }) this.keyringController.on('newAccount', (address) => { this.preferencesController.setSelectedAddress(address) @@ -92,10 +91,10 @@ module.exports = class MetamaskController extends EventEmitter { // tx mgmt this.txManager = new TxManager({ initState: initState.TransactionManager, - networkStore: this.networkStore, + networkStore: this.networkController.networkStore, preferencesStore: this.preferencesController.store, txHistoryLimit: 40, - getNetwork: this.getNetworkState.bind(this), + getNetwork: this.networkController.getNetworkState.bind(this), signTransaction: this.keyringController.signTransaction.bind(this.keyringController), provider: this.provider, blockTracker: this.provider, @@ -112,8 +111,34 @@ module.exports = class MetamaskController extends EventEmitter { this.shapeshiftController = new ShapeShiftController({ initState: initState.ShapeShiftController, }) + this.networkController.on('networkSwitch', (providerUtil, claimed) => { + delete this.provider + delete this.ethQuery + delete this.ethStore + console.log('order:@? 1') + this.provider = providerUtil.provider + this.provider.on('block', this.logBlock.bind(this)) + this.provider.on('error', this.networkController.verifyNetwork.bind(this.networkController)) + + this.ethQuery = providerUtil.ethQuery + this.ethStore = new EthStore({ + provider: this.provider, + blockTracker: this.provider, + }) + this.provider.once('block', claimed) + }) + this.networkController.on('networkSwitch', (_, claimed) => { + console.log('order:@? 2') + this.txManager.setupProviderAndEthQuery({ + provider: this.provider, + blockTracker: this.provider, + ethQuery: this.ethQuery, + }) + this.keyringController.setEthStore(this.ethStore) + .then(claimed) + }) - this.lookupNetwork() + this.networkController.lookupNetwork() this.messageManager = new MessageManager() this.personalMessageManager = new PersonalMessageManager() this.publicConfigStore = this.initPublicConfigStore() @@ -140,9 +165,12 @@ module.exports = class MetamaskController extends EventEmitter { this.shapeshiftController.store.subscribe((state) => { this.store.updateState({ ShapeShiftController: state }) }) + this.networkController.providerStore.subscribe((state) => { + this.store.updateState({ NetworkController: state }) + }) // manual mem state subscriptions - this.networkStore.subscribe(this.sendUpdate.bind(this)) + this.networkController.subscribe(this.sendUpdate.bind(this)) this.ethStore.subscribe(this.sendUpdate.bind(this)) this.txManager.memStore.subscribe(this.sendUpdate.bind(this)) this.messageManager.memStore.subscribe(this.sendUpdate.bind(this)) @@ -160,12 +188,12 @@ module.exports = class MetamaskController extends EventEmitter { // initializeProvider () { - const provider = MetaMaskProvider({ + this.networkController.initializeProvider({ static: { eth_syncing: false, web3_clientVersion: `MetaMask/v${version}`, }, - rpcUrl: this.configManager.getCurrentRpcAddress(), + rpcUrl: this.networkController.getCurrentRpcAddress(), // account mgmt getAccounts: (cb) => { const isUnlocked = this.keyringController.memStore.getState().isUnlocked @@ -185,7 +213,7 @@ module.exports = class MetamaskController extends EventEmitter { // new style msg signing processPersonalMessage: this.newUnsignedPersonalMessage.bind(this), }) - return provider + return this.networkController.provider } initPublicConfigStore () { @@ -221,7 +249,7 @@ module.exports = class MetamaskController extends EventEmitter { { isInitialized, }, - this.networkStore.getState(), + this.networkController.getState(), this.ethStore.getState(), this.txManager.memStore.getState(), this.messageManager.memStore.getState(), @@ -255,8 +283,8 @@ module.exports = class MetamaskController extends EventEmitter { return { // etc getState: (cb) => cb(null, this.getState()), - setProviderType: this.setProviderType.bind(this), - useEtherscanProvider: this.useEtherscanProvider.bind(this), + setProviderType: this.networkController.setProviderType.bind(this.networkController), + useEtherscanProvider: this.networkController.useEtherscanProvider.bind(this.networkController), setCurrentCurrency: this.setCurrentCurrency.bind(this), markAccountsFound: this.markAccountsFound.bind(this), // coinbase @@ -592,7 +620,7 @@ module.exports = class MetamaskController extends EventEmitter { // Log blocks logBlock (block) { log.info(`BLOCK CHANGED: #${block.number.toString('hex')} 0x${block.hash.toString('hex')}`) - this.verifyNetwork() + this.networkController.verifyNetwork() } setCurrentCurrency (currencyCode, cb) { @@ -612,7 +640,7 @@ module.exports = class MetamaskController extends EventEmitter { buyEth (address, amount) { if (!amount) amount = '5' - const network = this.getNetworkState() + const network = this.networkController.getNetworkState() const url = getBuyEthUrl({ network, address, amount }) if (url) this.platform.openWindow({ url }) } @@ -620,69 +648,21 @@ module.exports = class MetamaskController extends EventEmitter { createShapeShiftTx (depositAddress, depositType) { this.shapeshiftController.createShapeShiftTx(depositAddress, depositType) } - - // - // network - // - - verifyNetwork () { - // Check network when restoring connectivity: - if (this.isNetworkLoading()) this.lookupNetwork() - } +// network setDefaultRpc () { - this.configManager.setRpcTarget('http://localhost:8545') - this.platform.reload() - this.lookupNetwork() + this.networkController.setRpcTarget('http://localhost:8545') return Promise.resolve('http://localhost:8545') } setCustomRpc (rpcTarget, rpcList) { - this.configManager.setRpcTarget(rpcTarget) - return this.preferencesController.updateFrequentRpcList(rpcTarget) - .then(() => { - this.platform.reload() - this.lookupNetwork() - return Promise.resolve(rpcTarget) - }) - } - - setProviderType (type) { - this.configManager.setProviderType(type) - this.platform.reload() - this.lookupNetwork() - } - - useEtherscanProvider () { - this.configManager.useEtherscanProvider() - this.platform.reload() - } - - getNetworkState () { - return this.networkStore.getState().network - } - - setNetworkState (network) { - return this.networkStore.updateState({ network }) - } + this.networkController.setRpcTarget(rpcTarget) - isNetworkLoading () { - return this.getNetworkState() === 'loading' - } - - lookupNetwork (err) { - if (err) { - this.setNetworkState('loading') - } - - this.ethQuery.sendAsync({ method: 'net_version' }, (err, network) => { - if (err) { - this.setNetworkState('loading') - return - } - log.info('web3.getNetwork returned ' + network) - this.setNetworkState(network) + return this.preferencesController.updateFrequentRpcList(rpcTarget) + .then(() => { + return Promise.resolve(rpcTarget) }) } + } diff --git a/app/scripts/transaction-manager.js b/app/scripts/transaction-manager.js index 9f267160f..1e15128f9 100644 --- a/app/scripts/transaction-manager.js +++ b/app/scripts/transaction-manager.js @@ -4,7 +4,6 @@ const extend = require('xtend') const Semaphore = require('semaphore') const ObservableStore = require('obs-store') const ethUtil = require('ethereumjs-util') -const EthQuery = require('eth-query') const TxProviderUtil = require('./lib/tx-utils') const createId = require('./lib/random-id') @@ -18,11 +17,11 @@ module.exports = class TransactionManager extends EventEmitter { this.networkStore = opts.networkStore || new ObservableStore({}) this.preferencesStore = opts.preferencesStore || new ObservableStore({}) this.txHistoryLimit = opts.txHistoryLimit - this.provider = opts.provider - this.blockTracker = opts.blockTracker - this.query = new EthQuery(this.provider) - this.txProviderUtils = new TxProviderUtil(this.provider) - this.blockTracker.on('block', this.checkForTxInBlock.bind(this)) + this.setupProviderAndEthQuery({ + provider: opts.provider, + blockTracker: opts.blockTracker, + ethQuery: opts.ethQuery, + }) this.signEthTx = opts.signTransaction this.nonceLock = Semaphore(1) @@ -41,6 +40,20 @@ module.exports = class TransactionManager extends EventEmitter { return this.networkStore.getState().network } + setupProviderAndEthQuery ({provider, blockTracker, ethQuery}) { + if (this.provider) { + delete this.provider + delete this.blockTracker + delete this.query + delete this.txProviderUtils + } + this.provider = provider + this.query = ethQuery + this.txProviderUtils = new TxProviderUtil(ethQuery) + blockTracker ? this.blockTracker = blockTracker : this.blockTracker = provider + this.blockTracker.on('block', this.checkForTxInBlock.bind(this)) + } + getSelectedAddress () { return this.preferencesStore.getState().selectedAddress } From c5432da567b9953c1294e0bf598a0310127bf808 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Sat, 20 May 2017 20:37:47 -0700 Subject: [PATCH 286/372] Add new streaming subprovider but getting a loop Regarding #1458 Uses a new streaming subprovider architecture on an experimental branch of StreamProvider: https://github.com/flyswatter/web3-stream-provider/tree/StreamSubprovider --- app/scripts/lib/inpage-provider.js | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/app/scripts/lib/inpage-provider.js b/app/scripts/lib/inpage-provider.js index e5e398e24..88d81cca5 100644 --- a/app/scripts/lib/inpage-provider.js +++ b/app/scripts/lib/inpage-provider.js @@ -1,5 +1,6 @@ const pipe = require('pump') -const StreamProvider = require('web3-stream-provider') +const StreamSubprovider = require('web3-stream-provider/stream-subprovider') +const ProviderEngine = require('web3-provider-engine') const LocalStorageStore = require('obs-store') const ObjectMultiplex = require('./obj-multiplex') const createRandomId = require('./random-id') @@ -27,14 +28,21 @@ function MetamaskInpageProvider (connectionStream) { ) // connect to async provider - const asyncProvider = self.asyncProvider = new StreamProvider() + const engine = self.asyncProvider = new ProviderEngine() + + const stream = self.stream = new StreamSubprovider() + engine.addProvider(stream) + pipe( - asyncProvider, + stream, multiStream.createStream('provider'), - asyncProvider, + stream, (err) => logStreamDisconnectWarning('MetaMask RpcProvider', err) ) + // start polling + engine.start() + self.idMap = {} // handle sendAsync requests via asyncProvider self.sendAsync = function (payload, cb) { @@ -46,7 +54,9 @@ function MetamaskInpageProvider (connectionStream) { return message }) // forward to asyncProvider - asyncProvider.sendAsync(request, function (err, res) { + console.log('sending async to engine', request) + engine.sendAsync(request, function (err, res) { + console.log('send async returned !!', err, res) if (err) return cb(err) // transform messages to original ids eachJsonMessage(res, (message) => { From 6209224a6c1129817ebb4cd4a433cf456631c33a Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Sun, 21 May 2017 14:09:44 -0700 Subject: [PATCH 287/372] Add transaction number (nonce) to tx list --- CHANGELOG.md | 2 ++ ui/app/components/transaction-list-item.js | 21 +++++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 371b348ca..7f894f935 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Current Master +- Add Transaction Number (nonce) to transaction list. + ## 3.6.5 2017-5-17 - Fix bug where edited gas parameters would not take effect. diff --git a/ui/app/components/transaction-list-item.js b/ui/app/components/transaction-list-item.js index c2a585003..18ee10578 100644 --- a/ui/app/components/transaction-list-item.js +++ b/ui/app/components/transaction-list-item.js @@ -8,6 +8,7 @@ const explorerLink = require('../../lib/explorer-link') const CopyButton = require('./copyButton') const vreme = new (require('vreme')) const Tooltip = require('./tooltip') +const BN = require('ethereumjs-util').BN const TransactionIcon = require('./transaction-list-item-icon') const ShiftListItem = require('./shift-list-item') @@ -39,6 +40,8 @@ TransactionListItem.prototype.render = function () { txParams = transaction.msgParams } + const nonce = (new BN(txParams.nonce.substr(2))).toString(10) + const isClickable = ('hash' in transaction && isLinkable) || isPending return ( h(`.transaction-list-item.flex-row.flex-space-between${isClickable ? '.pointer' : ''}`, { @@ -69,6 +72,24 @@ TransactionListItem.prototype.render = function () { ]), ]), + h(Tooltip, { + title: 'Transaction Number', + position: 'bottom', + }, + [ + h('span', { + style: { + display: 'flex', + cursor: 'normal', + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'center', + padding: '10px', + }, + }, nonce), + ]), + + h('.flex-column', {style: {width: '200px', overflow: 'hidden'}}, [ domainField(txParams), h('div', date), From 3c90024564bee78fe0a9178d3772efaabe147ac5 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Sun, 21 May 2017 14:15:34 -0700 Subject: [PATCH 288/372] Label the pending tx icon with a tooltip --- CHANGELOG.md | 1 + ui/app/components/transaction-list-item-icon.js | 17 ++++++++++++----- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f894f935..50fa0d858 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Current Master - Add Transaction Number (nonce) to transaction list. +- Label the pending tx icon with a tooltip. ## 3.6.5 2017-5-17 diff --git a/ui/app/components/transaction-list-item-icon.js b/ui/app/components/transaction-list-item-icon.js index d63cae259..03c6f6615 100644 --- a/ui/app/components/transaction-list-item-icon.js +++ b/ui/app/components/transaction-list-item-icon.js @@ -1,6 +1,7 @@ const Component = require('react').Component const h = require('react-hyperscript') const inherits = require('util').inherits +const Tooltip = require('./tooltip') const Identicon = require('./identicon') @@ -32,11 +33,17 @@ TransactionIcon.prototype.render = function () { }) case 'submitted': - return h('i.fa.fa-ellipsis-h', { - style: { - fontSize: '27px', - }, - }) + return h(Tooltip, { + title: 'Pending', + position: 'bottom', + }, + [ + h('i.fa.fa-ellipsis-h', { + style: { + fontSize: '27px', + }, + }) + ]) } if (isMsg) { From 0ef9e8b7094374b44d7a3bed6730d9d6815a17ec Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Sun, 21 May 2017 14:18:23 -0700 Subject: [PATCH 289/372] Lint --- ui/app/components/transaction-list-item-icon.js | 5 ++--- ui/app/components/transaction-list-item.js | 4 +--- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/ui/app/components/transaction-list-item-icon.js b/ui/app/components/transaction-list-item-icon.js index 03c6f6615..431054340 100644 --- a/ui/app/components/transaction-list-item-icon.js +++ b/ui/app/components/transaction-list-item-icon.js @@ -36,13 +36,12 @@ TransactionIcon.prototype.render = function () { return h(Tooltip, { title: 'Pending', position: 'bottom', - }, - [ + }, [ h('i.fa.fa-ellipsis-h', { style: { fontSize: '27px', }, - }) + }), ]) } diff --git a/ui/app/components/transaction-list-item.js b/ui/app/components/transaction-list-item.js index 18ee10578..e0612c7bf 100644 --- a/ui/app/components/transaction-list-item.js +++ b/ui/app/components/transaction-list-item.js @@ -75,8 +75,7 @@ TransactionListItem.prototype.render = function () { h(Tooltip, { title: 'Transaction Number', position: 'bottom', - }, - [ + }, [ h('span', { style: { display: 'flex', @@ -89,7 +88,6 @@ TransactionListItem.prototype.render = function () { }, nonce), ]), - h('.flex-column', {style: {width: '200px', overflow: 'hidden'}}, [ domainField(txParams), h('div', date), From 954d8bd111ea70b823267b89edb415e2d28caec3 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Mon, 22 May 2017 14:14:13 -0700 Subject: [PATCH 290/372] Render txs with no nonce --- ui/app/components/transaction-list-item.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/app/components/transaction-list-item.js b/ui/app/components/transaction-list-item.js index e0612c7bf..4f0e6132a 100644 --- a/ui/app/components/transaction-list-item.js +++ b/ui/app/components/transaction-list-item.js @@ -40,7 +40,7 @@ TransactionListItem.prototype.render = function () { txParams = transaction.msgParams } - const nonce = (new BN(txParams.nonce.substr(2))).toString(10) + const nonce = txParams.nonce ? (new BN(txParams.nonce.substr(2))).toString(10) : '' const isClickable = ('hash' in transaction && isLinkable) || isPending return ( From 709c0eb307e2cda9aa16b67191a43e99e1b22fa0 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Mon, 22 May 2017 15:21:25 -0700 Subject: [PATCH 291/372] Use stream-provider v3 api --- app/scripts/popup-core.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/scripts/popup-core.js b/app/scripts/popup-core.js index f1eb394d7..7de1a6fda 100644 --- a/app/scripts/popup-core.js +++ b/app/scripts/popup-core.js @@ -29,9 +29,9 @@ function connectToAccountManager (connectionStream, cb) { function setupWeb3Connection (connectionStream) { var providerStream = new StreamProvider() - providerStream.pipe(connectionStream).pipe(providerStream) + providerStream.stream.pipe(connectionStream).pipe(providerStream.stream) connectionStream.on('error', console.error.bind(console)) - providerStream.on('error', console.error.bind(console)) + providerStream.stream.on('error', console.error.bind(console)) global.ethereumProvider = providerStream global.ethQuery = new EthQuery(providerStream) } From 48d9a2107130e3850077c6c1789b29a09634b168 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Mon, 22 May 2017 15:23:29 -0700 Subject: [PATCH 292/372] Use filter subprovider in-page to avoid filter leaks --- app/scripts/lib/inpage-provider.js | 8 ++++++-- package.json | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/app/scripts/lib/inpage-provider.js b/app/scripts/lib/inpage-provider.js index 88d81cca5..9dea05dbb 100644 --- a/app/scripts/lib/inpage-provider.js +++ b/app/scripts/lib/inpage-provider.js @@ -1,6 +1,7 @@ const pipe = require('pump') -const StreamSubprovider = require('web3-stream-provider/stream-subprovider') const ProviderEngine = require('web3-provider-engine') +const FilterSubprovider = require('web3-provider-engine/subproviders/filters') +const StreamSubprovider = require('web3-stream-provider/stream-subprovider') const LocalStorageStore = require('obs-store') const ObjectMultiplex = require('./obj-multiplex') const createRandomId = require('./random-id') @@ -28,7 +29,10 @@ function MetamaskInpageProvider (connectionStream) { ) // connect to async provider - const engine = self.asyncProvider = new ProviderEngine() + const engine = new ProviderEngine() + + const filterSubprovider = new FilterSubprovider() + engine.addProvider(filterSubprovider) const stream = self.stream = new StreamSubprovider() engine.addProvider(stream) diff --git a/package.json b/package.json index 14ddd2886..5512fa6a4 100644 --- a/package.json +++ b/package.json @@ -122,7 +122,7 @@ "vreme": "^3.0.2", "web3": "0.18.2", "web3-provider-engine": "^12.0.6", - "web3-stream-provider": "^2.0.6", + "web3-stream-provider": "^3.0.0", "xtend": "^4.0.1" }, "devDependencies": { From 39f9ffa18ab76fe154f9d5d4b3b2e5631d95fdc4 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Mon, 22 May 2017 15:25:31 -0700 Subject: [PATCH 293/372] Bump changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 371b348ca..c31f26f04 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Current Master +- Fix bug where website filters would pile up and not deallocate when leaving a site. + ## 3.6.5 2017-5-17 - Fix bug where edited gas parameters would not take effect. From cbfaa6f56f685d515e170de2bf305da76b8ec1e0 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Mon, 22 May 2017 15:41:13 -0700 Subject: [PATCH 294/372] Rename stream to streamSubprovider --- app/scripts/lib/inpage-provider.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/scripts/lib/inpage-provider.js b/app/scripts/lib/inpage-provider.js index 9dea05dbb..3b60756b9 100644 --- a/app/scripts/lib/inpage-provider.js +++ b/app/scripts/lib/inpage-provider.js @@ -34,13 +34,13 @@ function MetamaskInpageProvider (connectionStream) { const filterSubprovider = new FilterSubprovider() engine.addProvider(filterSubprovider) - const stream = self.stream = new StreamSubprovider() - engine.addProvider(stream) + const streamSubprovider = new StreamSubprovider() + engine.addProvider(streamSubprovider) pipe( - stream, + streamSubprovider, multiStream.createStream('provider'), - stream, + streamSubprovider, (err) => logStreamDisconnectWarning('MetaMask RpcProvider', err) ) From 058b73221393467549cca04937267635e471aae1 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Mon, 22 May 2017 15:43:20 -0700 Subject: [PATCH 295/372] Tolerate nonces of any format --- package.json | 1 + ui/app/components/transaction-list-item.js | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 14ddd2886..6fb9513d5 100644 --- a/package.json +++ b/package.json @@ -87,6 +87,7 @@ "mississippi": "^1.2.0", "mkdirp": "^0.5.1", "multiplex": "^6.7.0", + "number-to-bn": "^1.7.0", "obs-store": "^2.3.1", "once": "^1.3.3", "ping-pong-stream": "^1.0.0", diff --git a/ui/app/components/transaction-list-item.js b/ui/app/components/transaction-list-item.js index 4f0e6132a..dbda66a31 100644 --- a/ui/app/components/transaction-list-item.js +++ b/ui/app/components/transaction-list-item.js @@ -8,7 +8,7 @@ const explorerLink = require('../../lib/explorer-link') const CopyButton = require('./copyButton') const vreme = new (require('vreme')) const Tooltip = require('./tooltip') -const BN = require('ethereumjs-util').BN +const numberToBN = require('number-to-bn') const TransactionIcon = require('./transaction-list-item-icon') const ShiftListItem = require('./shift-list-item') @@ -40,7 +40,7 @@ TransactionListItem.prototype.render = function () { txParams = transaction.msgParams } - const nonce = txParams.nonce ? (new BN(txParams.nonce.substr(2))).toString(10) : '' + const nonce = txParams.nonce ? numberToBN(txParams.nonce).toString(10) : '' const isClickable = ('hash' in transaction && isLinkable) || isPending return ( From 1c1400b584a97e05e3f39748e5f44f076328d89b Mon Sep 17 00:00:00 2001 From: kumavis Date: Mon, 22 May 2017 15:59:07 -0700 Subject: [PATCH 296/372] deps - use stream-subprovider from provider-engine --- app/scripts/lib/inpage-provider.js | 2 +- app/scripts/popup-core.js | 4 ++-- package.json | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/scripts/lib/inpage-provider.js b/app/scripts/lib/inpage-provider.js index 3b60756b9..d24121ade 100644 --- a/app/scripts/lib/inpage-provider.js +++ b/app/scripts/lib/inpage-provider.js @@ -1,7 +1,7 @@ const pipe = require('pump') const ProviderEngine = require('web3-provider-engine') const FilterSubprovider = require('web3-provider-engine/subproviders/filters') -const StreamSubprovider = require('web3-stream-provider/stream-subprovider') +const StreamSubprovider = require('web3-provider-engine/subproviders/stream') const LocalStorageStore = require('obs-store') const ObjectMultiplex = require('./obj-multiplex') const createRandomId = require('./random-id') diff --git a/app/scripts/popup-core.js b/app/scripts/popup-core.js index 7de1a6fda..f1eb394d7 100644 --- a/app/scripts/popup-core.js +++ b/app/scripts/popup-core.js @@ -29,9 +29,9 @@ function connectToAccountManager (connectionStream, cb) { function setupWeb3Connection (connectionStream) { var providerStream = new StreamProvider() - providerStream.stream.pipe(connectionStream).pipe(providerStream.stream) + providerStream.pipe(connectionStream).pipe(providerStream) connectionStream.on('error', console.error.bind(console)) - providerStream.stream.on('error', console.error.bind(console)) + providerStream.on('error', console.error.bind(console)) global.ethereumProvider = providerStream global.ethQuery = new EthQuery(providerStream) } diff --git a/package.json b/package.json index 5512fa6a4..dba82d17c 100644 --- a/package.json +++ b/package.json @@ -121,8 +121,8 @@ "valid-url": "^1.0.9", "vreme": "^3.0.2", "web3": "0.18.2", - "web3-provider-engine": "^12.0.6", - "web3-stream-provider": "^3.0.0", + "web3-provider-engine": "^12.1.0", + "web3-stream-provider": "^2.0.6", "xtend": "^4.0.1" }, "devDependencies": { From b217ad1ae8109e9648da948bde06cdc88317396a Mon Sep 17 00:00:00 2001 From: kumavis Date: Mon, 22 May 2017 16:06:22 -0700 Subject: [PATCH 297/372] clean - remove console logs --- app/scripts/lib/inpage-provider.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/scripts/lib/inpage-provider.js b/app/scripts/lib/inpage-provider.js index d24121ade..e54f547bd 100644 --- a/app/scripts/lib/inpage-provider.js +++ b/app/scripts/lib/inpage-provider.js @@ -58,9 +58,7 @@ function MetamaskInpageProvider (connectionStream) { return message }) // forward to asyncProvider - console.log('sending async to engine', request) engine.sendAsync(request, function (err, res) { - console.log('send async returned !!', err, res) if (err) return cb(err) // transform messages to original ids eachJsonMessage(res, (message) => { From be5af7cb4bb44731504357e010bf78b3eda9d543 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Mon, 22 May 2017 17:45:29 -0700 Subject: [PATCH 298/372] Throw if ENS Resolver isn't set up Instead of resolving to name owners, which can encourage inconsistent usage of ENS. Fixes #1427. --- CHANGELOG.md | 1 + package.json | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 265c239b2..dab16c89a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - Add Transaction Number (nonce) to transaction list. - Label the pending tx icon with a tooltip. - Fix bug where website filters would pile up and not deallocate when leaving a site. +- ENS names will no longer resolve to their owner if no resolver is set. Resolvers must be explicitly set and configured. ## 3.6.5 2017-5-17 diff --git a/package.json b/package.json index 271e2a397..6b6996d9d 100644 --- a/package.json +++ b/package.json @@ -69,7 +69,7 @@ "ethereumjs-tx": "^1.3.0", "ethereumjs-util": "ethereumjs/ethereumjs-util#ac5d0908536b447083ea422b435da27f26615de9", "ethereumjs-wallet": "^0.6.0", - "ethjs-ens": "^1.0.2", + "ethjs-ens": "^2.0.0", "express": "^4.14.0", "extension-link-enabler": "^1.0.0", "extensionizer": "^1.0.0", From e08c1541e5a91d7958b15753982d22066c1a0a7d Mon Sep 17 00:00:00 2001 From: frankiebee Date: Tue, 23 May 2017 01:55:20 -0400 Subject: [PATCH 299/372] Add a migration for the network controller --- app/scripts/migrations/014.js | 34 +++++++++++++++++++++++++++++++++ app/scripts/migrations/index.js | 1 + 2 files changed, 35 insertions(+) create mode 100644 app/scripts/migrations/014.js diff --git a/app/scripts/migrations/014.js b/app/scripts/migrations/014.js new file mode 100644 index 000000000..0fe92125b --- /dev/null +++ b/app/scripts/migrations/014.js @@ -0,0 +1,34 @@ +const version = 14 + +/* + +This migration removes provider from config and moves it too NetworkController. + +*/ + +const clone = require('clone') + +module.exports = { + version, + + migrate: function (originalVersionedData) { + const versionedData = clone(originalVersionedData) + versionedData.meta.version = version + try { + const state = versionedData.data + const newState = transformState(state) + versionedData.data = newState + } catch (err) { + console.warn(`MetaMask Migration #${version}` + err.stack) + } + return Promise.resolve(versionedData) + }, +} + +function transformState (state) { + const newState = state + newState.NetworkController = {} + newState.NetworkController.provider = newState.config.provider + delete newState.config.provider + return newState +} diff --git a/app/scripts/migrations/index.js b/app/scripts/migrations/index.js index 3a95cf88e..fb1ad7863 100644 --- a/app/scripts/migrations/index.js +++ b/app/scripts/migrations/index.js @@ -24,4 +24,5 @@ module.exports = [ require('./011'), require('./012'), require('./013'), + require('./014'), ] From 529304c005318852b60bb93846a58d6eb3da2066 Mon Sep 17 00:00:00 2001 From: frankiebee Date: Tue, 23 May 2017 01:56:10 -0400 Subject: [PATCH 300/372] Wrap the provider in a proxy --- app/scripts/controllers/network.js | 110 +++++++++++------------- app/scripts/controllers/transactions.js | 27 ++---- app/scripts/keyring-controller.js | 6 -- app/scripts/lib/config-manager.js | 5 +- app/scripts/metamask-controller.js | 42 ++------- 5 files changed, 64 insertions(+), 126 deletions(-) diff --git a/app/scripts/controllers/network.js b/app/scripts/controllers/network.js index 82eabb573..97c2ccbc2 100644 --- a/app/scripts/controllers/network.js +++ b/app/scripts/controllers/network.js @@ -3,21 +3,29 @@ const MetaMaskProvider = require('web3-provider-engine/zero.js') const ObservableStore = require('obs-store') const extend = require('xtend') const EthQuery = require('eth-query') -const MetamaskConfig = require('../config.js') - -const TESTNET_RPC = MetamaskConfig.network.testnet -const MAINNET_RPC = MetamaskConfig.network.mainnet -const MORDEN_RPC = MetamaskConfig.network.morden -const KOVAN_RPC = MetamaskConfig.network.kovan -const RINKEBY_RPC = MetamaskConfig.network.rinkeby +const RPC_ADDRESS_LIST = require('../config.js').network module.exports = class NetworkController extends EventEmitter { constructor (providerOpts) { super() this.networkStore = new ObservableStore({ network: 'loading' }) - providerOpts.provider.rpcTarget = this.getRpcAddressForType(providerOpts.provider.type) + providerOpts.provider.rpcTarget = this.getRpcAddressForType(providerOpts.provider.type, providerOpts.provider) this.providerStore = new ObservableStore(providerOpts) - this._claimed = 0 + this.store = new ObservableStore(extend(this.networkStore.getState(), this.providerStore.getState())) + + this._providerListners = {} + + this.networkStore.subscribe((state) => this.store.updateState(state)) + this.providerStore.subscribe((state) => this.store.updateState(state)) + this.on('networkSwitch', this.lookupNetwork) + } + + get provider () { + return this._proxy + } + + set provider (provider) { + this._provider = provider } getState () { @@ -29,28 +37,35 @@ module.exports = class NetworkController extends EventEmitter { initializeProvider (opts) { this.providerConfig = opts - this.provider = MetaMaskProvider(opts) + this._provider = MetaMaskProvider(opts) + this._proxy = new Proxy(this._provider, { + get: (obj, name) => { + if (name === 'on') return this._on.bind(this) + return this._provider[name] + }, + set: (obj, name, value) => { + this._provider[name] = value + }, + }) + this.provider.on('block', this._logBlock.bind(this)) + this.provider.on('error', this.verifyNetwork.bind(this)) this.ethQuery = new EthQuery(this.provider) this.lookupNetwork() - return Promise.resolve(this.provider) + return this.provider } + switchNetwork (providerConfig) { - delete this.provider - delete this.ethQuery const newConfig = extend(this.providerConfig, providerConfig) this.providerConfig = newConfig + this.provider = MetaMaskProvider(newConfig) - this.ethQuery = new EthQuery(this.provider) - this.emit('networkSwitch', { - provider: this.provider, - ethQuery: this.ethQuery, - }, this.claim.bind(this)) + // apply the listners created by other controllers + Object.keys(this._providerListners).forEach((key) => { + this._providerListners[key].forEach((handler) => this._provider.addListener(key, handler)) + }) + this.emit('networkSwitch', this.provider) } - subscribe (cb) { - this.networkStore.subscribe(cb) - this.providerStore.subscribe(cb) - } verifyNetwork () { // Check network when restoring connectivity: @@ -74,7 +89,6 @@ module.exports = class NetworkController extends EventEmitter { this.ethQuery.sendAsync({ method: 'net_version' }, (err, network) => { if (err) return this.setNetworkState('loading') - log.info('web3.getNetwork returned ' + network) this.setNetworkState(network) }) @@ -102,51 +116,27 @@ module.exports = class NetworkController extends EventEmitter { this.switchNetwork({ rpcUrl: rpcTarget, }) - this.once('claimed', () => { - this.providerStore.updateState({provider: {type, rpcTarget}}) - console.log('CLAIMED') - this.lookupNetwork() - }) - - } - - useEtherscanProvider () { - this.setProviderType('etherscan') + this.providerStore.updateState({provider: {type, rpcTarget}}) } getProvider () { return this.providerStore.getState().provider } - getRpcAddressForType (type) { - const provider = this.getProvider() - switch (type) { - - case 'mainnet': - return MAINNET_RPC - - case 'testnet': - return TESTNET_RPC - - case 'morden': - return MORDEN_RPC - - case 'kovan': - return KOVAN_RPC - - case 'rinkeby': - return RINKEBY_RPC + getRpcAddressForType (type, provider = this.getProvider()) { + console.log(`#getRpcAddressForType: ${type}`) + if (type in RPC_ADDRESS_LIST) return RPC_ADDRESS_LIST[type] + return provider && provider.rpcTarget ? provider.rpcTarget : RPC_ADDRESS_LIST['rinkeby'] + } - default: - return provider && provider.rpcTarget ? provider.rpcTarget : TESTNET_RPC - } + _logBlock (block) { + log.info(`BLOCK CHANGED: #${block.number.toString('hex')} 0x${block.hash.toString('hex')}`) + this.verifyNetwork() } - claim () { - this._claimed += 1 - if (this._claimed === this.listenerCount('networkSwitch')) { - this.emit('claimed') - this._claimed = 0 - } + _on (event, handler) { + if (!this._providerListners[event]) this._providerListners[event] = [] + this._providerListners[event].push(handler) + this._provider.on(event, handler) } } diff --git a/app/scripts/controllers/transactions.js b/app/scripts/controllers/transactions.js index cfeeab6e6..b9bea2f1c 100644 --- a/app/scripts/controllers/transactions.js +++ b/app/scripts/controllers/transactions.js @@ -4,7 +4,6 @@ const extend = require('xtend') const Semaphore = require('semaphore') const ObservableStore = require('obs-store') const ethUtil = require('ethereumjs-util') -const EthQuery = require('eth-query') const TxProviderUtil = require('../lib/tx-utils') const createId = require('../lib/random-id') @@ -18,11 +17,13 @@ module.exports = class TransactionManager extends EventEmitter { this.networkStore = opts.networkStore || new ObservableStore({}) this.preferencesStore = opts.preferencesStore || new ObservableStore({}) this.txHistoryLimit = opts.txHistoryLimit - this.setupProviderAndEthQuery({ - provider: opts.provider, - blockTracker: opts.blockTracker, - ethQuery: opts.ethQuery, - }) + this.provider = opts.provider + this.blockTracker = opts.blockTracker + this.query = opts.ethQuery + this.txProviderUtils = new TxProviderUtil(this.query) + this.networkStore.subscribe((_) => this.blockTracker.on('block', this.checkForTxInBlock.bind(this))) + this.blockTracker.on('block', this.checkForTxInBlock.bind(this)) + this.signEthTx = opts.signTransaction this.nonceLock = Semaphore(1) @@ -41,20 +42,6 @@ module.exports = class TransactionManager extends EventEmitter { return this.networkStore.getState().network } - setupProviderAndEthQuery ({provider, blockTracker, ethQuery}) { - if (this.provider) { - delete this.provider - delete this.blockTracker - delete this.query - delete this.txProviderUtils - } - this.provider = provider - this.query = ethQuery - this.txProviderUtils = new TxProviderUtil(ethQuery) - blockTracker ? this.blockTracker = blockTracker : this.blockTracker = provider - this.blockTracker.on('block', this.checkForTxInBlock.bind(this)) - } - getSelectedAddress () { return this.preferencesStore.getState().selectedAddress } diff --git a/app/scripts/keyring-controller.js b/app/scripts/keyring-controller.js index bb699ca8b..5b3c80e40 100644 --- a/app/scripts/keyring-controller.js +++ b/app/scripts/keyring-controller.js @@ -41,12 +41,6 @@ class KeyringController extends EventEmitter { this.getNetwork = opts.getNetwork } - setEthStore (ethStore) { - delete this.ethStore - this.ethStore = ethStore - return this.setupAccounts() - } - // Full Update // returns Promise( @object state ) // diff --git a/app/scripts/lib/config-manager.js b/app/scripts/lib/config-manager.js index 6ca9bd9ea..fee8423fa 100644 --- a/app/scripts/lib/config-manager.js +++ b/app/scripts/lib/config-manager.js @@ -1,14 +1,13 @@ const ethUtil = require('ethereumjs-util') const normalize = require('eth-sig-util').normalize +const MetamaskConfig = require('../config.js') + -<<<<<<< HEAD -======= const MAINNET_RPC = MetamaskConfig.network.mainnet const ROPSTEN_RPC = MetamaskConfig.network.ropsten const KOVAN_RPC = MetamaskConfig.network.kovan const RINKEBY_RPC = MetamaskConfig.network.rinkeby ->>>>>>> master /* The config-manager is a convenience object * wrapping a pojo-migrator. * diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index b93f627bb..ef82da0d3 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -40,6 +40,7 @@ module.exports = class MetamaskController extends EventEmitter { this.store = new ObservableStore(initState) // network store + this.networkController = new NetworkController(initState.NetworkController) // config manager this.configManager = new ConfigManager({ @@ -60,12 +61,11 @@ module.exports = class MetamaskController extends EventEmitter { // rpc provider this.provider = this.initializeProvider() - this.provider.on('block', this.logBlock.bind(this)) - this.provider.on('error', this.networkController.verifyNetwork.bind(this.networkController)) // eth data query tools this.ethQuery = new EthQuery(this.provider) this.ethStore = new EthStore({ + network: this.networkController.networkStore, provider: this.provider, blockTracker: this.provider, }) @@ -111,32 +111,6 @@ module.exports = class MetamaskController extends EventEmitter { this.shapeshiftController = new ShapeShiftController({ initState: initState.ShapeShiftController, }) - this.networkController.on('networkSwitch', (providerUtil, claimed) => { - delete this.provider - delete this.ethQuery - delete this.ethStore - console.log('order:@? 1') - this.provider = providerUtil.provider - this.provider.on('block', this.logBlock.bind(this)) - this.provider.on('error', this.networkController.verifyNetwork.bind(this.networkController)) - - this.ethQuery = providerUtil.ethQuery - this.ethStore = new EthStore({ - provider: this.provider, - blockTracker: this.provider, - }) - this.provider.once('block', claimed) - }) - this.networkController.on('networkSwitch', (_, claimed) => { - console.log('order:@? 2') - this.txManager.setupProviderAndEthQuery({ - provider: this.provider, - blockTracker: this.provider, - ethQuery: this.ethQuery, - }) - this.keyringController.setEthStore(this.ethStore) - .then(claimed) - }) this.networkController.lookupNetwork() this.messageManager = new MessageManager() @@ -170,7 +144,7 @@ module.exports = class MetamaskController extends EventEmitter { }) // manual mem state subscriptions - this.networkController.subscribe(this.sendUpdate.bind(this)) + this.networkController.store.subscribe(this.sendUpdate.bind(this)) this.ethStore.subscribe(this.sendUpdate.bind(this)) this.txController.memStore.subscribe(this.sendUpdate.bind(this)) this.messageManager.memStore.subscribe(this.sendUpdate.bind(this)) @@ -188,7 +162,7 @@ module.exports = class MetamaskController extends EventEmitter { // initializeProvider () { - this.networkController.initializeProvider({ + return this.networkController.initializeProvider({ static: { eth_syncing: false, web3_clientVersion: `MetaMask/v${version}`, @@ -213,7 +187,6 @@ module.exports = class MetamaskController extends EventEmitter { // new style msg signing processPersonalMessage: this.newUnsignedPersonalMessage.bind(this), }) - return this.networkController.provider } initPublicConfigStore () { @@ -249,7 +222,7 @@ module.exports = class MetamaskController extends EventEmitter { { isInitialized, }, - this.networkController.getState(), + this.networkController.store.getState(), this.ethStore.getState(), this.txController.memStore.getState(), this.messageManager.memStore.getState(), @@ -284,7 +257,6 @@ module.exports = class MetamaskController extends EventEmitter { // etc getState: (cb) => cb(null, this.getState()), setProviderType: this.networkController.setProviderType.bind(this.networkController), - useEtherscanProvider: this.networkController.useEtherscanProvider.bind(this.networkController), setCurrentCurrency: this.setCurrentCurrency.bind(this), markAccountsFound: this.markAccountsFound.bind(this), // coinbase @@ -618,10 +590,6 @@ module.exports = class MetamaskController extends EventEmitter { // // Log blocks - logBlock (block) { - log.info(`BLOCK CHANGED: #${block.number.toString('hex')} 0x${block.hash.toString('hex')}`) - this.networkController.verifyNetwork() - } setCurrentCurrency (currencyCode, cb) { try { From 959038132a6780f1dd7a4db3696d3fdbaad83b88 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Tue, 23 May 2017 10:43:37 -0700 Subject: [PATCH 301/372] Increase accuracy of our rounding schemes. --- ui/app/components/bn-as-decimal-input.js | 29 +++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/ui/app/components/bn-as-decimal-input.js b/ui/app/components/bn-as-decimal-input.js index d0eebe09e..fbe36abfb 100644 --- a/ui/app/components/bn-as-decimal-input.js +++ b/ui/app/components/bn-as-decimal-input.js @@ -30,8 +30,8 @@ BnAsDecimalInput.prototype.render = function () { const suffix = props.suffix const style = props.style - const scale = Math.pow(10, precision) - const newValue = value.toNumber(10) / scale + const valueString = value.toString(10) + const newValue = downsize(valueString, precision, precision) return ( h('.flex-column', [ @@ -63,7 +63,9 @@ BnAsDecimalInput.prototype.render = function () { onChange: (event) => { this.updateValidity(event) const value = (event.target.value === '') ? '' : event.target.value - const scaledNumber = Math.floor(scale * value) + + + const scaledNumber = upsize(value, precision, precision) const precisionBN = new BN(scaledNumber, 10) onChange(precisionBN) }, @@ -141,3 +143,24 @@ BnAsDecimalInput.prototype.constructWarning = function () { return message } + + +function downsize (number, scale, precision) { + if (scale === 0) { + return Number(number) + } else { + var decimals = (scale === precision) ? -1 : scale - precision + return Number(number.slice(0, -scale) + '.' + number.slice(-scale, decimals)) + } +} + +function upsize (number, scale, precision) { + var string = number.toString() + var stringArray = string.split('.') + var decimalLength = stringArray[1] ? stringArray[1].length : 0 + var newString = ((scale === 0) || (decimalLength === 0)) ? stringArray[0] : stringArray[0] + stringArray[1].slice(0, precision) + for (var i = decimalLength; i < scale; i++) { + newString += '0' + } + return newString +} From cd2ad1733da16aa3d84158e5df9fbf33f51450aa Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Tue, 23 May 2017 11:49:25 -0700 Subject: [PATCH 302/372] Continually resubmit pending txs --- CHANGELOG.md | 1 + app/scripts/controllers/transactions.js | 54 ++++++++++++++++++++++++- 2 files changed, 53 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 265c239b2..e84413bd0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - Add Transaction Number (nonce) to transaction list. - Label the pending tx icon with a tooltip. - Fix bug where website filters would pile up and not deallocate when leaving a site. +- Continually resubmit pending txs for a period of time to ensure successful broadcast. ## 3.6.5 2017-5-17 diff --git a/app/scripts/controllers/transactions.js b/app/scripts/controllers/transactions.js index 21dd25b30..3d86a171e 100644 --- a/app/scripts/controllers/transactions.js +++ b/app/scripts/controllers/transactions.js @@ -7,6 +7,10 @@ const ethUtil = require('ethereumjs-util') const EthQuery = require('eth-query') const TxProviderUtil = require('../lib/tx-utils') const createId = require('../lib/random-id') +const denodeify = require('denodeify') + +const RETRY_LIMIT = 200 +const RESUBMIT_INTERVAL = 10000 // Ten seconds module.exports = class TransactionManager extends EventEmitter { constructor (opts) { @@ -31,6 +35,8 @@ module.exports = class TransactionManager extends EventEmitter { this.store.subscribe(() => this._updateMemstore()) this.networkStore.subscribe(() => this._updateMemstore()) this.preferencesStore.subscribe(() => this._updateMemstore()) + + this.continuallyResubmitPendingTxs() } getState () { @@ -230,7 +236,11 @@ module.exports = class TransactionManager extends EventEmitter { }) } - publishTransaction (txId, rawTx, cb) { + publishTransaction (txId, rawTx, cb = warn) { + const txMeta = this.getTx(txId) + txMeta.rawTx = rawTx + this.updateTx(txMeta) + this.txProviderUtils.publishTransaction(rawTx, (err, txHash) => { if (err) return cb(err) this.setTxHash(txId, txHash) @@ -353,7 +363,7 @@ module.exports = class TransactionManager extends EventEmitter { message: 'There was a problem loading this transaction.', } this.updateTx(txMeta) - return console.error(err) + return log.error(err) } if (txParams.blockNumber) { this.setTxStatusConfirmed(txId) @@ -379,6 +389,7 @@ module.exports = class TransactionManager extends EventEmitter { this.emit(`${txMeta.id}:${status}`, txId) if (status === 'submitted' || status === 'rejected') { this.emit(`${txMeta.id}:finished`, txMeta) + } this.updateTx(txMeta) this.emit('updateBadge') @@ -398,6 +409,45 @@ module.exports = class TransactionManager extends EventEmitter { }) this.memStore.updateState({ unapprovedTxs, selectedAddressTxList }) } + + continuallyResubmitPendingTxs () { + const pending = this.getTxsByMetaData('status', 'submitted') + const resubmit = denodeify(this.resubmitTx.bind(this)) + Promise.all(pending.map(txMeta => resubmit(txMeta))) + .catch((reason) => { + log.info('Problem resubmitting tx', reason) + }) + .then(() => { + global.setTimeout(() => { + this.continuallyResubmitPendingTxs() + }, RESUBMIT_INTERVAL) + }) + } + + resubmitTx (txMeta, cb) { + // Increment a try counter. + if (!('retryCount' in txMeta)) { + txMeta.retryCount = 0 + } + + // Only auto-submit already-signed txs: + if (!('rawTx' in txMeta)) { + return cb() + } + + if (txMeta.retryCount > RETRY_LIMIT) { + txMeta.err = { + isWarning: true, + message: 'Gave up submitting tx.', + } + this.updateTx(txMeta) + return log.error(txMeta.err.message) + } + + txMeta.retryCount++ + const rawTx = txMeta.rawTx + this.txProviderUtils.publishTransaction(rawTx, cb) + } } From 31d17c9e25458cd47f8c18ec3b967aecff236ba6 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Tue, 23 May 2017 14:26:37 -0700 Subject: [PATCH 303/372] Fix test, create new value for precision/scale --- test/unit/components/bn-as-decimal-input-test.js | 5 +++-- ui/app/components/bn-as-decimal-input.js | 6 +++--- ui/app/components/pending-tx.js | 2 ++ 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/test/unit/components/bn-as-decimal-input-test.js b/test/unit/components/bn-as-decimal-input-test.js index f515003bb..6fe684dc5 100644 --- a/test/unit/components/bn-as-decimal-input-test.js +++ b/test/unit/components/bn-as-decimal-input-test.js @@ -10,7 +10,6 @@ var BnInput = require('../../../ui/app/components/bn-as-decimal-input') describe('BnInput', function () { it('can tolerate a gas decimal number at a high precision', function (done) { - const renderer = ReactTestUtils.createRenderer() let valueStr = '20' @@ -27,10 +26,12 @@ describe('BnInput', function () { } const target = new BN(targetStr, 10) - const precision = 1e18 // ether precision + const precision = 13 // ether precision + const scale = 13 const props = { value, + scale, precision, onChange: (newBn) => { assert.equal(newBn.toString(), target.toString(), 'should tolerate increase') diff --git a/ui/app/components/bn-as-decimal-input.js b/ui/app/components/bn-as-decimal-input.js index fbe36abfb..8f56f29ab 100644 --- a/ui/app/components/bn-as-decimal-input.js +++ b/ui/app/components/bn-as-decimal-input.js @@ -26,12 +26,12 @@ BnAsDecimalInput.prototype.render = function () { const props = this.props const state = this.state - const { value, precision, onChange, min, max } = props + const { value, scale, precision, onChange, min, max } = props const suffix = props.suffix const style = props.style const valueString = value.toString(10) - const newValue = downsize(valueString, precision, precision) + const newValue = downsize(valueString, scale, precision) return ( h('.flex-column', [ @@ -65,7 +65,7 @@ BnAsDecimalInput.prototype.render = function () { const value = (event.target.value === '') ? '' : event.target.value - const scaledNumber = upsize(value, precision, precision) + const scaledNumber = upsize(value, scale, precision) const precisionBN = new BN(scaledNumber, 10) onChange(precisionBN) }, diff --git a/ui/app/components/pending-tx.js b/ui/app/components/pending-tx.js index 5b238187c..eed0fd9ae 100644 --- a/ui/app/components/pending-tx.js +++ b/ui/app/components/pending-tx.js @@ -156,6 +156,7 @@ PendingTx.prototype.render = function () { name: 'Gas Limit', value: gasBn, precision: 0, + scale: 0, // The hard lower limit for gas. min: MIN_GAS_LIMIT_BN.toString(10), suffix: 'UNITS', @@ -179,6 +180,7 @@ PendingTx.prototype.render = function () { name: 'Gas Price', value: gasPriceBn, precision: 9, + scale: 9, suffix: 'GWEI', min: MIN_GAS_PRICE_GWEI_BN.toString(10), style: { From e4d09aebf470f9c014f2b658ccf5042a3995d708 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Tue, 23 May 2017 14:49:10 -0700 Subject: [PATCH 304/372] Cleanup --- app/scripts/controllers/transactions.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/scripts/controllers/transactions.js b/app/scripts/controllers/transactions.js index 3d86a171e..4d3197cba 100644 --- a/app/scripts/controllers/transactions.js +++ b/app/scripts/controllers/transactions.js @@ -12,7 +12,7 @@ const denodeify = require('denodeify') const RETRY_LIMIT = 200 const RESUBMIT_INTERVAL = 10000 // Ten seconds -module.exports = class TransactionManager extends EventEmitter { +module.exports = class TransactionController extends EventEmitter { constructor (opts) { super() this.store = new ObservableStore(extend({ @@ -448,7 +448,8 @@ module.exports = class TransactionManager extends EventEmitter { const rawTx = txMeta.rawTx this.txProviderUtils.publishTransaction(rawTx, cb) } + } -const warn = () => console.warn('warn was used no cb provided') +const warn = () => log.warn('warn was used no cb provided') From 17604f1ef5c880a391026aa02d19493d05c1dd18 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Tue, 23 May 2017 14:49:45 -0700 Subject: [PATCH 305/372] Version 3.7.0 --- CHANGELOG.md | 2 ++ app/manifest.json | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 88d7a81e7..c0473baee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Current Master +## 3.7.0 2017-5-23 + - Add Transaction Number (nonce) to transaction list. - Label the pending tx icon with a tooltip. - Fix bug where website filters would pile up and not deallocate when leaving a site. diff --git a/app/manifest.json b/app/manifest.json index 31e4598c7..5fd4420be 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -1,7 +1,7 @@ { "name": "MetaMask", "short_name": "Metamask", - "version": "3.6.5", + "version": "3.7.0", "manifest_version": 2, "author": "https://metamask.io", "description": "Ethereum Browser Extension", From bffd174ec71ab21326884bd9e356477657bd0867 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Tue, 23 May 2017 15:02:03 -0700 Subject: [PATCH 306/372] Update announcer wording --- development/announcer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/development/announcer.js b/development/announcer.js index 110d41fd4..43ae60acb 100644 --- a/development/announcer.js +++ b/development/announcer.js @@ -7,6 +7,6 @@ var changelog = fs.readFileSync(path.join(__dirname, '..', 'CHANGELOG.md')).toSt var log = changelog.split(version)[1].split('##')[0].trim() -let msg = `*MetaMask ${version}* now published to the Chrome Store! It should auto-update over the next hour!\n${log}` +let msg = `*MetaMask ${version}* now published to the Chrome Store! It should auto-update soon!\n${log}` console.log(msg) From 243eeff7cb0d4c5d613a9250d234f81fdccbbf15 Mon Sep 17 00:00:00 2001 From: frankiebee Date: Tue, 23 May 2017 02:12:28 -0400 Subject: [PATCH 307/372] Fix for tests --- app/scripts/controllers/network.js | 84 +++++++++++-------------- app/scripts/controllers/transactions.js | 4 +- app/scripts/first-time-state.js | 3 +- app/scripts/lib/config-manager.js | 29 +++++++++ app/scripts/metamask-controller.js | 3 +- test/unit/network-contoller-test.js | 74 ++++++++++++++++++++++ test/unit/tx-controller-test.js | 4 +- test/unit/tx-utils-test.js | 6 +- 8 files changed, 150 insertions(+), 57 deletions(-) create mode 100644 test/unit/network-contoller-test.js diff --git a/app/scripts/controllers/network.js b/app/scripts/controllers/network.js index 97c2ccbc2..4fdd92921 100644 --- a/app/scripts/controllers/network.js +++ b/app/scripts/controllers/network.js @@ -1,23 +1,23 @@ const EventEmitter = require('events') const MetaMaskProvider = require('web3-provider-engine/zero.js') const ObservableStore = require('obs-store') +const ComposedStore = require('obs-store/lib/composed') const extend = require('xtend') const EthQuery = require('eth-query') const RPC_ADDRESS_LIST = require('../config.js').network +const DEFAULT_RPC = RPC_ADDRESS_LIST['rinkeby'] module.exports = class NetworkController extends EventEmitter { - constructor (providerOpts) { + constructor (config) { super() - this.networkStore = new ObservableStore({ network: 'loading' }) - providerOpts.provider.rpcTarget = this.getRpcAddressForType(providerOpts.provider.type, providerOpts.provider) - this.providerStore = new ObservableStore(providerOpts) - this.store = new ObservableStore(extend(this.networkStore.getState(), this.providerStore.getState())) + this.networkStore = new ObservableStore('loading') + config.provider.rpcTarget = this.getRpcAddressForType(config.provider.type, config.provider) + this.providerStore = new ObservableStore(config.provider) + this.store = new ComposedStore({ provider: this.providerStore, network: this.networkStore }) + this._providerListeners = {} - this._providerListners = {} - - this.networkStore.subscribe((state) => this.store.updateState(state)) - this.providerStore.subscribe((state) => this.store.updateState(state)) - this.on('networkSwitch', this.lookupNetwork) + this.on('networkDidChange', this.lookupNetwork) + this.providerStore.subscribe((state) => this.switchNetwork({rpcUrl: state.rpcTarget})) } get provider () { @@ -28,15 +28,8 @@ module.exports = class NetworkController extends EventEmitter { this._provider = provider } - getState () { - return extend({}, - this.networkStore.getState(), - this.providerStore.getState() - ) - } - initializeProvider (opts) { - this.providerConfig = opts + this.providerInit = opts this._provider = MetaMaskProvider(opts) this._proxy = new Proxy(this._provider, { get: (obj, name) => { @@ -54,16 +47,18 @@ module.exports = class NetworkController extends EventEmitter { return this.provider } - switchNetwork (providerConfig) { - const newConfig = extend(this.providerConfig, providerConfig) - this.providerConfig = newConfig + switchNetwork (providerInit) { + this.setNetworkState('loading') + const newInit = extend(this.providerInit, providerInit) + this.providerInit = newInit - this.provider = MetaMaskProvider(newConfig) + this._provider.removeAllListeners() + this.provider = MetaMaskProvider(newInit) // apply the listners created by other controllers - Object.keys(this._providerListners).forEach((key) => { - this._providerListners[key].forEach((handler) => this._provider.addListener(key, handler)) + Object.keys(this._providerListeners).forEach((key) => { + this._providerListeners[key].forEach((handler) => this._provider.addListener(key, handler)) }) - this.emit('networkSwitch', this.provider) + this.emit('networkDidChange') } @@ -73,20 +68,18 @@ module.exports = class NetworkController extends EventEmitter { } getNetworkState () { - return this.networkStore.getState().network + return this.networkStore.getState() } setNetworkState (network) { - return this.networkStore.updateState({ network }) + return this.networkStore.putState(network) } isNetworkLoading () { return this.getNetworkState() === 'loading' } - lookupNetwork (err) { - if (err) this.setNetworkState('loading') - + lookupNetwork () { this.ethQuery.sendAsync({ method: 'net_version' }, (err, network) => { if (err) return this.setNetworkState('loading') log.info('web3.getNetwork returned ' + network) @@ -96,37 +89,30 @@ module.exports = class NetworkController extends EventEmitter { setRpcTarget (rpcUrl) { this.providerStore.updateState({ - provider: { - type: 'rpc', - rpcTarget: rpcUrl, - }, + type: 'rpc', + rpcTarget: rpcUrl, }) } getCurrentRpcAddress () { - var provider = this.getProvider() + const provider = this.getProviderConfig() if (!provider) return null return this.getRpcAddressForType(provider.type) } setProviderType (type) { - if (type === this.getProvider().type) return + if (type === this.getProviderConfig().type) return const rpcTarget = this.getRpcAddressForType(type) - this.networkStore.updateState({network: 'loading'}) - this.switchNetwork({ - rpcUrl: rpcTarget, - }) - this.providerStore.updateState({provider: {type, rpcTarget}}) + this.providerStore.updateState({type, rpcTarget}) } - getProvider () { - return this.providerStore.getState().provider + getProviderConfig () { + return this.providerStore.getState() } - getRpcAddressForType (type, provider = this.getProvider()) { - console.log(`#getRpcAddressForType: ${type}`) - if (type in RPC_ADDRESS_LIST) return RPC_ADDRESS_LIST[type] - return provider && provider.rpcTarget ? provider.rpcTarget : RPC_ADDRESS_LIST['rinkeby'] + getRpcAddressForType (type, provider = this.getProviderConfig()) { + if (RPC_ADDRESS_LIST[type]) return RPC_ADDRESS_LIST[type] + return provider && provider.rpcTarget ? provider.rpcTarget : DEFAULT_RPC } _logBlock (block) { @@ -135,8 +121,8 @@ module.exports = class NetworkController extends EventEmitter { } _on (event, handler) { - if (!this._providerListners[event]) this._providerListners[event] = [] - this._providerListners[event].push(handler) + if (!this._providerListeners[event]) this._providerListeners[event] = [] + this._providerListeners[event].push(handler) this._provider.on(event, handler) } } diff --git a/app/scripts/controllers/transactions.js b/app/scripts/controllers/transactions.js index b9bea2f1c..2ebeed3ab 100644 --- a/app/scripts/controllers/transactions.js +++ b/app/scripts/controllers/transactions.js @@ -21,9 +21,7 @@ module.exports = class TransactionManager extends EventEmitter { this.blockTracker = opts.blockTracker this.query = opts.ethQuery this.txProviderUtils = new TxProviderUtil(this.query) - this.networkStore.subscribe((_) => this.blockTracker.on('block', this.checkForTxInBlock.bind(this))) this.blockTracker.on('block', this.checkForTxInBlock.bind(this)) - this.signEthTx = opts.signTransaction this.nonceLock = Semaphore(1) @@ -39,7 +37,7 @@ module.exports = class TransactionManager extends EventEmitter { } getNetwork () { - return this.networkStore.getState().network + return this.networkStore.getState() } getSelectedAddress () { diff --git a/app/scripts/first-time-state.js b/app/scripts/first-time-state.js index 29ec1d8d3..dc7788311 100644 --- a/app/scripts/first-time-state.js +++ b/app/scripts/first-time-state.js @@ -3,7 +3,8 @@ // module.exports = { - config: { + config: {}, + NetworkController: { provider: { type: 'rinkeby', }, diff --git a/app/scripts/lib/config-manager.js b/app/scripts/lib/config-manager.js index fee8423fa..9c0dffe9c 100644 --- a/app/scripts/lib/config-manager.js +++ b/app/scripts/lib/config-manager.js @@ -107,6 +107,35 @@ ConfigManager.prototype.getSeedWords = function () { var data = this.getData() return data.seedWords } +ConfigManager.prototype.setRpcTarget = function (rpcUrl) { + var config = this.getConfig() + config.provider = { + type: 'rpc', + rpcTarget: rpcUrl, + } + this.setConfig(config) +} + +ConfigManager.prototype.setProviderType = function (type) { + var config = this.getConfig() + config.provider = { + type: type, + } + this.setConfig(config) +} + +ConfigManager.prototype.useEtherscanProvider = function () { + var config = this.getConfig() + config.provider = { + type: 'etherscan', + } + this.setConfig(config) +} + +ConfigManager.prototype.getProvider = function () { + var config = this.getConfig() + return config.provider +} ConfigManager.prototype.getCurrentRpcAddress = function () { var provider = this.getProvider() diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index ef82da0d3..c0ad1e93b 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -65,7 +65,6 @@ module.exports = class MetamaskController extends EventEmitter { // eth data query tools this.ethQuery = new EthQuery(this.provider) this.ethStore = new EthStore({ - network: this.networkController.networkStore, provider: this.provider, blockTracker: this.provider, }) @@ -139,7 +138,7 @@ module.exports = class MetamaskController extends EventEmitter { this.shapeshiftController.store.subscribe((state) => { this.store.updateState({ ShapeShiftController: state }) }) - this.networkController.providerStore.subscribe((state) => { + this.networkController.store.subscribe((state) => { this.store.updateState({ NetworkController: state }) }) diff --git a/test/unit/network-contoller-test.js b/test/unit/network-contoller-test.js new file mode 100644 index 000000000..183e69cab --- /dev/null +++ b/test/unit/network-contoller-test.js @@ -0,0 +1,74 @@ +const EventEmitter = require('events') +const assert = require('assert') +const NetworkController = require('../../app/scripts/controllers/network') + +describe('# Network Controller', function () { + let networkController + + beforeEach(function () { + networkController = new NetworkController({ + provider: { + type: 'rinkeby', + }, + }) + // stub out provider + networkController._provider = new EventEmitter() + networkController.providerInit = { + getAccounts: () => {}, + } + + networkController.ethQuery = new Proxy({}, { + get: (obj, name) => { + return () => {} + }, + }) + }) + describe('network', function () { + describe('#provider', function() { + it('provider should be updatable without reassignment', function () { + networkController.initializeProvider(networkController.providerInit) + const provider = networkController.provider + networkController._provider = {test: true} + assert.ok(provider.test) + }) + }) + describe('#getNetworkState', function () { + it('should return loading when new', function () { + let networkState = networkController.getNetworkState() + assert.equal(networkState, 'loading', 'network is loading') + }) + }) + + describe('#setNetworkState', function () { + it('should update the network', function () { + networkController.setNetworkState(1) + let networkState = networkController.getNetworkState() + assert.equal(networkState, 1, 'network is 1') + }) + }) + + describe('#getRpcAddressForType', function () { + it('should return the right rpc address', function () { + let rpcTarget = networkController.getRpcAddressForType('mainnet') + assert.equal(rpcTarget, 'https://mainnet.infura.io/metamask', 'returns the right rpcAddress') + }) + }) + describe('#setProviderType', function () { + it('should update provider.type', function () { + networkController.setProviderType('mainnet') + const type = networkController.getProviderConfig().type + assert.equal(type, 'mainnet', 'provider type is updated') + }) + it('should set the network to loading', function () { + networkController.setProviderType('mainnet') + const loading = networkController.isNetworkLoading() + assert.ok(loading, 'network is loading') + }) + it('should set the right rpcTarget', function () { + networkController.setProviderType('mainnet') + const rpcTarget = networkController.getProviderConfig().rpcTarget + assert.equal(rpcTarget, 'https://mainnet.infura.io/metamask', 'returns the right rpcAddress') + }) + }) + }) +}) diff --git a/test/unit/tx-controller-test.js b/test/unit/tx-controller-test.js index d4e8d79f0..711e1ea79 100644 --- a/test/unit/tx-controller-test.js +++ b/test/unit/tx-controller-test.js @@ -2,6 +2,7 @@ const assert = require('assert') const EventEmitter = require('events') const ethUtil = require('ethereumjs-util') const EthTx = require('ethereumjs-tx') +const EthQuery = require('eth-query') const ObservableStore = require('obs-store') const clone = require('clone') const sinon = require('sinon') @@ -16,9 +17,10 @@ describe('Transaction Controller', function () { beforeEach(function () { txController = new TransactionController({ - networkStore: new ObservableStore({ network: currentNetworkId }), + networkStore: new ObservableStore(currentNetworkId), txHistoryLimit: 10, blockTracker: new EventEmitter(), + ethQuery: new EthQuery(new EventEmitter()), signTransaction: (ethTx) => new Promise((resolve) => { ethTx.sign(privKey) resolve() diff --git a/test/unit/tx-utils-test.js b/test/unit/tx-utils-test.js index 57d4638a0..7ace1f587 100644 --- a/test/unit/tx-utils-test.js +++ b/test/unit/tx-utils-test.js @@ -9,7 +9,11 @@ describe('txUtils', function () { let txUtils before(function () { - txUtils = new TxUtils() + txUtils = new TxUtils(new Proxy({}, { + get: (obj, name) => { + return () => {} + }, + })) }) describe('chain Id', function () { From c5d74e6421486140eba801375eaeeb3c9c0a3091 Mon Sep 17 00:00:00 2001 From: frankiebee Date: Tue, 23 May 2017 20:06:19 -0400 Subject: [PATCH 308/372] include ethQuery in txController --- app/scripts/metamask-controller.js | 1 + 1 file changed, 1 insertion(+) diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index c0ad1e93b..a7eb3d056 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -97,6 +97,7 @@ module.exports = class MetamaskController extends EventEmitter { signTransaction: this.keyringController.signTransaction.bind(this.keyringController), provider: this.provider, blockTracker: this.provider, + ethQuery: this.ethQuery, }) // notices From c1239608a62a4283d138b1a6586653ac791eb655 Mon Sep 17 00:00:00 2001 From: frankiebee Date: Tue, 23 May 2017 20:27:51 -0400 Subject: [PATCH 309/372] add to CHANGELOG --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 371b348ca..85dc9ce81 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Current Master +- Now when switching networks the extension does not restart + ## 3.6.5 2017-5-17 - Fix bug where edited gas parameters would not take effect. From e55329d28b61b2d1066cf794a2099a18a94be7a4 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Wed, 24 May 2017 00:15:59 -0700 Subject: [PATCH 310/372] Version 3.7.1 --- app/manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/manifest.json b/app/manifest.json index 31e4598c7..c3e2b7511 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -1,7 +1,7 @@ { "name": "MetaMask", "short_name": "Metamask", - "version": "3.6.5", + "version": "3.7.1", "manifest_version": 2, "author": "https://metamask.io", "description": "Ethereum Browser Extension", From db982cf795d30dd32b4722249daea9296430b5b9 Mon Sep 17 00:00:00 2001 From: frankiebee Date: Wed, 24 May 2017 11:52:18 -0400 Subject: [PATCH 311/372] stop polling when switching networks --- app/scripts/controllers/network.js | 1 + test/unit/network-contoller-test.js | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/app/scripts/controllers/network.js b/app/scripts/controllers/network.js index 4fdd92921..c07f13b8d 100644 --- a/app/scripts/controllers/network.js +++ b/app/scripts/controllers/network.js @@ -53,6 +53,7 @@ module.exports = class NetworkController extends EventEmitter { this.providerInit = newInit this._provider.removeAllListeners() + this._provider.stop() this.provider = MetaMaskProvider(newInit) // apply the listners created by other controllers Object.keys(this._providerListeners).forEach((key) => { diff --git a/test/unit/network-contoller-test.js b/test/unit/network-contoller-test.js index 183e69cab..46f473af2 100644 --- a/test/unit/network-contoller-test.js +++ b/test/unit/network-contoller-test.js @@ -12,7 +12,11 @@ describe('# Network Controller', function () { }, }) // stub out provider - networkController._provider = new EventEmitter() + networkController._provider = new Proxy(new EventEmitter(), { + get: (obj, name) => { + return () => {} + }, + }) networkController.providerInit = { getAccounts: () => {}, } From e23c0f98faef3282d684bba48a53a1f48425b52d Mon Sep 17 00:00:00 2001 From: frankiebee Date: Wed, 24 May 2017 11:57:31 -0400 Subject: [PATCH 312/372] clean up test --- test/unit/network-contoller-test.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/unit/network-contoller-test.js b/test/unit/network-contoller-test.js index 46f473af2..76452b303 100644 --- a/test/unit/network-contoller-test.js +++ b/test/unit/network-contoller-test.js @@ -1,4 +1,3 @@ -const EventEmitter = require('events') const assert = require('assert') const NetworkController = require('../../app/scripts/controllers/network') @@ -12,7 +11,7 @@ describe('# Network Controller', function () { }, }) // stub out provider - networkController._provider = new Proxy(new EventEmitter(), { + networkController._provider = new Proxy({}, { get: (obj, name) => { return () => {} }, From 60281f72506c6b1775e75e8426a09d91893ab6ac Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Wed, 24 May 2017 09:55:16 -0700 Subject: [PATCH 313/372] Cleanup code. --- ui/app/components/bn-as-decimal-input.js | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/ui/app/components/bn-as-decimal-input.js b/ui/app/components/bn-as-decimal-input.js index 8f56f29ab..de01f8b5f 100644 --- a/ui/app/components/bn-as-decimal-input.js +++ b/ui/app/components/bn-as-decimal-input.js @@ -31,7 +31,7 @@ BnAsDecimalInput.prototype.render = function () { const suffix = props.suffix const style = props.style const valueString = value.toString(10) - const newValue = downsize(valueString, scale, precision) + const newValue = this.downsize(valueString, scale, precision) return ( h('.flex-column', [ @@ -65,7 +65,7 @@ BnAsDecimalInput.prototype.render = function () { const value = (event.target.value === '') ? '' : event.target.value - const scaledNumber = upsize(value, scale, precision) + const scaledNumber = this.upsize(value, scale, precision) const precisionBN = new BN(scaledNumber, 10) onChange(precisionBN) }, @@ -145,20 +145,28 @@ BnAsDecimalInput.prototype.constructWarning = function () { } -function downsize (number, scale, precision) { +BnAsDecimalInput.prototype.downsize = function (number, scale, precision) { + // if there is no scaling, simply return the number if (scale === 0) { return Number(number) } else { + // if the scale is the same as the precision, account for this edge case. var decimals = (scale === precision) ? -1 : scale - precision return Number(number.slice(0, -scale) + '.' + number.slice(-scale, decimals)) } } -function upsize (number, scale, precision) { - var string = number.toString() - var stringArray = string.split('.') +BnAsDecimalInput.prototype.upsize = function (number, scale, precision) { + var stringArray = number.toString().split('.') var decimalLength = stringArray[1] ? stringArray[1].length : 0 - var newString = ((scale === 0) || (decimalLength === 0)) ? stringArray[0] : stringArray[0] + stringArray[1].slice(0, precision) + var newString = stringArray[0] + + // If there is scaling and decimal parts exist, integrate them in. + if ((scale !== 0) && (decimalLength !== 0)) { + newString += stringArray[1].slice(0, precision) + } + + // Add 0s to account for the upscaling. for (var i = decimalLength; i < scale; i++) { newString += '0' } From 2d739647b909732c26a7725cb78cf018c71d6621 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Wed, 24 May 2017 10:01:45 -0700 Subject: [PATCH 314/372] Bump changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 215aee936..2ad61254c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Current Master - Now when switching networks the extension does not restart +- Cleanup decimal bugs in our gas inputs. ## 3.7.0 2017-5-23 From 10ca3b6467af2bea723e661160ba5cf2a41ab3b0 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Wed, 24 May 2017 10:13:43 -0700 Subject: [PATCH 315/372] Fix bug where submit was enabled when invalid params were filled out. --- CHANGELOG.md | 1 + ui/app/components/bn-as-decimal-input.js | 2 +- ui/app/components/pending-tx.js | 14 ++++++++++---- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ad61254c..aea0df1fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - Now when switching networks the extension does not restart - Cleanup decimal bugs in our gas inputs. +- Fix bug where submit button was enabled for invalid gas inputs. ## 3.7.0 2017-5-23 diff --git a/ui/app/components/bn-as-decimal-input.js b/ui/app/components/bn-as-decimal-input.js index de01f8b5f..1d292ca2a 100644 --- a/ui/app/components/bn-as-decimal-input.js +++ b/ui/app/components/bn-as-decimal-input.js @@ -67,7 +67,7 @@ BnAsDecimalInput.prototype.render = function () { const scaledNumber = this.upsize(value, scale, precision) const precisionBN = new BN(scaledNumber, 10) - onChange(precisionBN) + onChange(precisionBN, event.target.checkValidity()) }, onInvalid: (event) => { const msg = this.constructWarning() diff --git a/ui/app/components/pending-tx.js b/ui/app/components/pending-tx.js index eed0fd9ae..8e63f5c76 100644 --- a/ui/app/components/pending-tx.js +++ b/ui/app/components/pending-tx.js @@ -346,18 +346,24 @@ PendingTx.prototype.miniAccountPanelForRecipient = function () { } } -PendingTx.prototype.gasPriceChanged = function (newBN) { +PendingTx.prototype.gasPriceChanged = function (newBN, valid) { log.info(`Gas price changed to: ${newBN.toString(10)}`) const txMeta = this.gatherTxMeta() txMeta.txParams.gasPrice = '0x' + newBN.toString('hex') - this.setState({ txData: clone(txMeta) }) + this.setState({ + txData: clone(txMeta), + valid, + }) } -PendingTx.prototype.gasLimitChanged = function (newBN) { +PendingTx.prototype.gasLimitChanged = function (newBN, valid) { log.info(`Gas limit changed to ${newBN.toString(10)}`) const txMeta = this.gatherTxMeta() txMeta.txParams.gas = '0x' + newBN.toString('hex') - this.setState({ txData: clone(txMeta) }) + this.setState({ + txData: clone(txMeta), + valid, + }) } PendingTx.prototype.resetGasFields = function () { From 5e19a4a8332703aa3467470073411ad9cccca52a Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Wed, 24 May 2017 10:51:44 -0700 Subject: [PATCH 316/372] Modfiy test to ether standards. --- test/unit/components/bn-as-decimal-input-test.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/unit/components/bn-as-decimal-input-test.js b/test/unit/components/bn-as-decimal-input-test.js index 6fe684dc5..b3365b6f9 100644 --- a/test/unit/components/bn-as-decimal-input-test.js +++ b/test/unit/components/bn-as-decimal-input-test.js @@ -13,7 +13,7 @@ describe('BnInput', function () { const renderer = ReactTestUtils.createRenderer() let valueStr = '20' - while (valueStr.length < 15) { + while (valueStr.length < 20) { valueStr += '0' } const value = new BN(valueStr, 10) @@ -21,13 +21,13 @@ describe('BnInput', function () { let inputStr = '2.3' let targetStr = '23' - while (targetStr.length < 14) { + while (targetStr.length < 19) { targetStr += '0' } const target = new BN(targetStr, 10) - const precision = 13 // ether precision - const scale = 13 + const precision = 18 // ether precision + const scale = 18 const props = { value, From 293d0b4a574e5b20662da244d54357138ac81d5b Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Wed, 24 May 2017 11:02:26 -0700 Subject: [PATCH 317/372] Minor cleanup --- ui/app/components/bn-as-decimal-input.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/app/components/bn-as-decimal-input.js b/ui/app/components/bn-as-decimal-input.js index 1d292ca2a..f3ace4720 100644 --- a/ui/app/components/bn-as-decimal-input.js +++ b/ui/app/components/bn-as-decimal-input.js @@ -47,8 +47,8 @@ BnAsDecimalInput.prototype.render = function () { type: 'number', step: 'any', required: true, - min: min, - max: max, + min, + max, style: extend({ display: 'block', textAlign: 'right', From 9554788c14eab7be51abe0496bab17f9fe40291b Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Wed, 24 May 2017 11:02:58 -0700 Subject: [PATCH 318/372] Minor cleanup of lint --- ui/app/components/pending-tx.js | 1 - 1 file changed, 1 deletion(-) diff --git a/ui/app/components/pending-tx.js b/ui/app/components/pending-tx.js index 8e63f5c76..d66d98dd5 100644 --- a/ui/app/components/pending-tx.js +++ b/ui/app/components/pending-tx.js @@ -367,7 +367,6 @@ PendingTx.prototype.gasLimitChanged = function (newBN, valid) { } PendingTx.prototype.resetGasFields = function () { - log.debug(`pending-tx resetGasFields`) this.inputs.forEach((hexInput) => { From e6b278569ea072aa88416e769d4803a73b77194c Mon Sep 17 00:00:00 2001 From: kumavis Date: Wed, 24 May 2017 11:34:26 -0700 Subject: [PATCH 319/372] inpage-provider - disable polling after first block --- app/scripts/lib/inpage-provider.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/scripts/lib/inpage-provider.js b/app/scripts/lib/inpage-provider.js index e54f547bd..39196e240 100644 --- a/app/scripts/lib/inpage-provider.js +++ b/app/scripts/lib/inpage-provider.js @@ -44,8 +44,9 @@ function MetamaskInpageProvider (connectionStream) { (err) => logStreamDisconnectWarning('MetaMask RpcProvider', err) ) - // start polling + // start and stop polling to unblock first block lock engine.start() + engine.once('latest', () => engine.stop()) self.idMap = {} // handle sendAsync requests via asyncProvider From 26fd016b63c4f3a15436d08dff9e94f72d2b4041 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Wed, 24 May 2017 16:17:03 -0700 Subject: [PATCH 320/372] Add new blockGasLimit property to txMeta object. --- app/scripts/lib/tx-utils.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/app/scripts/lib/tx-utils.js b/app/scripts/lib/tx-utils.js index 76b311653..8cf304d0b 100644 --- a/app/scripts/lib/tx-utils.js +++ b/app/scripts/lib/tx-utils.js @@ -21,19 +21,30 @@ module.exports = class txProviderUtils { this.query.getBlockByNumber('latest', true, (err, block) => { if (err) return cb(err) async.waterfall([ + self.setBlockGasLimit.bind(self, txMeta, block.gasLimit), self.estimateTxGas.bind(self, txMeta, block.gasLimit), self.setTxGas.bind(self, txMeta, block.gasLimit), ], cb) }) } + setBlockGasLimit (txMeta, blockGasLimitHex, cb) { + const blockGasLimitBN = hexToBn(blockGasLimitHex) + const saferGasLimitBN = blockGasLimitBN.muln(0.95) + txMeta.blockGasLimit = bnToHex(saferGasLimitBN) + cb() + return + } + estimateTxGas (txMeta, blockGasLimitHex, cb) { const txParams = txMeta.txParams // check if gasLimit is already specified txMeta.gasLimitSpecified = Boolean(txParams.gas) // if not, fallback to block gasLimit if (!txMeta.gasLimitSpecified) { - txParams.gas = blockGasLimitHex + const blockGasLimitBN = hexToBn(blockGasLimitHex) + const saferGasLimitBN = blockGasLimitBN.muln(0.95) + txParams.gas = bnToHex(saferGasLimitBN) } // run tx, see if it will OOG this.query.estimateGas(txParams, cb) From 51b5e2f6e74a91198816afee8ddaf468ab7ab583 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Wed, 24 May 2017 16:18:37 -0700 Subject: [PATCH 321/372] Add max gas limit to UI --- ui/app/components/pending-tx.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ui/app/components/pending-tx.js b/ui/app/components/pending-tx.js index d66d98dd5..b46f715bc 100644 --- a/ui/app/components/pending-tx.js +++ b/ui/app/components/pending-tx.js @@ -47,6 +47,7 @@ PendingTx.prototype.render = function () { // Gas const gas = txParams.gas const gasBn = hexToBn(gas) + const safeGasLimit = parseInt(txMeta.blockGasLimit) // Gas Price const gasPrice = txParams.gasPrice || MIN_GAS_PRICE_BN.toString(16) @@ -159,6 +160,7 @@ PendingTx.prototype.render = function () { scale: 0, // The hard lower limit for gas. min: MIN_GAS_LIMIT_BN.toString(10), + max: safeGasLimit, suffix: 'UNITS', style: { position: 'relative', From b95f2158bb3094f6a47236c2e0e4e0ae9b87bb91 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Wed, 24 May 2017 16:23:31 -0700 Subject: [PATCH 322/372] bump changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index aea0df1fa..caa87b697 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - Now when switching networks the extension does not restart - Cleanup decimal bugs in our gas inputs. - Fix bug where submit button was enabled for invalid gas inputs. +- Now enforce 95% of block's gasLimit to protect users. ## 3.7.0 2017-5-23 From 473b88f399478b47bfa53d44ef9981aeb6d9960b Mon Sep 17 00:00:00 2001 From: frankiebee Date: Wed, 24 May 2017 22:13:35 -0400 Subject: [PATCH 323/372] Reload the page when switching networks for sites that use web3 --- app/scripts/contentscript.js | 1 - app/scripts/inpage.js | 21 +++------------ app/scripts/lib/auto-reload.js | 47 ++++++++++++++++++---------------- 3 files changed, 28 insertions(+), 41 deletions(-) diff --git a/app/scripts/contentscript.js b/app/scripts/contentscript.js index f7237b32e..291b922e8 100644 --- a/app/scripts/contentscript.js +++ b/app/scripts/contentscript.js @@ -61,7 +61,6 @@ function setupStreams () { // ignore unused channels (handled by background) mx.ignoreStream('provider') mx.ignoreStream('publicConfig') - mx.ignoreStream('reload') } function shouldInjectWeb3 () { diff --git a/app/scripts/inpage.js b/app/scripts/inpage.js index 419f78cd6..ec764535e 100644 --- a/app/scripts/inpage.js +++ b/app/scripts/inpage.js @@ -31,26 +31,11 @@ web3.setProvider = function () { console.log('MetaMask - overrode web3.setProvider') } console.log('MetaMask - injected web3') -// export global web3, with usage-detection reload fn -var triggerReload = setupDappAutoReload(web3) - -// listen for reset requests from metamask -var reloadStream = inpageProvider.multiStream.createStream('reload') -reloadStream.once('data', triggerReload) - -// setup ping timeout autoreload -// LocalMessageDuplexStream does not self-close, so reload if pingStream fails -// var pingChannel = inpageProvider.multiStream.createStream('pingpong') -// var pingStream = new PingStream({ objectMode: true }) -// wait for first successful reponse - -// disable pingStream until https://github.com/MetaMask/metamask-plugin/issues/746 is resolved more gracefully -// metamaskStream.once('data', function(){ -// pingStream.pipe(pingChannel).pipe(pingStream) -// }) -// endOfStream(pingStream, triggerReload) +// export global web3, with usage-detection +setupDappAutoReload(web3, inpageProvider.publicConfigStore) // set web3 defaultAccount + inpageProvider.publicConfigStore.subscribe(function (state) { web3.eth.defaultAccount = state.selectedAddress }) diff --git a/app/scripts/lib/auto-reload.js b/app/scripts/lib/auto-reload.js index 1302df35f..30ddd2395 100644 --- a/app/scripts/lib/auto-reload.js +++ b/app/scripts/lib/auto-reload.js @@ -1,30 +1,33 @@ -const once = require('once') -const ensnare = require('ensnare') - module.exports = setupDappAutoReload -function setupDappAutoReload (web3) { +function setupDappAutoReload (web3, observable) { // export web3 as a global, checking for usage - var pageIsUsingWeb3 = false - var resetWasRequested = false - global.web3 = ensnare(web3, once(function () { - // if web3 usage happened after a reset request, trigger reset late - if (resetWasRequested) return triggerReset() - // mark web3 as used - pageIsUsingWeb3 = true - // reset web3 reference - global.web3 = web3 - })) + global.web3 = new Proxy(web3, { + get: (_web3, name) => { + // get the time of use + if (name !== '_used') _web3._used = Date.now() + return _web3[name] + }, + set: (_web3, name, value) => { + _web3[name] = value + }, + }) + var networkVersion - return handleResetRequest + observable.subscribe(function (state) { + // get the initial network + const curentNetVersion = state.networkVersion + if (!networkVersion) networkVersion = curentNetVersion - function handleResetRequest () { - resetWasRequested = true - // ignore if web3 was not used - if (!pageIsUsingWeb3) return - // reload after short timeout - setTimeout(triggerReset, 500) - } + if (curentNetVersion !== networkVersion && web3._used) { + const timeSenseUse = Date.now() - web3._used + // if web3 was recently used then delay the reloading of the page + timeSenseUse > 500 ? triggerReset() : setTimeout(triggerReset, 500) + // prevent reentry into if statement if state updates again before + // reload + networkVersion = curentNetVersion + } + }) } // reload the page From a2064bd16e3f37c537ef6c818a5a10024ef24a2a Mon Sep 17 00:00:00 2001 From: frankiebee Date: Wed, 24 May 2017 23:18:09 -0400 Subject: [PATCH 324/372] add to CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index caa87b697..b755e3999 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Current Master +- Now when switching networks sites that use web3 will reload - Now when switching networks the extension does not restart - Cleanup decimal bugs in our gas inputs. - Fix bug where submit button was enabled for invalid gas inputs. From 717dceede84980050420fc3e3ff015caf2bcd553 Mon Sep 17 00:00:00 2001 From: frankiebee Date: Wed, 24 May 2017 23:36:10 -0400 Subject: [PATCH 325/372] fix spelling --- app/scripts/lib/auto-reload.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/scripts/lib/auto-reload.js b/app/scripts/lib/auto-reload.js index 30ddd2395..534047330 100644 --- a/app/scripts/lib/auto-reload.js +++ b/app/scripts/lib/auto-reload.js @@ -20,9 +20,9 @@ function setupDappAutoReload (web3, observable) { if (!networkVersion) networkVersion = curentNetVersion if (curentNetVersion !== networkVersion && web3._used) { - const timeSenseUse = Date.now() - web3._used + const timeSinceUse = Date.now() - web3._used // if web3 was recently used then delay the reloading of the page - timeSenseUse > 500 ? triggerReset() : setTimeout(triggerReset, 500) + timeSinceUse > 500 ? triggerReset() : setTimeout(triggerReset, 500) // prevent reentry into if statement if state updates again before // reload networkVersion = curentNetVersion From ad40e4d2608e0b1e329a6f9af851fbe2cc54e747 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Thu, 25 May 2017 12:37:04 -0700 Subject: [PATCH 326/372] Remove stream subprovider Since the polling leak seems to be coming from elsewhere, and new bugs came from this, I'm rolling back this change so that we can push the other improvements sooner and fix the bug at its true root. --- app/scripts/lib/inpage-provider.js | 22 +++++----------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/app/scripts/lib/inpage-provider.js b/app/scripts/lib/inpage-provider.js index 39196e240..8b8623974 100644 --- a/app/scripts/lib/inpage-provider.js +++ b/app/scripts/lib/inpage-provider.js @@ -1,7 +1,5 @@ const pipe = require('pump') -const ProviderEngine = require('web3-provider-engine') -const FilterSubprovider = require('web3-provider-engine/subproviders/filters') -const StreamSubprovider = require('web3-provider-engine/subproviders/stream') +const StreamProvider = require('web3-stream-provider') const LocalStorageStore = require('obs-store') const ObjectMultiplex = require('./obj-multiplex') const createRandomId = require('./random-id') @@ -29,24 +27,14 @@ function MetamaskInpageProvider (connectionStream) { ) // connect to async provider - const engine = new ProviderEngine() - - const filterSubprovider = new FilterSubprovider() - engine.addProvider(filterSubprovider) - - const streamSubprovider = new StreamSubprovider() - engine.addProvider(streamSubprovider) - + const asyncProvider = self.asyncProvider = new StreamProvider() pipe( - streamSubprovider, + asyncProvider, multiStream.createStream('provider'), - streamSubprovider, + asyncProvider, (err) => logStreamDisconnectWarning('MetaMask RpcProvider', err) ) - // start and stop polling to unblock first block lock - engine.start() - engine.once('latest', () => engine.stop()) self.idMap = {} // handle sendAsync requests via asyncProvider @@ -59,7 +47,7 @@ function MetamaskInpageProvider (connectionStream) { return message }) // forward to asyncProvider - engine.sendAsync(request, function (err, res) { + asyncProvider.sendAsync(request, function (err, res) { if (err) return cb(err) // transform messages to original ids eachJsonMessage(res, (message) => { From 606416121508342bd6eb0c2f40f6c482bc7d3fa0 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Thu, 25 May 2017 13:43:53 -0700 Subject: [PATCH 327/372] Bump changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index caa87b697..9b0a47157 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - Cleanup decimal bugs in our gas inputs. - Fix bug where submit button was enabled for invalid gas inputs. - Now enforce 95% of block's gasLimit to protect users. +- Removing provider-engine from the inpage provider. This fixes some error handling inconsistencies introduced in 3.7.0. ## 3.7.0 2017-5-23 From d8c94fca75ca2aed11387c0b1d4c6064bead447e Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Fri, 26 May 2017 00:19:24 -0700 Subject: [PATCH 328/372] Add address image map to icon factory Deriving from the new address image map repository I've added here: https://github.com/MetaMask/ethereum-contract-icons With this PR, images for addresses added to that repository will be shown instead of jazzicons in MetaMask. --- app/scripts/inpage.js | 4 +-- app/scripts/lib/inpage-provider.js | 2 ++ gulpfile.js | 10 ++++++++ package.json | 1 + ui/lib/icon-factory.js | 39 ++++++++++++++++-------------- 5 files changed, 36 insertions(+), 20 deletions(-) diff --git a/app/scripts/inpage.js b/app/scripts/inpage.js index ec764535e..10e5ea39b 100644 --- a/app/scripts/inpage.js +++ b/app/scripts/inpage.js @@ -1,6 +1,6 @@ /*global Web3*/ cleanContextForImports() -require('web3/dist/web3.min.js') +require('web3') const LocalMessageDuplexStream = require('post-message-stream') // const PingStream = require('ping-pong-stream/ping') // const endOfStream = require('end-of-stream') @@ -30,7 +30,7 @@ var web3 = new Web3(inpageProvider) web3.setProvider = function () { console.log('MetaMask - overrode web3.setProvider') } -console.log('MetaMask - injected web3') +console.log('MetaMask - injected modified web3') // export global web3, with usage-detection setupDappAutoReload(web3, inpageProvider.publicConfigStore) diff --git a/app/scripts/lib/inpage-provider.js b/app/scripts/lib/inpage-provider.js index 8b8623974..5206adc82 100644 --- a/app/scripts/lib/inpage-provider.js +++ b/app/scripts/lib/inpage-provider.js @@ -39,6 +39,8 @@ function MetamaskInpageProvider (connectionStream) { self.idMap = {} // handle sendAsync requests via asyncProvider self.sendAsync = function (payload, cb) { + console.trace('sending async ' + payload.method) + console.dir(payload) // rewrite request ids var request = eachJsonMessage(payload, (message) => { var newId = createRandomId() diff --git a/gulpfile.js b/gulpfile.js index 9f4a606be..5bba1b9b3 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -52,6 +52,15 @@ gulp.task('copy:images', copyTask({ './dist/opera/images', ], })) +gulp.task('copy:contractImages', copyTask({ + source: './node_modules/ethereum-contract-icons/images/', + destinations: [ + './dist/firefox/images/contract', + './dist/chrome/images/contract', + './dist/edge/images/contract', + './dist/opera/images/contract', + ], +})) gulp.task('copy:fonts', copyTask({ source: './app/fonts/', destinations: [ @@ -127,6 +136,7 @@ const staticFiles = [ ] var copyStrings = staticFiles.map(staticFile => `copy:${staticFile}`) +copyStrings.push('copy:contractImages') if (!disableLiveReload) { copyStrings.push('copy:reload') diff --git a/package.json b/package.json index 6b6996d9d..9f47d76cb 100644 --- a/package.json +++ b/package.json @@ -66,6 +66,7 @@ "eth-query": "^2.1.1", "eth-sig-util": "^1.1.1", "eth-simple-keyring": "^1.1.1", + "ethereum-contract-icons": "^1.0.0", "ethereumjs-tx": "^1.3.0", "ethereumjs-util": "ethereumjs/ethereumjs-util#ac5d0908536b447083ea422b435da27f26615de9", "ethereumjs-wallet": "^0.6.0", diff --git a/ui/lib/icon-factory.js b/ui/lib/icon-factory.js index 82cc839d6..4aed9109b 100644 --- a/ui/lib/icon-factory.js +++ b/ui/lib/icon-factory.js @@ -1,4 +1,7 @@ var iconFactory +const isValidAddress = require('ethereumjs-util').isValidAddress +const toChecksumAddress = require('ethereumjs-util').toChecksumAddress +const iconMap = require('ethereum-contract-icons') module.exports = function (jazzicon) { if (!iconFactory) { @@ -12,22 +15,12 @@ function IconFactory (jazzicon) { this.cache = {} } -IconFactory.prototype.iconForAddress = function (address, diameter, imageify) { - if (imageify) { - return this.generateIdenticonImg(address, diameter) - } else { - return this.generateIdenticonSvg(address, diameter) +IconFactory.prototype.iconForAddress = function (address, diameter) { + const addr = toChecksumAddress(address) + if (iconExistsFor(addr)) { + return imageElFor(addr) } -} - -// returns img dom element -IconFactory.prototype.generateIdenticonImg = function (address, diameter) { - var identicon = this.generateIdenticonSvg(address, diameter) - var identiconSrc = identicon.innerHTML - var dataUri = toDataUri(identiconSrc) - var img = document.createElement('img') - img.src = dataUri - return img + return this.generateIdenticonSvg(address, diameter) } // returns svg dom element @@ -49,12 +42,22 @@ IconFactory.prototype.generateNewIdenticon = function (address, diameter) { // util +function iconExistsFor (address) { + return (address in iconMap) && isValidAddress(address) +} + +function imageElFor (address) { + const fileName = iconMap[address] + const path = `images/contract/${fileName}` + const img = document.createElement('img') + img.src = path + img.style.width = '100%' + return img +} + function jsNumberForAddress (address) { var addr = address.slice(2, 10) var seed = parseInt(addr, 16) return seed } -function toDataUri (identiconSrc) { - return 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(identiconSrc) -} From 7cd42ae9ba07b04bae8e86be1536610ea4931f51 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Fri, 26 May 2017 00:45:58 -0700 Subject: [PATCH 329/372] Bump changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ad1b2f50..ca9cc6f5a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - Fix bug where submit button was enabled for invalid gas inputs. - Now enforce 95% of block's gasLimit to protect users. - Removing provider-engine from the inpage provider. This fixes some error handling inconsistencies introduced in 3.7.0. +- Some contracts will now display logos instead of jazzicons. ## 3.7.0 2017-5-23 From 1dfc0f74bf578524107b796672f96eb71a2fed62 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Fri, 26 May 2017 00:50:27 -0700 Subject: [PATCH 330/372] Correct inpage to be not modified --- app/scripts/inpage.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/scripts/inpage.js b/app/scripts/inpage.js index 10e5ea39b..ec764535e 100644 --- a/app/scripts/inpage.js +++ b/app/scripts/inpage.js @@ -1,6 +1,6 @@ /*global Web3*/ cleanContextForImports() -require('web3') +require('web3/dist/web3.min.js') const LocalMessageDuplexStream = require('post-message-stream') // const PingStream = require('ping-pong-stream/ping') // const endOfStream = require('end-of-stream') @@ -30,7 +30,7 @@ var web3 = new Web3(inpageProvider) web3.setProvider = function () { console.log('MetaMask - overrode web3.setProvider') } -console.log('MetaMask - injected modified web3') +console.log('MetaMask - injected web3') // export global web3, with usage-detection setupDappAutoReload(web3, inpageProvider.publicConfigStore) From 7268fcb694b1cf43e1983b2f693ed731d3c2ec9b Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Fri, 26 May 2017 00:50:59 -0700 Subject: [PATCH 331/372] Revert inpage-provider --- app/scripts/lib/inpage-provider.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/scripts/lib/inpage-provider.js b/app/scripts/lib/inpage-provider.js index 5206adc82..8b8623974 100644 --- a/app/scripts/lib/inpage-provider.js +++ b/app/scripts/lib/inpage-provider.js @@ -39,8 +39,6 @@ function MetamaskInpageProvider (connectionStream) { self.idMap = {} // handle sendAsync requests via asyncProvider self.sendAsync = function (payload, cb) { - console.trace('sending async ' + payload.method) - console.dir(payload) // rewrite request ids var request = eachJsonMessage(payload, (message) => { var newId = createRandomId() From f06ad954b900aa94a36fbb3e4765d0a9222e0920 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Fri, 26 May 2017 09:58:33 -0700 Subject: [PATCH 332/372] Move to eth-contract-metadata --- package.json | 2 +- ui/lib/icon-factory.js | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 9f47d76cb..9efba3866 100644 --- a/package.json +++ b/package.json @@ -62,11 +62,11 @@ "end-of-stream": "^1.1.0", "ensnare": "^1.0.0", "eth-bin-to-ops": "^1.0.1", + "eth-contract-metadata": "^1.0.0", "eth-hd-keyring": "^1.1.1", "eth-query": "^2.1.1", "eth-sig-util": "^1.1.1", "eth-simple-keyring": "^1.1.1", - "ethereum-contract-icons": "^1.0.0", "ethereumjs-tx": "^1.3.0", "ethereumjs-util": "ethereumjs/ethereumjs-util#ac5d0908536b447083ea422b435da27f26615de9", "ethereumjs-wallet": "^0.6.0", diff --git a/ui/lib/icon-factory.js b/ui/lib/icon-factory.js index 4aed9109b..c16507527 100644 --- a/ui/lib/icon-factory.js +++ b/ui/lib/icon-factory.js @@ -1,7 +1,7 @@ var iconFactory const isValidAddress = require('ethereumjs-util').isValidAddress const toChecksumAddress = require('ethereumjs-util').toChecksumAddress -const iconMap = require('ethereum-contract-icons') +const contractMap = require('eth-contract-metadata') module.exports = function (jazzicon) { if (!iconFactory) { @@ -43,11 +43,12 @@ IconFactory.prototype.generateNewIdenticon = function (address, diameter) { // util function iconExistsFor (address) { - return (address in iconMap) && isValidAddress(address) + return (address in contractMap) && isValidAddress(address) && ('logo' in contractMap[address]) } function imageElFor (address) { - const fileName = iconMap[address] + const contract = contractMap[address] + const fileName = contract.logo const path = `images/contract/${fileName}` const img = document.createElement('img') img.src = path From 1203bd15c2861332b62e4a39a3d961f61daed6dc Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Fri, 26 May 2017 10:25:00 -0700 Subject: [PATCH 333/372] Add names to contract map & conf view --- CHANGELOG.md | 1 + ui/lib/contract-namer.js | 11 ++++++++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ca9cc6f5a..e050a7509 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ - Now enforce 95% of block's gasLimit to protect users. - Removing provider-engine from the inpage provider. This fixes some error handling inconsistencies introduced in 3.7.0. - Some contracts will now display logos instead of jazzicons. +- Some contracts will now have names displayed in the confirmation view. ## 3.7.0 2017-5-23 diff --git a/ui/lib/contract-namer.js b/ui/lib/contract-namer.js index a94c62b62..0800ee7df 100644 --- a/ui/lib/contract-namer.js +++ b/ui/lib/contract-namer.js @@ -6,13 +6,18 @@ */ // Nickname keys must be stored in lower case. -const nicknames = {} +const contractMap = require('eth-contract-metadata') +const ethUtil = require('ethereumjs-util') module.exports = function (addr, identities = {}) { + const checksummed = ethUtil.toChecksumAddress(addr) + if (checksummed in contractMap && 'name' in contractMap[checksummed]) { + return contractMap[checksummed].name + } + const address = addr.toLowerCase() const ids = hashFromIdentities(identities) - - return addrFromHash(address, ids) || addrFromHash(address, nicknames) + return addrFromHash(address, ids) } function hashFromIdentities (identities) { From 5e6b23082147f530adbf52cacf61202d7edf1111 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Fri, 26 May 2017 10:54:16 -0700 Subject: [PATCH 334/372] Move off in operator --- ui/lib/contract-namer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/lib/contract-namer.js b/ui/lib/contract-namer.js index 0800ee7df..2400fa576 100644 --- a/ui/lib/contract-namer.js +++ b/ui/lib/contract-namer.js @@ -11,7 +11,7 @@ const ethUtil = require('ethereumjs-util') module.exports = function (addr, identities = {}) { const checksummed = ethUtil.toChecksumAddress(addr) - if (checksummed in contractMap && 'name' in contractMap[checksummed]) { + if (contractMap.checksummed && contractMap[checksummed].name) { return contractMap[checksummed].name } From fd42d7bfd5cede30f14e232b6d93ba4205dbcf1d Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Fri, 26 May 2017 11:05:51 -0700 Subject: [PATCH 335/372] Fix contract map reference --- ui/lib/contract-namer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/lib/contract-namer.js b/ui/lib/contract-namer.js index 2400fa576..86bba1edc 100644 --- a/ui/lib/contract-namer.js +++ b/ui/lib/contract-namer.js @@ -11,7 +11,7 @@ const ethUtil = require('ethereumjs-util') module.exports = function (addr, identities = {}) { const checksummed = ethUtil.toChecksumAddress(addr) - if (contractMap.checksummed && contractMap[checksummed].name) { + if (contractMap[checksummed] && contractMap[checksummed].name) { return contractMap[checksummed].name } From fb2565c9d1f6c3cc6601c0f317fe10651ad21097 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Fri, 26 May 2017 11:13:39 -0700 Subject: [PATCH 336/372] Remove comment --- ui/lib/contract-namer.js | 1 - 1 file changed, 1 deletion(-) diff --git a/ui/lib/contract-namer.js b/ui/lib/contract-namer.js index 86bba1edc..f05e770cc 100644 --- a/ui/lib/contract-namer.js +++ b/ui/lib/contract-namer.js @@ -5,7 +5,6 @@ * otherwise returns null. */ -// Nickname keys must be stored in lower case. const contractMap = require('eth-contract-metadata') const ethUtil = require('ethereumjs-util') From 9d2844c7128c79314529e163b473353d42200e9c Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Fri, 26 May 2017 11:16:58 -0700 Subject: [PATCH 337/372] remove more in operators --- ui/lib/icon-factory.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/lib/icon-factory.js b/ui/lib/icon-factory.js index c16507527..45be47b7a 100644 --- a/ui/lib/icon-factory.js +++ b/ui/lib/icon-factory.js @@ -43,7 +43,7 @@ IconFactory.prototype.generateNewIdenticon = function (address, diameter) { // util function iconExistsFor (address) { - return (address in contractMap) && isValidAddress(address) && ('logo' in contractMap[address]) + return (contractMap.address) && isValidAddress(address) && (contractMap[address].logo) } function imageElFor (address) { From 76a78fdb3b4d5341682a37ef523651ca163bfe15 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Wed, 31 May 2017 14:06:13 -0700 Subject: [PATCH 338/372] Version 3.7.2 --- CHANGELOG.md | 4 ++++ app/manifest.json | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e050a7509..ea7723ac2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,12 +2,16 @@ ## Current Master +## 3.7.2 2017-5-31 + - Now when switching networks sites that use web3 will reload - Now when switching networks the extension does not restart - Cleanup decimal bugs in our gas inputs. - Fix bug where submit button was enabled for invalid gas inputs. - Now enforce 95% of block's gasLimit to protect users. - Removing provider-engine from the inpage provider. This fixes some error handling inconsistencies introduced in 3.7.0. +- Added "inflight cache", which prevents identical requests from clogging up the network, dramatically improving ENS performance. +- Fixed bug where filter subscriptions would sometimes fail to unsubscribe. - Some contracts will now display logos instead of jazzicons. - Some contracts will now have names displayed in the confirmation view. diff --git a/app/manifest.json b/app/manifest.json index c3e2b7511..2b59002b0 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -1,7 +1,7 @@ { "name": "MetaMask", "short_name": "Metamask", - "version": "3.7.1", + "version": "3.7.2", "manifest_version": 2, "author": "https://metamask.io", "description": "Ethereum Browser Extension", From d59021f7548e5b6c4cedc39b753b41840340398a Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Thu, 1 Jun 2017 10:18:20 -0700 Subject: [PATCH 339/372] Version 3.7.3 --- CHANGELOG.md | 4 + app/manifest.json | 2 +- package-lock.json | 7348 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 7353 insertions(+), 1 deletion(-) create mode 100644 package-lock.json diff --git a/CHANGELOG.md b/CHANGELOG.md index ea7723ac2..b48889f5d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Current Master +## 3.7.3 2017-6-1 + +- Rebuilt to fix cache clearing bug. + ## 3.7.2 2017-5-31 - Now when switching networks sites that use web3 will reload diff --git a/app/manifest.json b/app/manifest.json index 2b59002b0..4dcd6df31 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -1,7 +1,7 @@ { "name": "MetaMask", "short_name": "Metamask", - "version": "3.7.2", + "version": "3.7.3", "manifest_version": 2, "author": "https://metamask.io", "description": "Ethereum Browser Extension", diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 000000000..cd2a6ee8c --- /dev/null +++ b/package-lock.json @@ -0,0 +1,7348 @@ +{ + "name": "metamask-crx", + "version": "0.0.0", + "lockfileVersion": 1, + "dependencies": { + "@gulp-sourcemaps/map-sources": { + "version": "https://registry.npmjs.org/@gulp-sourcemaps/map-sources/-/map-sources-1.0.0.tgz", + "integrity": "sha1-iQrnxdjId/bThIYCFazp1+yUW9o=", + "dev": true + }, + "abab": { + "version": "https://registry.npmjs.org/abab/-/abab-1.0.3.tgz", + "integrity": "sha1-uB3l9ydOxOdW15fNg08wNkJyTl0=", + "dev": true + }, + "abbrev": { + "version": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.0.tgz", + "integrity": "sha1-0FVMIlZjbi9W58LlrRg/hZQo2B8=", + "dev": true + }, + "abstract-leveldown": { + "version": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-2.4.1.tgz", + "integrity": "sha1-s7/tuITraToSd18MVenwpCDM7mQ=" + }, + "accepts": { + "version": "https://registry.npmjs.org/accepts/-/accepts-1.3.3.tgz", + "integrity": "sha1-w8p0NJOGSMPg2cHjKN1otiLChMo=" + }, + "acorn": { + "version": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz", + "integrity": "sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c=" + }, + "acorn-globals": { + "version": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-1.0.9.tgz", + "integrity": "sha1-VbtemGkVB7dFedBRNBMhfDgMVM8=", + "dev": true, + "dependencies": { + "acorn": { + "version": "https://registry.npmjs.org/acorn/-/acorn-2.7.0.tgz", + "integrity": "sha1-q259nYhqrKiwhbwzEreaGYQz8Oc=", + "dev": true + } + } + }, + "acorn-jsx": { + "version": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz", + "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=", + "dependencies": { + "acorn": { + "version": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", + "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=" + } + } + }, + "aes-js": { + "version": "https://registry.npmjs.org/aes-js/-/aes-js-0.2.4.tgz", + "integrity": "sha1-lLiBq3FyhtAV+iGeCPtmcJ3aWj0=" + }, + "after": { + "version": "https://registry.npmjs.org/after/-/after-0.8.1.tgz", + "integrity": "sha1-q11PuIP1loFtNRX495HAr0ht1ic=", + "dev": true + }, + "ajv": { + "version": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", + "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=" + }, + "ajv-keywords": { + "version": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-1.5.1.tgz", + "integrity": "sha1-MU3QpLM2j609/NxU7eYXG4htrzw=" + }, + "amdefine": { + "version": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", + "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=" + }, + "ansi-escapes": { + "version": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz", + "integrity": "sha1-06ioOzGapneTZisT52HHkRQiMG4=" + }, + "ansi-regex": { + "version": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, + "ansi-styles": { + "version": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" + }, + "ansicolors": { + "version": "https://registry.npmjs.org/ansicolors/-/ansicolors-0.3.2.tgz", + "integrity": "sha1-ZlWX3oap/+Oqm/vmyuXG6kJrSXk=", + "dev": true + }, + "any-promise": { + "version": "https://registry.npmjs.org/any-promise/-/any-promise-0.1.0.tgz", + "integrity": "sha1-gwtoCqflbzNFHUsEnzvYBESY7ic=" + }, + "anymatch": { + "version": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.0.tgz", + "integrity": "sha1-o+Uvo5FoyCX/V7AkgSbOWo/5VQc=", + "dev": true + }, + "aproba": { + "version": "https://registry.npmjs.org/aproba/-/aproba-1.1.1.tgz", + "integrity": "sha1-ldNgDwdxCqDpKYxyatXs8urLq6s=" + }, + "archy": { + "version": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", + "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", + "dev": true + }, + "are-we-there-yet": { + "version": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz", + "integrity": "sha1-u13KOCu5TwXhUZQ3PRb9O6HKEQ0=" + }, + "argparse": { + "version": "https://registry.npmjs.org/argparse/-/argparse-1.0.9.tgz", + "integrity": "sha1-c9g7wmP4bpf4zE9rrhsOkKfSLIY=" + }, + "argsparser": { + "version": "https://registry.npmjs.org/argsparser/-/argsparser-0.0.6.tgz", + "integrity": "sha1-/0XrW5LABCJc8UalHTOdzLJlvmM=", + "dev": true + }, + "arr-diff": { + "version": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", + "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", + "dev": true + }, + "arr-filter": { + "version": "https://registry.npmjs.org/arr-filter/-/arr-filter-1.1.2.tgz", + "integrity": "sha1-Q/3d0JHo7xGqTEXZzcGOLf8XEe4=", + "dev": true + }, + "arr-flatten": { + "version": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.0.3.tgz", + "integrity": "sha1-onTthawIhJtr14R8RYB0XcUa37E=", + "dev": true + }, + "arr-map": { + "version": "https://registry.npmjs.org/arr-map/-/arr-map-2.0.2.tgz", + "integrity": "sha1-Onc0X/wc814qkYJWAfnljy4kysQ=", + "dev": true + }, + "array-differ": { + "version": "https://registry.npmjs.org/array-differ/-/array-differ-1.0.0.tgz", + "integrity": "sha1-7/UuN1gknTO+QCuLuOVkuytdQDE=" + }, + "array-each": { + "version": "https://registry.npmjs.org/array-each/-/array-each-1.0.1.tgz", + "integrity": "sha1-p5SvDAWrF1KEbudTofIRoFugxE8=", + "dev": true + }, + "array-equal": { + "version": "https://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz", + "integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=", + "dev": true + }, + "array-filter": { + "version": "https://registry.npmjs.org/array-filter/-/array-filter-0.0.1.tgz", + "integrity": "sha1-fajPLiZijtcygDWB/SH2fKzS7uw=", + "dev": true + }, + "array-flatten": { + "version": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" + }, + "array-initial": { + "version": "https://registry.npmjs.org/array-initial/-/array-initial-1.0.0.tgz", + "integrity": "sha1-CbE8WNVqBQNC53erb/zllbEI2tk=", + "dev": true, + "dependencies": { + "array-slice": { + "version": "https://registry.npmjs.org/array-slice/-/array-slice-0.2.3.tgz", + "integrity": "sha1-3Tz7gO15c6dRF82sabC5nshhhvU=", + "dev": true + }, + "is-number": { + "version": "https://registry.npmjs.org/is-number/-/is-number-0.1.1.tgz", + "integrity": "sha1-aaevEWlj1HIG7JvZtIoUIW8eOAY=", + "dev": true + } + } + }, + "array-last": { + "version": "https://registry.npmjs.org/array-last/-/array-last-1.1.1.tgz", + "integrity": "sha1-9GWPmI2SEya1itARPPdtM3x7IKo=", + "dev": true, + "dependencies": { + "is-number": { + "version": "https://registry.npmjs.org/is-number/-/is-number-0.1.1.tgz", + "integrity": "sha1-aaevEWlj1HIG7JvZtIoUIW8eOAY=", + "dev": true + } + } + }, + "array-map": { + "version": "https://registry.npmjs.org/array-map/-/array-map-0.0.0.tgz", + "integrity": "sha1-iKK6tz0c97zVwbEYoAP2b2ZfpmI=", + "dev": true + }, + "array-reduce": { + "version": "https://registry.npmjs.org/array-reduce/-/array-reduce-0.0.0.tgz", + "integrity": "sha1-FziZ0//Rx9k4PkR5Ul2+J4yrXys=", + "dev": true + }, + "array-slice": { + "version": "https://registry.npmjs.org/array-slice/-/array-slice-1.0.0.tgz", + "integrity": "sha1-5zA08A3MH0CHYAj9IP6ud71LfC8=", + "dev": true + }, + "array-union": { + "version": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=" + }, + "array-uniq": { + "version": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=" + }, + "array-unique": { + "version": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", + "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", + "dev": true + }, + "arraybuffer.slice": { + "version": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.6.tgz", + "integrity": "sha1-8zshWfBTKj8xB6JywMz70a0peco=", + "dev": true + }, + "arrify": { + "version": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=" + }, + "asap": { + "version": "https://registry.npmjs.org/asap/-/asap-2.0.5.tgz", + "integrity": "sha1-UidltQw1EEkOUtfc/ghe+bqWlY8=" + }, + "asn1": { + "version": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", + "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=" + }, + "asn1.js": { + "version": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.9.1.tgz", + "integrity": "sha1-SLokC0WpKA6UdImQull9IWYX/UA=", + "dev": true + }, + "assert": { + "version": "https://registry.npmjs.org/assert/-/assert-1.4.1.tgz", + "integrity": "sha1-mZEtWRg2tab1s0XA8H7vwI/GXZE=", + "dev": true + }, + "assert-plus": { + "version": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz", + "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ=" + }, + "assertion-error": { + "version": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.0.2.tgz", + "integrity": "sha1-E8pRXYYgbaC6xm6DTdOX2HWBCUw=", + "dev": true + }, + "astw": { + "version": "https://registry.npmjs.org/astw/-/astw-2.2.0.tgz", + "integrity": "sha1-e9QXhNMkk5h66yOba04cV6hzuRc=", + "dev": true + }, + "async": { + "version": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=" + }, + "async-done": { + "version": "https://registry.npmjs.org/async-done/-/async-done-1.2.2.tgz", + "integrity": "sha1-ukKA2lWhbhX0u4vzqESpGHh0DjE=", + "dev": true + }, + "async-each": { + "version": "https://registry.npmjs.org/async-each/-/async-each-1.0.1.tgz", + "integrity": "sha1-GdOGodntxufByF04iu28xW0zYC0=", + "dev": true + }, + "async-eventemitter": { + "version": "https://registry.npmjs.org/async-eventemitter/-/async-eventemitter-0.2.3.tgz", + "integrity": "sha1-959IDf2mZFqXvWFCwBcVDWO05w4=", + "dependencies": { + "async": { + "version": "https://registry.npmjs.org/async/-/async-2.4.1.tgz", + "integrity": "sha1-YqVrJ5yYoR0JhwlqAcw+6463u9c=" + } + } + }, + "async-reduce": { + "version": "https://registry.npmjs.org/async-reduce/-/async-reduce-0.0.1.tgz", + "integrity": "sha1-sja183bW+uOBze2QBqp/LHOxfzE=" + }, + "async-settle": { + "version": "https://registry.npmjs.org/async-settle/-/async-settle-1.0.0.tgz", + "integrity": "sha1-HQqRS7Aldb7IqPOnTlCA9yssDGs=", + "dev": true + }, + "asynckit": { + "version": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "atob": { + "version": "https://registry.npmjs.org/atob/-/atob-1.1.3.tgz", + "integrity": "sha1-lfE2KbEsOlGl0hWr3OKqnzL4B3M=", + "dev": true + }, + "aws-sign2": { + "version": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz", + "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8=" + }, + "aws4": { + "version": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz", + "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=" + }, + "babel-code-frame": { + "version": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.22.0.tgz", + "integrity": "sha1-AnYgvuVnqIwyVhV05/0IAdMxGOQ=" + }, + "babel-core": { + "version": "https://registry.npmjs.org/babel-core/-/babel-core-6.24.1.tgz", + "integrity": "sha1-jEKFZNzh4fQfszfsNPTDsCK1rYM=" + }, + "babel-eslint": { + "version": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-6.1.2.tgz", + "integrity": "sha1-UpNBn+NnLWZZjTJ9qWlFZ7pqXy8=", + "dev": true + }, + "babel-generator": { + "version": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.24.1.tgz", + "integrity": "sha1-5xX0hsWN7SVknYiJRNUqoHxdlJc=", + "dependencies": { + "jsesc": { + "version": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz", + "integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s=" + } + } + }, + "babel-helper-bindify-decorators": { + "version": "https://registry.npmjs.org/babel-helper-bindify-decorators/-/babel-helper-bindify-decorators-6.24.1.tgz", + "integrity": "sha1-FMGeXxQte0fxmlJDHlKxzLxAozA=", + "dev": true + }, + "babel-helper-builder-binary-assignment-operator-visitor": { + "version": "https://registry.npmjs.org/babel-helper-builder-binary-assignment-operator-visitor/-/babel-helper-builder-binary-assignment-operator-visitor-6.24.1.tgz", + "integrity": "sha1-zORReto1b0IgvK6KAsKzRvmlZmQ=", + "dev": true + }, + "babel-helper-call-delegate": { + "version": "https://registry.npmjs.org/babel-helper-call-delegate/-/babel-helper-call-delegate-6.24.1.tgz", + "integrity": "sha1-7Oaqzdx25Bw0YfiL/Fdb0Nqi340=" + }, + "babel-helper-define-map": { + "version": "https://registry.npmjs.org/babel-helper-define-map/-/babel-helper-define-map-6.24.1.tgz", + "integrity": "sha1-epdH8ljYlH0y1RX2qhx70CIEoIA=" + }, + "babel-helper-explode-assignable-expression": { + "version": "https://registry.npmjs.org/babel-helper-explode-assignable-expression/-/babel-helper-explode-assignable-expression-6.24.1.tgz", + "integrity": "sha1-8luCz33BBDPFX3BZLVdGQArCLKo=", + "dev": true + }, + "babel-helper-explode-class": { + "version": "https://registry.npmjs.org/babel-helper-explode-class/-/babel-helper-explode-class-6.24.1.tgz", + "integrity": "sha1-fcKjkQ3uAHBW4eMdZAztPVTqqes=", + "dev": true + }, + "babel-helper-function-name": { + "version": "https://registry.npmjs.org/babel-helper-function-name/-/babel-helper-function-name-6.24.1.tgz", + "integrity": "sha1-00dbjAPtmCQqJbSDUasYOZ01gKk=" + }, + "babel-helper-get-function-arity": { + "version": "https://registry.npmjs.org/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.24.1.tgz", + "integrity": "sha1-j3eCqpNAfEHTqlCQj4mwMbG2hT0=" + }, + "babel-helper-hoist-variables": { + "version": "https://registry.npmjs.org/babel-helper-hoist-variables/-/babel-helper-hoist-variables-6.24.1.tgz", + "integrity": "sha1-HssnaJydJVE+rbyZFKc/VAi+enY=" + }, + "babel-helper-optimise-call-expression": { + "version": "https://registry.npmjs.org/babel-helper-optimise-call-expression/-/babel-helper-optimise-call-expression-6.24.1.tgz", + "integrity": "sha1-96E0J7qfc/j0+pk8VKl4gtEkQlc=" + }, + "babel-helper-regex": { + "version": "https://registry.npmjs.org/babel-helper-regex/-/babel-helper-regex-6.24.1.tgz", + "integrity": "sha1-024i+rEAjXnYhkjjIRaGgShFbOg=" + }, + "babel-helper-remap-async-to-generator": { + "version": "https://registry.npmjs.org/babel-helper-remap-async-to-generator/-/babel-helper-remap-async-to-generator-6.24.1.tgz", + "integrity": "sha1-XsWBgnrXI/7N04HxySg5BnbkVRs=", + "dev": true + }, + "babel-helper-replace-supers": { + "version": "https://registry.npmjs.org/babel-helper-replace-supers/-/babel-helper-replace-supers-6.24.1.tgz", + "integrity": "sha1-v22/5Dk40XNpohPKiov3S2qQqxo=" + }, + "babel-helpers": { + "version": "https://registry.npmjs.org/babel-helpers/-/babel-helpers-6.24.1.tgz", + "integrity": "sha1-NHHenK7DiOXIUOWX5Yom3fN2ArI=" + }, + "babel-messages": { + "version": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz", + "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=" + }, + "babel-plugin-check-es2015-constants": { + "version": "https://registry.npmjs.org/babel-plugin-check-es2015-constants/-/babel-plugin-check-es2015-constants-6.22.0.tgz", + "integrity": "sha1-NRV7EBQm/S/9PaP3XH0ekYNbv4o=" + }, + "babel-plugin-syntax-async-functions": { + "version": "https://registry.npmjs.org/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz", + "integrity": "sha1-ytnK0RkbWtY0vzCuCHI5HgZHvpU=", + "dev": true + }, + "babel-plugin-syntax-async-generators": { + "version": "https://registry.npmjs.org/babel-plugin-syntax-async-generators/-/babel-plugin-syntax-async-generators-6.13.0.tgz", + "integrity": "sha1-a8lj67FuzLrmuStZbrfzXDQqi5o=", + "dev": true + }, + "babel-plugin-syntax-class-constructor-call": { + "version": "https://registry.npmjs.org/babel-plugin-syntax-class-constructor-call/-/babel-plugin-syntax-class-constructor-call-6.18.0.tgz", + "integrity": "sha1-nLnTn+Q8hgC+yBRkVt3L1OGnZBY=", + "dev": true + }, + "babel-plugin-syntax-class-properties": { + "version": "https://registry.npmjs.org/babel-plugin-syntax-class-properties/-/babel-plugin-syntax-class-properties-6.13.0.tgz", + "integrity": "sha1-1+sjt5oxf4VDlixQW4J8fWysJ94=", + "dev": true + }, + "babel-plugin-syntax-decorators": { + "version": "https://registry.npmjs.org/babel-plugin-syntax-decorators/-/babel-plugin-syntax-decorators-6.13.0.tgz", + "integrity": "sha1-MSVjtNvePMgGzuPkFszurd0RrAs=", + "dev": true + }, + "babel-plugin-syntax-do-expressions": { + "version": "https://registry.npmjs.org/babel-plugin-syntax-do-expressions/-/babel-plugin-syntax-do-expressions-6.13.0.tgz", + "integrity": "sha1-V0d1YTmqJtOQ0JQQsDdEugfkeW0=", + "dev": true + }, + "babel-plugin-syntax-dynamic-import": { + "version": "https://registry.npmjs.org/babel-plugin-syntax-dynamic-import/-/babel-plugin-syntax-dynamic-import-6.18.0.tgz", + "integrity": "sha1-jWomIpyDdFqZgqRBBRVyyqF5sdo=", + "dev": true + }, + "babel-plugin-syntax-exponentiation-operator": { + "version": "https://registry.npmjs.org/babel-plugin-syntax-exponentiation-operator/-/babel-plugin-syntax-exponentiation-operator-6.13.0.tgz", + "integrity": "sha1-nufoM3KQ2pUoggGmpX9BcDF4MN4=", + "dev": true + }, + "babel-plugin-syntax-export-extensions": { + "version": "https://registry.npmjs.org/babel-plugin-syntax-export-extensions/-/babel-plugin-syntax-export-extensions-6.13.0.tgz", + "integrity": "sha1-cKFITw+QiaToStRLrDU8lbmxJyE=", + "dev": true + }, + "babel-plugin-syntax-function-bind": { + "version": "https://registry.npmjs.org/babel-plugin-syntax-function-bind/-/babel-plugin-syntax-function-bind-6.13.0.tgz", + "integrity": "sha1-SMSV8Xe98xqYHnMvVa3AvdJgH0Y=", + "dev": true + }, + "babel-plugin-syntax-object-rest-spread": { + "version": "https://registry.npmjs.org/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz", + "integrity": "sha1-/WU28rzhODb/o6VFjEkDpZe7O/U=", + "dev": true + }, + "babel-plugin-syntax-trailing-function-commas": { + "version": "https://registry.npmjs.org/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-6.22.0.tgz", + "integrity": "sha1-ugNgk3+NBuQBgKQ/4NVhb/9TLPM=", + "dev": true + }, + "babel-plugin-transform-async-generator-functions": { + "version": "https://registry.npmjs.org/babel-plugin-transform-async-generator-functions/-/babel-plugin-transform-async-generator-functions-6.24.1.tgz", + "integrity": "sha1-8FiQAUX9PpkHpt3yjaWfIVJYpds=", + "dev": true + }, + "babel-plugin-transform-async-to-generator": { + "version": "https://registry.npmjs.org/babel-plugin-transform-async-to-generator/-/babel-plugin-transform-async-to-generator-6.24.1.tgz", + "integrity": "sha1-ZTbjeK/2yx1VF6wOQOs+n8jQh2E=", + "dev": true + }, + "babel-plugin-transform-class-constructor-call": { + "version": "https://registry.npmjs.org/babel-plugin-transform-class-constructor-call/-/babel-plugin-transform-class-constructor-call-6.24.1.tgz", + "integrity": "sha1-gNwoVQWsBn3LjWxl4vbxGrd2Xvk=", + "dev": true + }, + "babel-plugin-transform-class-properties": { + "version": "https://registry.npmjs.org/babel-plugin-transform-class-properties/-/babel-plugin-transform-class-properties-6.24.1.tgz", + "integrity": "sha1-anl2PqYdM9NvN7YRqp3vgagbRqw=", + "dev": true + }, + "babel-plugin-transform-decorators": { + "version": "https://registry.npmjs.org/babel-plugin-transform-decorators/-/babel-plugin-transform-decorators-6.24.1.tgz", + "integrity": "sha1-eIAT2PjGtSIr33s0Q5Df13Vp4k0=", + "dev": true + }, + "babel-plugin-transform-do-expressions": { + "version": "https://registry.npmjs.org/babel-plugin-transform-do-expressions/-/babel-plugin-transform-do-expressions-6.22.0.tgz", + "integrity": "sha1-KMyvkoEtlJws0SgfaQyP3EaK6bs=", + "dev": true + }, + "babel-plugin-transform-es2015-arrow-functions": { + "version": "https://registry.npmjs.org/babel-plugin-transform-es2015-arrow-functions/-/babel-plugin-transform-es2015-arrow-functions-6.22.0.tgz", + "integrity": "sha1-RSaSy3EdX3ncf4XkQM5BufJE0iE=" + }, + "babel-plugin-transform-es2015-block-scoped-functions": { + "version": "https://registry.npmjs.org/babel-plugin-transform-es2015-block-scoped-functions/-/babel-plugin-transform-es2015-block-scoped-functions-6.22.0.tgz", + "integrity": "sha1-u8UbSflk1wy42OC5ToICRs46YUE=" + }, + "babel-plugin-transform-es2015-block-scoping": { + "version": "https://registry.npmjs.org/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.24.1.tgz", + "integrity": "sha1-dsKV3DpHQbFmWt/TFnIV3P8ypXY=" + }, + "babel-plugin-transform-es2015-classes": { + "version": "https://registry.npmjs.org/babel-plugin-transform-es2015-classes/-/babel-plugin-transform-es2015-classes-6.24.1.tgz", + "integrity": "sha1-WkxYpQyclGHlZLSyo7+ryXolhNs=" + }, + "babel-plugin-transform-es2015-computed-properties": { + "version": "https://registry.npmjs.org/babel-plugin-transform-es2015-computed-properties/-/babel-plugin-transform-es2015-computed-properties-6.24.1.tgz", + "integrity": "sha1-b+Ko0WiV1WNPTNmZttNICjCBWbM=" + }, + "babel-plugin-transform-es2015-destructuring": { + "version": "https://registry.npmjs.org/babel-plugin-transform-es2015-destructuring/-/babel-plugin-transform-es2015-destructuring-6.23.0.tgz", + "integrity": "sha1-mXux8auWf2gtKwh2/jWNYOdlxW0=" + }, + "babel-plugin-transform-es2015-duplicate-keys": { + "version": "https://registry.npmjs.org/babel-plugin-transform-es2015-duplicate-keys/-/babel-plugin-transform-es2015-duplicate-keys-6.24.1.tgz", + "integrity": "sha1-c+s9MQypaePvnskcU3QabxV2Qj4=" + }, + "babel-plugin-transform-es2015-for-of": { + "version": "https://registry.npmjs.org/babel-plugin-transform-es2015-for-of/-/babel-plugin-transform-es2015-for-of-6.23.0.tgz", + "integrity": "sha1-9HyVsrYT3x0+zC/bdXNiPHUkhpE=" + }, + "babel-plugin-transform-es2015-function-name": { + "version": "https://registry.npmjs.org/babel-plugin-transform-es2015-function-name/-/babel-plugin-transform-es2015-function-name-6.24.1.tgz", + "integrity": "sha1-g0yJhTvDaxrw86TF26qU/Y6sqos=" + }, + "babel-plugin-transform-es2015-literals": { + "version": "https://registry.npmjs.org/babel-plugin-transform-es2015-literals/-/babel-plugin-transform-es2015-literals-6.22.0.tgz", + "integrity": "sha1-T1SgLWzWbPkVKAAZox0xklN3yi4=" + }, + "babel-plugin-transform-es2015-modules-amd": { + "version": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-amd/-/babel-plugin-transform-es2015-modules-amd-6.24.1.tgz", + "integrity": "sha1-Oz5UAXI5hC1tGcMBHEvS8AoA0VQ=" + }, + "babel-plugin-transform-es2015-modules-commonjs": { + "version": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.24.1.tgz", + "integrity": "sha1-0+MQtA72ZKNmIiAAl8bUQCmPK/4=" + }, + "babel-plugin-transform-es2015-modules-systemjs": { + "version": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-systemjs/-/babel-plugin-transform-es2015-modules-systemjs-6.24.1.tgz", + "integrity": "sha1-/4mhQrkRmpBhlfXxBuzzBdlAfSM=" + }, + "babel-plugin-transform-es2015-modules-umd": { + "version": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-umd/-/babel-plugin-transform-es2015-modules-umd-6.24.1.tgz", + "integrity": "sha1-rJl+YoXNGO1hdq22B9YCNErThGg=" + }, + "babel-plugin-transform-es2015-object-super": { + "version": "https://registry.npmjs.org/babel-plugin-transform-es2015-object-super/-/babel-plugin-transform-es2015-object-super-6.24.1.tgz", + "integrity": "sha1-JM72muIcuDp/hgPa0CH1cusnj40=" + }, + "babel-plugin-transform-es2015-parameters": { + "version": "https://registry.npmjs.org/babel-plugin-transform-es2015-parameters/-/babel-plugin-transform-es2015-parameters-6.24.1.tgz", + "integrity": "sha1-V6w1GrScrxSpfNE7CfZv3wpiXys=" + }, + "babel-plugin-transform-es2015-shorthand-properties": { + "version": "https://registry.npmjs.org/babel-plugin-transform-es2015-shorthand-properties/-/babel-plugin-transform-es2015-shorthand-properties-6.24.1.tgz", + "integrity": "sha1-JPh11nIch2YbvZmkYi5R8U3jiqA=" + }, + "babel-plugin-transform-es2015-spread": { + "version": "https://registry.npmjs.org/babel-plugin-transform-es2015-spread/-/babel-plugin-transform-es2015-spread-6.22.0.tgz", + "integrity": "sha1-1taKmfia7cRTbIGlQujdnxdG+NE=" + }, + "babel-plugin-transform-es2015-sticky-regex": { + "version": "https://registry.npmjs.org/babel-plugin-transform-es2015-sticky-regex/-/babel-plugin-transform-es2015-sticky-regex-6.24.1.tgz", + "integrity": "sha1-AMHNsaynERLN8M9hJsLta0V8zbw=" + }, + "babel-plugin-transform-es2015-template-literals": { + "version": "https://registry.npmjs.org/babel-plugin-transform-es2015-template-literals/-/babel-plugin-transform-es2015-template-literals-6.22.0.tgz", + "integrity": "sha1-qEs0UPfp+PH2g51taH2oS7EjbY0=" + }, + "babel-plugin-transform-es2015-typeof-symbol": { + "version": "https://registry.npmjs.org/babel-plugin-transform-es2015-typeof-symbol/-/babel-plugin-transform-es2015-typeof-symbol-6.23.0.tgz", + "integrity": "sha1-3sCfHN3/lLUqxz1QXITfWdzOs3I=" + }, + "babel-plugin-transform-es2015-unicode-regex": { + "version": "https://registry.npmjs.org/babel-plugin-transform-es2015-unicode-regex/-/babel-plugin-transform-es2015-unicode-regex-6.24.1.tgz", + "integrity": "sha1-04sS9C6nMj9yk4fxinxa4frrNek=" + }, + "babel-plugin-transform-exponentiation-operator": { + "version": "https://registry.npmjs.org/babel-plugin-transform-exponentiation-operator/-/babel-plugin-transform-exponentiation-operator-6.24.1.tgz", + "integrity": "sha1-KrDJx/MJj6SJB3cruBP+QejeOg4=", + "dev": true + }, + "babel-plugin-transform-export-extensions": { + "version": "https://registry.npmjs.org/babel-plugin-transform-export-extensions/-/babel-plugin-transform-export-extensions-6.22.0.tgz", + "integrity": "sha1-U3OLR+deghhYnuqUbLvTkQm75lM=", + "dev": true + }, + "babel-plugin-transform-function-bind": { + "version": "https://registry.npmjs.org/babel-plugin-transform-function-bind/-/babel-plugin-transform-function-bind-6.22.0.tgz", + "integrity": "sha1-xvuOlqwpajELjPjqQBRiQH3fapc=", + "dev": true + }, + "babel-plugin-transform-object-rest-spread": { + "version": "https://registry.npmjs.org/babel-plugin-transform-object-rest-spread/-/babel-plugin-transform-object-rest-spread-6.23.0.tgz", + "integrity": "sha1-h11ryb52HFiirj/u5dxIldjH+SE=", + "dev": true + }, + "babel-plugin-transform-regenerator": { + "version": "https://registry.npmjs.org/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.24.1.tgz", + "integrity": "sha1-uNowWtQ8PJm0hI5P5AN7dw0jxBg=" + }, + "babel-plugin-transform-runtime": { + "version": "https://registry.npmjs.org/babel-plugin-transform-runtime/-/babel-plugin-transform-runtime-6.23.0.tgz", + "integrity": "sha1-iEkNRGUC6puOfvsP4J7E2ZR5se4=", + "dev": true + }, + "babel-plugin-transform-strict-mode": { + "version": "https://registry.npmjs.org/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.24.1.tgz", + "integrity": "sha1-1fr3qleKZbvlkc9e2uBKDGcCB1g=" + }, + "babel-preset-es2015": { + "version": "https://registry.npmjs.org/babel-preset-es2015/-/babel-preset-es2015-6.24.1.tgz", + "integrity": "sha1-1EBQ1rwsn+6nAqrzjXJ6AhBTiTk=" + }, + "babel-preset-stage-0": { + "version": "https://registry.npmjs.org/babel-preset-stage-0/-/babel-preset-stage-0-6.24.1.tgz", + "integrity": "sha1-VkLRUEL5E4TX5a+LyIsduVsDnmo=", + "dev": true + }, + "babel-preset-stage-1": { + "version": "https://registry.npmjs.org/babel-preset-stage-1/-/babel-preset-stage-1-6.24.1.tgz", + "integrity": "sha1-dpLNfc1oSZB+auSgqFWJz7niv7A=", + "dev": true + }, + "babel-preset-stage-2": { + "version": "https://registry.npmjs.org/babel-preset-stage-2/-/babel-preset-stage-2-6.24.1.tgz", + "integrity": "sha1-2eKWD7PXEYfw5k7sYrwHdnIZvcE=", + "dev": true + }, + "babel-preset-stage-3": { + "version": "https://registry.npmjs.org/babel-preset-stage-3/-/babel-preset-stage-3-6.24.1.tgz", + "integrity": "sha1-g2raCp56f6N8sTj7kyb4eTSkg5U=", + "dev": true + }, + "babel-register": { + "version": "https://registry.npmjs.org/babel-register/-/babel-register-6.24.1.tgz", + "integrity": "sha1-fhDhOi9xBlvfrVoXh7pFvKbe118=" + }, + "babel-runtime": { + "version": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.23.0.tgz", + "integrity": "sha1-CpSJ8UTecO+zzkMArM2zKeL8VDs=" + }, + "babel-template": { + "version": "https://registry.npmjs.org/babel-template/-/babel-template-6.24.1.tgz", + "integrity": "sha1-BK5RTx+Ts6JTfyoPYKWkX7gwgzM=" + }, + "babel-traverse": { + "version": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.24.1.tgz", + "integrity": "sha1-qzZnP9NW+aCUhlnnszjV/q2zFpU=" + }, + "babel-types": { + "version": "https://registry.npmjs.org/babel-types/-/babel-types-6.24.1.tgz", + "integrity": "sha1-oTaHncFbNga9oNkMH8dDBML/CXU=" + }, + "babelify": { + "version": "https://registry.npmjs.org/babelify/-/babelify-7.3.0.tgz", + "integrity": "sha1-qlau3nBn/XvVSWZu4W3ChQh+iOU=" + }, + "babylon": { + "version": "https://registry.npmjs.org/babylon/-/babylon-6.17.1.tgz", + "integrity": "sha1-F/FP3fNhtpWYH+Z5OF5PHAHr2G8=" + }, + "bach": { + "version": "https://registry.npmjs.org/bach/-/bach-1.1.0.tgz", + "integrity": "sha1-z+VC25Jcs3BR/EkK0QLHO8slioQ=", + "dev": true + }, + "backbone": { + "version": "https://registry.npmjs.org/backbone/-/backbone-1.3.3.tgz", + "integrity": "sha1-TMgOp8sWMaxHSInOQPL4vGg7KZk=", + "dev": true + }, + "backo2": { + "version": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", + "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=", + "dev": true + }, + "balanced-match": { + "version": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz", + "integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg=" + }, + "base-x": { + "version": "https://registry.npmjs.org/base-x/-/base-x-1.1.0.tgz", + "integrity": "sha1-QtPXF0dPnqAiB/bRqh9CaRPut6w=" + }, + "base64-arraybuffer": { + "version": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz", + "integrity": "sha1-c5JncZI7Whl0etZmqlzUv5xunOg=", + "dev": true + }, + "base64-js": { + "version": "https://registry.npmjs.org/base64-js/-/base64-js-0.0.2.tgz", + "integrity": "sha1-Ak8Pcq+iW3X5wO5zzU9V7Bvtl4Q=" + }, + "base64id": { + "version": "https://registry.npmjs.org/base64id/-/base64id-0.1.0.tgz", + "integrity": "sha1-As4P3u4M709ACA4ec+g08LG/zj8=", + "dev": true + }, + "bcrypt-pbkdf": { + "version": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", + "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", + "optional": true + }, + "beefy": { + "version": "https://registry.npmjs.org/beefy/-/beefy-2.1.8.tgz", + "integrity": "sha1-e8Ebmkh6mjRnnYXinTtS83T9ACk=", + "dev": true, + "dependencies": { + "isarray": { + "version": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "mime": { + "version": "https://registry.npmjs.org/mime/-/mime-1.2.11.tgz", + "integrity": "sha1-WCA+7Ybjpe8XrtK32evUfwpg3RA=", + "dev": true + }, + "minimist": { + "version": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + }, + "object-keys": { + "version": "https://registry.npmjs.org/object-keys/-/object-keys-0.4.0.tgz", + "integrity": "sha1-KKaq50KN0sOpLz2V8hM13SBOAzY=", + "dev": true + }, + "open": { + "version": "https://registry.npmjs.org/open/-/open-0.0.3.tgz", + "integrity": "sha1-+jd/T/MIIS2SqbjmOVJAhUZGpxM=", + "dev": true + }, + "readable-stream": { + "version": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "dev": true + }, + "resolve": { + "version": "https://registry.npmjs.org/resolve/-/resolve-0.6.3.tgz", + "integrity": "sha1-3ZV5gufnNt699TtYpN2RdUV13UY=", + "dev": true + }, + "string_decoder": { + "version": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + }, + "through": { + "version": "https://registry.npmjs.org/through/-/through-2.2.7.tgz", + "integrity": "sha1-bo4hIAGR1OtqmfbwEN9Gqhxusr0=", + "dev": true + }, + "xtend": { + "version": "https://registry.npmjs.org/xtend/-/xtend-2.1.2.tgz", + "integrity": "sha1-bv7MKk2tjmlixJAbM3znuoe10os=", + "dev": true + } + } + }, + "beeper": { + "version": "https://registry.npmjs.org/beeper/-/beeper-1.1.1.tgz", + "integrity": "sha1-5tXqjF2tABMEpwsiY4RH9pyy+Ak=" + }, + "better-assert": { + "version": "https://registry.npmjs.org/better-assert/-/better-assert-1.0.2.tgz", + "integrity": "sha1-QIZrnhueC1W0gYlDEeaPr/rrxSI=", + "dev": true + }, + "bignumber.js": { + "version": "git+https://github.com/debris/bignumber.js.git#94d7146671b9719e00a09c29b01a691bc85048c2" + }, + "binary-extensions": { + "version": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.8.0.tgz", + "integrity": "sha1-SOyNFt9Dd+rl+liEaCSAr02Vx3Q=", + "dev": true + }, + "binaryextensions": { + "version": "https://registry.npmjs.org/binaryextensions/-/binaryextensions-1.0.1.tgz", + "integrity": "sha1-HmN0iLNbWL2l9HdL+WpSEqjJB1U=", + "dev": true + }, + "bindings": { + "version": "https://registry.npmjs.org/bindings/-/bindings-1.2.1.tgz", + "integrity": "sha1-FK1hE4EtLTfXLme0ystLtyZQXxE=" + }, + "bip39": { + "version": "https://registry.npmjs.org/bip39/-/bip39-2.3.1.tgz", + "integrity": "sha1-yCOKvAnXGcbwETbvBC2szF3DWBs=" + }, + "bip66": { + "version": "https://registry.npmjs.org/bip66/-/bip66-1.1.5.tgz", + "integrity": "sha1-AfqHSHhcpwlV1QESF9GzE5lpyiI=" + }, + "bl": { + "version": "https://registry.npmjs.org/bl/-/bl-0.7.0.tgz", + "integrity": "sha1-P7BnBgKsKHjrdw3CA58YNr5irls=", + "dependencies": { + "isarray": { + "version": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, + "readable-stream": { + "version": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=" + }, + "string_decoder": { + "version": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + } + } + }, + "blob": { + "version": "https://registry.npmjs.org/blob/-/blob-0.0.4.tgz", + "integrity": "sha1-vPEwUspURj8w+fx+lbmkdjCpSSE=", + "dev": true + }, + "bluebird": { + "version": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.0.tgz", + "integrity": "sha1-eRQg1/VR7qKJdFOop3ZT+WYG1nw=" + }, + "bn.js": { + "version": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.6.tgz", + "integrity": "sha1-UzRK2xRhehP26N0s4okF0cC6MhU=" + }, + "body-parser": { + "version": "https://registry.npmjs.org/body-parser/-/body-parser-1.14.2.tgz", + "integrity": "sha1-EBXLH+LEQ4WCWVgdtTMy+NDPUPk=", + "dev": true, + "dependencies": { + "debug": { + "version": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", + "dev": true + }, + "http-errors": { + "version": "https://registry.npmjs.org/http-errors/-/http-errors-1.3.1.tgz", + "integrity": "sha1-GX4izevUGYWF6GlO9nhhl7ke2UI=", + "dev": true + }, + "iconv-lite": { + "version": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.13.tgz", + "integrity": "sha1-H4irpKsLFQjoMSrMOTRfNumS4vI=", + "dev": true + }, + "ms": { + "version": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", + "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=", + "dev": true + }, + "qs": { + "version": "https://registry.npmjs.org/qs/-/qs-5.2.0.tgz", + "integrity": "sha1-qfMRQq9GjLcrJbMBNrokVoNJFr4=", + "dev": true + } + } + }, + "boolbase": { + "version": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=", + "dev": true + }, + "boom": { + "version": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz", + "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=" + }, + "bops": { + "version": "https://registry.npmjs.org/bops/-/bops-0.0.6.tgz", + "integrity": "sha1-CC0dVfoB5g29wuvC26N/ZZVUzzo=" + }, + "brace-expansion": { + "version": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.7.tgz", + "integrity": "sha1-Pv/DxQ4ABTH7cg6v+A8K6O8jz1k=" + }, + "braces": { + "version": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", + "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", + "dev": true + }, + "brfs": { + "version": "https://registry.npmjs.org/brfs/-/brfs-1.4.3.tgz", + "integrity": "sha1-22ddb16SPm3wh/ylhZyQkKrtMhY=" + }, + "brorand": { + "version": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=" + }, + "browser-pack": { + "version": "https://registry.npmjs.org/browser-pack/-/browser-pack-6.0.2.tgz", + "integrity": "sha1-+GzWzvT1MAyOY+B6TVEvZfv/RTE=", + "dev": true + }, + "browser-passworder": { + "version": "https://registry.npmjs.org/browser-passworder/-/browser-passworder-2.0.3.tgz", + "integrity": "sha1-b90gguUWoXbtvLPc7gt/n85PeRc=" + }, + "browser-resolve": { + "version": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-1.11.2.tgz", + "integrity": "sha1-j/CbCixCFxihBRwmCzLkj0QpOM4=", + "dev": true + }, + "browser-unpack": { + "version": "https://registry.npmjs.org/browser-unpack/-/browser-unpack-0.2.3.tgz", + "integrity": "sha1-iP4EzCZiV+UmUAlc2OBYXce24vE=", + "dependencies": { + "concat-stream": { + "version": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.2.1.tgz", + "integrity": "sha1-81EAtsRjeL+6i2uA+fDQzN8T3GA=" + } + } + }, + "browserify": { + "version": "https://registry.npmjs.org/browserify/-/browserify-13.3.0.tgz", + "integrity": "sha1-tanJAgJD8McORnW+yCI7xifkFc4=", + "dev": true, + "dependencies": { + "concat-stream": { + "version": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.5.2.tgz", + "integrity": "sha1-cIl4Yk2FavQaWnQd790mHadSwmY=", + "dev": true, + "dependencies": { + "readable-stream": { + "version": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", + "integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44=", + "dev": true + } + } + }, + "duplexer2": { + "version": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", + "integrity": "sha1-ixLauHjA1p4+eJEFFmKjL8a93ME=", + "dev": true + }, + "process": { + "version": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", + "dev": true + }, + "punycode": { + "version": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true + }, + "string_decoder": { + "version": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + } + } + }, + "browserify-aes": { + "version": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.0.6.tgz", + "integrity": "sha1-Xncl297x/Vkw1OurSFZ85FHEigo=" + }, + "browserify-cipher": { + "version": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.0.tgz", + "integrity": "sha1-mYgkSHS/XtTijalWZtzWasj8Njo=", + "dev": true + }, + "browserify-derequire": { + "version": "https://registry.npmjs.org/browserify-derequire/-/browserify-derequire-0.9.4.tgz", + "integrity": "sha1-ZNYeVs/f8LjxdP2MV/i0Az4oeJU=", + "dependencies": { + "isarray": { + "version": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, + "readable-stream": { + "version": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=" + }, + "string_decoder": { + "version": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + }, + "through2": { + "version": "https://registry.npmjs.org/through2/-/through2-1.1.1.tgz", + "integrity": "sha1-CEfLxESfNAVXTb3M2buEG4OsNUU=" + } + } + }, + "browserify-des": { + "version": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.0.tgz", + "integrity": "sha1-2qJ3cXRwki7S/hhZQRihdUOXId0=", + "dev": true + }, + "browserify-rsa": { + "version": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", + "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", + "dev": true + }, + "browserify-sha3": { + "version": "https://registry.npmjs.org/browserify-sha3/-/browserify-sha3-0.0.1.tgz", + "integrity": "sha1-P/NKMAbvFcD7NWflQbkaI0ASPRE=" + }, + "browserify-sign": { + "version": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.0.4.tgz", + "integrity": "sha1-qk62jl17ZYuqa/alfmMMvXqT0pg=", + "dev": true + }, + "browserify-unibabel": { + "version": "https://registry.npmjs.org/browserify-unibabel/-/browserify-unibabel-3.0.0.tgz", + "integrity": "sha1-WmuPD3BM44jTkn30czfiWDD3Hdo=" + }, + "browserify-zlib": { + "version": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.1.4.tgz", + "integrity": "sha1-uzX4pRn2AOD6a4SFJByXnQFB+y0=", + "dev": true + }, + "bs58": { + "version": "https://registry.npmjs.org/bs58/-/bs58-3.1.0.tgz", + "integrity": "sha1-1MJjiL9IBMrHFBQbGUWqR+XrJI4=" + }, + "bs58check": { + "version": "https://registry.npmjs.org/bs58check/-/bs58check-1.3.4.tgz", + "integrity": "sha1-xSVABzdJEXcU+gQsMEfrj5FRy/g=" + }, + "buffer": { + "version": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", + "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", + "dev": true, + "dependencies": { + "base64-js": { + "version": "https://registry.npmjs.org/base64-js/-/base64-js-1.2.0.tgz", + "integrity": "sha1-o5mS1yNYSBGYK+XikLtqU9hnAPE=", + "dev": true + } + } + }, + "buffer-crc32": { + "version": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", + "dev": true + }, + "buffer-equal": { + "version": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-0.0.1.tgz", + "integrity": "sha1-kbx0sR6kBbyRa8aqkI+q+ltKrEs=" + }, + "buffer-shims": { + "version": "https://registry.npmjs.org/buffer-shims/-/buffer-shims-1.0.0.tgz", + "integrity": "sha1-mXjOMXOIxkmth5MCjDR37wRKi1E=" + }, + "buffer-xor": { + "version": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", + "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=" + }, + "bufferstreams": { + "version": "https://registry.npmjs.org/bufferstreams/-/bufferstreams-1.1.1.tgz", + "integrity": "sha1-AWE3MGCsWYjv+ZBYcxEU9uGV1R4=" + }, + "builtin-modules": { + "version": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=" + }, + "builtin-status-codes": { + "version": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", + "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=", + "dev": true + }, + "builtins": { + "version": "https://registry.npmjs.org/builtins/-/builtins-0.0.3.tgz", + "integrity": "sha1-XQBhZtpxYQvCvPcwGfDwzEMwl1U=" + }, + "bytes": { + "version": "https://registry.npmjs.org/bytes/-/bytes-2.2.0.tgz", + "integrity": "sha1-/TVGSkA/b5EXwt42Cez/nK4ABYg=", + "dev": true + }, + "cached-path-relative": { + "version": "https://registry.npmjs.org/cached-path-relative/-/cached-path-relative-1.0.1.tgz", + "integrity": "sha1-0JxLUoAKpMB44t2BqGmqyQ0uVOc=", + "dev": true + }, + "caller-path": { + "version": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", + "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=" + }, + "callsite": { + "version": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz", + "integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA=", + "dev": true + }, + "callsites": { + "version": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", + "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=" + }, + "camelcase": { + "version": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", + "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=" + }, + "caseless": { + "version": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + }, + "chai": { + "version": "https://registry.npmjs.org/chai/-/chai-3.5.0.tgz", + "integrity": "sha1-TQJjewZ/6Vi9v906QOxW/vc3Mkc=", + "dev": true + }, + "chalk": { + "version": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=" + }, + "charm": { + "version": "https://registry.npmjs.org/charm/-/charm-1.0.2.tgz", + "integrity": "sha1-it02cVOm2aWBMxBSxAkJkdqZXjU=", + "dev": true + }, + "checkpoint-store": { + "version": "https://registry.npmjs.org/checkpoint-store/-/checkpoint-store-1.1.0.tgz", + "integrity": "sha1-BOTLUWuRQziTWB5tRgGnjpVS6gY=" + }, + "cheerio": { + "version": "https://registry.npmjs.org/cheerio/-/cheerio-0.22.0.tgz", + "integrity": "sha1-qbqoYKP5tZWmuBsahocxIe06Jp4=", + "dev": true + }, + "chokidar": { + "version": "https://registry.npmjs.org/chokidar/-/chokidar-1.7.0.tgz", + "integrity": "sha1-eY5ol3gVHIB2tLNg5e3SjNortGg=", + "dev": true + }, + "chownr": { + "version": "https://registry.npmjs.org/chownr/-/chownr-1.0.1.tgz", + "integrity": "sha1-4qdQQqlVGQi+vSW4Uj1fl2nXkYE=" + }, + "cipher-base": { + "version": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.3.tgz", + "integrity": "sha1-7qvxlEGc6QDaMBjCB9IS8qbfCgc=" + }, + "circular-json": { + "version": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.1.tgz", + "integrity": "sha1-vos2rvzN6LPKeqLWr8B6NyQsDS0=" + }, + "classnames": { + "version": "https://registry.npmjs.org/classnames/-/classnames-2.2.5.tgz", + "integrity": "sha1-+zgB1FNGdknvNgPH1hoCvRKb3m0=" + }, + "cli-cursor": { + "version": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz", + "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=" + }, + "cli-table": { + "version": "https://registry.npmjs.org/cli-table/-/cli-table-0.3.1.tgz", + "integrity": "sha1-9TsFJmqLGguTSz0IIebi3FkUriM=", + "dev": true, + "dependencies": { + "colors": { + "version": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", + "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=", + "dev": true + } + } + }, + "cli-width": { + "version": "https://registry.npmjs.org/cli-width/-/cli-width-2.1.0.tgz", + "integrity": "sha1-sjTKIJsp72b8UY2bmNWEewDt8Ao=" + }, + "client-sw-ready-event": { + "version": "https://registry.npmjs.org/client-sw-ready-event/-/client-sw-ready-event-3.1.0.tgz", + "integrity": "sha1-SR1E+BFiEH/TfKBDWLSzPs/gpy4=" + }, + "cliui": { + "version": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", + "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=" + }, + "clone": { + "version": "https://registry.npmjs.org/clone/-/clone-1.0.2.tgz", + "integrity": "sha1-Jgt6meux7f4kdTgXX3gyQ8sZ0Uk=" + }, + "clone-stats": { + "version": "https://registry.npmjs.org/clone-stats/-/clone-stats-0.0.1.tgz", + "integrity": "sha1-uI+UqCzzi4eR1YBG6kAprYjKmdE=" + }, + "co": { + "version": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" + }, + "code-point-at": { + "version": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" + }, + "coinstring": { + "version": "https://registry.npmjs.org/coinstring/-/coinstring-2.3.0.tgz", + "integrity": "sha1-zbYzY6lhUCQEolr7gsLibV/2J6Q=", + "dependencies": { + "bs58": { + "version": "https://registry.npmjs.org/bs58/-/bs58-2.0.1.tgz", + "integrity": "sha1-VZCNWPGYKrogCPob7Y+RmYopv40=" + } + } + }, + "collection-map": { + "version": "https://registry.npmjs.org/collection-map/-/collection-map-0.1.0.tgz", + "integrity": "sha1-TP+R0lEI159O3uzObs7j5IjyZ8I=", + "dev": true, + "dependencies": { + "make-iterator": { + "version": "https://registry.npmjs.org/make-iterator/-/make-iterator-0.1.1.tgz", + "integrity": "sha1-hz0nuBmKRlqBSDtvXRbaToY+z1s=", + "dev": true + } + } + }, + "color": { + "version": "https://registry.npmjs.org/color/-/color-0.11.4.tgz", + "integrity": "sha1-bXtcdPtl6EHNSHkq0e1eB7kE12Q=" + }, + "color-convert": { + "version": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.0.tgz", + "integrity": "sha1-Gsz5fdc5uYO/mU1W/sj5WFNkG3o=" + }, + "color-name": { + "version": "https://registry.npmjs.org/color-name/-/color-name-1.1.2.tgz", + "integrity": "sha1-XIq3K2S9IhXWF66VWeuxSEdc+Y0=" + }, + "color-string": { + "version": "https://registry.npmjs.org/color-string/-/color-string-0.3.0.tgz", + "integrity": "sha1-J9RvtnAlxcL6JZk7+/V55HhBuZE=" + }, + "colors": { + "version": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz", + "integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=", + "dev": true + }, + "combine-source-map": { + "version": "https://registry.npmjs.org/combine-source-map/-/combine-source-map-0.7.2.tgz", + "integrity": "sha1-CHAxKFazB6h8xKxIbzqaYq7MwJ4=", + "dev": true, + "dependencies": { + "convert-source-map": { + "version": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.1.3.tgz", + "integrity": "sha1-SCnId+n+SbMWHzvzZziI4gRpmGA=", + "dev": true + } + } + }, + "combined-stream": { + "version": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz", + "integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk=" + }, + "commander": { + "version": "https://registry.npmjs.org/commander/-/commander-2.3.0.tgz", + "integrity": "sha1-/UMOiJgy7DU7ms0d4hfBHLPu+HM=", + "dev": true + }, + "commondir": { + "version": "https://registry.npmjs.org/commondir/-/commondir-0.0.1.tgz", + "integrity": "sha1-ifAP3NUbUZxXhzP+xWPmptp/W+I=" + }, + "commonmark": { + "version": "https://registry.npmjs.org/commonmark/-/commonmark-0.24.0.tgz", + "integrity": "sha1-uA3gGCxUY1VkOqFdsSv7KCNoJ48=" + }, + "commonmark-react-renderer": { + "version": "https://registry.npmjs.org/commonmark-react-renderer/-/commonmark-react-renderer-4.3.3.tgz", + "integrity": "sha1-nEvKE4vIMoe655LM8TNzi+nLxvo=" + }, + "component-bind": { + "version": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz", + "integrity": "sha1-AMYIq33Nk4l8AAllGx06jh5zu9E=", + "dev": true + }, + "component-emitter": { + "version": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.1.2.tgz", + "integrity": "sha1-KWWU8nU9qmOZbSrwjRWpURbJrsM=", + "dev": true + }, + "component-inherit": { + "version": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz", + "integrity": "sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM=", + "dev": true + }, + "concat-map": { + "version": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "concat-stream": { + "version": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.0.tgz", + "integrity": "sha1-CqxmL9Ur54lk1VMvaUeE5wEQrPc=" + }, + "config-chain": { + "version": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.11.tgz", + "integrity": "sha1-q6CXR9++TD5w52am5BWG4YWfxvI=", + "dev": true + }, + "console-browserify": { + "version": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz", + "integrity": "sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA=", + "dev": true + }, + "console-control-strings": { + "version": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" + }, + "consolidate": { + "version": "https://registry.npmjs.org/consolidate/-/consolidate-0.14.5.tgz", + "integrity": "sha1-WiUEe8dvcwcmZ8jLUsmJiI9JTGM=", + "dev": true + }, + "constants-browserify": { + "version": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", + "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=", + "dev": true + }, + "content-disposition": { + "version": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", + "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=" + }, + "content-type": { + "version": "https://registry.npmjs.org/content-type/-/content-type-1.0.2.tgz", + "integrity": "sha1-t9ETrueo3Se9IRM8TcJSnfFyHu0=" + }, + "convert-source-map": { + "version": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.5.0.tgz", + "integrity": "sha1-ms1whRxtXf3ZPZKC5e35SgP/RrU=" + }, + "cookie": { + "version": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", + "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" + }, + "cookie-signature": { + "version": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + }, + "copy-props": { + "version": "https://registry.npmjs.org/copy-props/-/copy-props-1.6.0.tgz", + "integrity": "sha1-8DJLvumXcRAeezraES8xPDk9uO0=", + "dev": true + }, + "copy-to-clipboard": { + "version": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-2.1.0.tgz", + "integrity": "sha1-Az8WUqU9gpBYU3sPCNK86STt/FM=" + }, + "core-js": { + "version": "https://registry.npmjs.org/core-js/-/core-js-2.4.1.tgz", + "integrity": "sha1-TekR5mew6ukSTjQlS1OupvxhjT4=" + }, + "core-util-is": { + "version": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "create-ecdh": { + "version": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.0.tgz", + "integrity": "sha1-iIxyNZbN92EvZJgjPuvXo1MBc30=", + "dev": true + }, + "create-hash": { + "version": "https://registry.npmjs.org/create-hash/-/create-hash-1.1.3.tgz", + "integrity": "sha1-YGBCrIuSYnUPSDyt2rD1gZFy2P0=" + }, + "create-hmac": { + "version": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.6.tgz", + "integrity": "sha1-rLniIaThe9sHbpBlfEK5PjcmzwY=" + }, + "create-react-class": { + "version": "https://registry.npmjs.org/create-react-class/-/create-react-class-15.5.3.tgz", + "integrity": "sha1-+w98rnkznpoXnhlO9Gbvo5I4IP4=" + }, + "cross-spawn": { + "version": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "dev": true, + "dependencies": { + "lru-cache": { + "version": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.0.2.tgz", + "integrity": "sha1-HRdnnAac2l0ECZGgnbwsDbN35V4=", + "dev": true + }, + "which": { + "version": "https://registry.npmjs.org/which/-/which-1.2.14.tgz", + "integrity": "sha1-mofEN48D6CfOyvGs31bHNsAcFOU=", + "dev": true + } + } + }, + "cryptiles": { + "version": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz", + "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=" + }, + "crypto-browserify": { + "version": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.11.0.tgz", + "integrity": "sha1-NlKgkGq5sqfgw85mpAjpV6JIVSI=", + "dev": true + }, + "crypto-js": { + "version": "https://registry.npmjs.org/crypto-js/-/crypto-js-3.1.8.tgz", + "integrity": "sha1-cV8HC/YBTyrpkqmLOSkli3E/CNU=" + }, + "css": { + "version": "https://registry.npmjs.org/css/-/css-2.2.1.tgz", + "integrity": "sha1-c6TIHehdtmTU7mdPfUcIXjstVdw=", + "dev": true, + "dependencies": { + "source-map": { + "version": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz", + "integrity": "sha1-wkvBRspRfBRx9drL4lcbK3+eM0Y=", + "dev": true + } + } + }, + "css-select": { + "version": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", + "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=", + "dev": true + }, + "css-what": { + "version": "https://registry.npmjs.org/css-what/-/css-what-2.1.0.tgz", + "integrity": "sha1-lGfQMsOM+u+58teVASUwYvh/ob0=", + "dev": true + }, + "cssauron": { + "version": "https://registry.npmjs.org/cssauron/-/cssauron-1.4.0.tgz", + "integrity": "sha1-pmAt/34EqDBtwNuaVR6S6LVmKtg=", + "dev": true + }, + "cssom": { + "version": "https://registry.npmjs.org/cssom/-/cssom-0.3.2.tgz", + "integrity": "sha1-uANhcMefB6kP8vFuIihAJ6JDhIs=", + "dev": true + }, + "cssstyle": { + "version": "https://registry.npmjs.org/cssstyle/-/cssstyle-0.2.37.tgz", + "integrity": "sha1-VBCXI0yyUTyDzu06zdwn/yeYfVQ=", + "dev": true + }, + "cycle": { + "version": "https://registry.npmjs.org/cycle/-/cycle-1.0.3.tgz", + "integrity": "sha1-IegLK+hYD5i0aPN5QwZisEbDStI=", + "dev": true + }, + "cyclist": { + "version": "https://registry.npmjs.org/cyclist/-/cyclist-0.2.2.tgz", + "integrity": "sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA=" + }, + "d": { + "version": "https://registry.npmjs.org/d/-/d-1.0.0.tgz", + "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=" + }, + "d3": { + "version": "https://registry.npmjs.org/d3/-/d3-3.5.17.tgz", + "integrity": "sha1-vEZ0gAQ3iyGjYMn8fPUjF5B2L7g=" + }, + "dashdash": { + "version": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "dependencies": { + "assert-plus": { + "version": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + } + } + }, + "date-now": { + "version": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz", + "integrity": "sha1-6vQ5/U1ISK105cx9vvIAZyueNFs=", + "dev": true + }, + "dateformat": { + "version": "https://registry.npmjs.org/dateformat/-/dateformat-2.0.0.tgz", + "integrity": "sha1-J0Pjq7XD/CRi5SfcpEXgTp9N7hc=" + }, + "debounce": { + "version": "https://registry.npmjs.org/debounce/-/debounce-1.0.2.tgz", + "integrity": "sha1-UDzGdNjX9zcJlmT7dd29NrlibcY=" + }, + "debug": { + "version": "https://registry.npmjs.org/debug/-/debug-2.6.7.tgz", + "integrity": "sha1-krrR9tBbu2u6Isyoi80OyJTChh4=" + }, + "debug-fabulous": { + "version": "https://registry.npmjs.org/debug-fabulous/-/debug-fabulous-0.0.4.tgz", + "integrity": "sha1-+gccXYdIRoVCSAdCHKSxawsaB2M=", + "dev": true, + "dependencies": { + "object-assign": { + "version": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.0.tgz", + "integrity": "sha1-ejs9DpgGPUP0wD8uiubNUahog6A=", + "dev": true + } + } + }, + "decamelize": { + "version": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" + }, + "deep-diff": { + "version": "https://registry.npmjs.org/deep-diff/-/deep-diff-0.3.4.tgz", + "integrity": "sha1-qsXDmVIjar5fA3ojSQYLoBsArkg=" + }, + "deep-eql": { + "version": "https://registry.npmjs.org/deep-eql/-/deep-eql-0.1.3.tgz", + "integrity": "sha1-71WKyrjeJSBs1xOQbXTlaTDrafI=", + "dev": true, + "dependencies": { + "type-detect": { + "version": "https://registry.npmjs.org/type-detect/-/type-detect-0.1.1.tgz", + "integrity": "sha1-C6XsKohWQORw6k6FBZcZANrFiCI=", + "dev": true + } + } + }, + "deep-equal": { + "version": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", + "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=" + }, + "deep-extend": { + "version": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.4.2.tgz", + "integrity": "sha1-SLaZwn4zS/ifEIkr5DL25MfTSn8=" + }, + "deep-freeze-strict": { + "version": "https://registry.npmjs.org/deep-freeze-strict/-/deep-freeze-strict-1.1.1.tgz", + "integrity": "sha1-d9BYPKJKab5LvZrC+uQV1VUj5bA=", + "dev": true + }, + "deep-is": { + "version": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=" + }, + "deepmerge": { + "version": "https://registry.npmjs.org/deepmerge/-/deepmerge-0.2.10.tgz", + "integrity": "sha1-iQa/nlJaT78bIDsq/LRkAkmCEhk=", + "dev": true + }, + "default-resolution": { + "version": "https://registry.npmjs.org/default-resolution/-/default-resolution-2.0.0.tgz", + "integrity": "sha1-vLgrqnKtebQmp2cy8aga1t8m1oQ=", + "dev": true + }, + "deferred-leveldown": { + "version": "https://registry.npmjs.org/deferred-leveldown/-/deferred-leveldown-1.2.1.tgz", + "integrity": "sha1-XSXDMQ9f6QmUb2JA3J+Q3RCace8=" + }, + "define-properties": { + "version": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.2.tgz", + "integrity": "sha1-g6c/L+pWmJj7c3GTyPhzyvbUXJQ=" + }, + "defined": { + "version": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", + "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=" + }, + "del": { + "version": "https://registry.npmjs.org/del/-/del-2.2.2.tgz", + "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=" + }, + "delayed-stream": { + "version": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + }, + "delegates": { + "version": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" + }, + "denodeify": { + "version": "https://registry.npmjs.org/denodeify/-/denodeify-1.2.1.tgz", + "integrity": "sha1-OjYof1A05pnnV3kBBSwubJQlFjE=" + }, + "depd": { + "version": "https://registry.npmjs.org/depd/-/depd-1.1.0.tgz", + "integrity": "sha1-4b2Cxqq2ztlluXuIsX7T5SjKGMM=" + }, + "deps-sort": { + "version": "https://registry.npmjs.org/deps-sort/-/deps-sort-2.0.0.tgz", + "integrity": "sha1-CRckkC6EZYJg65EHSMzNGvbiH7U=", + "dev": true + }, + "derequire": { + "version": "https://registry.npmjs.org/derequire/-/derequire-2.0.6.tgz", + "integrity": "sha1-MaQUu3yhdiOfp4sRZjbvd9UX52g=" + }, + "des.js": { + "version": "https://registry.npmjs.org/des.js/-/des.js-1.0.0.tgz", + "integrity": "sha1-wHTS4qpqipoH29YfmhXCzYPsjsw=", + "dev": true + }, + "destroy": { + "version": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + }, + "detect-file": { + "version": "https://registry.npmjs.org/detect-file/-/detect-file-0.1.0.tgz", + "integrity": "sha1-STXe39lIhkjgBrASlWbpOGcR6mM=", + "dev": true + }, + "detect-indent": { + "version": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz", + "integrity": "sha1-920GQ1LN9Docts5hnE7jqUdd4gg=" + }, + "detect-newline": { + "version": "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz", + "integrity": "sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I=", + "dev": true + }, + "detect-node": { + "version": "https://registry.npmjs.org/detect-node/-/detect-node-2.0.3.tgz", + "integrity": "sha1-ogM8CcyOFY03dI+951B4Mr1s4Sc=" + }, + "detective": { + "version": "https://registry.npmjs.org/detective/-/detective-4.5.0.tgz", + "integrity": "sha1-blqMaybmx6JUsca210kNmOyR7dE=", + "dev": true + }, + "diff": { + "version": "https://registry.npmjs.org/diff/-/diff-1.4.0.tgz", + "integrity": "sha1-fyjS657nsVqX79ic5j3P2qPMur8=", + "dev": true + }, + "diffie-hellman": { + "version": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.2.tgz", + "integrity": "sha1-tYNXOScM/ias9jIJn97SoH8gnl4=", + "dev": true + }, + "disc": { + "version": "https://registry.npmjs.org/disc/-/disc-1.3.2.tgz", + "integrity": "sha1-MqbwLkhu33eGClNj0icYQl0pbkA=" + }, + "dnode": { + "version": "https://registry.npmjs.org/dnode/-/dnode-1.2.2.tgz", + "integrity": "sha1-SsPP4m4pKzs5uCWK59lO3FgTLvo=" + }, + "dnode-protocol": { + "version": "https://registry.npmjs.org/dnode-protocol/-/dnode-protocol-0.2.2.tgz", + "integrity": "sha1-URUdFvw7X4SBXuC5SXoQYdDRlJ0=" + }, + "doctrine": { + "version": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", + "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=" + }, + "dom-serializer": { + "version": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.0.tgz", + "integrity": "sha1-BzxpdUbOB4DOI75KKOKT5AvDDII=", + "dev": true, + "dependencies": { + "domelementtype": { + "version": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz", + "integrity": "sha1-vSh3PiZCiBrsUVRJJCmcXNgiGFs=", + "dev": true + } + } + }, + "dom-walk": { + "version": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.1.tgz", + "integrity": "sha1-ZyIm3HTI95mtNTB9+TaroRrNYBg=" + }, + "domain-browser": { + "version": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.1.7.tgz", + "integrity": "sha1-hnqksJP6oF8d4IwG9NeyH9+GmLw=", + "dev": true + }, + "domelementtype": { + "version": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.0.tgz", + "integrity": "sha1-sXrtguirWeUt2cGbF1bg/BhyBMI=", + "dev": true + }, + "domhandler": { + "version": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.1.tgz", + "integrity": "sha1-iS5HAAqZvlW783dP/qBWHYh5wlk=", + "dev": true + }, + "domutils": { + "version": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", + "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", + "dev": true + }, + "drbg.js": { + "version": "https://registry.npmjs.org/drbg.js/-/drbg.js-1.0.1.tgz", + "integrity": "sha1-Pja2xCs3BDgjzbwzLVjzHiRFSAs=" + }, + "duplexer": { + "version": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", + "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=" + }, + "duplexer2": { + "version": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.0.2.tgz", + "integrity": "sha1-xhTc9n4vsUmVqRcR5aYX6KYKMds=", + "dependencies": { + "isarray": { + "version": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, + "readable-stream": { + "version": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=" + }, + "string_decoder": { + "version": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + } + } + }, + "duplexify": { + "version": "https://registry.npmjs.org/duplexify/-/duplexify-3.5.0.tgz", + "integrity": "sha1-GqdzAC4VeEV+nZ1KULDMquvL1gQ=", + "dependencies": { + "end-of-stream": { + "version": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.0.0.tgz", + "integrity": "sha1-1FlucCc0qT5A6a+GQxnqvZn/Lw4=" + }, + "once": { + "version": "https://registry.npmjs.org/once/-/once-1.3.3.tgz", + "integrity": "sha1-suJhVXzkwxTsgwTz+oJmPkKXyiA=" + } + } + }, + "each-props": { + "version": "https://registry.npmjs.org/each-props/-/each-props-1.3.0.tgz", + "integrity": "sha1-ftgDHJJ2iK7bSoluuRSFtEh7kOo=", + "dev": true + }, + "ecc-jsbn": { + "version": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", + "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", + "optional": true + }, + "ee-first": { + "version": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + }, + "elliptic": { + "version": "https://registry.npmjs.org/elliptic/-/elliptic-6.4.0.tgz", + "integrity": "sha1-ysmvh2LIWDYYcAPI3+GT5eLq5d8=" + }, + "encodeurl": { + "version": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.1.tgz", + "integrity": "sha1-eePVhlU0aQn+bw9Fpd5oEDspTSA=" + }, + "encoding": { + "version": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz", + "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=" + }, + "end-of-stream": { + "version": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.0.tgz", + "integrity": "sha1-epDYM+/abPpurA9JSduw+tOmMgY=" + }, + "engine.io": { + "version": "https://registry.npmjs.org/engine.io/-/engine.io-1.8.0.tgz", + "integrity": "sha1-PutfJky3XbvsG6rqJtYfWk6s4qo=", + "dev": true, + "dependencies": { + "debug": { + "version": "https://registry.npmjs.org/debug/-/debug-2.3.3.tgz", + "integrity": "sha1-QMRT5n5uE8kB3ewxeviYbNqe/4w=", + "dev": true + }, + "ms": { + "version": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", + "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=", + "dev": true + } + } + }, + "engine.io-client": { + "version": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-1.8.0.tgz", + "integrity": "sha1-e3MOQSdBQIdZbZvjyI0rxf22z1w=", + "dev": true, + "dependencies": { + "component-emitter": { + "version": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", + "dev": true + }, + "debug": { + "version": "https://registry.npmjs.org/debug/-/debug-2.3.3.tgz", + "integrity": "sha1-QMRT5n5uE8kB3ewxeviYbNqe/4w=", + "dev": true + }, + "ms": { + "version": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", + "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=", + "dev": true + } + } + }, + "engine.io-parser": { + "version": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-1.3.1.tgz", + "integrity": "sha1-lVTxrjMQfW+9FwylRm0vgz9qB88=", + "dev": true, + "dependencies": { + "has-binary": { + "version": "https://registry.npmjs.org/has-binary/-/has-binary-0.1.6.tgz", + "integrity": "sha1-JTJvOc+k9hath4eJTjryz7x7bhA=", + "dev": true + }, + "isarray": { + "version": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + } + } + }, + "ensnare": { + "version": "https://registry.npmjs.org/ensnare/-/ensnare-1.0.0.tgz", + "integrity": "sha1-ctK/fvSKuiH2at8p0AoJBO3bYcc=" + }, + "entities": { + "version": "https://registry.npmjs.org/entities/-/entities-1.1.1.tgz", + "integrity": "sha1-blwtClYhtdra7O+AuQ7ftc13cvA=" + }, + "envify": { + "version": "https://registry.npmjs.org/envify/-/envify-4.0.0.tgz", + "integrity": "sha1-95E0Pj0RzCnM5BFQMAqK9hxmyrA=", + "dev": true, + "dependencies": { + "esprima": { + "version": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz", + "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=", + "dev": true + } + } + }, + "enzyme": { + "version": "https://registry.npmjs.org/enzyme/-/enzyme-2.8.2.tgz", + "integrity": "sha1-bIvLBQEqvEqkvDIT+yN4C5tbFxQ=", + "dev": true + }, + "errno": { + "version": "https://registry.npmjs.org/errno/-/errno-0.1.4.tgz", + "integrity": "sha1-uJbiOp5ei6M4cfyZar02NfyaHH0=", + "dependencies": { + "prr": { + "version": "https://registry.npmjs.org/prr/-/prr-0.0.0.tgz", + "integrity": "sha1-GoS4WQgyVQFBGFPQCB7j+obikmo=" + } + } + }, + "error-ex": { + "version": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz", + "integrity": "sha1-+FWobOYa3E6GIcPNoh56dhLDqNw=" + }, + "es-abstract": { + "version": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.7.0.tgz", + "integrity": "sha1-363ndOAb/Nl/lhgCmMRJyGI/uUw=" + }, + "es-to-primitive": { + "version": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.1.1.tgz", + "integrity": "sha1-RTVSSKiJeQNLZ5Lhm7gfK3l13Q0=" + }, + "es5-ext": { + "version": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.21.tgz", + "integrity": "sha1-Gacl+eUdAwC7wejoIRCf2dr1WSU=" + }, + "es6-iterator": { + "version": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.1.tgz", + "integrity": "sha1-jjGcnwRTv1ddN0lAplWSDlnKVRI=" + }, + "es6-map": { + "version": "https://registry.npmjs.org/es6-map/-/es6-map-0.1.5.tgz", + "integrity": "sha1-kTbgUD3MBqMBaQ8LsU/042TpSfA=" + }, + "es6-set": { + "version": "https://registry.npmjs.org/es6-set/-/es6-set-0.1.5.tgz", + "integrity": "sha1-0rPsXU2ADO2BjbU40ol02wpzzLE=" + }, + "es6-symbol": { + "version": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz", + "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=" + }, + "es6-weak-map": { + "version": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.2.tgz", + "integrity": "sha1-XjqzIlH/0VOKH45f+hNXdy+S2W8=" + }, + "escape-html": { + "version": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + }, + "escape-string-regexp": { + "version": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + }, + "escodegen": { + "version": "https://registry.npmjs.org/escodegen/-/escodegen-1.3.3.tgz", + "integrity": "sha1-8CQBb1qI4Eb9EgBQVek5gC5sXyM=", + "dependencies": { + "esprima": { + "version": "https://registry.npmjs.org/esprima/-/esprima-1.1.1.tgz", + "integrity": "sha1-W28VR/TRAuZw4UDFCb5ncdautUk=" + }, + "estraverse": { + "version": "https://registry.npmjs.org/estraverse/-/estraverse-1.5.1.tgz", + "integrity": "sha1-hno+jlip+EYYr7bC3bzZFrfLr3E=" + }, + "esutils": { + "version": "https://registry.npmjs.org/esutils/-/esutils-1.0.0.tgz", + "integrity": "sha1-gVHTWOIMisx/t0XnRywAJf5JZXA=" + }, + "source-map": { + "version": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz", + "integrity": "sha1-wkvBRspRfBRx9drL4lcbK3+eM0Y=", + "optional": true + } + } + }, + "escope": { + "version": "https://registry.npmjs.org/escope/-/escope-3.6.0.tgz", + "integrity": "sha1-4Bl16BJ4GhY6ba392AOY3GTIicM=" + }, + "eslint": { + "version": "https://registry.npmjs.org/eslint/-/eslint-2.13.1.tgz", + "integrity": "sha1-5MyPoPAJ+4KaquI4VaKTYL4fbBE=", + "dependencies": { + "strip-json-comments": { + "version": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-1.0.4.tgz", + "integrity": "sha1-HhX7ysl9Pumb8tc7TGVrCCu6+5E=" + } + } + }, + "eslint-plugin-chai": { + "version": "https://registry.npmjs.org/eslint-plugin-chai/-/eslint-plugin-chai-0.0.1.tgz", + "integrity": "sha1-mh3qWLM1wxJCIZ0Fmzf/sUMJ9uE=", + "dev": true + }, + "eslint-plugin-mocha": { + "version": "https://registry.npmjs.org/eslint-plugin-mocha/-/eslint-plugin-mocha-4.9.0.tgz", + "integrity": "sha1-kXqLSZq40MAdacbk+B02LuCZtv0=", + "dev": true + }, + "espree": { + "version": "https://registry.npmjs.org/espree/-/espree-3.4.3.tgz", + "integrity": "sha1-KRC1zNSc6JPC//+qtP2LOjG4I3Q=", + "dependencies": { + "acorn": { + "version": "https://registry.npmjs.org/acorn/-/acorn-5.0.3.tgz", + "integrity": "sha1-xGDfCEkUY/AozLguqzcwvwEIez0=" + } + } + }, + "esprima-fb": { + "version": "https://registry.npmjs.org/esprima-fb/-/esprima-fb-3001.0001.0000-dev-harmony-fb.tgz", + "integrity": "sha1-t303q8046gt3Qmu4vCkizmtCZBE=" + }, + "esrecurse": { + "version": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.1.0.tgz", + "integrity": "sha1-RxO2U2rffyrE8yfVWed1a/9kgiA=", + "dependencies": { + "estraverse": { + "version": "https://registry.npmjs.org/estraverse/-/estraverse-4.1.1.tgz", + "integrity": "sha1-9srKcokzqFDvkGYdDheYK6RxEaI=" + } + } + }, + "estraverse": { + "version": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", + "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=" + }, + "esutils": { + "version": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=" + }, + "etag": { + "version": "https://registry.npmjs.org/etag/-/etag-1.8.0.tgz", + "integrity": "sha1-b2Ma7zNtbEY2K1F2QETOIWvjwFE=" + }, + "eth-bin-to-ops": { + "version": "https://registry.npmjs.org/eth-bin-to-ops/-/eth-bin-to-ops-1.0.1.tgz", + "integrity": "sha1-TScDuYeIJbw4xiWZEOkLTbAFx94=" + }, + "eth-block-tracker": { + "version": "https://registry.npmjs.org/eth-block-tracker/-/eth-block-tracker-1.0.17.tgz", + "integrity": "sha1-9jwEkCyB2N+mIk1EARWhaRa6w1w=" + }, + "eth-contract-metadata": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/eth-contract-metadata/-/eth-contract-metadata-1.0.0.tgz", + "integrity": "sha1-YV/Z1jvjpwU0FDJdt7hdv/5tFWg=" + }, + "eth-ens-namehash": { + "version": "https://registry.npmjs.org/eth-ens-namehash/-/eth-ens-namehash-1.0.2.tgz", + "integrity": "sha1-Bezda6wtf9e8XKhKmTxrrZ2k7bk=", + "dependencies": { + "js-sha3": { + "version": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.5.7.tgz", + "integrity": "sha1-DU/9gALVMzqrr0oj7tL2N0yfKOc=" + } + } + }, + "eth-hd-keyring": { + "version": "https://registry.npmjs.org/eth-hd-keyring/-/eth-hd-keyring-1.2.0.tgz", + "integrity": "sha1-QLzH6od+9cdG9UwMh6aznOte3eM=", + "dependencies": { + "ethereumjs-util": { + "version": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.1.1.tgz", + "integrity": "sha1-Ei+zjep0fcYrOuv8Nl0b1Ivktz4=" + } + } + }, + "eth-query": { + "version": "https://registry.npmjs.org/eth-query/-/eth-query-2.1.1.tgz", + "integrity": "sha1-Co7lvSTHtcBKDMyLpH/Gq6QpjZI=" + }, + "eth-sig-util": { + "version": "https://registry.npmjs.org/eth-sig-util/-/eth-sig-util-1.2.1.tgz", + "integrity": "sha1-JUo+csXCzLYMncXmRl/H4XS2v5E=", + "dependencies": { + "ethereumjs-util": { + "version": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.1.1.tgz", + "integrity": "sha1-Ei+zjep0fcYrOuv8Nl0b1Ivktz4=" + } + } + }, + "eth-simple-keyring": { + "version": "https://registry.npmjs.org/eth-simple-keyring/-/eth-simple-keyring-1.1.1.tgz", + "integrity": "sha1-bdddfMbt6nx4jPGe+UMcgwzZYa4=", + "dependencies": { + "ethereumjs-util": { + "version": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.1.1.tgz", + "integrity": "sha1-Ei+zjep0fcYrOuv8Nl0b1Ivktz4=" + } + } + }, + "ethereum-common": { + "version": "https://registry.npmjs.org/ethereum-common/-/ethereum-common-0.0.18.tgz", + "integrity": "sha1-L9w1dvIykDNYl26znaeDIT/5Uj8=" + }, + "ethereum-ens-network-map": { + "version": "https://registry.npmjs.org/ethereum-ens-network-map/-/ethereum-ens-network-map-1.0.0.tgz", + "integrity": "sha1-Q812ac6VCnieFRABEY1NZfIQ7rc=" + }, + "ethereumjs-account": { + "version": "https://registry.npmjs.org/ethereumjs-account/-/ethereumjs-account-2.0.4.tgz", + "integrity": "sha1-+MMCMby3B/RRTYoFLB+doQNiTUc=", + "dependencies": { + "ethereumjs-util": { + "version": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-4.5.0.tgz", + "integrity": "sha1-PpQosxfuvaPXJg2FT93alUsfG8Y=" + } + } + }, + "ethereumjs-block": { + "version": "https://registry.npmjs.org/ethereumjs-block/-/ethereumjs-block-1.5.0.tgz", + "integrity": "sha1-sLkBjpzXMUbGAdx9svaypFYeRow=", + "dependencies": { + "async": { + "version": "https://registry.npmjs.org/async/-/async-2.4.1.tgz", + "integrity": "sha1-YqVrJ5yYoR0JhwlqAcw+6463u9c=" + } + } + }, + "ethereumjs-tx": { + "version": "https://registry.npmjs.org/ethereumjs-tx/-/ethereumjs-tx-1.3.1.tgz", + "integrity": "sha1-1pCavPs32mQE/BgSTTUe2iAEfaw=" + }, + "ethereumjs-util": { + "version": "git://github.com/ethereumjs/ethereumjs-util.git#ac5d0908536b447083ea422b435da27f26615de9" + }, + "ethereumjs-vm": { + "version": "https://registry.npmjs.org/ethereumjs-vm/-/ethereumjs-vm-2.0.2.tgz", + "integrity": "sha1-hOI3KlcVqApi9/KjEvjGRTfoqEI=", + "dependencies": { + "async": { + "version": "https://registry.npmjs.org/async/-/async-2.4.1.tgz", + "integrity": "sha1-YqVrJ5yYoR0JhwlqAcw+6463u9c=" + }, + "ethereumjs-util": { + "version": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-4.5.0.tgz", + "integrity": "sha1-PpQosxfuvaPXJg2FT93alUsfG8Y=" + } + } + }, + "ethereumjs-wallet": { + "version": "https://registry.npmjs.org/ethereumjs-wallet/-/ethereumjs-wallet-0.6.0.tgz", + "integrity": "sha1-gnY7Fpfuenlr5xVdqd+0my+Yz9s=", + "dependencies": { + "ethereumjs-util": { + "version": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-4.5.0.tgz", + "integrity": "sha1-PpQosxfuvaPXJg2FT93alUsfG8Y=" + } + } + }, + "ethjs-abi": { + "version": "https://registry.npmjs.org/ethjs-abi/-/ethjs-abi-0.2.0.tgz", + "integrity": "sha1-0+LCIQEVIPxJm3FoIDbBT8wvWyU=", + "dependencies": { + "js-sha3": { + "version": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.5.5.tgz", + "integrity": "sha1-uvDA6MVK1ZA0R9+Wreekobynmko=" + } + } + }, + "ethjs-contract": { + "version": "https://registry.npmjs.org/ethjs-contract/-/ethjs-contract-0.1.9.tgz", + "integrity": "sha1-HCdmiWpW1H7B1tZhgpxJzDilUgo=", + "dependencies": { + "ethjs-util": { + "version": "https://registry.npmjs.org/ethjs-util/-/ethjs-util-0.1.3.tgz", + "integrity": "sha1-39XqSkANxeQhqInK9H4IGtp4u1U=" + }, + "js-sha3": { + "version": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.5.5.tgz", + "integrity": "sha1-uvDA6MVK1ZA0R9+Wreekobynmko=" + } + } + }, + "ethjs-ens": { + "version": "https://registry.npmjs.org/ethjs-ens/-/ethjs-ens-2.0.0.tgz", + "integrity": "sha1-ZyLvx4fBe5pbJ+a0Jc2ZhvYlFOo=" + }, + "ethjs-filter": { + "version": "https://registry.npmjs.org/ethjs-filter/-/ethjs-filter-0.1.5.tgz", + "integrity": "sha1-ARKvYBfCRnfjK4/esg5hlgGbdZg=" + }, + "ethjs-format": { + "version": "https://registry.npmjs.org/ethjs-format/-/ethjs-format-0.2.0.tgz", + "integrity": "sha1-tKpRP8HVAnDY8QK/BvA8lJDTE5E=", + "dependencies": { + "ethjs-util": { + "version": "https://registry.npmjs.org/ethjs-util/-/ethjs-util-0.1.3.tgz", + "integrity": "sha1-39XqSkANxeQhqInK9H4IGtp4u1U=" + } + } + }, + "ethjs-query": { + "version": "https://registry.npmjs.org/ethjs-query/-/ethjs-query-0.2.6.tgz", + "integrity": "sha1-nY5gRLi/dt0zQPhDcWoiWbnJHTw=" + }, + "ethjs-rpc": { + "version": "https://registry.npmjs.org/ethjs-rpc/-/ethjs-rpc-0.1.5.tgz", + "integrity": "sha1-CZ4i8n3EwYtpeKSF/DaxsPeWkIA=" + }, + "ethjs-schema": { + "version": "https://registry.npmjs.org/ethjs-schema/-/ethjs-schema-0.1.5.tgz", + "integrity": "sha1-WXQOOzl3vNu5sRvDBoIB6Kzquw0=" + }, + "ethjs-util": { + "version": "https://registry.npmjs.org/ethjs-util/-/ethjs-util-0.1.4.tgz", + "integrity": "sha1-HItoeSV0RO9NPz+7rC3tEs2ZfZM=" + }, + "eve-raphael": { + "version": "https://registry.npmjs.org/eve-raphael/-/eve-raphael-0.5.0.tgz", + "integrity": "sha1-F8dUt5K+7z+maE15z1pHxjxM2jA=" + }, + "event-emitter": { + "version": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=" + }, + "event-stream": { + "version": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", + "integrity": "sha1-SrTJoPWlTbkzi0w02Gv86PSzVXE=", + "dev": true + }, + "eventemitter3": { + "version": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-1.2.0.tgz", + "integrity": "sha1-HIaZHYFq0eUEdQ5zh0Ik7PO+xQg=", + "dev": true + }, + "events": { + "version": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", + "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=" + }, + "events-to-array": { + "version": "https://registry.npmjs.org/events-to-array/-/events-to-array-1.1.2.tgz", + "integrity": "sha1-LUH1Y+H+QA7Uli/hpNXGp1Od9/Y=", + "dev": true + }, + "evp_bytestokey": { + "version": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.0.tgz", + "integrity": "sha1-SXtmrZ/vZc18CKYYCCS6FHa2blM=" + }, + "exit-hook": { + "version": "https://registry.npmjs.org/exit-hook/-/exit-hook-1.1.1.tgz", + "integrity": "sha1-8FyiM7SMBdVP/wd2XfhQfpXAL/g=" + }, + "expand-brackets": { + "version": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", + "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", + "dev": true + }, + "expand-range": { + "version": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", + "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=", + "dev": true + }, + "expand-template": { + "version": "https://registry.npmjs.org/expand-template/-/expand-template-1.0.3.tgz", + "integrity": "sha1-bDAzIxd6YrGyLAcCefeGEoe2mxo=" + }, + "expand-tilde": { + "version": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-1.2.2.tgz", + "integrity": "sha1-C4HrqJflo9MdHD0QL48BRB5VlEk=", + "dev": true + }, + "express": { + "version": "https://registry.npmjs.org/express/-/express-4.15.3.tgz", + "integrity": "sha1-urZdDwOqgMNYQIly/HAPkWlEtmI=" + }, + "extend": { + "version": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", + "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=" + }, + "extend-shallow": { + "version": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true + }, + "extension-link-enabler": { + "version": "https://registry.npmjs.org/extension-link-enabler/-/extension-link-enabler-1.0.0.tgz", + "integrity": "sha1-V7kZru7fOL6XJwuYmM7nimN+RvM=" + }, + "extensionizer": { + "version": "https://registry.npmjs.org/extensionizer/-/extensionizer-1.0.0.tgz", + "integrity": "sha1-AcIJu+ptnArLp3Epw6pKmpj8NTg=" + }, + "extglob": { + "version": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", + "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", + "dev": true + }, + "extsprintf": { + "version": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.0.2.tgz", + "integrity": "sha1-4QgOBljjALBilJkMxw4VAiNf1VA=" + }, + "eyes": { + "version": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz", + "integrity": "sha1-Ys8SAjTGg3hdkCNIqADvPgzCC8A=", + "dev": true + }, + "fake-merkle-patricia-tree": { + "version": "https://registry.npmjs.org/fake-merkle-patricia-tree/-/fake-merkle-patricia-tree-1.0.1.tgz", + "integrity": "sha1-S4w6z7Ugr635hgsfFM2M40As3dM=" + }, + "falafel": { + "version": "https://registry.npmjs.org/falafel/-/falafel-1.2.0.tgz", + "integrity": "sha1-wY0k71CRF0pJfzGM0ksCaiXN2rQ=", + "dependencies": { + "acorn": { + "version": "https://registry.npmjs.org/acorn/-/acorn-1.2.2.tgz", + "integrity": "sha1-yM4n3grMdtiW0rH6099YjZ6C8BQ=" + }, + "isarray": { + "version": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + } + } + }, + "fancy-log": { + "version": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.3.0.tgz", + "integrity": "sha1-Rb4X0Cu5kX1gzP/UmVyZnmyMmUg=" + }, + "fast-levenshtein": { + "version": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=" + }, + "faye-websocket": { + "version": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.7.3.tgz", + "integrity": "sha1-zEB0x/Sk39A69U3WXDVLE1EyzhE=", + "dev": true + }, + "fbjs": { + "version": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.12.tgz", + "integrity": "sha1-ELXZL3bUVXX9Y6IX1OoCvqL47QQ=", + "dependencies": { + "core-js": { + "version": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz", + "integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY=" + } + } + }, + "fetch-ponyfill": { + "version": "https://registry.npmjs.org/fetch-ponyfill/-/fetch-ponyfill-4.0.0.tgz", + "integrity": "sha1-GM/jjWnN5Crsccs6znnh8idik9o=", + "dependencies": { + "node-fetch": { + "version": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.6.3.tgz", + "integrity": "sha1-3CNO3WSJmC1Y6PDbT2lQKavNjAQ=" + } + } + }, + "figures": { + "version": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", + "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=" + }, + "file-entry-cache": { + "version": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-1.3.1.tgz", + "integrity": "sha1-RMYepgeuS+nBQC9B9EJwy/4zT/g=" + }, + "file-tree": { + "version": "https://registry.npmjs.org/file-tree/-/file-tree-1.0.0.tgz", + "integrity": "sha1-/a2ZnLf6REODULUUx4+TWzBuk+M=" + }, + "filename-regex": { + "version": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", + "integrity": "sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=", + "dev": true + }, + "fileset": { + "version": "https://registry.npmjs.org/fileset/-/fileset-0.1.8.tgz", + "integrity": "sha1-UGuRqTluqn4y+0KoQHfHoMc2t0E=", + "dev": true, + "optional": true, + "dependencies": { + "glob": { + "version": "https://registry.npmjs.org/glob/-/glob-3.2.11.tgz", + "integrity": "sha1-Spc/Y1uRkPcV0QmH1cAP0oFevj0=", + "dev": true, + "optional": true, + "dependencies": { + "minimatch": { + "version": "https://registry.npmjs.org/minimatch/-/minimatch-0.3.0.tgz", + "integrity": "sha1-J12O2qxPG7MyZHIInnlJyDlGmd0=", + "dev": true, + "optional": true + } + } + }, + "minimatch": { + "version": "https://registry.npmjs.org/minimatch/-/minimatch-0.4.0.tgz", + "integrity": "sha1-vSx9Bg0sjI/Xzefx8u0tWycP2xs=", + "dev": true, + "optional": true + } + } + }, + "fill-range": { + "version": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.3.tgz", + "integrity": "sha1-ULd9/X5Gm8dJJHCWNpn+eoSFpyM=", + "dev": true + }, + "finalhandler": { + "version": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.0.3.tgz", + "integrity": "sha1-70fneVDpmXgOhgIqVg4yF+DQzIk=" + }, + "find-global-packages": { + "version": "https://registry.npmjs.org/find-global-packages/-/find-global-packages-0.0.1.tgz", + "integrity": "sha1-S6f9/xfun6fagzCV94tejNvfPis=", + "dev": true + }, + "find-up": { + "version": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=" + }, + "findup-sync": { + "version": "https://registry.npmjs.org/findup-sync/-/findup-sync-0.4.3.tgz", + "integrity": "sha1-QAQ5Kee8YK3wt/SCfExudaDeyhI=", + "dev": true + }, + "fined": { + "version": "https://registry.npmjs.org/fined/-/fined-1.0.2.tgz", + "integrity": "sha1-WyhCS3YNdZiWC374SA3/itNmDpc=", + "dev": true + }, + "fireworm": { + "version": "https://registry.npmjs.org/fireworm/-/fireworm-0.7.1.tgz", + "integrity": "sha1-zPIPeUHxCIg/zduZOD2+bhhhx1g=", + "dev": true, + "dependencies": { + "async": { + "version": "https://registry.npmjs.org/async/-/async-0.2.10.tgz", + "integrity": "sha1-trvgsGdLnXGXCMo43owjfLUmw9E=", + "dev": true + }, + "lodash.debounce": { + "version": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-3.1.1.tgz", + "integrity": "sha1-gSIRw3ipTMKdWqTjNGzwv846ffU=", + "dev": true + }, + "lodash.flatten": { + "version": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-3.0.2.tgz", + "integrity": "sha1-3hz1d1j49EeTGdNcPpzGDEUBk4w=", + "dev": true + } + } + }, + "first-chunk-stream": { + "version": "https://registry.npmjs.org/first-chunk-stream/-/first-chunk-stream-1.0.0.tgz", + "integrity": "sha1-Wb+1DNkF9g18OUzT2ayqtOatk04=", + "dev": true + }, + "flagged-respawn": { + "version": "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-0.3.2.tgz", + "integrity": "sha1-/xke3c1wiKZ1smEP/8l2vpuAdLU=", + "dev": true + }, + "flat": { + "version": "https://registry.npmjs.org/flat/-/flat-1.0.0.tgz", + "integrity": "sha1-Ad/dW8vBScZrNe1AHh11PxqtjVk=" + }, + "flat-cache": { + "version": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.2.2.tgz", + "integrity": "sha1-+oZxTnLCHbiGAXYezy9VXRq8a5Y=" + }, + "flatten": { + "version": "https://registry.npmjs.org/flatten/-/flatten-0.0.1.tgz", + "integrity": "sha1-VURAdm2goNYDmZ9DNFP2wvxqdcE=" + }, + "flush-write-stream": { + "version": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.0.2.tgz", + "integrity": "sha1-yBuQ2HRnZvGmCaRoCZRsRd2K5Bc=" + }, + "for-each": { + "version": "https://registry.npmjs.org/for-each/-/for-each-0.3.2.tgz", + "integrity": "sha1-LEBFC5NI6X8oEyJZO6lnBLmr1NQ=" + }, + "for-in": { + "version": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", + "dev": true + }, + "for-own": { + "version": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", + "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", + "dev": true + }, + "foreach": { + "version": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", + "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=" + }, + "forever-agent": { + "version": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" + }, + "fork-stream": { + "version": "https://registry.npmjs.org/fork-stream/-/fork-stream-0.0.4.tgz", + "integrity": "sha1-24Sfznf2cIpfjzhq5TOgkHtUrnA=", + "dev": true + }, + "form-data": { + "version": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz", + "integrity": "sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE=" + }, + "formatio": { + "version": "https://registry.npmjs.org/formatio/-/formatio-1.1.1.tgz", + "integrity": "sha1-XtPM1jZVEJc4NGXZlhmRAOhhYek=", + "dev": true + }, + "forwarded": { + "version": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.0.tgz", + "integrity": "sha1-Ge+YdMSuHCl7zweP3mOgm2aoQ2M=" + }, + "fresh": { + "version": "https://registry.npmjs.org/fresh/-/fresh-0.5.0.tgz", + "integrity": "sha1-9HTKXmqSRtb9jglTz6m5yAWvp44=" + }, + "from": { + "version": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", + "integrity": "sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4=", + "dev": true + }, + "from2": { + "version": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", + "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=" + }, + "fs-exists-sync": { + "version": "https://registry.npmjs.org/fs-exists-sync/-/fs-exists-sync-0.1.0.tgz", + "integrity": "sha1-mC1ok6+RjnLQjeyehnP/K1qNat0=", + "dev": true + }, + "fs-extra": { + "version": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.30.0.tgz", + "integrity": "sha1-8jP/zAjU2n1DLapEl3aYnbHfk/A=" + }, + "fs-promise": { + "version": "https://registry.npmjs.org/fs-promise/-/fs-promise-1.0.0.tgz", + "integrity": "sha1-QkakzUVJfS7Vfm5LIhZ9OGSyNnk=", + "dev": true, + "dependencies": { + "any-promise": { + "version": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=", + "dev": true + }, + "fs-extra": { + "version": "https://registry.npmjs.org/fs-extra/-/fs-extra-1.0.0.tgz", + "integrity": "sha1-zTzl9+fLYUWIP8rjGR6Yd/hYeVA=", + "dev": true + } + } + }, + "fs.realpath": { + "version": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "fsevents": { + "version": "https://registry.npmjs.org/fsevents/-/fsevents-1.1.1.tgz", + "integrity": "sha1-8Z/Sj0Pur3YWgOUZogPE0LPTGv8=", + "dev": true, + "optional": true, + "dependencies": { + "abbrev": { + "version": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.0.tgz", + "integrity": "sha1-0FVMIlZjbi9W58LlrRg/hZQo2B8=", + "dev": true, + "optional": true + }, + "ansi-regex": { + "version": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "ansi-styles": { + "version": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true, + "optional": true + }, + "aproba": { + "version": "https://registry.npmjs.org/aproba/-/aproba-1.1.1.tgz", + "integrity": "sha1-ldNgDwdxCqDpKYxyatXs8urLq6s=", + "dev": true, + "optional": true + }, + "are-we-there-yet": { + "version": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.2.tgz", + "integrity": "sha1-gORw6VoIR5T+GJkmLFZnxuiN4bM=", + "dev": true, + "optional": true + }, + "asn1": { + "version": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", + "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=", + "dev": true, + "optional": true + }, + "assert-plus": { + "version": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz", + "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ=", + "dev": true, + "optional": true + }, + "asynckit": { + "version": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true, + "optional": true + }, + "aws-sign2": { + "version": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz", + "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8=", + "dev": true, + "optional": true + }, + "aws4": { + "version": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz", + "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=", + "dev": true, + "optional": true + }, + "balanced-match": { + "version": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz", + "integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg=", + "dev": true + }, + "bcrypt-pbkdf": { + "version": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", + "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", + "dev": true, + "optional": true + }, + "block-stream": { + "version": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz", + "integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=", + "dev": true + }, + "boom": { + "version": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz", + "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=", + "dev": true + }, + "brace-expansion": { + "version": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.6.tgz", + "integrity": "sha1-cZfX6qm4fmSDkOph/GbIRCdCDfk=", + "dev": true + }, + "buffer-shims": { + "version": "https://registry.npmjs.org/buffer-shims/-/buffer-shims-1.0.0.tgz", + "integrity": "sha1-mXjOMXOIxkmth5MCjDR37wRKi1E=", + "dev": true + }, + "caseless": { + "version": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz", + "integrity": "sha1-cVuW6phBWTzDMGeSP17GDr2k99c=", + "dev": true, + "optional": true + }, + "chalk": { + "version": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "optional": true + }, + "code-point-at": { + "version": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "dev": true + }, + "combined-stream": { + "version": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz", + "integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk=", + "dev": true + }, + "commander": { + "version": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz", + "integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=", + "dev": true, + "optional": true + }, + "concat-map": { + "version": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "console-control-strings": { + "version": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", + "dev": true + }, + "core-util-is": { + "version": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "cryptiles": { + "version": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz", + "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=", + "dev": true, + "optional": true + }, + "dashdash": { + "version": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "dev": true, + "optional": true, + "dependencies": { + "assert-plus": { + "version": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true, + "optional": true + } + } + }, + "debug": { + "version": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", + "dev": true, + "optional": true + }, + "deep-extend": { + "version": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.4.1.tgz", + "integrity": "sha1-7+QRPQgIX05vlod1mBD4B0aeIlM=", + "dev": true, + "optional": true + }, + "delayed-stream": { + "version": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true + }, + "delegates": { + "version": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", + "dev": true, + "optional": true + }, + "ecc-jsbn": { + "version": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", + "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", + "dev": true, + "optional": true + }, + "escape-string-regexp": { + "version": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true, + "optional": true + }, + "extend": { + "version": "https://registry.npmjs.org/extend/-/extend-3.0.0.tgz", + "integrity": "sha1-WkdDU7nzNT3dgXbf03uRyDpG8dQ=", + "dev": true, + "optional": true + }, + "extsprintf": { + "version": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.0.2.tgz", + "integrity": "sha1-4QgOBljjALBilJkMxw4VAiNf1VA=", + "dev": true + }, + "forever-agent": { + "version": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "dev": true, + "optional": true + }, + "form-data": { + "version": "https://registry.npmjs.org/form-data/-/form-data-2.1.2.tgz", + "integrity": "sha1-icNTQAi5fq2ky7FX1Y9vXfAl6uQ=", + "dev": true, + "optional": true + }, + "fs.realpath": { + "version": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "fstream": { + "version": "https://registry.npmjs.org/fstream/-/fstream-1.0.10.tgz", + "integrity": "sha1-YE6Kkv4m/9n2+uMDmdSYThqyKCI=", + "dev": true + }, + "fstream-ignore": { + "version": "https://registry.npmjs.org/fstream-ignore/-/fstream-ignore-1.0.5.tgz", + "integrity": "sha1-nDHa40dnAY/h0kmyTa2mfQktoQU=", + "dev": true, + "optional": true + }, + "gauge": { + "version": "https://registry.npmjs.org/gauge/-/gauge-2.7.3.tgz", + "integrity": "sha1-HCOFX5YvF7OtPQ3HRD8wRULt/gk=", + "dev": true, + "optional": true + }, + "generate-function": { + "version": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz", + "integrity": "sha1-aFj+fAlpt9TpCTM3ZHrHn2DfvnQ=", + "dev": true, + "optional": true + }, + "generate-object-property": { + "version": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz", + "integrity": "sha1-nA4cQDCM6AT0eDYYuTf6iPmdUNA=", + "dev": true, + "optional": true + }, + "getpass": { + "version": "https://registry.npmjs.org/getpass/-/getpass-0.1.6.tgz", + "integrity": "sha1-KD/9n8ElaECHUxHBtg6MQBhxEOY=", + "dev": true, + "optional": true, + "dependencies": { + "assert-plus": { + "version": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true, + "optional": true + } + } + }, + "glob": { + "version": "https://registry.npmjs.org/glob/-/glob-7.1.1.tgz", + "integrity": "sha1-gFIR3wT6rxxjo2ADBs31reULLsg=", + "dev": true + }, + "graceful-fs": { + "version": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", + "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", + "dev": true + }, + "graceful-readlink": { + "version": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", + "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=", + "dev": true, + "optional": true + }, + "har-validator": { + "version": "https://registry.npmjs.org/har-validator/-/har-validator-2.0.6.tgz", + "integrity": "sha1-zcvAgYgmWtEZtqWnyKtw7s+10n0=", + "dev": true, + "optional": true + }, + "has-ansi": { + "version": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "dev": true, + "optional": true + }, + "has-unicode": { + "version": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", + "dev": true, + "optional": true + }, + "hawk": { + "version": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz", + "integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=", + "dev": true, + "optional": true + }, + "hoek": { + "version": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", + "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=", + "dev": true + }, + "http-signature": { + "version": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz", + "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=", + "dev": true, + "optional": true + }, + "inflight": { + "version": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true + }, + "inherits": { + "version": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "ini": { + "version": "https://registry.npmjs.org/ini/-/ini-1.3.4.tgz", + "integrity": "sha1-BTfLedr1m1mhpRff9wbIbsA5Fi4=", + "dev": true, + "optional": true + }, + "is-fullwidth-code-point": { + "version": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true + }, + "is-my-json-valid": { + "version": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.15.0.tgz", + "integrity": "sha1-k27do8o8IR/ZjzstPgjaQ/eykVs=", + "dev": true, + "optional": true + }, + "is-property": { + "version": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", + "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=", + "dev": true, + "optional": true + }, + "is-typedarray": { + "version": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true, + "optional": true + }, + "isarray": { + "version": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "isstream": { + "version": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", + "dev": true, + "optional": true + }, + "jodid25519": { + "version": "https://registry.npmjs.org/jodid25519/-/jodid25519-1.0.2.tgz", + "integrity": "sha1-BtSRIlUJNBlHfUJWM2BuDpB4KWc=", + "dev": true, + "optional": true + }, + "jsbn": { + "version": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "dev": true, + "optional": true + }, + "json-schema": { + "version": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", + "dev": true, + "optional": true + }, + "json-stringify-safe": { + "version": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true, + "optional": true + }, + "jsonpointer": { + "version": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz", + "integrity": "sha1-T9kss04OnbPInIYi7PUfm5eMbLk=", + "dev": true, + "optional": true + }, + "jsprim": { + "version": "https://registry.npmjs.org/jsprim/-/jsprim-1.3.1.tgz", + "integrity": "sha1-KnJW9wQSop7jZwqspiWZTE3P8lI=", + "dev": true, + "optional": true + }, + "mime-db": { + "version": "https://registry.npmjs.org/mime-db/-/mime-db-1.26.0.tgz", + "integrity": "sha1-6v/NDk/Gk1z4E02iRuLmw1MFrf8=", + "dev": true + }, + "mime-types": { + "version": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.14.tgz", + "integrity": "sha1-9+99l1g/yvO30oK2+LVnnaselO4=", + "dev": true + }, + "minimatch": { + "version": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.3.tgz", + "integrity": "sha1-Kk5AkLlrLbBqnX3wEFWmKnfJt3Q=", + "dev": true + }, + "minimist": { + "version": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + }, + "mkdirp": { + "version": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true + }, + "ms": { + "version": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", + "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=", + "dev": true, + "optional": true + }, + "node-pre-gyp": { + "version": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.6.33.tgz", + "integrity": "sha1-ZArFUZj2qSWXLgwWxKwmoDTV7Mk=", + "dev": true, + "optional": true + }, + "nopt": { + "version": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", + "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", + "dev": true, + "optional": true + }, + "npmlog": { + "version": "https://registry.npmjs.org/npmlog/-/npmlog-4.0.2.tgz", + "integrity": "sha1-0DlQ4OeM4VJ7om0qdZLpNIrD518=", + "dev": true, + "optional": true + }, + "number-is-nan": { + "version": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true + }, + "oauth-sign": { + "version": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", + "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=", + "dev": true, + "optional": true + }, + "object-assign": { + "version": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true, + "optional": true + }, + "once": { + "version": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true + }, + "path-is-absolute": { + "version": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "pinkie": { + "version": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "dev": true, + "optional": true + }, + "pinkie-promise": { + "version": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "dev": true, + "optional": true + }, + "process-nextick-args": { + "version": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", + "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=", + "dev": true + }, + "punycode": { + "version": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true, + "optional": true + }, + "qs": { + "version": "https://registry.npmjs.org/qs/-/qs-6.3.1.tgz", + "integrity": "sha1-kYwLO802Z5dyuvE1say0wWUe150=", + "dev": true, + "optional": true + }, + "rc": { + "version": "https://registry.npmjs.org/rc/-/rc-1.1.7.tgz", + "integrity": "sha1-xepWS7B6/5/TpbMukGwdOmWUD+o=", + "dev": true, + "optional": true, + "dependencies": { + "minimist": { + "version": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true, + "optional": true + } + } + }, + "readable-stream": { + "version": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.2.2.tgz", + "integrity": "sha1-qeb+w8fdqF+LsbO6cChgRVb8gl4=", + "dev": true, + "optional": true + }, + "request": { + "version": "https://registry.npmjs.org/request/-/request-2.79.0.tgz", + "integrity": "sha1-Tf5b9r6LjNw3/Pk+BLZVd3InEN4=", + "dev": true, + "optional": true + }, + "rimraf": { + "version": "https://registry.npmjs.org/rimraf/-/rimraf-2.5.4.tgz", + "integrity": "sha1-loAAk8vxoMhr2VtGJUZ1NcKd+gQ=", + "dev": true + }, + "semver": { + "version": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", + "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=", + "dev": true, + "optional": true + }, + "set-blocking": { + "version": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true, + "optional": true + }, + "signal-exit": { + "version": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "dev": true, + "optional": true + }, + "sntp": { + "version": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz", + "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=", + "dev": true, + "optional": true + }, + "sshpk": { + "version": "https://registry.npmjs.org/sshpk/-/sshpk-1.10.2.tgz", + "integrity": "sha1-1agEziJpVRVjjnmNviMnPeBwpfo=", + "dev": true, + "optional": true, + "dependencies": { + "assert-plus": { + "version": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true, + "optional": true + } + } + }, + "string_decoder": { + "version": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + }, + "string-width": { + "version": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true + }, + "stringstream": { + "version": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", + "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=", + "dev": true, + "optional": true + }, + "strip-ansi": { + "version": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true + }, + "strip-json-comments": { + "version": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true, + "optional": true + }, + "supports-color": { + "version": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true, + "optional": true + }, + "tar": { + "version": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz", + "integrity": "sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=", + "dev": true + }, + "tar-pack": { + "version": "https://registry.npmjs.org/tar-pack/-/tar-pack-3.3.0.tgz", + "integrity": "sha1-MJMYFkGPVa/E0hd1r91nIM7kXa4=", + "dev": true, + "optional": true, + "dependencies": { + "once": { + "version": "https://registry.npmjs.org/once/-/once-1.3.3.tgz", + "integrity": "sha1-suJhVXzkwxTsgwTz+oJmPkKXyiA=", + "dev": true, + "optional": true + }, + "readable-stream": { + "version": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.1.5.tgz", + "integrity": "sha1-ZvqLcg4UOLNkaB8q0aY8YYRIydA=", + "dev": true, + "optional": true + } + } + }, + "tough-cookie": { + "version": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.2.tgz", + "integrity": "sha1-8IH3bkyFcg5sN6X6ztc3FQ2EByo=", + "dev": true, + "optional": true + }, + "tunnel-agent": { + "version": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz", + "integrity": "sha1-Y3PbdpCf5XDgjXNYM2Xtgop07us=", + "dev": true, + "optional": true + }, + "tweetnacl": { + "version": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "dev": true, + "optional": true + }, + "uid-number": { + "version": "https://registry.npmjs.org/uid-number/-/uid-number-0.0.6.tgz", + "integrity": "sha1-DqEOgDXo61uOREnwbaHHMGY7qoE=", + "dev": true, + "optional": true + }, + "util-deprecate": { + "version": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "uuid": { + "version": "https://registry.npmjs.org/uuid/-/uuid-3.0.1.tgz", + "integrity": "sha1-ZUS7ot/ajBzxfmKaOjBeK7H+5sE=", + "dev": true, + "optional": true + }, + "verror": { + "version": "https://registry.npmjs.org/verror/-/verror-1.3.6.tgz", + "integrity": "sha1-z/XfEpRtKX0rqu+qJoniW+AcAFw=", + "dev": true, + "optional": true + }, + "wide-align": { + "version": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.0.tgz", + "integrity": "sha1-QO3egCpx/qHwcNo+YtzaLnrdlq0=", + "dev": true, + "optional": true + }, + "wrappy": { + "version": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "xtend": { + "version": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", + "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=", + "dev": true, + "optional": true + } + } + }, + "function-bind": { + "version": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.0.tgz", + "integrity": "sha1-FhdnFMgBeY5Ojyz391KUZ7tKV3E=" + }, + "function.prototype.name": { + "version": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.0.0.tgz", + "integrity": "sha1-X1I8pk5JGl+Vq6gMweORCAoUSC4=", + "dev": true + }, + "functional-red-black-tree": { + "version": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=" + }, + "gauge": { + "version": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=" + }, + "generate-function": { + "version": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz", + "integrity": "sha1-aFj+fAlpt9TpCTM3ZHrHn2DfvnQ=" + }, + "generate-object-property": { + "version": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz", + "integrity": "sha1-nA4cQDCM6AT0eDYYuTf6iPmdUNA=" + }, + "get-caller-file": { + "version": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.2.tgz", + "integrity": "sha1-9wLmMSfn4jHBYKgMFVSstw1QR+U=" + }, + "get-stdin": { + "version": "https://registry.npmjs.org/get-stdin/-/get-stdin-3.0.2.tgz", + "integrity": "sha1-wc7SS5A5s43thb3xYeV3E7bdSr4=", + "dev": true + }, + "get-values": { + "version": "https://registry.npmjs.org/get-values/-/get-values-0.1.0.tgz", + "integrity": "sha1-OsA1tlpEkj012y/Ct7ojIrbD8p4=", + "dev": true + }, + "getpass": { + "version": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dependencies": { + "assert-plus": { + "version": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + } + } + }, + "github-from-package": { + "version": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha1-l/tdlr/eiXMxPyDoKI75oWf6ZM4=" + }, + "gl-mat4": { + "version": "https://registry.npmjs.org/gl-mat4/-/gl-mat4-1.1.4.tgz", + "integrity": "sha1-HolbVYkuVqiWhnq9g30483oXgIY=" + }, + "gl-vec3": { + "version": "https://registry.npmjs.org/gl-vec3/-/gl-vec3-1.0.3.tgz", + "integrity": "sha1-EQ/Yl9Byn2OYMHOBVn0JRJQb8is=" + }, + "glob": { + "version": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha1-wZyd+aAocC1nhhI4SmVSQExjbRU=" + }, + "glob-all": { + "version": "https://registry.npmjs.org/glob-all/-/glob-all-3.1.0.tgz", + "integrity": "sha1-iRPd+17hrHgSZWJBsD1SF8ZLAqs=", + "dev": true, + "dependencies": { + "minimist": { + "version": "https://registry.npmjs.org/minimist/-/minimist-0.1.0.tgz", + "integrity": "sha1-md9lelJXTCHJBXSX33QnkLK0wN4=", + "dev": true + }, + "yargs": { + "version": "https://registry.npmjs.org/yargs/-/yargs-1.2.6.tgz", + "integrity": "sha1-nHtKgv1dWVsr8Xq23MQxNUMv40s=", + "dev": true + } + } + }, + "glob-base": { + "version": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz", + "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=", + "dev": true + }, + "glob-parent": { + "version": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", + "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", + "dev": true + }, + "glob-stream": { + "version": "https://registry.npmjs.org/glob-stream/-/glob-stream-5.3.5.tgz", + "integrity": "sha1-pVZlqajM3EGRWofHAeMtTgFvrSI=", + "dev": true, + "dependencies": { + "glob": { + "version": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", + "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", + "dev": true + }, + "glob-parent": { + "version": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "dev": true + }, + "is-extglob": { + "version": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-glob": { + "version": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true + }, + "isarray": { + "version": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "readable-stream": { + "version": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "dev": true + }, + "string_decoder": { + "version": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + }, + "through2": { + "version": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz", + "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=", + "dev": true + } + } + }, + "glob-watcher": { + "version": "https://registry.npmjs.org/glob-watcher/-/glob-watcher-3.2.0.tgz", + "integrity": "sha1-/8Gi09B3g7Zy9eIXmaTQs/7ZLa8=", + "dev": true + }, + "global": { + "version": "https://registry.npmjs.org/global/-/global-4.3.2.tgz", + "integrity": "sha1-52mJJopsdMOJCLEwWxD8DjlOnQ8=" + }, + "global-modules": { + "version": "https://registry.npmjs.org/global-modules/-/global-modules-0.2.3.tgz", + "integrity": "sha1-6lo77ULG1s6ZWk+KEmm12uIjgo0=", + "dev": true + }, + "global-prefix": { + "version": "https://registry.npmjs.org/global-prefix/-/global-prefix-0.1.5.tgz", + "integrity": "sha1-jTvGuNo8qBEqFg2NSW/wRiv+948=", + "dev": true, + "dependencies": { + "which": { + "version": "https://registry.npmjs.org/which/-/which-1.2.14.tgz", + "integrity": "sha1-mofEN48D6CfOyvGs31bHNsAcFOU=", + "dev": true + } + } + }, + "globals": { + "version": "https://registry.npmjs.org/globals/-/globals-9.17.0.tgz", + "integrity": "sha1-DAymltm5u2lNLlRwvTd3fKrVAoY=" + }, + "globby": { + "version": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz", + "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=" + }, + "glogg": { + "version": "https://registry.npmjs.org/glogg/-/glogg-1.0.0.tgz", + "integrity": "sha1-f+DxmfV6yQbPUS/urY+Q7kooT8U=" + }, + "graceful-fs": { + "version": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", + "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=" + }, + "graceful-readlink": { + "version": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", + "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=", + "dev": true + }, + "growl": { + "version": "https://registry.npmjs.org/growl/-/growl-1.9.2.tgz", + "integrity": "sha1-Dqd0NxXbjY3ixe3hd14bRayFwC8=", + "dev": true + }, + "growly": { + "version": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", + "integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=", + "dev": true + }, + "gulp": { + "version": "git://github.com/gulpjs/gulp.git#38246c3f8b6dbb8d4ef657183e92d90c8299e22f", + "dev": true, + "dependencies": { + "camelcase": { + "version": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", + "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=", + "dev": true + }, + "gulp-cli": { + "version": "https://registry.npmjs.org/gulp-cli/-/gulp-cli-1.3.0.tgz", + "integrity": "sha1-pr+7i+NTQb4pCuRc0+QBBxIW7dQ=", + "dev": true + }, + "window-size": { + "version": "https://registry.npmjs.org/window-size/-/window-size-0.1.4.tgz", + "integrity": "sha1-+OGqHuWlPsW/FR/6CXQqatdpeHY=", + "dev": true + }, + "yargs": { + "version": "https://registry.npmjs.org/yargs/-/yargs-3.32.0.tgz", + "integrity": "sha1-AwiOnr+edWtpdRYR0qXvWRSCyZU=", + "dev": true + } + } + }, + "gulp-eslint": { + "version": "https://registry.npmjs.org/gulp-eslint/-/gulp-eslint-2.1.0.tgz", + "integrity": "sha1-P9X+C3I2ZR8VuNS/sUB8O3TQE2w=" + }, + "gulp-if": { + "version": "https://registry.npmjs.org/gulp-if/-/gulp-if-2.0.2.tgz", + "integrity": "sha1-pJe351cwBQQcqivIt92jyARE1ik=", + "dev": true + }, + "gulp-json-editor": { + "version": "https://registry.npmjs.org/gulp-json-editor/-/gulp-json-editor-2.2.1.tgz", + "integrity": "sha1-fE3XR36NBtxdxJwLgedFzbBPl7s=", + "dev": true, + "dependencies": { + "detect-indent": { + "version": "https://registry.npmjs.org/detect-indent/-/detect-indent-2.0.0.tgz", + "integrity": "sha1-cg/1Hk2Xt2iE9r9XKSNIsT396Tk=", + "dev": true + }, + "isarray": { + "version": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "minimist": { + "version": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + }, + "readable-stream": { + "version": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "dev": true + }, + "repeating": { + "version": "https://registry.npmjs.org/repeating/-/repeating-1.1.3.tgz", + "integrity": "sha1-PUEUIYh3U3SU+X93+Xhfq4EPpKw=", + "dev": true + }, + "string_decoder": { + "version": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + }, + "through2": { + "version": "https://registry.npmjs.org/through2/-/through2-0.5.1.tgz", + "integrity": "sha1-390BLrnHAOIyP9M084rGIqs3Lac=", + "dev": true + }, + "xtend": { + "version": "https://registry.npmjs.org/xtend/-/xtend-3.0.0.tgz", + "integrity": "sha1-XM50B7r2Qsunvs2laBEcST9ZZlo=", + "dev": true + } + } + }, + "gulp-livereload": { + "version": "https://registry.npmjs.org/gulp-livereload/-/gulp-livereload-3.8.1.tgz", + "integrity": "sha1-APdEstdJ0+njdGWJyKRKysd5tQ8=", + "dev": true, + "dependencies": { + "ansi-regex": { + "version": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-0.2.1.tgz", + "integrity": "sha1-DY6UaWej2BQ/k+JOKYUl/BsiNfk=", + "dev": true + }, + "ansi-styles": { + "version": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-1.1.0.tgz", + "integrity": "sha1-6uy/Zs1waIJ2Cy9GkVgrj1XXp94=", + "dev": true + }, + "chalk": { + "version": "https://registry.npmjs.org/chalk/-/chalk-0.5.1.tgz", + "integrity": "sha1-Zjs6ZItotV0EaQ1JFnqoN4WPIXQ=", + "dev": true + }, + "has-ansi": { + "version": "https://registry.npmjs.org/has-ansi/-/has-ansi-0.1.0.tgz", + "integrity": "sha1-hPJlqujA5qiKEtcCKJS3VoiUxi4=", + "dev": true + }, + "lodash.assign": { + "version": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-3.2.0.tgz", + "integrity": "sha1-POnwI0tLIiPilrj6CsH+6OvKZPo=", + "dev": true + }, + "strip-ansi": { + "version": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-0.3.0.tgz", + "integrity": "sha1-JfSOoiynkYfzF0pNuHWTR7sSYiA=", + "dev": true + }, + "supports-color": { + "version": "https://registry.npmjs.org/supports-color/-/supports-color-0.2.0.tgz", + "integrity": "sha1-2S3iaU6z9nMjlz1649i1W0wiGQo=", + "dev": true + } + } + }, + "gulp-match": { + "version": "https://registry.npmjs.org/gulp-match/-/gulp-match-1.0.3.tgz", + "integrity": "sha1-kcfA1/Kb7NZgbVfYCn+Hdqh6uo4=", + "dev": true + }, + "gulp-replace": { + "version": "https://registry.npmjs.org/gulp-replace/-/gulp-replace-0.5.4.tgz", + "integrity": "sha1-aaZ5FLvRPFYr/xT1BKQDeWqg2qk=", + "dev": true + }, + "gulp-sourcemaps": { + "version": "https://registry.npmjs.org/gulp-sourcemaps/-/gulp-sourcemaps-1.12.0.tgz", + "integrity": "sha1-eG+XyUoPloSSRl1wVY4EJCxnlZg=", + "dev": true, + "dependencies": { + "vinyl": { + "version": "https://registry.npmjs.org/vinyl/-/vinyl-1.2.0.tgz", + "integrity": "sha1-XIgDbPVl5d8FVYv8kR+GVt8hiIQ=", + "dev": true + } + } + }, + "gulp-util": { + "version": "https://registry.npmjs.org/gulp-util/-/gulp-util-3.0.8.tgz", + "integrity": "sha1-AFTh50RQLifATBh8PsxQXdVLu08=", + "dependencies": { + "minimist": { + "version": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" + }, + "object-assign": { + "version": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz", + "integrity": "sha1-m+3VygiXlJvKR+f/QIBi1Un1h/I=" + } + } + }, + "gulp-watch": { + "version": "https://registry.npmjs.org/gulp-watch/-/gulp-watch-4.3.11.tgz", + "integrity": "sha1-Fi/FY96fx3DpH5p845VVE6mhGMA=", + "dev": true, + "dependencies": { + "glob-parent": { + "version": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "dev": true + }, + "is-extglob": { + "version": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-glob": { + "version": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true + }, + "vinyl": { + "version": "https://registry.npmjs.org/vinyl/-/vinyl-1.2.0.tgz", + "integrity": "sha1-XIgDbPVl5d8FVYv8kR+GVt8hiIQ=", + "dev": true + } + } + }, + "gulp-zip": { + "version": "https://registry.npmjs.org/gulp-zip/-/gulp-zip-3.2.0.tgz", + "integrity": "sha1-69GY2ubcLV9E2BRWnI7EIRipPvk=", + "dev": true + }, + "gulplog": { + "version": "https://registry.npmjs.org/gulplog/-/gulplog-1.0.0.tgz", + "integrity": "sha1-4oxNRdBey77YGDY86PnFkmIp/+U=" + }, + "handlebars": { + "version": "https://registry.npmjs.org/handlebars/-/handlebars-1.3.0.tgz", + "integrity": "sha1-npsTCpPjiUkTItl1zz7BgYw3zjQ=", + "dev": true, + "optional": true, + "dependencies": { + "optimist": { + "version": "https://registry.npmjs.org/optimist/-/optimist-0.3.7.tgz", + "integrity": "sha1-yQlBrVnkJzMokjB00s8ufLxuwNk=", + "dev": true, + "optional": true + } + } + }, + "har-schema": { + "version": "https://registry.npmjs.org/har-schema/-/har-schema-1.0.5.tgz", + "integrity": "sha1-0mMTX0MwfALGAq/I/pWXDAFRNp4=" + }, + "har-validator": { + "version": "https://registry.npmjs.org/har-validator/-/har-validator-4.2.1.tgz", + "integrity": "sha1-M0gdDxu/9gDdID11gSpqX7oALio=" + }, + "has": { + "version": "https://registry.npmjs.org/has/-/has-1.0.1.tgz", + "integrity": "sha1-hGFzP1OLCDfJNh45qauelwTcLyg=" + }, + "has-ansi": { + "version": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=" + }, + "has-binary": { + "version": "https://registry.npmjs.org/has-binary/-/has-binary-0.1.7.tgz", + "integrity": "sha1-aOYesWIQyVRaClzOBqhzkS/h5ow=", + "dev": true, + "dependencies": { + "isarray": { + "version": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + } + } + }, + "has-color": { + "version": "https://registry.npmjs.org/has-color/-/has-color-0.1.7.tgz", + "integrity": "sha1-ZxRKUmDDT8PMpnfQQdr1L+e3iy8=", + "dev": true + }, + "has-cors": { + "version": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz", + "integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk=", + "dev": true + }, + "has-gulplog": { + "version": "https://registry.npmjs.org/has-gulplog/-/has-gulplog-0.1.0.tgz", + "integrity": "sha1-ZBTIKRNpfaUVkDl9r7EvIpZ4Ec4=" + }, + "has-unicode": { + "version": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" + }, + "hash-base": { + "version": "https://registry.npmjs.org/hash-base/-/hash-base-2.0.2.tgz", + "integrity": "sha1-ZuodhW206KVHDK32/OI65SRO8uE=" + }, + "hash.js": { + "version": "https://registry.npmjs.org/hash.js/-/hash.js-1.0.3.tgz", + "integrity": "sha1-EzL/ABVsCg/92CNgE9B7d6BFFXM=" + }, + "hat": { + "version": "https://registry.npmjs.org/hat/-/hat-0.0.3.tgz", + "integrity": "sha1-uwFKnmSzeIrtgAWRdBPU/z1QLYo=" + }, + "hawk": { + "version": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz", + "integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=" + }, + "hdkey": { + "version": "https://registry.npmjs.org/hdkey/-/hdkey-0.7.1.tgz", + "integrity": "sha1-yu5L6BqneSHpCbjSKN0PKayu5jI=" + }, + "hmac-drbg": { + "version": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=" + }, + "hoek": { + "version": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", + "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=" + }, + "hoist-non-react-statics": { + "version": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-1.2.0.tgz", + "integrity": "sha1-qkSM8JhtVcxAdzsXF0t90GbLfPs=" + }, + "home-or-tmp": { + "version": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-2.0.0.tgz", + "integrity": "sha1-42w/LSyufXRqhX440Y1fMqeILbg=" + }, + "homedir-polyfill": { + "version": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.1.tgz", + "integrity": "sha1-TCu8inWJmP7r9e1oWA921GdotLw=", + "dev": true + }, + "hosted-git-info": { + "version": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.4.2.tgz", + "integrity": "sha1-AHa59GonBQbduq6lZJaJdGBhKmc=" + }, + "html-select": { + "version": "https://registry.npmjs.org/html-select/-/html-select-2.3.24.tgz", + "integrity": "sha1-Rq1tcS5zLPMcZznV0BEKX6vxdYU=", + "dev": true, + "dependencies": { + "isarray": { + "version": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "minimist": { + "version": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", + "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=", + "dev": true + }, + "readable-stream": { + "version": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "dev": true + }, + "string_decoder": { + "version": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + }, + "through2": { + "version": "https://registry.npmjs.org/through2/-/through2-1.1.1.tgz", + "integrity": "sha1-CEfLxESfNAVXTb3M2buEG4OsNUU=", + "dev": true + } + } + }, + "html-tokenize": { + "version": "https://registry.npmjs.org/html-tokenize/-/html-tokenize-1.2.5.tgz", + "integrity": "sha1-flupnstR75Buyaf83ubKMmfHiX4=", + "dev": true, + "dependencies": { + "isarray": { + "version": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "minimist": { + "version": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", + "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=", + "dev": true + }, + "object-keys": { + "version": "https://registry.npmjs.org/object-keys/-/object-keys-0.4.0.tgz", + "integrity": "sha1-KKaq50KN0sOpLz2V8hM13SBOAzY=", + "dev": true + }, + "readable-stream": { + "version": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "dev": true + }, + "string_decoder": { + "version": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + }, + "through2": { + "version": "https://registry.npmjs.org/through2/-/through2-0.4.2.tgz", + "integrity": "sha1-2/WGYDEVHsg1K7bE22SiKSqEC5s=", + "dev": true + }, + "xtend": { + "version": "https://registry.npmjs.org/xtend/-/xtend-2.1.2.tgz", + "integrity": "sha1-bv7MKk2tjmlixJAbM3znuoe10os=", + "dev": true + } + } + }, + "htmlescape": { + "version": "https://registry.npmjs.org/htmlescape/-/htmlescape-1.1.1.tgz", + "integrity": "sha1-OgPtwiFLyjtmQko+eVk0lQnLA1E=", + "dev": true + }, + "htmlparser2": { + "version": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.9.2.tgz", + "integrity": "sha1-G9+HrMoPP55T+k/M6w9LTLsAszg=", + "dev": true + }, + "http-errors": { + "version": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.1.tgz", + "integrity": "sha1-X4uO2YrKVFZWv1cplzh/kEpyIlc=" + }, + "http-proxy": { + "version": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.16.2.tgz", + "integrity": "sha1-Bt/ykpUr9k2+hHH6nfcwZtTzd0I=", + "dev": true + }, + "http-signature": { + "version": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz", + "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=" + }, + "https-browserify": { + "version": "https://registry.npmjs.org/https-browserify/-/https-browserify-0.0.1.tgz", + "integrity": "sha1-P5E2XKvmC3ftDruiS0VOPgnZWoI=", + "dev": true + }, + "i": { + "version": "https://registry.npmjs.org/i/-/i-0.3.5.tgz", + "integrity": "sha1-HSuFQVjsgWkRPGy39raAHpniEdU=", + "dev": true + }, + "iconv-lite": { + "version": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.17.tgz", + "integrity": "sha1-T9qjs4rLwsAxsEXQ7c3+HsqxjI0=" + }, + "idb-global": { + "version": "https://registry.npmjs.org/idb-global/-/idb-global-1.0.0.tgz", + "integrity": "sha1-srLkgDqO+mN2yTrzBW5qQ1atiW4=" + }, + "identicon.js": { + "version": "https://registry.npmjs.org/identicon.js/-/identicon.js-1.3.0.tgz", + "integrity": "sha1-e/uzhrd14HgalVV4urcegk5oaeA=" + }, + "idna-uts46": { + "version": "https://registry.npmjs.org/idna-uts46/-/idna-uts46-1.1.0.tgz", + "integrity": "sha1-vgmLK3wcq/vvh6i4D2JvrDc2auo=" + }, + "ieee754": { + "version": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.8.tgz", + "integrity": "sha1-vjPUCsEO8ZJnAfbwii2G+/0a0+Q=", + "dev": true + }, + "iframe": { + "version": "https://registry.npmjs.org/iframe/-/iframe-1.0.0.tgz", + "integrity": "sha1-WOdIIrF4oFedCc0WlkD7lTdHDvU=" + }, + "iframe-stream": { + "version": "https://registry.npmjs.org/iframe-stream/-/iframe-stream-1.0.2.tgz", + "integrity": "sha1-PH622TTnXX3V5l0l76XPR8sGPAs=" + }, + "ignore": { + "version": "https://registry.npmjs.org/ignore/-/ignore-3.3.3.tgz", + "integrity": "sha1-QyNS5XrM2HqzEQ6C0/6g5HgSFW0=" + }, + "ignorepatterns": { + "version": "https://registry.npmjs.org/ignorepatterns/-/ignorepatterns-1.1.0.tgz", + "integrity": "sha1-rI9DbyI5td+2bV8NOpBKh6xnzF4=", + "dev": true + }, + "immediate": { + "version": "https://registry.npmjs.org/immediate/-/immediate-3.2.3.tgz", + "integrity": "sha1-0UD6j2FGWb1lQSMwl92qwlzdmRw=" + }, + "imurmurhash": { + "version": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=" + }, + "in-publish": { + "version": "https://registry.npmjs.org/in-publish/-/in-publish-2.0.0.tgz", + "integrity": "sha1-4g/146KvwmkDILbcVSaCqcf631E=" + }, + "indexof": { + "version": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", + "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=", + "dev": true + }, + "inflight": { + "version": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=" + }, + "inherits": { + "version": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "ini": { + "version": "https://registry.npmjs.org/ini/-/ini-1.3.4.tgz", + "integrity": "sha1-BTfLedr1m1mhpRff9wbIbsA5Fi4=" + }, + "inject-css": { + "version": "https://registry.npmjs.org/inject-css/-/inject-css-0.1.1.tgz", + "integrity": "sha1-7z/8eOwCbJbiNV2g3zKRfjUmQVw=" + }, + "inline-source-map": { + "version": "https://registry.npmjs.org/inline-source-map/-/inline-source-map-0.6.2.tgz", + "integrity": "sha1-+Tk0ccGKedFyT4Y/o4tYY3Ct4qU=", + "dev": true + }, + "inquirer": { + "version": "https://registry.npmjs.org/inquirer/-/inquirer-0.12.0.tgz", + "integrity": "sha1-HvK/1jUE3wvHV4X/+MLEHfEvB34=" + }, + "insert-module-globals": { + "version": "https://registry.npmjs.org/insert-module-globals/-/insert-module-globals-7.0.1.tgz", + "integrity": "sha1-wDv04BywhtW15azorQr+eInWOMM=", + "dev": true, + "dependencies": { + "concat-stream": { + "version": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.5.2.tgz", + "integrity": "sha1-cIl4Yk2FavQaWnQd790mHadSwmY=", + "dev": true + }, + "process": { + "version": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", + "dev": true + }, + "readable-stream": { + "version": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", + "integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44=", + "dev": true + }, + "string_decoder": { + "version": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + } + } + }, + "interpret": { + "version": "https://registry.npmjs.org/interpret/-/interpret-1.0.3.tgz", + "integrity": "sha1-y8NcYu7uc/Gat7EKgBURQBr8D5A=", + "dev": true + }, + "invariant": { + "version": "https://registry.npmjs.org/invariant/-/invariant-2.2.2.tgz", + "integrity": "sha1-nh9WrArNtr8wMwbzOL47IErmA2A=" + }, + "invert-kv": { + "version": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", + "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=" + }, + "ipaddr.js": { + "version": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.3.0.tgz", + "integrity": "sha1-HgOlL9rYOou7KyXL9JmLTP/NPew=" + }, + "is-absolute": { + "version": "https://registry.npmjs.org/is-absolute/-/is-absolute-0.2.6.tgz", + "integrity": "sha1-IN5p89uULvLYe5wto28XIjWxtes=", + "dev": true + }, + "is-arrayish": { + "version": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" + }, + "is-binary-path": { + "version": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", + "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", + "dev": true + }, + "is-buffer": { + "version": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.5.tgz", + "integrity": "sha1-Hzsm72E7IUuIy8ojzGwB2Hlh7sw=", + "dev": true + }, + "is-builtin-module": { + "version": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", + "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=" + }, + "is-callable": { + "version": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.3.tgz", + "integrity": "sha1-hut1OSgF3cM69xySoO7fdO52BLI=" + }, + "is-date-object": { + "version": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", + "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=" + }, + "is-dotfile": { + "version": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz", + "integrity": "sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE=", + "dev": true + }, + "is-equal-shallow": { + "version": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz", + "integrity": "sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=", + "dev": true + }, + "is-extendable": { + "version": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true + }, + "is-extglob": { + "version": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", + "dev": true + }, + "is-finite": { + "version": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", + "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=" + }, + "is-fn": { + "version": "https://registry.npmjs.org/is-fn/-/is-fn-1.0.0.tgz", + "integrity": "sha1-lUPV3nvPWwiiLsiiC65uKG1RDYw=" + }, + "is-fullwidth-code-point": { + "version": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=" + }, + "is-function": { + "version": "https://registry.npmjs.org/is-function/-/is-function-1.0.1.tgz", + "integrity": "sha1-Es+5i2W1fdPRk6MSH19uL0N2ArU=" + }, + "is-glob": { + "version": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", + "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", + "dev": true + }, + "is-hex-prefixed": { + "version": "https://registry.npmjs.org/is-hex-prefixed/-/is-hex-prefixed-1.0.0.tgz", + "integrity": "sha1-fY035q135dEnFIkTxXPggtd39VQ=" + }, + "is-my-json-valid": { + "version": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.16.0.tgz", + "integrity": "sha1-8Hndm/2uZe4gOKrorLyGqxCeNpM=" + }, + "is-number": { + "version": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", + "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", + "dev": true + }, + "is-path-cwd": { + "version": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", + "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=" + }, + "is-path-in-cwd": { + "version": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.0.tgz", + "integrity": "sha1-ZHdYK4IU1gI0YJRWcAO+ip6sBNw=" + }, + "is-path-inside": { + "version": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.0.tgz", + "integrity": "sha1-/AbloWg/vaE95mev9xe7wQpI838=" + }, + "is-plain-object": { + "version": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.3.tgz", + "integrity": "sha1-wVvz5LZrYtcu+vKSWEhmPsvGGbY=", + "dev": true, + "dependencies": { + "isobject": { + "version": "https://registry.npmjs.org/isobject/-/isobject-3.0.0.tgz", + "integrity": "sha1-OVZSF/NmF4nooKDAgNX35rxG4aA=", + "dev": true + } + } + }, + "is-posix-bracket": { + "version": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz", + "integrity": "sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q=", + "dev": true + }, + "is-primitive": { + "version": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz", + "integrity": "sha1-IHurkWOEmcB7Kt8kCkGochADRXU=", + "dev": true + }, + "is-property": { + "version": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", + "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=" + }, + "is-regex": { + "version": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", + "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=" + }, + "is-relative": { + "version": "https://registry.npmjs.org/is-relative/-/is-relative-0.2.1.tgz", + "integrity": "sha1-0n9MfVFtF1+2ENuEu+7yPDvJeqU=", + "dev": true + }, + "is-resolvable": { + "version": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.0.0.tgz", + "integrity": "sha1-jfV8YeouPFAUCNEA+wE8+NbgzGI=" + }, + "is-stream": { + "version": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" + }, + "is-subset": { + "version": "https://registry.npmjs.org/is-subset/-/is-subset-0.1.1.tgz", + "integrity": "sha1-ilkRfZMt4d4A8kX83TnOQ/HpOaY=", + "dev": true + }, + "is-symbol": { + "version": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.1.tgz", + "integrity": "sha1-PMWfAAJRlLarLjjbrmaJJWtmBXI=" + }, + "is-type": { + "version": "https://registry.npmjs.org/is-type/-/is-type-0.0.1.tgz", + "integrity": "sha1-9lHYXDZdRJVdFKUdjXBh8/a0d5w=", + "dev": true + }, + "is-typedarray": { + "version": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + }, + "is-unc-path": { + "version": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-0.1.2.tgz", + "integrity": "sha1-arBTpyVzwQJQ/0FqOBTDUXivObk=", + "dev": true + }, + "is-utf8": { + "version": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", + "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=" + }, + "is-valid-glob": { + "version": "https://registry.npmjs.org/is-valid-glob/-/is-valid-glob-0.3.0.tgz", + "integrity": "sha1-1LVcafUYhvm2XHDWwmItN+KfSP4=", + "dev": true + }, + "is-windows": { + "version": "https://registry.npmjs.org/is-windows/-/is-windows-0.2.0.tgz", + "integrity": "sha1-3hqm1j6indJIc3tp8f+LgALSEIw=", + "dev": true + }, + "isarray": { + "version": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "isexe": { + "version": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "isobject": { + "version": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true + }, + "isomorphic-fetch": { + "version": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz", + "integrity": "sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=" + }, + "isstream": { + "version": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + }, + "istanbul": { + "version": "https://github.com/gotwarlost/istanbul/tarball/harmony", + "integrity": "sha1-Atq/03Q7aVlC0XCoCRcdOSRGYzE=", + "dev": true, + "optional": true, + "dependencies": { + "abbrev": { + "version": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz", + "integrity": "sha1-kbR5JYinc4wl813W9jdSovh3YTU=", + "dev": true + }, + "async": { + "version": "https://registry.npmjs.org/async/-/async-0.2.10.tgz", + "integrity": "sha1-trvgsGdLnXGXCMo43owjfLUmw9E=", + "dev": true, + "optional": true + }, + "escodegen": { + "version": "https://registry.npmjs.org/escodegen/-/escodegen-1.2.0.tgz", + "integrity": "sha1-Cd55Z3kcyVi3+Jot220jRRrzJ+E=", + "dev": true, + "optional": true, + "dependencies": { + "esprima": { + "version": "https://registry.npmjs.org/esprima/-/esprima-1.0.4.tgz", + "integrity": "sha1-n1V+CPw7TSbs6d00+Pv0drYlha0=", + "dev": true, + "optional": true + } + } + }, + "esprima": { + "version": "git://github.com/ariya/esprima.git#a65a3eb93b9a5dce9a1184ca2d1bd0b184c6b8fd", + "dev": true, + "optional": true + }, + "estraverse": { + "version": "https://registry.npmjs.org/estraverse/-/estraverse-1.5.1.tgz", + "integrity": "sha1-hno+jlip+EYYr7bC3bzZFrfLr3E=", + "dev": true, + "optional": true + }, + "esutils": { + "version": "https://registry.npmjs.org/esutils/-/esutils-1.0.0.tgz", + "integrity": "sha1-gVHTWOIMisx/t0XnRywAJf5JZXA=", + "dev": true, + "optional": true + }, + "mkdirp": { + "version": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.5.tgz", + "integrity": "sha1-3j5fiWHIjHh+4TaN+EmsRBPsqNc=", + "dev": true, + "optional": true + }, + "nopt": { + "version": "https://registry.npmjs.org/nopt/-/nopt-2.2.1.tgz", + "integrity": "sha1-KqCbfRdoSHs7ianFqlIzW/8Lrqc=", + "dev": true, + "optional": true + }, + "resolve": { + "version": "https://registry.npmjs.org/resolve/-/resolve-0.6.3.tgz", + "integrity": "sha1-3ZV5gufnNt699TtYpN2RdUV13UY=", + "dev": true, + "optional": true + }, + "source-map": { + "version": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz", + "integrity": "sha1-wkvBRspRfBRx9drL4lcbK3+eM0Y=", + "dev": true, + "optional": true + } + } + }, + "istextorbinary": { + "version": "https://registry.npmjs.org/istextorbinary/-/istextorbinary-1.0.2.tgz", + "integrity": "sha1-rOGTVNGpoBc+/rEITOD4ewrX3s8=", + "dev": true + }, + "jade": { + "version": "https://registry.npmjs.org/jade/-/jade-0.26.3.tgz", + "integrity": "sha1-jxDXl32NefL2/4YqgbBRPMslaGw=", + "dev": true, + "dependencies": { + "commander": { + "version": "https://registry.npmjs.org/commander/-/commander-0.6.1.tgz", + "integrity": "sha1-+mihT2qUXVTbvlDYzbMyDp47GgY=", + "dev": true + }, + "mkdirp": { + "version": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.0.tgz", + "integrity": "sha1-G79asbqCevI1dRQ0kEJkVfSB/h4=", + "dev": true + } + } + }, + "jazzicon": { + "version": "https://registry.npmjs.org/jazzicon/-/jazzicon-1.5.0.tgz", + "integrity": "sha1-1/NrUWAj2znubqwRf0BU6Te2Xpk=" + }, + "jodid25519": { + "version": "https://registry.npmjs.org/jodid25519/-/jodid25519-1.0.2.tgz", + "integrity": "sha1-BtSRIlUJNBlHfUJWM2BuDpB4KWc=", + "optional": true + }, + "js-beautify": { + "version": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.5.10.tgz", + "integrity": "sha1-TZU3FwJpk0SlFsomv1nwonu3Vxk=", + "dev": true + }, + "js-sha3": { + "version": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.3.1.tgz", + "integrity": "sha1-hhIoAhQvCChQKg0d7h2V4lO7AkM=" + }, + "js-tokens": { + "version": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.1.tgz", + "integrity": "sha1-COnxMkhKLEWjCQfp3E1VZ7fxFNc=" + }, + "js-yaml": { + "version": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.8.4.tgz", + "integrity": "sha1-UgtFZPhlc7qWZir4Woyvp7S1pvY=", + "dependencies": { + "esprima": { + "version": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz", + "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=" + } + } + }, + "jsbn": { + "version": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "optional": true + }, + "jsdom": { + "version": "https://registry.npmjs.org/jsdom/-/jsdom-8.5.0.tgz", + "integrity": "sha1-1Nj12/J2hjW2KmKCO5R89wcevJg=", + "dev": true, + "dependencies": { + "acorn": { + "version": "https://registry.npmjs.org/acorn/-/acorn-2.7.0.tgz", + "integrity": "sha1-q259nYhqrKiwhbwzEreaGYQz8Oc=", + "dev": true + }, + "escodegen": { + "version": "https://registry.npmjs.org/escodegen/-/escodegen-1.8.1.tgz", + "integrity": "sha1-WltTr0aTEQvrsIZ6o0MN07cKEBg=", + "dev": true + }, + "esprima": { + "version": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", + "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", + "dev": true + }, + "estraverse": { + "version": "https://registry.npmjs.org/estraverse/-/estraverse-1.9.3.tgz", + "integrity": "sha1-r2fy3JIlgkFZUJJgkaQAXSnJu0Q=", + "dev": true + }, + "source-map": { + "version": "https://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz", + "integrity": "sha1-2rc/vPwrqBm03gO9b26qSBZLP50=", + "dev": true, + "optional": true + } + } + }, + "jsdom-global": { + "version": "https://registry.npmjs.org/jsdom-global/-/jsdom-global-1.7.0.tgz", + "integrity": "sha1-mWe0Cb5xXPf88Ev9N5RblFtJTVI=", + "dev": true + }, + "jsesc": { + "version": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=" + }, + "jshint-stylish": { + "version": "https://registry.npmjs.org/jshint-stylish/-/jshint-stylish-0.1.5.tgz", + "integrity": "sha1-1Btu744GpN37NlQL9lk/4xuYcjY=", + "dev": true, + "dependencies": { + "ansi-styles": { + "version": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-1.0.0.tgz", + "integrity": "sha1-yxAt8cVvUSPquLZ817mAJ6AnkXg=", + "dev": true + }, + "chalk": { + "version": "https://registry.npmjs.org/chalk/-/chalk-0.4.0.tgz", + "integrity": "sha1-UZmj3c0MHv4jvAjBsCewYXbgxk8=", + "dev": true + }, + "strip-ansi": { + "version": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-0.1.1.tgz", + "integrity": "sha1-OeipjQRNFQZgq+SmgIrPcLt7yZE=", + "dev": true + } + } + }, + "json-rpc-error": { + "version": "https://registry.npmjs.org/json-rpc-error/-/json-rpc-error-2.0.0.tgz", + "integrity": "sha1-p6+cICg4tekFxyUOVH8a/3cligI=" + }, + "json-rpc-random-id": { + "version": "https://registry.npmjs.org/json-rpc-random-id/-/json-rpc-random-id-1.0.1.tgz", + "integrity": "sha1-uknZat7RRE27jaPSA3SKy7zeyMg=" + }, + "json-schema": { + "version": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" + }, + "json-stable-stringify": { + "version": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", + "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=" + }, + "json-stringify-safe": { + "version": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + }, + "json3": { + "version": "https://registry.npmjs.org/json3/-/json3-3.3.2.tgz", + "integrity": "sha1-PAQ0dD35Pi9cQq7nsZvLSDV19OE=", + "dev": true + }, + "json5": { + "version": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", + "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=" + }, + "jsonfile": { + "version": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", + "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=" + }, + "jsonify": { + "version": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", + "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=" + }, + "jsonparse": { + "version": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", + "dev": true + }, + "jsonpointer": { + "version": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz", + "integrity": "sha1-T9kss04OnbPInIYi7PUfm5eMbLk=" + }, + "JSONStream": { + "version": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.1.tgz", + "integrity": "sha1-cH92HgHa6eFvG8+TcDt4xwlmV5o=", + "dev": true + }, + "jsprim": { + "version": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.0.tgz", + "integrity": "sha1-o7h+QCmNjDgFUtjMdiigu5WiKRg=", + "dependencies": { + "assert-plus": { + "version": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + } + } + }, + "keccak": { + "version": "https://registry.npmjs.org/keccak/-/keccak-1.2.0.tgz", + "integrity": "sha1-tTYY/HlhtkL25z8VRu7DMp9+/+A=" + }, + "keccakjs": { + "version": "https://registry.npmjs.org/keccakjs/-/keccakjs-0.2.1.tgz", + "integrity": "sha1-HWM6+QfvMFu/ny+mFtVsRFYd+k0=" + }, + "kind-of": { + "version": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true + }, + "klaw": { + "version": "https://registry.npmjs.org/klaw/-/klaw-1.3.1.tgz", + "integrity": "sha1-QIhDO0azsbolnXh4XY6W9zugJDk=" + }, + "labeled-stream-splicer": { + "version": "https://registry.npmjs.org/labeled-stream-splicer/-/labeled-stream-splicer-2.0.0.tgz", + "integrity": "sha1-pS4dE4AkwAuGscDJH2d5GLiuClk=", + "dev": true, + "dependencies": { + "isarray": { + "version": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "stream-splicer": { + "version": "https://registry.npmjs.org/stream-splicer/-/stream-splicer-2.0.0.tgz", + "integrity": "sha1-G2O+Q4oTPktnHMGTUZdgAXWRDYM=", + "dev": true + } + } + }, + "last-run": { + "version": "https://registry.npmjs.org/last-run/-/last-run-1.1.1.tgz", + "integrity": "sha1-RblpQsF7HHnHchmCWbqUO+v4yls=", + "dev": true + }, + "lazy-debug-legacy": { + "version": "https://registry.npmjs.org/lazy-debug-legacy/-/lazy-debug-legacy-0.0.1.tgz", + "integrity": "sha1-U3cWwHduTPeePtG2IfdljCkRsbE=", + "dev": true + }, + "lazystream": { + "version": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.0.tgz", + "integrity": "sha1-9plf4PggOS9hOWvolGJAe7dxaOQ=", + "dev": true + }, + "lcid": { + "version": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", + "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=" + }, + "leftpad": { + "version": "https://registry.npmjs.org/leftpad/-/leftpad-0.0.0.tgz", + "integrity": "sha1-Agya0HhyFroPMNedR5tLNV19OcM=", + "dev": true + }, + "level-codec": { + "version": "https://registry.npmjs.org/level-codec/-/level-codec-6.1.0.tgz", + "integrity": "sha1-9d8KmVgvdtrEOFUVGrb05NDWAEU=" + }, + "level-errors": { + "version": "https://registry.npmjs.org/level-errors/-/level-errors-1.0.4.tgz", + "integrity": "sha1-NYXmI5dMc3qTdVSSpDwCZ82kQl8=" + }, + "level-iterator-stream": { + "version": "https://registry.npmjs.org/level-iterator-stream/-/level-iterator-stream-1.3.1.tgz", + "integrity": "sha1-5Dt4sagUPm+pek9IXrjqUwNS8u0=", + "dependencies": { + "isarray": { + "version": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, + "readable-stream": { + "version": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=" + }, + "string_decoder": { + "version": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + } + } + }, + "level-ws": { + "version": "https://registry.npmjs.org/level-ws/-/level-ws-0.0.0.tgz", + "integrity": "sha1-Ny5RIXeSSgBCSwtDrvK7QkltIos=", + "dependencies": { + "isarray": { + "version": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, + "object-keys": { + "version": "https://registry.npmjs.org/object-keys/-/object-keys-0.4.0.tgz", + "integrity": "sha1-KKaq50KN0sOpLz2V8hM13SBOAzY=" + }, + "readable-stream": { + "version": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=" + }, + "string_decoder": { + "version": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + }, + "xtend": { + "version": "https://registry.npmjs.org/xtend/-/xtend-2.1.2.tgz", + "integrity": "sha1-bv7MKk2tjmlixJAbM3znuoe10os=" + } + } + }, + "levelup": { + "version": "https://registry.npmjs.org/levelup/-/levelup-1.3.8.tgz", + "integrity": "sha1-+0QsSI776hBD9+uZKaeSp0+9HaY=", + "dependencies": { + "semver": { + "version": "https://registry.npmjs.org/semver/-/semver-5.1.1.tgz", + "integrity": "sha1-oykqNz5vPgeY2gsgZBuanFvEfhk=" + } + } + }, + "levn": { + "version": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=" + }, + "lexical-scope": { + "version": "https://registry.npmjs.org/lexical-scope/-/lexical-scope-1.2.0.tgz", + "integrity": "sha1-/Ope3HBKSzqHls3KQZw6CvryLfQ=", + "dev": true + }, + "liftoff": { + "version": "https://registry.npmjs.org/liftoff/-/liftoff-2.3.0.tgz", + "integrity": "sha1-qY8v9nGD2Lp8+soQVIvX/wVQs4U=", + "dev": true + }, + "livereload-js": { + "version": "https://registry.npmjs.org/livereload-js/-/livereload-js-2.2.2.tgz", + "integrity": "sha1-bIclfmSKtHW8JOoldFftzB+NC8I=", + "dev": true + }, + "load-json-file": { + "version": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=" + }, + "lodash": { + "version": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", + "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=" + }, + "lodash-es": { + "version": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.4.tgz", + "integrity": "sha1-3MHXVS4VCgZABzupyzHXDwMpUOc=" + }, + "lodash._baseassign": { + "version": "https://registry.npmjs.org/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz", + "integrity": "sha1-jDigmVAPIVrQnlnxci/QxSv+Ck4=", + "dev": true + }, + "lodash._basecopy": { + "version": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", + "integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=" + }, + "lodash._baseflatten": { + "version": "https://registry.npmjs.org/lodash._baseflatten/-/lodash._baseflatten-3.1.4.tgz", + "integrity": "sha1-B3D/gBMa9uNPO1EXlqe6UhTmX/c=", + "dev": true + }, + "lodash._basetostring": { + "version": "https://registry.npmjs.org/lodash._basetostring/-/lodash._basetostring-3.0.1.tgz", + "integrity": "sha1-0YYdh3+CSlL2aYMtyvPuFVZqB9U=" + }, + "lodash._basevalues": { + "version": "https://registry.npmjs.org/lodash._basevalues/-/lodash._basevalues-3.0.0.tgz", + "integrity": "sha1-W3dXYoAr3j0yl1A+JjAIIP32Ybc=" + }, + "lodash._bindcallback": { + "version": "https://registry.npmjs.org/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz", + "integrity": "sha1-5THCdkTPi1epnhftlbNcdIeJOS4=", + "dev": true + }, + "lodash._createassigner": { + "version": "https://registry.npmjs.org/lodash._createassigner/-/lodash._createassigner-3.1.1.tgz", + "integrity": "sha1-g4pbri/aymOsIt7o4Z+k5taXCxE=", + "dev": true + }, + "lodash._getnative": { + "version": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", + "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=" + }, + "lodash._isiterateecall": { + "version": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz", + "integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=" + }, + "lodash._reescape": { + "version": "https://registry.npmjs.org/lodash._reescape/-/lodash._reescape-3.0.0.tgz", + "integrity": "sha1-Kx1vXf4HyKNVdT5fJ/rH8c3hYWo=" + }, + "lodash._reevaluate": { + "version": "https://registry.npmjs.org/lodash._reevaluate/-/lodash._reevaluate-3.0.0.tgz", + "integrity": "sha1-WLx0xAZklTrgsSTYBpltrKQx4u0=" + }, + "lodash._reinterpolate": { + "version": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", + "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=" + }, + "lodash._root": { + "version": "https://registry.npmjs.org/lodash._root/-/lodash._root-3.0.1.tgz", + "integrity": "sha1-+6HEUkwZ7ppfgTa0YJ8BfPTe1pI=" + }, + "lodash.assign": { + "version": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz", + "integrity": "sha1-DZnzzNem0mHRm9rrkkUAXShYCOc=" + }, + "lodash.assignin": { + "version": "https://registry.npmjs.org/lodash.assignin/-/lodash.assignin-4.2.0.tgz", + "integrity": "sha1-uo31+4QesKPoBEIysOJjqNxqKKI=", + "dev": true + }, + "lodash.assignwith": { + "version": "https://registry.npmjs.org/lodash.assignwith/-/lodash.assignwith-4.2.0.tgz", + "integrity": "sha1-EnqX8CrcQXUalU0ksN4X4QDgOOs=", + "dev": true + }, + "lodash.bind": { + "version": "https://registry.npmjs.org/lodash.bind/-/lodash.bind-4.2.1.tgz", + "integrity": "sha1-euMBfpOWIqwxt9fX3LGzTbFpDTU=", + "dev": true + }, + "lodash.clonedeep": { + "version": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", + "dev": true + }, + "lodash.debounce": { + "version": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=", + "dev": true + }, + "lodash.defaults": { + "version": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=", + "dev": true + }, + "lodash.escape": { + "version": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-3.2.0.tgz", + "integrity": "sha1-mV7g3BjBtIzJLv+ucaEKq1tIdpg=" + }, + "lodash.filter": { + "version": "https://registry.npmjs.org/lodash.filter/-/lodash.filter-4.6.0.tgz", + "integrity": "sha1-ZosdSYFgOuHMWm+nYBQ+SAtMSs4=", + "dev": true + }, + "lodash.find": { + "version": "https://registry.npmjs.org/lodash.find/-/lodash.find-4.6.0.tgz", + "integrity": "sha1-ywcE1Hq3F4n/oN6Ll92Sb7iLE7E=", + "dev": true + }, + "lodash.flatten": { + "version": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", + "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=", + "dev": true + }, + "lodash.foreach": { + "version": "https://registry.npmjs.org/lodash.foreach/-/lodash.foreach-4.5.0.tgz", + "integrity": "sha1-Gmo16s5AEoDH8G3d7DUWWrJ+PlM=", + "dev": true + }, + "lodash.isarguments": { + "version": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", + "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=" + }, + "lodash.isarray": { + "version": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", + "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=" + }, + "lodash.isempty": { + "version": "https://registry.npmjs.org/lodash.isempty/-/lodash.isempty-4.4.0.tgz", + "integrity": "sha1-b4bL7di+TsmHvpqvM8loTbGzHn4=", + "dev": true + }, + "lodash.isequal": { + "version": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=", + "dev": true + }, + "lodash.isfunction": { + "version": "https://registry.npmjs.org/lodash.isfunction/-/lodash.isfunction-3.0.8.tgz", + "integrity": "sha1-TbcJ/IG8So/XEnpFilNGxc3OLGs=", + "dev": true + }, + "lodash.isplainobject": { + "version": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" + }, + "lodash.isstring": { + "version": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=", + "dev": true + }, + "lodash.keys": { + "version": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", + "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=" + }, + "lodash.map": { + "version": "https://registry.npmjs.org/lodash.map/-/lodash.map-4.6.0.tgz", + "integrity": "sha1-dx7Hg540c9nEzeKLGTlMNWL09tM=", + "dev": true + }, + "lodash.mapvalues": { + "version": "https://registry.npmjs.org/lodash.mapvalues/-/lodash.mapvalues-4.6.0.tgz", + "integrity": "sha1-G6+lAF3p3W9PJmaMMMo3IwzJaJw=", + "dev": true + }, + "lodash.memoize": { + "version": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-3.0.4.tgz", + "integrity": "sha1-LcvSwofLwKVcxCMovQxzYVDVPj8=", + "dev": true + }, + "lodash.merge": { + "version": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.0.tgz", + "integrity": "sha1-aYhLoUSsM/5plzemCG3v+t0PicU=", + "dev": true + }, + "lodash.pick": { + "version": "https://registry.npmjs.org/lodash.pick/-/lodash.pick-4.4.0.tgz", + "integrity": "sha1-UvBWEP/53tQiYRRB7R/BI6AwAbM=", + "dev": true + }, + "lodash.pickby": { + "version": "https://registry.npmjs.org/lodash.pickby/-/lodash.pickby-4.6.0.tgz", + "integrity": "sha1-feoh2MGNdwOifHBMFdO4SmfjOv8=", + "dev": true + }, + "lodash.reduce": { + "version": "https://registry.npmjs.org/lodash.reduce/-/lodash.reduce-4.6.0.tgz", + "integrity": "sha1-8atrg5KZrUj3hKu/R2WW8DuRTTs=", + "dev": true + }, + "lodash.reject": { + "version": "https://registry.npmjs.org/lodash.reject/-/lodash.reject-4.6.0.tgz", + "integrity": "sha1-gNZJLcFHCGS79YNTO2UfQqn1JBU=", + "dev": true + }, + "lodash.restparam": { + "version": "https://registry.npmjs.org/lodash.restparam/-/lodash.restparam-3.6.1.tgz", + "integrity": "sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU=" + }, + "lodash.some": { + "version": "https://registry.npmjs.org/lodash.some/-/lodash.some-4.6.0.tgz", + "integrity": "sha1-G7nzFO9ri63tE7VJFpsqlF62jk0=", + "dev": true + }, + "lodash.sortby": { + "version": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", + "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=", + "dev": true + }, + "lodash.template": { + "version": "https://registry.npmjs.org/lodash.template/-/lodash.template-3.6.2.tgz", + "integrity": "sha1-+M3sxhaaJVvpCYrosMU9N4kx0U8=" + }, + "lodash.templatesettings": { + "version": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-3.1.1.tgz", + "integrity": "sha1-+zB4RHU7Zrnxr6VOJix0UwfbqOU=" + }, + "lodash.uniqby": { + "version": "https://registry.npmjs.org/lodash.uniqby/-/lodash.uniqby-4.7.0.tgz", + "integrity": "sha1-2ZwHpmnp5tJOE2Lf4mbGdhavEwI=", + "dev": true + }, + "loglevel": { + "version": "https://registry.npmjs.org/loglevel/-/loglevel-1.4.1.tgz", + "integrity": "sha1-lbOD+Ro8J1b9SrCTZn5DCRYfK80=" + }, + "lolex": { + "version": "https://registry.npmjs.org/lolex/-/lolex-1.3.2.tgz", + "integrity": "sha1-fD2mL/yzDw9agKJWbKJORdigHzE=", + "dev": true + }, + "loose-envify": { + "version": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz", + "integrity": "sha1-0aitM/qc4OcT1l/dCsi3SNR4yEg=" + }, + "lru-cache": { + "version": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", + "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI=", + "dev": true + }, + "ltgt": { + "version": "https://registry.npmjs.org/ltgt/-/ltgt-2.1.3.tgz", + "integrity": "sha1-EIUaBtmWS5cReEQcI8nlJpjuzjQ=" + }, + "make-iterator": { + "version": "https://registry.npmjs.org/make-iterator/-/make-iterator-1.0.0.tgz", + "integrity": "sha1-V7713IXSOSO6I3ZzJNjo+PPZaUs=", + "dev": true + }, + "map-async": { + "version": "https://registry.npmjs.org/map-async/-/map-async-0.1.1.tgz", + "integrity": "sha1-yJfARJ+Fhkx0taPxlu20IVZDF0U=" + }, + "map-cache": { + "version": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", + "dev": true + }, + "map-stream": { + "version": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz", + "integrity": "sha1-5WqpTEyAVaFkBKBnS3jyFffI4ZQ=", + "dev": true + }, + "matchdep": { + "version": "https://registry.npmjs.org/matchdep/-/matchdep-1.0.1.tgz", + "integrity": "sha1-pXozgESR+64girqPaDgEN6vC3KU=", + "dev": true, + "dependencies": { + "findup-sync": { + "version": "https://registry.npmjs.org/findup-sync/-/findup-sync-0.3.0.tgz", + "integrity": "sha1-N5MKpdgWt3fANEXhlmzGeQpMCxY=", + "dev": true + }, + "glob": { + "version": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", + "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", + "dev": true + } + } + }, + "mdurl": { + "version": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", + "integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=" + }, + "media-typer": { + "version": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" + }, + "memdown": { + "version": "https://registry.npmjs.org/memdown/-/memdown-1.2.4.tgz", + "integrity": "sha1-zZo0qvB01TRFonEQjrS43U7A8n8=" + }, + "memorystream": { + "version": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", + "integrity": "sha1-htcJCzDORV1j+64S3aUaR93K+bI=" + }, + "menu-droppo": { + "version": "https://registry.npmjs.org/menu-droppo/-/menu-droppo-1.1.5.tgz", + "integrity": "sha1-qHqOfjcA7AK+A1+f3B2siTVFCVQ=" + }, + "merge-descriptors": { + "version": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + }, + "merge-stream": { + "version": "https://registry.npmjs.org/merge-stream/-/merge-stream-1.0.1.tgz", + "integrity": "sha1-QEEgLVCKNCugAXQAjfDCUbjBNeE=", + "dev": true + }, + "merkle-patricia-tree": { + "version": "https://registry.npmjs.org/merkle-patricia-tree/-/merkle-patricia-tree-2.1.2.tgz", + "integrity": "sha1-ckSD1Ut1YxpI/t2lXhFAUXBqcpE=", + "dependencies": { + "ethereumjs-util": { + "version": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-4.5.0.tgz", + "integrity": "sha1-PpQosxfuvaPXJg2FT93alUsfG8Y=" + } + } + }, + "mersenne-twister": { + "version": "https://registry.npmjs.org/mersenne-twister/-/mersenne-twister-1.1.0.tgz", + "integrity": "sha1-+RZhjuQ9cXnvz2Qb7EUx65Zwl4o=" + }, + "metamask-logo": { + "version": "https://registry.npmjs.org/metamask-logo/-/metamask-logo-2.1.3.tgz", + "integrity": "sha1-F1zleuUMc0Szsdwy0v0LCOOXj9A=" + }, + "methods": { + "version": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" + }, + "micromatch": { + "version": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", + "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", + "dev": true + }, + "miller-rabin": { + "version": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.0.tgz", + "integrity": "sha1-SmL7HUKTPAVYOYL0xxb2+55sbT0=", + "dev": true + }, + "mime": { + "version": "https://registry.npmjs.org/mime/-/mime-1.3.4.tgz", + "integrity": "sha1-EV+eO2s9rylZmDyzjxSaLUDrXVM=" + }, + "mime-db": { + "version": "https://registry.npmjs.org/mime-db/-/mime-db-1.27.0.tgz", + "integrity": "sha1-gg9XIpa70g7CXtVeW13oaeVDbrE=" + }, + "mime-types": { + "version": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.15.tgz", + "integrity": "sha1-pOv1BkCUVpI3uM9wBGd20J/JKu0=" + }, + "min-document": { + "version": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz", + "integrity": "sha1-e9KC4/WELtKVu3SM3Z8f+iyCRoU=" + }, + "mini-lr": { + "version": "https://registry.npmjs.org/mini-lr/-/mini-lr-0.1.9.tgz", + "integrity": "sha1-AhmdJzR5U9H9HW297UJh8Yey0PY=", + "dev": true, + "dependencies": { + "qs": { + "version": "https://registry.npmjs.org/qs/-/qs-2.2.5.tgz", + "integrity": "sha1-EIirr53MCuWuRbcJ5sa1iIsjkjw=", + "dev": true + } + } + }, + "minimalistic-assert": { + "version": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.0.tgz", + "integrity": "sha1-cCvi3aazf0g2vLP121ZkG2Sh09M=" + }, + "minimalistic-crypto-utils": { + "version": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=" + }, + "minimatch": { + "version": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM=" + }, + "minimist": { + "version": "https://registry.npmjs.org/minimist/-/minimist-0.0.5.tgz", + "integrity": "sha1-16oye87PUY+RBqxrjwA/o7zqhWY=" + }, + "mississippi": { + "version": "https://registry.npmjs.org/mississippi/-/mississippi-1.3.0.tgz", + "integrity": "sha1-0gFYPrEjJ+PFwWQqQEqcrPlONPU=" + }, + "mkdirp": { + "version": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dependencies": { + "minimist": { + "version": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + } + } + }, + "mocha": { + "version": "https://registry.npmjs.org/mocha/-/mocha-2.5.3.tgz", + "integrity": "sha1-FhvlvetJZ3HrmzV0UFC2IrWu/Fg=", + "dev": true, + "dependencies": { + "debug": { + "version": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", + "dev": true + }, + "escape-string-regexp": { + "version": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.2.tgz", + "integrity": "sha1-Tbwv5nTnGUnK8/smlc5/LcHZqNE=", + "dev": true + }, + "glob": { + "version": "https://registry.npmjs.org/glob/-/glob-3.2.11.tgz", + "integrity": "sha1-Spc/Y1uRkPcV0QmH1cAP0oFevj0=", + "dev": true + }, + "minimatch": { + "version": "https://registry.npmjs.org/minimatch/-/minimatch-0.3.0.tgz", + "integrity": "sha1-J12O2qxPG7MyZHIInnlJyDlGmd0=", + "dev": true + }, + "ms": { + "version": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", + "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=", + "dev": true + }, + "supports-color": { + "version": "https://registry.npmjs.org/supports-color/-/supports-color-1.2.0.tgz", + "integrity": "sha1-/x7R5hFp0Gs88tWI4YixjYhH4X4=", + "dev": true + } + } + }, + "mocha-eslint": { + "version": "https://registry.npmjs.org/mocha-eslint/-/mocha-eslint-2.1.1.tgz", + "integrity": "sha1-S0drRAPwPXANmAEMsxbasCqeFuA=", + "dev": true + }, + "mocha-jsdom": { + "version": "https://registry.npmjs.org/mocha-jsdom/-/mocha-jsdom-1.1.0.tgz", + "integrity": "sha1-4VdvvQYBzInTWKIToOVYXRt8egE=", + "dev": true + }, + "mocha-sinon": { + "version": "https://registry.npmjs.org/mocha-sinon/-/mocha-sinon-1.2.0.tgz", + "integrity": "sha1-lfA0qNreTalmoPOvyJwSbYtYkSM=", + "dev": true + }, + "module-deps": { + "version": "https://registry.npmjs.org/module-deps/-/module-deps-4.1.1.tgz", + "integrity": "sha1-IyFYM/HaE/1gbMuAh7RIUty4If0=", + "dev": true, + "dependencies": { + "concat-stream": { + "version": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.5.2.tgz", + "integrity": "sha1-cIl4Yk2FavQaWnQd790mHadSwmY=", + "dev": true, + "dependencies": { + "readable-stream": { + "version": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", + "integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44=", + "dev": true + } + } + }, + "duplexer2": { + "version": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", + "integrity": "sha1-ixLauHjA1p4+eJEFFmKjL8a93ME=", + "dev": true + }, + "string_decoder": { + "version": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + } + } + }, + "ms": { + "version": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "multipipe": { + "version": "https://registry.npmjs.org/multipipe/-/multipipe-0.1.2.tgz", + "integrity": "sha1-Ko8t33Du1WTf8tV/HhoTfZ8FB4s=" + }, + "multiplex": { + "version": "https://registry.npmjs.org/multiplex/-/multiplex-6.7.0.tgz", + "integrity": "sha1-/3Pk5AB5FwxEQtFgllZY+N75YMI=" + }, + "mustache": { + "version": "https://registry.npmjs.org/mustache/-/mustache-2.3.0.tgz", + "integrity": "sha1-QCj3d4sXcIpImTCm5SrDvKDaQdA=", + "dev": true + }, + "mute-stdout": { + "version": "https://registry.npmjs.org/mute-stdout/-/mute-stdout-1.0.0.tgz", + "integrity": "sha1-WzLqB+tDyd7WEwQ0z5JvRrKn/U0=", + "dev": true + }, + "mute-stream": { + "version": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.5.tgz", + "integrity": "sha1-j7+rsKmKJT0xhDMfno3rc3L6xsA=" + }, + "mz": { + "version": "https://registry.npmjs.org/mz/-/mz-2.6.0.tgz", + "integrity": "sha1-yLhSHZWN8KTydoAl22nHGe5O8c4=", + "dev": true, + "dependencies": { + "any-promise": { + "version": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=", + "dev": true + } + } + }, + "nan": { + "version": "https://registry.npmjs.org/nan/-/nan-2.6.2.tgz", + "integrity": "sha1-5P805slf37WuzAjeZZb0NgWn20U=" + }, + "ncp": { + "version": "https://registry.npmjs.org/ncp/-/ncp-1.0.1.tgz", + "integrity": "sha1-0VNn5cuHQyuhF9K/gP30Wuz7QkY=", + "dev": true + }, + "negotiator": { + "version": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", + "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" + }, + "next-tick": { + "version": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", + "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=", + "dev": true + }, + "nock": { + "version": "https://registry.npmjs.org/nock/-/nock-8.2.1.tgz", + "integrity": "sha1-ZMxl4b3TiT9Yy6fhq/3Dj0DwNko=", + "dev": true, + "dependencies": { + "lodash": { + "version": "https://registry.npmjs.org/lodash/-/lodash-4.9.0.tgz", + "integrity": "sha1-TCDXQvA86F3HAODderm8q4Xm/BQ=", + "dev": true + } + } + }, + "node-abi": { + "version": "https://registry.npmjs.org/node-abi/-/node-abi-2.0.3.tgz", + "integrity": "sha1-DKZ+XmZ7jhNDVJyhcVOoFdC7/ao=" + }, + "node-fetch": { + "version": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.0.tgz", + "integrity": "sha1-P/bFZUT5t/sAaCM4u1Xub1SooO8=" + }, + "node-notifier": { + "version": "https://registry.npmjs.org/node-notifier/-/node-notifier-5.1.2.tgz", + "integrity": "sha1-L6nhJgX6EACdRFSdb82KY93g5P8=", + "dev": true, + "dependencies": { + "which": { + "version": "https://registry.npmjs.org/which/-/which-1.2.14.tgz", + "integrity": "sha1-mofEN48D6CfOyvGs31bHNsAcFOU=", + "dev": true + } + } + }, + "noop-logger": { + "version": "https://registry.npmjs.org/noop-logger/-/noop-logger-0.1.1.tgz", + "integrity": "sha1-lKKxYzxPExdVMAfYlm/Q6EG2pMI=" + }, + "nopt": { + "version": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", + "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", + "dev": true + }, + "normalize-package-data": { + "version": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.3.8.tgz", + "integrity": "sha1-2Bntoqne29H/pWPqQHHZNngilbs=" + }, + "normalize-path": { + "version": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true + }, + "now-and-later": { + "version": "https://registry.npmjs.org/now-and-later/-/now-and-later-1.0.0.tgz", + "integrity": "sha1-I+eYzKrw6Ky+8Gh/gghidHRuCJM=", + "dev": true + }, + "npmlog": { + "version": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.0.tgz", + "integrity": "sha1-3Fm+6F9k8A7UJO+yrweD3yXRwLU=" + }, + "nth-check": { + "version": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.1.tgz", + "integrity": "sha1-mSms32KPwsQQmN6rgqxYDPFJquQ=", + "dev": true + }, + "number-is-nan": { + "version": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" + }, + "number-to-bn": { + "version": "https://registry.npmjs.org/number-to-bn/-/number-to-bn-1.7.0.tgz", + "integrity": "sha1-uzYjWS9+X54AMLGXe9QaDFP+HqA=" + }, + "nwmatcher": { + "version": "https://registry.npmjs.org/nwmatcher/-/nwmatcher-1.4.0.tgz", + "integrity": "sha1-tDiTYhcOfvl5jDx3FtgOvAEG/M8=", + "dev": true + }, + "oauth-sign": { + "version": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", + "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=" + }, + "object-assign": { + "version": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "object-component": { + "version": "https://registry.npmjs.org/object-component/-/object-component-0.0.3.tgz", + "integrity": "sha1-8MaapQ78lbhmwYb0AKM3acsvEpE=", + "dev": true + }, + "object-inspect": { + "version": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.2.2.tgz", + "integrity": "sha1-yCEV5PzIiK6hTWTCLk8X9qcNXlo=" + }, + "object-is": { + "version": "https://registry.npmjs.org/object-is/-/object-is-1.0.1.tgz", + "integrity": "sha1-CqYOyZiaCz7Xlc9NBvYs8a1lObY=", + "dev": true + }, + "object-keys": { + "version": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.11.tgz", + "integrity": "sha1-xUYBd4rVYPEULODgG8yotW0TQm0=" + }, + "object.assign": { + "version": "https://registry.npmjs.org/object.assign/-/object.assign-4.0.4.tgz", + "integrity": "sha1-scnMBE7xuf5jYG/BQau7MuFHMMw=", + "dev": true + }, + "object.defaults": { + "version": "https://registry.npmjs.org/object.defaults/-/object.defaults-1.1.0.tgz", + "integrity": "sha1-On+GgzS0B96gbaFtiNXNKeQ1/s8=", + "dev": true, + "dependencies": { + "for-own": { + "version": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", + "integrity": "sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=", + "dev": true + }, + "isobject": { + "version": "https://registry.npmjs.org/isobject/-/isobject-3.0.0.tgz", + "integrity": "sha1-OVZSF/NmF4nooKDAgNX35rxG4aA=", + "dev": true + } + } + }, + "object.entries": { + "version": "https://registry.npmjs.org/object.entries/-/object.entries-1.0.4.tgz", + "integrity": "sha1-G/mk3SKI9bM/Opk9JXZh8F0WGl8=", + "dev": true + }, + "object.omit": { + "version": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", + "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=", + "dev": true + }, + "object.reduce": { + "version": "https://registry.npmjs.org/object.reduce/-/object.reduce-0.1.7.tgz", + "integrity": "sha1-0YDoT3LSGDSK9FNStVFlJGuVBG0=", + "dev": true + }, + "object.values": { + "version": "https://registry.npmjs.org/object.values/-/object.values-1.0.4.tgz", + "integrity": "sha1-5STaCbT2b/Bd9FdUbscqyZ8TBpo=", + "dev": true + }, + "obs-store": { + "version": "https://registry.npmjs.org/obs-store/-/obs-store-2.3.2.tgz", + "integrity": "sha1-JA0Ga1zNZMhj3ob0sOvVH4MQglY=" + }, + "on-finished": { + "version": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=" + }, + "once": { + "version": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=" + }, + "onetime": { + "version": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", + "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=" + }, + "open": { + "version": "https://registry.npmjs.org/open/-/open-0.0.5.tgz", + "integrity": "sha1-QsPhjslUZra/DcQvOilFw/DK2Pw=", + "dev": true + }, + "opener": { + "version": "https://registry.npmjs.org/opener/-/opener-1.4.3.tgz", + "integrity": "sha1-XG2ixdflgx6P+jlklQ+NZnSskLg=" + }, + "optimist": { + "version": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", + "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=" + }, + "optionator": { + "version": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", + "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", + "dependencies": { + "wordwrap": { + "version": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=" + } + } + }, + "options": { + "version": "https://registry.npmjs.org/options/-/options-0.0.6.tgz", + "integrity": "sha1-7CLTEoBrtT5zF3Pnza788cZDEo8=", + "dev": true + }, + "ordered-read-streams": { + "version": "https://registry.npmjs.org/ordered-read-streams/-/ordered-read-streams-0.3.0.tgz", + "integrity": "sha1-cTfmmzKYuzQiR6G77jiByA4v14s=", + "dev": true + }, + "os-browserify": { + "version": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.1.2.tgz", + "integrity": "sha1-ScoCk+CxlZCl9d4Qx/JlphfY/lQ=", + "dev": true + }, + "os-homedir": { + "version": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" + }, + "os-locale": { + "version": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", + "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=" + }, + "os-tmpdir": { + "version": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" + }, + "outpipe": { + "version": "https://registry.npmjs.org/outpipe/-/outpipe-1.1.1.tgz", + "integrity": "sha1-UM+GFjZeh+Ax4ppeyTOaPaRyX6I=", + "dev": true + }, + "pako": { + "version": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", + "integrity": "sha1-8/dSL073gjSNqBYbrZ7P1Rv4OnU=", + "dev": true + }, + "parallel-transform": { + "version": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.1.0.tgz", + "integrity": "sha1-1BDwZbBdojCB/NEPKIVMKb2jOwY=" + }, + "parents": { + "version": "https://registry.npmjs.org/parents/-/parents-1.0.1.tgz", + "integrity": "sha1-/t1NK/GTp3dF/nHjcdc8MwfZx1E=", + "dev": true + }, + "parse-asn1": { + "version": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.0.tgz", + "integrity": "sha1-N8T5t+06tlx0gXtfJICTf7+XxxI=", + "dev": true + }, + "parse-filepath": { + "version": "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.1.tgz", + "integrity": "sha1-FZ1hVdQ5BNFsEO9piRHaHpGWm3M=", + "dev": true + }, + "parse-glob": { + "version": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", + "integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=", + "dev": true + }, + "parse-headers": { + "version": "https://registry.npmjs.org/parse-headers/-/parse-headers-2.0.1.tgz", + "integrity": "sha1-aug6eqJanZtwCswoaYzR8e1+lTY=" + }, + "parse-json": { + "version": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=" + }, + "parse-passwd": { + "version": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", + "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=", + "dev": true + }, + "parse5": { + "version": "https://registry.npmjs.org/parse5/-/parse5-1.5.1.tgz", + "integrity": "sha1-m387DeMr543CQBsXVzzK8Pb1nZQ=", + "dev": true + }, + "parsejson": { + "version": "https://registry.npmjs.org/parsejson/-/parsejson-0.0.3.tgz", + "integrity": "sha1-q343WfIJ7OmUN5c/fQ8fZK4OZKs=", + "dev": true + }, + "parseqs": { + "version": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.5.tgz", + "integrity": "sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0=", + "dev": true + }, + "parseuri": { + "version": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.5.tgz", + "integrity": "sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo=", + "dev": true + }, + "parseurl": { + "version": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.1.tgz", + "integrity": "sha1-yKuMkiO6NIiKpkopeyiFO+wY2lY=" + }, + "pascalcase": { + "version": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=" + }, + "path-browserify": { + "version": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.0.tgz", + "integrity": "sha1-oLhwcpquIUAFt9UDLsLLuw+0RRo=", + "dev": true + }, + "path-dirname": { + "version": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", + "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", + "dev": true + }, + "path-exists": { + "version": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=" + }, + "path-is-absolute": { + "version": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + }, + "path-is-inside": { + "version": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=" + }, + "path-platform": { + "version": "https://registry.npmjs.org/path-platform/-/path-platform-0.11.15.tgz", + "integrity": "sha1-6GQhf3TDaFDwhSt43Hv31KVyG/I=", + "dev": true + }, + "path-root": { + "version": "https://registry.npmjs.org/path-root/-/path-root-0.1.1.tgz", + "integrity": "sha1-mkpoFMrBwM1zNgqV8yCDyOpHRbc=", + "dev": true + }, + "path-root-regex": { + "version": "https://registry.npmjs.org/path-root-regex/-/path-root-regex-0.1.2.tgz", + "integrity": "sha1-v8zcjfWxLcUsi0PsONGNcsBLqW0=", + "dev": true + }, + "path-to-regexp": { + "version": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + }, + "path-type": { + "version": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", + "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=" + }, + "pause-stream": { + "version": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", + "integrity": "sha1-/lo0sMvOErWqaitAPuLnO2AvFEU=", + "dev": true + }, + "pbkdf2": { + "version": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.12.tgz", + "integrity": "sha1-vjZ4XFBn6kjYBv+SMojF91C2uKI=" + }, + "performance-now": { + "version": "https://registry.npmjs.org/performance-now/-/performance-now-0.2.0.tgz", + "integrity": "sha1-M+8wxcd9TqIcWlOGnZG1bY8lVeU=" + }, + "pify": { + "version": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" + }, + "ping-pong-stream": { + "version": "https://registry.npmjs.org/ping-pong-stream/-/ping-pong-stream-1.0.0.tgz", + "integrity": "sha1-TF6wm6atsCGInawNyr+45XcGhUo=" + }, + "pinkie": { + "version": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=" + }, + "pinkie-promise": { + "version": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=" + }, + "pkginfo": { + "version": "https://registry.npmjs.org/pkginfo/-/pkginfo-0.4.0.tgz", + "integrity": "sha1-NJ27f/04CB/K3AhT32h/DHdEzWU=", + "dev": true + }, + "plucker": { + "version": "https://registry.npmjs.org/plucker/-/plucker-0.0.0.tgz", + "integrity": "sha1-L/ok4Dqyz/pOda3B33DyViPEXQk=" + }, + "pluralize": { + "version": "https://registry.npmjs.org/pluralize/-/pluralize-1.2.1.tgz", + "integrity": "sha1-0aIUg/0iu0HlihL6NCGCMUCJfEU=" + }, + "pojo-migrator": { + "version": "https://registry.npmjs.org/pojo-migrator/-/pojo-migrator-2.1.0.tgz", + "integrity": "sha1-PCo7n4C6Wp+367kh0zRNtO+l9mk=" + }, + "polyfill-crypto.getrandomvalues": { + "version": "https://registry.npmjs.org/polyfill-crypto.getrandomvalues/-/polyfill-crypto.getrandomvalues-1.0.0.tgz", + "integrity": "sha1-XJVgKXbrthVbFjy2XXe57t47YaQ=" + }, + "portfinder": { + "version": "https://registry.npmjs.org/portfinder/-/portfinder-0.2.1.tgz", + "integrity": "sha1-srmwFk+eF/o6nH2yME0KdRQMca0=", + "dev": true, + "dependencies": { + "mkdirp": { + "version": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.0.7.tgz", + "integrity": "sha1-2JtPDkw+XlylQjWTFnXglP4aUHI=", + "dev": true + } + } + }, + "post-message-stream": { + "version": "https://registry.npmjs.org/post-message-stream/-/post-message-stream-1.0.0.tgz", + "integrity": "sha1-UO/gVjKuQza1HpSjFuVS6fFtqpw=" + }, + "prebuild-install": { + "version": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-2.1.2.tgz", + "integrity": "sha1-2a4MqFMw4Dli2TKS+VqLRMLr9QU=", + "dependencies": { + "minimist": { + "version": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" + } + } + }, + "prelude-ls": { + "version": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=" + }, + "preserve": { + "version": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz", + "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=", + "dev": true + }, + "pretty-bytes": { + "version": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-0.1.2.tgz", + "integrity": "sha1-zZApTVihyk6KXQ+5yCJZmIgazwA=", + "dev": true + }, + "pretty-hrtime": { + "version": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", + "integrity": "sha1-t+PqQkNaTJsnWdmeDyAesZWALuE=", + "dev": true + }, + "printf": { + "version": "https://registry.npmjs.org/printf/-/printf-0.2.5.tgz", + "integrity": "sha1-xDjKLKM+OSdnHbSracDlL5NqTw8=", + "dev": true + }, + "private": { + "version": "https://registry.npmjs.org/private/-/private-0.1.7.tgz", + "integrity": "sha1-aM5eih7woju1cMwoU3tTMqumPvE=" + }, + "process": { + "version": "https://registry.npmjs.org/process/-/process-0.5.2.tgz", + "integrity": "sha1-FjjYqONML0QKkduVq5rrZ3/Bhc8=" + }, + "process-nextick-args": { + "version": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", + "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=" + }, + "progress": { + "version": "https://registry.npmjs.org/progress/-/progress-1.1.8.tgz", + "integrity": "sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74=" + }, + "promise": { + "version": "https://registry.npmjs.org/promise/-/promise-7.1.1.tgz", + "integrity": "sha1-SJZUxpJha4qlWwck+oCbt9tJxb8=" + }, + "promise-filter": { + "version": "https://registry.npmjs.org/promise-filter/-/promise-filter-1.1.0.tgz", + "integrity": "sha1-fsPOmQyGfMud6GONvRnuF6UqS1k=" + }, + "promise-to-callback": { + "version": "https://registry.npmjs.org/promise-to-callback/-/promise-to-callback-1.0.0.tgz", + "integrity": "sha1-XSp0kBC/tn2WNZj805YHRqaP7vc=" + }, + "prompt": { + "version": "https://registry.npmjs.org/prompt/-/prompt-1.0.0.tgz", + "integrity": "sha1-jlcSPDlquYiJf7Mn/Trtw+c15P4=", + "dev": true + }, + "prop-types": { + "version": "https://registry.npmjs.org/prop-types/-/prop-types-15.5.10.tgz", + "integrity": "sha1-J5ffwxJhguOpXj37suiT3ddFYVQ=" + }, + "propagate": { + "version": "https://registry.npmjs.org/propagate/-/propagate-0.4.0.tgz", + "integrity": "sha1-8/zKCm/gZzanulcpZgaWF8EwtIE=", + "dev": true + }, + "proto-list": { + "version": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", + "integrity": "sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk=", + "dev": true + }, + "proxy-addr": { + "version": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-1.1.4.tgz", + "integrity": "sha1-J+VF9pYKRKYn2bREZ+NcG2tM4vM=" + }, + "prr": { + "version": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=" + }, + "pseudomap": { + "version": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", + "dev": true + }, + "public-encrypt": { + "version": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.0.tgz", + "integrity": "sha1-OfaZ86RlYN1eusvKaTyvfGXBjMY=", + "dev": true + }, + "pump": { + "version": "https://registry.npmjs.org/pump/-/pump-1.0.2.tgz", + "integrity": "sha1-Oz7mUS+U8OV1U4wXmV+fFpkKXVE=" + }, + "pumpify": { + "version": "https://registry.npmjs.org/pumpify/-/pumpify-1.3.5.tgz", + "integrity": "sha1-G2ccYZlAq8rqwK0OOjwWS+dgmTs=" + }, + "punycode": { + "version": "https://registry.npmjs.org/punycode/-/punycode-2.1.0.tgz", + "integrity": "sha1-X4Y+3Im5bbCQdLrXlHvwkFbKTn0=" + }, + "qrcode-npm": { + "version": "https://registry.npmjs.org/qrcode-npm/-/qrcode-npm-0.0.3.tgz", + "integrity": "sha1-d+5vvvqcDyn6CdTRUggHxqYEK5o=" + }, + "qs": { + "version": "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz", + "integrity": "sha1-E+JtKK1rD/qpExLNO/cI7TUecjM=" + }, + "querystring": { + "version": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", + "dev": true + }, + "querystring-es3": { + "version": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", + "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=", + "dev": true + }, + "qunit": { + "version": "https://registry.npmjs.org/qunit/-/qunit-0.9.3.tgz", + "integrity": "sha1-qR8HM06FR7rbqmrhhBwSaZC4EpU=", + "dev": true, + "dependencies": { + "co": { + "version": "https://registry.npmjs.org/co/-/co-3.1.0.tgz", + "integrity": "sha1-TqVOpaCJOBUxheFSEMaNkJK8G3g=", + "dev": true + } + } + }, + "qunitjs": { + "version": "https://registry.npmjs.org/qunitjs/-/qunitjs-1.23.1.tgz", + "integrity": "sha1-GXHPl6yb4Bpk0jFVCNLkjm/U5xk=", + "dev": true + }, + "quote-stream": { + "version": "https://registry.npmjs.org/quote-stream/-/quote-stream-1.0.2.tgz", + "integrity": "sha1-hJY/jJwmuULhU/7rU6rnRlK34LI=", + "dependencies": { + "minimist": { + "version": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" + } + } + }, + "ramda": { + "version": "https://registry.npmjs.org/ramda/-/ramda-0.23.0.tgz", + "integrity": "sha1-zNE//3NJepOXTj6GMnv9h71ujis=", + "dev": true + }, + "randomatic": { + "version": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.6.tgz", + "integrity": "sha1-EQ3Kv/OX6dz/fAeJzMCkmt8exbs=", + "dev": true + }, + "randombytes": { + "version": "https://registry.npmjs.org/randombytes/-/randombytes-2.0.3.tgz", + "integrity": "sha1-Z0yZdgkBw8QRJ3GjHlIdw0nMCew=" + }, + "range-parser": { + "version": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", + "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=" + }, + "raphael": { + "version": "https://registry.npmjs.org/raphael/-/raphael-2.2.7.tgz", + "integrity": "sha1-IxsZFB+NCGmG2PrOtm+LVi7iyBA=" + }, + "raw-body": { + "version": "https://registry.npmjs.org/raw-body/-/raw-body-2.1.7.tgz", + "integrity": "sha1-rf6s4uT7MJgFgBTQjActzFl1h3Q=", + "dev": true, + "dependencies": { + "bytes": { + "version": "https://registry.npmjs.org/bytes/-/bytes-2.4.0.tgz", + "integrity": "sha1-fZcZb51br39pNeJZhVSe3SpsIzk=", + "dev": true + }, + "iconv-lite": { + "version": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.13.tgz", + "integrity": "sha1-H4irpKsLFQjoMSrMOTRfNumS4vI=", + "dev": true + } + } + }, + "rc": { + "version": "https://registry.npmjs.org/rc/-/rc-1.2.1.tgz", + "integrity": "sha1-LgPo5C7kULjLPc5lvhv4l04d/ZU=", + "dependencies": { + "minimist": { + "version": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" + } + } + }, + "react": { + "version": "https://registry.npmjs.org/react/-/react-15.5.4.tgz", + "integrity": "sha1-+oPrAVBqsjfNwcjDsc6o3gEr8Ec=" + }, + "react-addons-css-transition-group": { + "version": "https://registry.npmjs.org/react-addons-css-transition-group/-/react-addons-css-transition-group-15.5.2.tgz", + "integrity": "sha1-6n4Knw4cJ8pCbaTv01WZFb1C6tI=" + }, + "react-addons-test-utils": { + "version": "https://registry.npmjs.org/react-addons-test-utils/-/react-addons-test-utils-15.5.1.tgz", + "integrity": "sha1-4NJYzaKhIq0N/2n4OCYNDDlY9fc=", + "dev": true + }, + "react-dom": { + "version": "https://registry.npmjs.org/react-dom/-/react-dom-15.5.4.tgz", + "integrity": "sha1-ugwoeG/VLtfk8hNf4CiNRirvk9o=" + }, + "react-hyperscript": { + "version": "https://registry.npmjs.org/react-hyperscript/-/react-hyperscript-2.4.2.tgz", + "integrity": "sha1-wZsfWhYcot8QvM5t0imehUepgv4=" + }, + "react-input-autosize": { + "version": "https://registry.npmjs.org/react-input-autosize/-/react-input-autosize-1.1.4.tgz", + "integrity": "sha1-y8RQctQITdxXgG2447NOZEuDZqw=" + }, + "react-markdown": { + "version": "https://registry.npmjs.org/react-markdown/-/react-markdown-2.5.0.tgz", + "integrity": "sha1-scYZBP7liViGgDvZ332yPD3DqJ4=" + }, + "react-redux": { + "version": "https://registry.npmjs.org/react-redux/-/react-redux-4.4.8.tgz", + "integrity": "sha1-57wd0QDotk6WrIIS2xEyObni4I8=" + }, + "react-select": { + "version": "https://registry.npmjs.org/react-select/-/react-select-1.0.0-rc.5.tgz", + "integrity": "sha1-nTFvJSsa3Dct21zfHxGca3z9tdY=" + }, + "react-simple-file-input": { + "version": "https://registry.npmjs.org/react-simple-file-input/-/react-simple-file-input-1.0.0.tgz", + "integrity": "sha1-DVmJtRub8sJbtIoMP9fnPkE+qkg=" + }, + "react-test-renderer": { + "version": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-15.5.4.tgz", + "integrity": "sha1-1OuyP2E9aF6o9TkBCcLSD798g7w=", + "dev": true + }, + "react-testutils-additions": { + "version": "https://registry.npmjs.org/react-testutils-additions/-/react-testutils-additions-15.2.0.tgz", + "integrity": "sha1-eAKm8o3/nPtnPL6vMoAc1qBU5rc=", + "dev": true, + "dependencies": { + "object-assign": { + "version": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz", + "integrity": "sha1-m+3VygiXlJvKR+f/QIBi1Un1h/I=", + "dev": true + } + } + }, + "react-tooltip-component": { + "version": "https://registry.npmjs.org/react-tooltip-component/-/react-tooltip-component-0.3.0.tgz", + "integrity": "sha1-+z7HjDJw/pGWkrwx8UBBCLz0eF4=" + }, + "read": { + "version": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", + "integrity": "sha1-s9oZvQUkMal2cdRKQmNK33ELQMQ=", + "dev": true + }, + "read-only-stream": { + "version": "https://registry.npmjs.org/read-only-stream/-/read-only-stream-2.0.0.tgz", + "integrity": "sha1-JyT9aoET1zdkrCiNQ4YnDB2/F/A=", + "dev": true + }, + "read-pkg": { + "version": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", + "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=" + }, + "read-pkg-up": { + "version": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", + "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=" + }, + "readable-stream": { + "version": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.2.9.tgz", + "integrity": "sha1-z3jsb0ptHrQ9JkiMrJfwQudLf8g=" + }, + "readable-wrap": { + "version": "https://registry.npmjs.org/readable-wrap/-/readable-wrap-1.0.0.tgz", + "integrity": "sha1-O1ohHGMeEjA6VJkcgGwX564ga/8=", + "dev": true, + "dependencies": { + "isarray": { + "version": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "readable-stream": { + "version": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "dev": true + }, + "string_decoder": { + "version": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + } + } + }, + "readdirp": { + "version": "https://registry.npmjs.org/readdirp/-/readdirp-2.1.0.tgz", + "integrity": "sha1-TtCtBg3zBzMAxIRANz9y0cxkLXg=", + "dev": true + }, + "readline2": { + "version": "https://registry.npmjs.org/readline2/-/readline2-1.0.1.tgz", + "integrity": "sha1-QQWWCP/BVHV7cV2ZidGZ/783LjU=" + }, + "rechoir": { + "version": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", + "dev": true + }, + "redux": { + "version": "https://registry.npmjs.org/redux/-/redux-3.6.0.tgz", + "integrity": "sha1-iHwrPQub2G7KK+cFccJ2VMGeGI0=" + }, + "redux-logger": { + "version": "https://registry.npmjs.org/redux-logger/-/redux-logger-2.10.2.tgz", + "integrity": "sha1-PFpfCm8yV3wd6t9mVfJX+CxsOTc=" + }, + "redux-thunk": { + "version": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-1.0.3.tgz", + "integrity": "sha1-d4qgCZ7qBZUDGrazkWX2Zw2NJr0=" + }, + "regenerate": { + "version": "https://registry.npmjs.org/regenerate/-/regenerate-1.3.2.tgz", + "integrity": "sha1-0ZQcZ7rUN+G+dkM63Vs4X5WxkmA=" + }, + "regenerator-runtime": { + "version": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz", + "integrity": "sha1-M2w+/BIgrc7dosn6tntaeVWjNlg=" + }, + "regenerator-transform": { + "version": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.9.11.tgz", + "integrity": "sha1-On0GdSDLe3F2dp61/4aGkb7+EoM=" + }, + "regex-cache": { + "version": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.3.tgz", + "integrity": "sha1-mxpsNdTQ3871cRrmUejp09cRQUU=", + "dev": true + }, + "regexpu-core": { + "version": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-2.0.0.tgz", + "integrity": "sha1-SdA4g3uNz4v6W5pCE5k45uoq4kA=" + }, + "regjsgen": { + "version": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz", + "integrity": "sha1-bAFq3qxVT3WCP+N6wFuS1aTtsfc=" + }, + "regjsparser": { + "version": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.1.5.tgz", + "integrity": "sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw=" + }, + "remove-trailing-separator": { + "version": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.0.1.tgz", + "integrity": "sha1-YV67lq9VlVLUv0BXyENtSGq2PMQ=", + "dev": true + }, + "repeat-element": { + "version": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.2.tgz", + "integrity": "sha1-7wiaF40Ug7quTZPrmLT55OEdmQo=", + "dev": true + }, + "repeat-string": { + "version": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", + "dev": true + }, + "repeating": { + "version": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", + "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=" + }, + "replace-ext": { + "version": "https://registry.npmjs.org/replace-ext/-/replace-ext-0.0.1.tgz", + "integrity": "sha1-KbvZIHinOfC8zitO5B6DeVNSKSQ=" + }, + "replaceall": { + "version": "https://registry.npmjs.org/replaceall/-/replaceall-0.1.6.tgz", + "integrity": "sha1-gdgax663LX9cSUKt8ml6MiBojY4=", + "dev": true + }, + "replacestream": { + "version": "https://registry.npmjs.org/replacestream/-/replacestream-4.0.2.tgz", + "integrity": "sha1-DEFAcH5PAyP1DeBEhRcIz1i8N70=", + "dev": true + }, + "request": { + "version": "https://registry.npmjs.org/request/-/request-2.81.0.tgz", + "integrity": "sha1-xpKJRqDgbF+Nb4qTM0af/aRimKA=", + "dependencies": { + "tunnel-agent": { + "version": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=" + }, + "uuid": { + "version": "https://registry.npmjs.org/uuid/-/uuid-3.0.1.tgz", + "integrity": "sha1-ZUS7ot/ajBzxfmKaOjBeK7H+5sE=" + } + } + }, + "request-promise": { + "version": "https://registry.npmjs.org/request-promise/-/request-promise-4.2.1.tgz", + "integrity": "sha1-fuxWyJMXqCLL/qmbA5zlQ8LhX2c=" + }, + "request-promise-core": { + "version": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.1.tgz", + "integrity": "sha1-Pu4AssWqgyOc+wTFcA2jb4HNCLY=" + }, + "require-directory": { + "version": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" + }, + "require-from-string": { + "version": "https://registry.npmjs.org/require-from-string/-/require-from-string-1.2.1.tgz", + "integrity": "sha1-UpyczvJzgK3+yaL5ZbZJu+5jZBg=" + }, + "require-main-filename": { + "version": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", + "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=" + }, + "require-uncached": { + "version": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", + "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=" + }, + "requires-port": { + "version": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", + "dev": true + }, + "resolve": { + "version": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", + "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=" + }, + "resolve-dir": { + "version": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-0.1.1.tgz", + "integrity": "sha1-shklmlYC+sXFxJatiUpujMQwJh4=", + "dev": true + }, + "resolve-from": { + "version": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz", + "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=" + }, + "resolve-url": { + "version": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", + "dev": true + }, + "response-stream": { + "version": "https://registry.npmjs.org/response-stream/-/response-stream-0.0.0.tgz", + "integrity": "sha1-2ksXzHaEyYyWK+tNlfZoyNytCdU=", + "dev": true + }, + "restore-cursor": { + "version": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz", + "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=" + }, + "resumer": { + "version": "https://registry.npmjs.org/resumer/-/resumer-0.0.0.tgz", + "integrity": "sha1-8ej0YeQGS6Oegq883CqMiT0HZ1k=" + }, + "revalidator": { + "version": "https://registry.npmjs.org/revalidator/-/revalidator-0.1.8.tgz", + "integrity": "sha1-/s5hv6DBtSoga9axgZgYS91SOjs=", + "dev": true + }, + "rimraf": { + "version": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.1.tgz", + "integrity": "sha1-wjOOxkPfeht/5cVPqG9XQopV8z0=" + }, + "ripemd160": { + "version": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.1.tgz", + "integrity": "sha1-D0WEKVxTo2KK9+bXmsohzlfRxuc=" + }, + "rlp": { + "version": "https://registry.npmjs.org/rlp/-/rlp-2.0.0.tgz", + "integrity": "sha1-nbOE/0uJqPYVY9kjldhiWxjzr7A=" + }, + "run-async": { + "version": "https://registry.npmjs.org/run-async/-/run-async-0.1.0.tgz", + "integrity": "sha1-yK1KXhEGYeQCp9IbUw4AnyX444k=" + }, + "rx-lite": { + "version": "https://registry.npmjs.org/rx-lite/-/rx-lite-3.1.2.tgz", + "integrity": "sha1-Gc5QLKVyZl87ZHsQk5+X/RYV8QI=" + }, + "safe-buffer": { + "version": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.0.1.tgz", + "integrity": "sha1-0mPKVGls2KMGtcplUekt5XkY++c=" + }, + "samsam": { + "version": "https://registry.npmjs.org/samsam/-/samsam-1.1.2.tgz", + "integrity": "sha1-vsEf3IOp/aBjQBIQ5AF2wwJNFWc=", + "dev": true + }, + "sandwich-expando": { + "version": "https://registry.npmjs.org/sandwich-expando/-/sandwich-expando-1.1.1.tgz", + "integrity": "sha1-g4BvzKI3Wvi2ww5vUu1PmJ3rsWU=" + }, + "sax": { + "version": "https://registry.npmjs.org/sax/-/sax-1.2.2.tgz", + "integrity": "sha1-/YYxojvHgmvvXYcb24c3jJVkeCg=", + "dev": true + }, + "script-injector": { + "version": "https://registry.npmjs.org/script-injector/-/script-injector-1.0.0.tgz", + "integrity": "sha1-9vTH9qXcxZ4IJG52vfyDoKFAaSY=", + "dev": true, + "dependencies": { + "isarray": { + "version": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "readable-stream": { + "version": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "dev": true + }, + "string_decoder": { + "version": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + }, + "through2": { + "version": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz", + "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=", + "dev": true + } + } + }, + "scrypt": { + "version": "https://registry.npmjs.org/scrypt/-/scrypt-6.0.3.tgz", + "integrity": "sha1-BOAUpWgrU/pQwtXM4WfXGcBthw0=" + }, + "scrypt.js": { + "version": "https://registry.npmjs.org/scrypt.js/-/scrypt.js-0.2.0.tgz", + "integrity": "sha1-r40UZbcemZARC+38WTuUeeA6ito=" + }, + "scryptsy": { + "version": "https://registry.npmjs.org/scryptsy/-/scryptsy-1.2.1.tgz", + "integrity": "sha1-oyJfpLJST4AnAHYeKFW987LZIWM=" + }, + "secp256k1": { + "version": "https://registry.npmjs.org/secp256k1/-/secp256k1-3.2.5.tgz", + "integrity": "sha1-Dd5bJ+UCFmX23/ynssPgEMbBPJM=" + }, + "semaphore": { + "version": "https://registry.npmjs.org/semaphore/-/semaphore-1.0.5.tgz", + "integrity": "sha1-tJJXbmavGT25XWXiXsU/Xxl5jWA=" + }, + "semver": { + "version": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", + "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=" + }, + "semver-greatest-satisfied-range": { + "version": "https://registry.npmjs.org/semver-greatest-satisfied-range/-/semver-greatest-satisfied-range-1.0.0.tgz", + "integrity": "sha1-T7RB4qjSbEC1mDJ1VzGN4nKlWKA=", + "dev": true, + "dependencies": { + "semver": { + "version": "https://registry.npmjs.org/semver/-/semver-4.3.6.tgz", + "integrity": "sha1-MAvG4OhjdPe6YQaLWx7NV/xlMto=", + "dev": true + } + } + }, + "semver-regex": { + "version": "https://registry.npmjs.org/semver-regex/-/semver-regex-1.0.0.tgz", + "integrity": "sha1-kqSWkGX5xwxpR1PVUkj8aPj2Usk=", + "dev": true + }, + "send": { + "version": "https://registry.npmjs.org/send/-/send-0.15.3.tgz", + "integrity": "sha1-UBP5+ZAj31DRvZiSwZ4979HVMwk=" + }, + "serve-static": { + "version": "https://registry.npmjs.org/serve-static/-/serve-static-1.12.3.tgz", + "integrity": "sha1-n0uhni8wMMVH+K+ZEHg47DjVseI=" + }, + "set-blocking": { + "version": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" + }, + "set-immediate-shim": { + "version": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", + "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=" + }, + "setimmediate": { + "version": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=" + }, + "setprototypeof": { + "version": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", + "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=" + }, + "sha.js": { + "version": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.8.tgz", + "integrity": "sha1-NwaMLEdra69ALRSknGf1l5IfY08=" + }, + "sha3": { + "version": "https://registry.npmjs.org/sha3/-/sha3-1.2.0.tgz", + "integrity": "sha1-aYnxtwpJhwWHajc+LGKs6WqpOZo=" + }, + "shallow-copy": { + "version": "https://registry.npmjs.org/shallow-copy/-/shallow-copy-0.0.1.tgz", + "integrity": "sha1-QV9CcC1z2BAzApLMXuhurhoRoXA=" + }, + "shasum": { + "version": "https://registry.npmjs.org/shasum/-/shasum-1.0.2.tgz", + "integrity": "sha1-5wEjENj0F/TetXEhUOVni4euVl8=", + "dev": true, + "dependencies": { + "json-stable-stringify": { + "version": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-0.0.1.tgz", + "integrity": "sha1-YRwj6BTbN1Un34URk9tZ3Sryf0U=", + "dev": true + } + } + }, + "shebang-command": { + "version": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true + }, + "shebang-regex": { + "version": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true + }, + "shell-quote": { + "version": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.6.1.tgz", + "integrity": "sha1-9HgZSczkAmlxJ0MOo7PFR29IF2c=", + "dev": true + }, + "shelljs": { + "version": "https://registry.npmjs.org/shelljs/-/shelljs-0.6.1.tgz", + "integrity": "sha1-7GIRvtGSBEIIj+D3Cyg3Iy7SyKg=" + }, + "shellwords": { + "version": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.0.tgz", + "integrity": "sha1-Zq/Ue2oSky2Qccv9mKUueFzQuhQ=", + "dev": true + }, + "sigmund": { + "version": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", + "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=", + "dev": true + }, + "signal-exit": { + "version": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" + }, + "simple-get": { + "version": "https://registry.npmjs.org/simple-get/-/simple-get-1.4.3.tgz", + "integrity": "sha1-6XVe2kB+ltpAxeUVjJ6jezO+y+s=" + }, + "sinon": { + "version": "https://registry.npmjs.org/sinon/-/sinon-1.17.7.tgz", + "integrity": "sha1-RUKk9JugxFwF6y6d2dID4rjv4L8=", + "dev": true + }, + "sizzle": { + "version": "https://registry.npmjs.org/sizzle/-/sizzle-2.3.3.tgz", + "integrity": "sha1-TrB4w3IxpWtS5Bk/cB5++JN+YGs=", + "dev": true + }, + "slash": { + "version": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", + "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=" + }, + "slice-ansi": { + "version": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz", + "integrity": "sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=" + }, + "sntp": { + "version": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz", + "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=" + }, + "socket.io": { + "version": "https://registry.npmjs.org/socket.io/-/socket.io-1.6.0.tgz", + "integrity": "sha1-PkDZMmN+a9kjmBslyvfFPoO24uE=", + "dev": true, + "dependencies": { + "debug": { + "version": "https://registry.npmjs.org/debug/-/debug-2.3.3.tgz", + "integrity": "sha1-QMRT5n5uE8kB3ewxeviYbNqe/4w=", + "dev": true + }, + "ms": { + "version": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", + "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=", + "dev": true + }, + "object-assign": { + "version": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.0.tgz", + "integrity": "sha1-ejs9DpgGPUP0wD8uiubNUahog6A=", + "dev": true + } + } + }, + "socket.io-adapter": { + "version": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-0.5.0.tgz", + "integrity": "sha1-y21LuL7IHhB4uZZ3+c7QBGBmu4s=", + "dev": true, + "dependencies": { + "debug": { + "version": "https://registry.npmjs.org/debug/-/debug-2.3.3.tgz", + "integrity": "sha1-QMRT5n5uE8kB3ewxeviYbNqe/4w=", + "dev": true + }, + "ms": { + "version": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", + "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=", + "dev": true + } + } + }, + "socket.io-client": { + "version": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-1.6.0.tgz", + "integrity": "sha1-W2aPT3cTBN/u0XkGRwg4b6ZxeFM=", + "dev": true, + "dependencies": { + "component-emitter": { + "version": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", + "dev": true + }, + "debug": { + "version": "https://registry.npmjs.org/debug/-/debug-2.3.3.tgz", + "integrity": "sha1-QMRT5n5uE8kB3ewxeviYbNqe/4w=", + "dev": true + }, + "ms": { + "version": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", + "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=", + "dev": true + } + } + }, + "socket.io-parser": { + "version": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-2.3.1.tgz", + "integrity": "sha1-3VMgJRA85Clpcya+/WQAX8/ltKA=", + "dev": true, + "dependencies": { + "debug": { + "version": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", + "dev": true + }, + "isarray": { + "version": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "ms": { + "version": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", + "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=", + "dev": true + } + } + }, + "solc": { + "version": "https://registry.npmjs.org/solc/-/solc-0.4.11.tgz", + "integrity": "sha1-JSLrQ+fAQZusIGC5biCiWTv7Xos=", + "dependencies": { + "yargs": { + "version": "https://registry.npmjs.org/yargs/-/yargs-4.8.1.tgz", + "integrity": "sha1-wMQpJMpKqmsObaFznfshZDn53cA=" + }, + "yargs-parser": { + "version": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-2.4.1.tgz", + "integrity": "sha1-hVaN488VD/SfpRgl8DqMiA3cxcQ=" + } + } + }, + "source-map": { + "version": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz", + "integrity": "sha1-dc449SvwczxafwwRjYEzSiu19BI=" + }, + "source-map-resolve": { + "version": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.3.1.tgz", + "integrity": "sha1-YQ9hIqRFuN1RU1oqcbeD38Ekh2E=", + "dev": true + }, + "source-map-support": { + "version": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.15.tgz", + "integrity": "sha1-AyAt9lwG0r2MfsI2KhkwVv7407E=" + }, + "source-map-url": { + "version": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.3.0.tgz", + "integrity": "sha1-fsrxO1e80J2opAxdJp2zN5nUqvk=", + "dev": true + }, + "sparkles": { + "version": "https://registry.npmjs.org/sparkles/-/sparkles-1.0.0.tgz", + "integrity": "sha1-Gsu/tZJDbRC76PeFt8xvgoFQEsM=" + }, + "spawn-args": { + "version": "https://registry.npmjs.org/spawn-args/-/spawn-args-0.2.0.tgz", + "integrity": "sha1-+30L0dcP1DFr2ePew4nmX51jYbs=", + "dev": true + }, + "spdx-correct": { + "version": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-1.0.2.tgz", + "integrity": "sha1-SzBz2TP/UfORLwOsVRlJikFQ20A=" + }, + "spdx-expression-parse": { + "version": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-1.0.4.tgz", + "integrity": "sha1-m98vIOH0DtRH++JzJmGR/O1RYmw=" + }, + "spdx-license-ids": { + "version": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz", + "integrity": "sha1-yd96NCRZSt5r0RkA1ZZpbcBrrFc=" + }, + "split": { + "version": "https://registry.npmjs.org/split/-/split-0.3.3.tgz", + "integrity": "sha1-zQ7qXmOiEd//frDwkcQTPi0N0o8=", + "dev": true + }, + "sprintf-js": { + "version": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" + }, + "sshpk": { + "version": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.0.tgz", + "integrity": "sha1-/yo+T9BEl1Vf7Zezmg/YL6+zozw=", + "dependencies": { + "assert-plus": { + "version": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + } + } + }, + "stack-trace": { + "version": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.9.tgz", + "integrity": "sha1-qPbq7KkGdMMz58Q5U/J1tFFRBpU=", + "dev": true + }, + "static-eval": { + "version": "https://registry.npmjs.org/static-eval/-/static-eval-0.2.4.tgz", + "integrity": "sha1-t9NNg4k3uWn5ZBygfUj47eJj6ns=", + "dependencies": { + "escodegen": { + "version": "https://registry.npmjs.org/escodegen/-/escodegen-0.0.28.tgz", + "integrity": "sha1-Dk/xcV8yh3XWyrUaxEpAbNer/9M=" + }, + "esprima": { + "version": "https://registry.npmjs.org/esprima/-/esprima-1.0.4.tgz", + "integrity": "sha1-n1V+CPw7TSbs6d00+Pv0drYlha0=" + }, + "estraverse": { + "version": "https://registry.npmjs.org/estraverse/-/estraverse-1.3.2.tgz", + "integrity": "sha1-N8K4k+8T1yPydth41g2FNRUqbEI=" + } + } + }, + "static-module": { + "version": "https://registry.npmjs.org/static-module/-/static-module-1.3.2.tgz", + "integrity": "sha1-Mp+58iOlZiZr2nGEO32TLHZxdPM=", + "dependencies": { + "isarray": { + "version": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, + "minimist": { + "version": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + }, + "object-inspect": { + "version": "https://registry.npmjs.org/object-inspect/-/object-inspect-0.4.0.tgz", + "integrity": "sha1-9RV8EWwUVbJDsG7pdwM5LFrYn+w=" + }, + "object-keys": { + "version": "https://registry.npmjs.org/object-keys/-/object-keys-0.4.0.tgz", + "integrity": "sha1-KKaq50KN0sOpLz2V8hM13SBOAzY=" + }, + "quote-stream": { + "version": "https://registry.npmjs.org/quote-stream/-/quote-stream-0.0.0.tgz", + "integrity": "sha1-zeKelMQJsW4Z3HCYuJtmWPlyHTs=" + }, + "readable-stream": { + "version": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=" + }, + "string_decoder": { + "version": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + }, + "through2": { + "version": "https://registry.npmjs.org/through2/-/through2-0.4.2.tgz", + "integrity": "sha1-2/WGYDEVHsg1K7bE22SiKSqEC5s=" + }, + "xtend": { + "version": "https://registry.npmjs.org/xtend/-/xtend-2.1.2.tgz", + "integrity": "sha1-bv7MKk2tjmlixJAbM3znuoe10os=" + } + } + }, + "statuses": { + "version": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", + "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=" + }, + "stealthy-require": { + "version": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", + "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=" + }, + "stream-browserify": { + "version": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.1.tgz", + "integrity": "sha1-ZiZu5fm9uZQKTkUUyvtDu3Hlyds=", + "dev": true + }, + "stream-combiner": { + "version": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz", + "integrity": "sha1-TV5DPBhSYd3mI8o/RMWGvPXErRQ=", + "dev": true + }, + "stream-combiner2": { + "version": "https://registry.npmjs.org/stream-combiner2/-/stream-combiner2-1.1.1.tgz", + "integrity": "sha1-+02KFCDqNidk4hrUeAOXvry0HL4=", + "dev": true, + "dependencies": { + "duplexer2": { + "version": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", + "integrity": "sha1-ixLauHjA1p4+eJEFFmKjL8a93ME=", + "dev": true + } + } + }, + "stream-each": { + "version": "https://registry.npmjs.org/stream-each/-/stream-each-1.2.0.tgz", + "integrity": "sha1-HpXUdXP1gNgU3A/4zQ9m8c5TyZE=" + }, + "stream-exhaust": { + "version": "https://registry.npmjs.org/stream-exhaust/-/stream-exhaust-1.0.1.tgz", + "integrity": "sha1-wMRFXlTOWhecqHNuczNLTn/WdVM=", + "dev": true + }, + "stream-http": { + "version": "https://registry.npmjs.org/stream-http/-/stream-http-2.7.1.tgz", + "integrity": "sha1-VGpRdBrVprB+njGwsQRBqRffUoo=", + "dev": true + }, + "stream-shift": { + "version": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz", + "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=" + }, + "stream-splicer": { + "version": "https://registry.npmjs.org/stream-splicer/-/stream-splicer-1.3.2.tgz", + "integrity": "sha1-PARBvhW5v04iYnXm3IOWR0VUZmE=", + "dev": true, + "dependencies": { + "isarray": { + "version": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "readable-stream": { + "version": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "dev": true + }, + "string_decoder": { + "version": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + }, + "through2": { + "version": "https://registry.npmjs.org/through2/-/through2-1.1.1.tgz", + "integrity": "sha1-CEfLxESfNAVXTb3M2buEG4OsNUU=", + "dev": true + } + } + }, + "string_decoder": { + "version": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.1.tgz", + "integrity": "sha1-YuIA8DmVWmgQ2N8KM//A8BNmLZg=" + }, + "string-width": { + "version": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=" + }, + "string.prototype.repeat": { + "version": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-0.2.0.tgz", + "integrity": "sha1-q6Nt4I3O5qWjN9SbLqHaGyj8Ds8=" + }, + "string.prototype.trim": { + "version": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.1.2.tgz", + "integrity": "sha1-0E3iyJ4Tf019IG8Ia17S+ua+jOo=" + }, + "stringstream": { + "version": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", + "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=" + }, + "strip-ansi": { + "version": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=" + }, + "strip-bom": { + "version": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=" + }, + "strip-bom-stream": { + "version": "https://registry.npmjs.org/strip-bom-stream/-/strip-bom-stream-1.0.0.tgz", + "integrity": "sha1-5xRDmFd9Uaa+0PoZlPoF9D/ZiO4=", + "dev": true + }, + "strip-hex-prefix": { + "version": "https://registry.npmjs.org/strip-hex-prefix/-/strip-hex-prefix-1.0.0.tgz", + "integrity": "sha1-DF8VX+8RUTczd96du1iNoFUA428=" + }, + "strip-json-comments": { + "version": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" + }, + "styled_string": { + "version": "https://registry.npmjs.org/styled_string/-/styled_string-0.0.1.tgz", + "integrity": "sha1-0ieCvYEpVFm8Tx3xjEutjpTdEko=", + "dev": true + }, + "subarg": { + "version": "https://registry.npmjs.org/subarg/-/subarg-1.0.0.tgz", + "integrity": "sha1-9izxdYHplrSPyWVpn1TAauJouNI=", + "dev": true, + "dependencies": { + "minimist": { + "version": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + } + } + }, + "supports-color": { + "version": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" + }, + "sw-stream": { + "version": "https://registry.npmjs.org/sw-stream/-/sw-stream-2.0.0.tgz", + "integrity": "sha1-Yo677rnu4LZrA+xS/FX8xO6yPPM=" + }, + "symbol-observable": { + "version": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.0.4.tgz", + "integrity": "sha1-Kb9hXUqnEhvdiYsi1LP5vE4qoD0=" + }, + "symbol-tree": { + "version": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.2.tgz", + "integrity": "sha1-rifbOPZgp64uHDt9G8KQgZuFGeY=", + "dev": true + }, + "syntax-error": { + "version": "https://registry.npmjs.org/syntax-error/-/syntax-error-1.3.0.tgz", + "integrity": "sha1-HtkmbE1AvnXcVb+bsct3Biu5bKE=", + "dev": true + }, + "table": { + "version": "https://registry.npmjs.org/table/-/table-3.8.3.tgz", + "integrity": "sha1-K7xULw/amGGnVdOUf+/Ys/UThV8=", + "dependencies": { + "is-fullwidth-code-point": { + "version": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" + }, + "string-width": { + "version": "https://registry.npmjs.org/string-width/-/string-width-2.0.0.tgz", + "integrity": "sha1-Y1xUNsxypuDDh87KJ41OLuxSaH4=" + } + } + }, + "tap-parser": { + "version": "https://registry.npmjs.org/tap-parser/-/tap-parser-5.3.3.tgz", + "integrity": "sha1-U+yKkPJ11v/0PxaeVqZ5UCp0EYU=", + "dev": true + }, + "tape": { + "version": "https://registry.npmjs.org/tape/-/tape-4.6.3.tgz", + "integrity": "sha1-Y353WB6ass4XV36b1M5PV1gG2LY=", + "dependencies": { + "minimist": { + "version": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" + } + } + }, + "tar-fs": { + "version": "https://registry.npmjs.org/tar-fs/-/tar-fs-1.15.2.tgz", + "integrity": "sha1-dh9bMpMsezlGGmDVN/rqDYCEgww=" + }, + "tar-stream": { + "version": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.5.4.tgz", + "integrity": "sha1-NlSc8E7RrumyowwBQyUiONr5QBY=", + "dependencies": { + "bl": { + "version": "https://registry.npmjs.org/bl/-/bl-1.2.1.tgz", + "integrity": "sha1-ysMo977kVzDUBLaSID/LWQ4XLV4=" + } + } + }, + "ternary-stream": { + "version": "https://registry.npmjs.org/ternary-stream/-/ternary-stream-2.0.1.tgz", + "integrity": "sha1-Bk5Im0tb9gumpre8fy9cJ07Pgmk=", + "dev": true + }, + "testem": { + "version": "https://registry.npmjs.org/testem/-/testem-1.16.2.tgz", + "integrity": "sha1-lURtMQoQ6FLT69vAzis/11N4uik=", + "dev": true, + "dependencies": { + "commander": { + "version": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz", + "integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=", + "dev": true + } + } + }, + "text-table": { + "version": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=" + }, + "textarea-caret": { + "version": "https://registry.npmjs.org/textarea-caret/-/textarea-caret-3.0.2.tgz", + "integrity": "sha1-82DEhpmqGr9xhoCkOjGoUGZcLK8=" + }, + "textextensions": { + "version": "https://registry.npmjs.org/textextensions/-/textextensions-1.0.2.tgz", + "integrity": "sha1-ZUhjk+4fK7A5pgy7oFsLaL2VAdI=", + "dev": true + }, + "thenify": { + "version": "https://registry.npmjs.org/thenify/-/thenify-3.3.0.tgz", + "integrity": "sha1-5p44obq+lpsBCCB5eLn2K4hgSDk=", + "dev": true, + "dependencies": { + "any-promise": { + "version": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=", + "dev": true + } + } + }, + "thenify-all": { + "version": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha1-GhkY1ALY/D+Y+/I02wvMjMEOlyY=", + "dev": true + }, + "three": { + "version": "https://registry.npmjs.org/three/-/three-0.73.0.tgz", + "integrity": "sha1-jyWKxG2BVsRa8kT+E6T4u3oEUSk=" + }, + "three.js": { + "version": "https://registry.npmjs.org/three.js/-/three.js-0.73.2.tgz", + "integrity": "sha1-3JARPxgT9AShjhYkqajjnGwZSpY=" + }, + "through": { + "version": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" + }, + "through2": { + "version": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz", + "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=" + }, + "through2-filter": { + "version": "https://registry.npmjs.org/through2-filter/-/through2-filter-2.0.0.tgz", + "integrity": "sha1-YLxVoNrLdghdsfna6Zq0P4PWIuw=", + "dev": true + }, + "tildify": { + "version": "https://registry.npmjs.org/tildify/-/tildify-1.2.0.tgz", + "integrity": "sha1-3OwD9V3Km3qj5bBPIYF+tW5jWIo=", + "dev": true + }, + "time-stamp": { + "version": "https://registry.npmjs.org/time-stamp/-/time-stamp-1.1.0.tgz", + "integrity": "sha1-dkpaEa9QVhkhsTPztE5hhofg9cM=" + }, + "timers-browserify": { + "version": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-1.4.2.tgz", + "integrity": "sha1-ycWLV1voQHN1y14kYtrO50NZ9B0=", + "dev": true, + "dependencies": { + "process": { + "version": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", + "dev": true + } + } + }, + "to-absolute-glob": { + "version": "https://registry.npmjs.org/to-absolute-glob/-/to-absolute-glob-0.1.1.tgz", + "integrity": "sha1-HN+kcqnvUMI57maZm2YsoOs5k38=", + "dev": true + }, + "to-array": { + "version": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz", + "integrity": "sha1-F+bBH3PdTz10zaek/zI46a2b+JA=", + "dev": true + }, + "to-arraybuffer": { + "version": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", + "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=", + "dev": true + }, + "to-fast-properties": { + "version": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", + "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=" + }, + "to-iso-string": { + "version": "https://registry.npmjs.org/to-iso-string/-/to-iso-string-0.0.2.tgz", + "integrity": "sha1-TcGeZk38y+Jb2NtQiwDG2hWCVdE=", + "dev": true + }, + "to-utf8": { + "version": "https://registry.npmjs.org/to-utf8/-/to-utf8-0.0.1.tgz", + "integrity": "sha1-0Xrqcv8vujm55DYBvns/9y4ImFI=" + }, + "toggle-selection": { + "version": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.5.tgz", + "integrity": "sha1-cmxwPeYHGTpzwyx99JzSSVD8V08=" + }, + "tough-cookie": { + "version": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.2.tgz", + "integrity": "sha1-8IH3bkyFcg5sN6X6ztc3FQ2EByo=", + "dependencies": { + "punycode": { + "version": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" + } + } + }, + "tr46": { + "version": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=", + "dev": true + }, + "tracejs": { + "version": "https://registry.npmjs.org/tracejs/-/tracejs-0.1.8.tgz", + "integrity": "sha1-bCZ4exhT8TcWNGIsHIC8RAJsXXA=", + "dev": true + }, + "traverse": { + "version": "https://registry.npmjs.org/traverse/-/traverse-0.6.6.tgz", + "integrity": "sha1-y99WD9e5r2MlAv7UD5GMFX6pcTc=" + }, + "trim": { + "version": "https://registry.npmjs.org/trim/-/trim-0.0.1.tgz", + "integrity": "sha1-WFhUf2spB1fulczMZm+1AITEYN0=" + }, + "trim-right": { + "version": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", + "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=" + }, + "trumpet": { + "version": "https://registry.npmjs.org/trumpet/-/trumpet-1.7.2.tgz", + "integrity": "sha1-sCxp5GXRcfVeRJJL+bW90gl0yDA=", + "dev": true, + "dependencies": { + "isarray": { + "version": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "readable-stream": { + "version": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "dev": true + }, + "string_decoder": { + "version": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + }, + "through2": { + "version": "https://registry.npmjs.org/through2/-/through2-1.1.1.tgz", + "integrity": "sha1-CEfLxESfNAVXTb3M2buEG4OsNUU=", + "dev": true + } + } + }, + "tryit": { + "version": "https://registry.npmjs.org/tryit/-/tryit-1.0.3.tgz", + "integrity": "sha1-OTvnMKlEb9Hq1tpZoBQwjzbCics=" + }, + "tty-browserify": { + "version": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", + "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=", + "dev": true + }, + "tunnel-agent": { + "version": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz", + "integrity": "sha1-Y3PbdpCf5XDgjXNYM2Xtgop07us=" + }, + "tweetnacl": { + "version": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "optional": true + }, + "type-check": { + "version": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=" + }, + "type-detect": { + "version": "https://registry.npmjs.org/type-detect/-/type-detect-1.0.0.tgz", + "integrity": "sha1-diIXzAbbJY7EiQihKY6LlRIejqI=", + "dev": true + }, + "type-is": { + "version": "https://registry.npmjs.org/type-is/-/type-is-1.6.15.tgz", + "integrity": "sha1-yrEPtJCeRByChC6v4a1kbIGARBA=" + }, + "typedarray": { + "version": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" + }, + "ua-parser-js": { + "version": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.12.tgz", + "integrity": "sha1-BMgamb3V3FImPqKdJMa/jUgYpLs=" + }, + "uglify-js": { + "version": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.3.6.tgz", + "integrity": "sha1-+gmEdwtCi3qbKoBY9GNV0U/vIRo=", + "dev": true, + "dependencies": { + "async": { + "version": "https://registry.npmjs.org/async/-/async-0.2.10.tgz", + "integrity": "sha1-trvgsGdLnXGXCMo43owjfLUmw9E=", + "dev": true + }, + "optimist": { + "version": "https://registry.npmjs.org/optimist/-/optimist-0.3.7.tgz", + "integrity": "sha1-yQlBrVnkJzMokjB00s8ufLxuwNk=", + "dev": true + }, + "source-map": { + "version": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz", + "integrity": "sha1-wkvBRspRfBRx9drL4lcbK3+eM0Y=", + "dev": true + } + } + }, + "uglifyify": { + "version": "https://registry.npmjs.org/uglifyify/-/uglifyify-3.0.4.tgz", + "integrity": "sha1-SH4IClp3mIgOaOkN75sGaB+xO9I=", + "dev": true, + "dependencies": { + "convert-source-map": { + "version": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.1.3.tgz", + "integrity": "sha1-SCnId+n+SbMWHzvzZziI4gRpmGA=", + "dev": true + }, + "extend": { + "version": "https://registry.npmjs.org/extend/-/extend-1.3.0.tgz", + "integrity": "sha1-0VFvsP9WJNLr+RI+odrFoZlABPg=", + "dev": true + } + } + }, + "ultron": { + "version": "https://registry.npmjs.org/ultron/-/ultron-1.0.2.tgz", + "integrity": "sha1-rOEWq1V80Zc4ak6I9GhTeMiy5Po=", + "dev": true + }, + "umd": { + "version": "https://registry.npmjs.org/umd/-/umd-3.0.1.tgz", + "integrity": "sha1-iuVW4RAR9jwllnCKiDclnwGz1g4=", + "dev": true + }, + "unc-path-regex": { + "version": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", + "integrity": "sha1-5z3T17DXxe2G+6xrCufYxqadUPo=", + "dev": true + }, + "underscore": { + "version": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz", + "integrity": "sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI=", + "dev": true + }, + "undertaker": { + "version": "https://registry.npmjs.org/undertaker/-/undertaker-1.1.0.tgz", + "integrity": "sha1-C6AOb7aor+HpKGMVZar226YRGus=", + "dev": true, + "dependencies": { + "array-each": { + "version": "https://registry.npmjs.org/array-each/-/array-each-0.1.1.tgz", + "integrity": "sha1-xdUrqCJfNtcoF4unrsQTrPrd0Pk=", + "dev": true + }, + "array-slice": { + "version": "https://registry.npmjs.org/array-slice/-/array-slice-0.2.3.tgz", + "integrity": "sha1-3Tz7gO15c6dRF82sabC5nshhhvU=", + "dev": true + }, + "isobject": { + "version": "https://registry.npmjs.org/isobject/-/isobject-1.0.2.tgz", + "integrity": "sha1-8Pm4zpLdVA+gdAiC44NaLgIux4o=", + "dev": true + }, + "object.defaults": { + "version": "https://registry.npmjs.org/object.defaults/-/object.defaults-0.3.0.tgz", + "integrity": "sha1-seucvHjEx71WysbK496tWnETiCo=", + "dev": true + } + } + }, + "undertaker-registry": { + "version": "https://registry.npmjs.org/undertaker-registry/-/undertaker-registry-1.0.0.tgz", + "integrity": "sha1-LacWx2WZnYyUufntLABt9JI7BSs=", + "dev": true + }, + "uniq": { + "version": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz", + "integrity": "sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=" + }, + "unique-stream": { + "version": "https://registry.npmjs.org/unique-stream/-/unique-stream-2.2.1.tgz", + "integrity": "sha1-WqADz76Uxf+GbE59ZouxxNuts2k=", + "dev": true + }, + "unorm": { + "version": "https://registry.npmjs.org/unorm/-/unorm-1.4.1.tgz", + "integrity": "sha1-NkIA1fE2RsqLzURJAnEzVhR5IwA=" + }, + "unpipe": { + "version": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" + }, + "unzip-response": { + "version": "https://registry.npmjs.org/unzip-response/-/unzip-response-1.0.2.tgz", + "integrity": "sha1-uYTwh3/AqJwsdzzB73tbIytbBv4=" + }, + "urix": { + "version": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", + "dev": true + }, + "url": { + "version": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", + "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", + "dev": true, + "dependencies": { + "punycode": { + "version": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", + "dev": true + } + } + }, + "user-home": { + "version": "https://registry.npmjs.org/user-home/-/user-home-2.0.0.tgz", + "integrity": "sha1-nHC/2Babwdy/SGBODwS4tJzenp8=" + }, + "utf8": { + "version": "https://registry.npmjs.org/utf8/-/utf8-2.1.2.tgz", + "integrity": "sha1-H6DZJw6b6FDZsFAn9jUZv0ZFfZY=" + }, + "util": { + "version": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", + "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", + "dev": true, + "dependencies": { + "inherits": { + "version": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", + "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=", + "dev": true + } + } + }, + "util-deprecate": { + "version": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "utile": { + "version": "https://registry.npmjs.org/utile/-/utile-0.3.0.tgz", + "integrity": "sha1-E1LDQOuCDk2N26A5pPv6oy7U7zo=", + "dev": true, + "dependencies": { + "async": { + "version": "https://registry.npmjs.org/async/-/async-0.9.2.tgz", + "integrity": "sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0=", + "dev": true + }, + "deep-equal": { + "version": "https://registry.npmjs.org/deep-equal/-/deep-equal-0.2.2.tgz", + "integrity": "sha1-hLdFiW80xoTpjyzg5Cq69Du6AX0=", + "dev": true + } + } + }, + "utils-merge": { + "version": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.0.tgz", + "integrity": "sha1-ApT7kiu5N1FTVBxPcJYjHyh8ivg=" + }, + "uuid": { + "version": "https://registry.npmjs.org/uuid/-/uuid-2.0.3.tgz", + "integrity": "sha1-Z+LoY3lyFVMN/zGOW/nc6/1Hsho=" + }, + "v8flags": { + "version": "https://registry.npmjs.org/v8flags/-/v8flags-2.1.1.tgz", + "integrity": "sha1-qrGh+jDUX4jdMhFIh1rALAtV5bQ=", + "dev": true, + "dependencies": { + "user-home": { + "version": "https://registry.npmjs.org/user-home/-/user-home-1.1.1.tgz", + "integrity": "sha1-K1viOjK2Onyd640PKNSFcko98ZA=", + "dev": true + } + } + }, + "vali-date": { + "version": "https://registry.npmjs.org/vali-date/-/vali-date-1.0.0.tgz", + "integrity": "sha1-G5BKWWCfsyjvB4E4Qgk09rhnCaY=", + "dev": true + }, + "valid-url": { + "version": "https://registry.npmjs.org/valid-url/-/valid-url-1.0.9.tgz", + "integrity": "sha1-HBRHm0DxOXp1eC8RXkCGRHQzogA=" + }, + "validate-npm-package-license": { + "version": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz", + "integrity": "sha1-KAS6vnEq0zeUWaz74kdGqywwP7w=" + }, + "varint": { + "version": "https://registry.npmjs.org/varint/-/varint-4.0.1.tgz", + "integrity": "sha1-SQgpuULSSEY7KzUJeZXDv3NxmOk=" + }, + "vary": { + "version": "https://registry.npmjs.org/vary/-/vary-1.1.1.tgz", + "integrity": "sha1-Z1Neu2lMHVIldFeYRmUyP1h+jTc=" + }, + "verror": { + "version": "https://registry.npmjs.org/verror/-/verror-1.3.6.tgz", + "integrity": "sha1-z/XfEpRtKX0rqu+qJoniW+AcAFw=" + }, + "vinyl": { + "version": "https://registry.npmjs.org/vinyl/-/vinyl-0.5.3.tgz", + "integrity": "sha1-sEVbOPxeDPMNQyUTLkYZcMIJHN4=" + }, + "vinyl-buffer": { + "version": "https://registry.npmjs.org/vinyl-buffer/-/vinyl-buffer-1.0.0.tgz", + "integrity": "sha1-ygZ+oIQx1QdyKx3lCD9gJhbrwjQ=", + "dev": true, + "dependencies": { + "bl": { + "version": "https://registry.npmjs.org/bl/-/bl-0.9.5.tgz", + "integrity": "sha1-wGt5evCF6gC8Unr8jvzxHeIjIFQ=", + "dev": true + }, + "isarray": { + "version": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "readable-stream": { + "version": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "dev": true + }, + "string_decoder": { + "version": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + }, + "through2": { + "version": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz", + "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=", + "dev": true + } + } + }, + "vinyl-file": { + "version": "https://registry.npmjs.org/vinyl-file/-/vinyl-file-2.0.0.tgz", + "integrity": "sha1-p+v1/779obfRjRQPyweyI++2dRo=", + "dev": true, + "dependencies": { + "first-chunk-stream": { + "version": "https://registry.npmjs.org/first-chunk-stream/-/first-chunk-stream-2.0.0.tgz", + "integrity": "sha1-G97NuOCDwGZLkZRVgVd6Q6nzHXA=", + "dev": true + }, + "strip-bom-stream": { + "version": "https://registry.npmjs.org/strip-bom-stream/-/strip-bom-stream-2.0.0.tgz", + "integrity": "sha1-+H217yYT9paKpUWr/h7HKLaoKco=", + "dev": true + }, + "vinyl": { + "version": "https://registry.npmjs.org/vinyl/-/vinyl-1.2.0.tgz", + "integrity": "sha1-XIgDbPVl5d8FVYv8kR+GVt8hiIQ=", + "dev": true + } + } + }, + "vinyl-fs": { + "version": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-2.4.4.tgz", + "integrity": "sha1-vm/zJwy1Xf19MGNkDegfJddTIjk=", + "dev": true, + "dependencies": { + "gulp-sourcemaps": { + "version": "https://registry.npmjs.org/gulp-sourcemaps/-/gulp-sourcemaps-1.6.0.tgz", + "integrity": "sha1-uG/zSdgBzrVuHZ59x7vLS33uYAw=", + "dev": true + }, + "vinyl": { + "version": "https://registry.npmjs.org/vinyl/-/vinyl-1.2.0.tgz", + "integrity": "sha1-XIgDbPVl5d8FVYv8kR+GVt8hiIQ=", + "dev": true + } + } + }, + "vinyl-source-stream": { + "version": "https://registry.npmjs.org/vinyl-source-stream/-/vinyl-source-stream-1.1.0.tgz", + "integrity": "sha1-RMvlEIIFJ53rDFZTwJSiiHk4sas=", + "dev": true, + "dependencies": { + "clone": { + "version": "https://registry.npmjs.org/clone/-/clone-0.2.0.tgz", + "integrity": "sha1-xhJqkK1Pctv1rNskPMN3JP6T/B8=", + "dev": true + }, + "isarray": { + "version": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "readable-stream": { + "version": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "dev": true + }, + "string_decoder": { + "version": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + }, + "through2": { + "version": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz", + "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=", + "dev": true + }, + "vinyl": { + "version": "https://registry.npmjs.org/vinyl/-/vinyl-0.4.6.tgz", + "integrity": "sha1-LzVsh6VQolVGHza76ypbqL94SEc=", + "dev": true + } + } + }, + "vm-browserify": { + "version": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-0.0.4.tgz", + "integrity": "sha1-XX6kW7755Kb/ZflUOOCofDV9WnM=", + "dev": true + }, + "vreme": { + "version": "https://registry.npmjs.org/vreme/-/vreme-3.0.2.tgz", + "integrity": "sha1-RyE3a0SUV/796KhJ0zQJM7kLVoY=" + }, + "watchify": { + "version": "https://registry.npmjs.org/watchify/-/watchify-3.9.0.tgz", + "integrity": "sha1-8HX9LoqGrN6Eztum5cKgvt1SPZ4=", + "dev": true, + "dependencies": { + "base64-js": { + "version": "https://registry.npmjs.org/base64-js/-/base64-js-1.2.0.tgz", + "integrity": "sha1-o5mS1yNYSBGYK+XikLtqU9hnAPE=", + "dev": true + }, + "browserify": { + "version": "https://registry.npmjs.org/browserify/-/browserify-14.4.0.tgz", + "integrity": "sha1-CJo0Y69Y0OSNjNQHCz90ZU1avKk=", + "dev": true + }, + "buffer": { + "version": "https://registry.npmjs.org/buffer/-/buffer-5.0.6.tgz", + "integrity": "sha1-LqZp9+7Atu2gWwj4tf9mGyhXNYg=", + "dev": true + }, + "concat-stream": { + "version": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.5.2.tgz", + "integrity": "sha1-cIl4Yk2FavQaWnQd790mHadSwmY=", + "dev": true, + "dependencies": { + "readable-stream": { + "version": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", + "integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44=", + "dev": true + }, + "string_decoder": { + "version": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + } + } + }, + "duplexer2": { + "version": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", + "integrity": "sha1-ixLauHjA1p4+eJEFFmKjL8a93ME=", + "dev": true + }, + "https-browserify": { + "version": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", + "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", + "dev": true + }, + "process": { + "version": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", + "dev": true + }, + "punycode": { + "version": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true + } + } + }, + "weak": { + "version": "https://registry.npmjs.org/weak/-/weak-1.0.1.tgz", + "integrity": "sha1-q5mqswcGlZqgIAy4z1RbucszuZ4=", + "optional": true + }, + "web3": { + "version": "https://registry.npmjs.org/web3/-/web3-0.18.2.tgz", + "integrity": "sha1-YbGm7fUFaCDiLh7wgvVMJ59L91g=" + }, + "web3-provider-engine": { + "version": "https://registry.npmjs.org/web3-provider-engine/-/web3-provider-engine-12.2.1.tgz", + "integrity": "sha1-hIwu4Yf5cBsKOC4iB8mxDxdKjXI=", + "dependencies": { + "async": { + "version": "https://registry.npmjs.org/async/-/async-2.4.1.tgz", + "integrity": "sha1-YqVrJ5yYoR0JhwlqAcw+6463u9c=" + }, + "clone": { + "version": "https://registry.npmjs.org/clone/-/clone-2.1.1.tgz", + "integrity": "sha1-0hfR6WERjjrJpLi7oyhVU79kfNs=" + }, + "ethereumjs-util": { + "version": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.1.1.tgz", + "integrity": "sha1-Ei+zjep0fcYrOuv8Nl0b1Ivktz4=" + } + } + }, + "web3-stream-provider": { + "version": "https://registry.npmjs.org/web3-stream-provider/-/web3-stream-provider-2.0.8.tgz", + "integrity": "sha1-AgNxn9XtoWwsr1hQ+krtVRjt7qo=" + }, + "webidl-conversions": { + "version": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=", + "dev": true + }, + "websocket-driver": { + "version": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.6.5.tgz", + "integrity": "sha1-XLJVbOuF9Dc8bYI4qmkchFThOjY=", + "dev": true + }, + "websocket-extensions": { + "version": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.1.tgz", + "integrity": "sha1-domUmcGEtu91Q3fC27DNbLVdKec=", + "dev": true + }, + "whatwg-fetch": { + "version": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz", + "integrity": "sha1-nITsLc9oGH/wC8ZOEnS0QhduHIQ=" + }, + "whatwg-url": { + "version": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-2.0.1.tgz", + "integrity": "sha1-U5ayBD8CDub3BNnEXqhRnnJN5lk=", + "dev": true + }, + "which": { + "version": "https://registry.npmjs.org/which/-/which-1.0.9.tgz", + "integrity": "sha1-RgwdoPgQED0DIam2M6+eV15kSG8=", + "dev": true + }, + "which-module": { + "version": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", + "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=" + }, + "wide-align": { + "version": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.2.tgz", + "integrity": "sha1-Vx4PGwYEY268DfwhsDObvjE0FxA=" + }, + "window-size": { + "version": "https://registry.npmjs.org/window-size/-/window-size-0.2.0.tgz", + "integrity": "sha1-tDFbtCFKPXBY6+7okuE/ok2YsHU=" + }, + "winston": { + "version": "https://registry.npmjs.org/winston/-/winston-2.1.1.tgz", + "integrity": "sha1-PJNJ0ZYgf9G9/51LxD73JRDjoS4=", + "dev": true, + "dependencies": { + "async": { + "version": "https://registry.npmjs.org/async/-/async-1.0.0.tgz", + "integrity": "sha1-+PwEyjoTeErenhZBr5hXjPvWR6k=", + "dev": true + }, + "colors": { + "version": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", + "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=", + "dev": true + }, + "pkginfo": { + "version": "https://registry.npmjs.org/pkginfo/-/pkginfo-0.3.1.tgz", + "integrity": "sha1-Wyn2qB9wcXFC4J52W76rl7T4HiE=", + "dev": true + } + } + }, + "wordwrap": { + "version": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", + "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=" + }, + "wrap-ansi": { + "version": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=" + }, + "wrappy": { + "version": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "wreck": { + "version": "https://registry.npmjs.org/wreck/-/wreck-6.3.0.tgz", + "integrity": "sha1-oTaXafB7u2LWo3gzanhx/Hc8dAs=", + "dev": true + }, + "write": { + "version": "https://registry.npmjs.org/write/-/write-0.2.1.tgz", + "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=" + }, + "ws": { + "version": "https://registry.npmjs.org/ws/-/ws-1.1.1.tgz", + "integrity": "sha1-CC3bbGQehdS7RR8D1S8G6r2x8Bg=", + "dev": true + }, + "wtf-8": { + "version": "https://registry.npmjs.org/wtf-8/-/wtf-8-1.0.0.tgz", + "integrity": "sha1-OS2LotDxw00e4tYw8V0O+2jhBIo=", + "dev": true + }, + "xhr": { + "version": "https://registry.npmjs.org/xhr/-/xhr-2.4.0.tgz", + "integrity": "sha1-4W5mpF+GmGHu76tBbV7/ci3ECZM=" + }, + "xhr2": { + "version": "https://registry.npmjs.org/xhr2/-/xhr2-0.1.4.tgz", + "integrity": "sha1-f4dliEdxbbUCYyOBL4GMras4el8=" + }, + "xml-name-validator": { + "version": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-2.0.1.tgz", + "integrity": "sha1-TYuPHszTQZqjYgYb7O9RXh5VljU=", + "dev": true + }, + "xmldom": { + "version": "https://registry.npmjs.org/xmldom/-/xmldom-0.1.27.tgz", + "integrity": "sha1-1QH5ezvbQDr4757MIFcxh6rawOk=", + "dev": true + }, + "xmlhttprequest": { + "version": "https://registry.npmjs.org/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz", + "integrity": "sha1-Z/4HXFwk/vOfnWX197f+dRcZaPw=" + }, + "xmlhttprequest-ssl": { + "version": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.3.tgz", + "integrity": "sha1-GFqIjATspGw+QHDZn3tJ3jUomS0=", + "dev": true + }, + "xss-filters": { + "version": "https://registry.npmjs.org/xss-filters/-/xss-filters-1.2.7.tgz", + "integrity": "sha1-Wfod4gHzby80cNysX1jMwoMLCpo=" + }, + "xtend": { + "version": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", + "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" + }, + "y18n": { + "version": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", + "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=" + }, + "yallist": { + "version": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", + "dev": true + }, + "yargs": { + "version": "https://registry.npmjs.org/yargs/-/yargs-6.6.0.tgz", + "integrity": "sha1-eC7CHvQDNF+DCoCMo9UTr1YGUgg=" + }, + "yargs-parser": { + "version": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-4.2.1.tgz", + "integrity": "sha1-KczqwNxPA8bIe0qfIX3RjJ90hxw=" + }, + "yazl": { + "version": "https://registry.npmjs.org/yazl/-/yazl-2.4.2.tgz", + "integrity": "sha1-FMsZCD4eJacAksFYiqvg9OTdTYg=", + "dev": true + }, + "yeast": { + "version": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", + "integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk=", + "dev": true + } + } +} From 82cbfaa826cc4d731dfbeab7482420c66c0e832b Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Thu, 1 Jun 2017 12:53:16 -0700 Subject: [PATCH 340/372] Convert gasLimit to not use muln in BN --- app/scripts/lib/tx-utils.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/app/scripts/lib/tx-utils.js b/app/scripts/lib/tx-utils.js index 8cf304d0b..658f3bedc 100644 --- a/app/scripts/lib/tx-utils.js +++ b/app/scripts/lib/tx-utils.js @@ -30,7 +30,7 @@ module.exports = class txProviderUtils { setBlockGasLimit (txMeta, blockGasLimitHex, cb) { const blockGasLimitBN = hexToBn(blockGasLimitHex) - const saferGasLimitBN = blockGasLimitBN.muln(0.95) + const saferGasLimitBN = BnMultiplyByFraction(blockGasLimitBN, 19, 20) txMeta.blockGasLimit = bnToHex(saferGasLimitBN) cb() return @@ -43,7 +43,7 @@ module.exports = class txProviderUtils { // if not, fallback to block gasLimit if (!txMeta.gasLimitSpecified) { const blockGasLimitBN = hexToBn(blockGasLimitHex) - const saferGasLimitBN = blockGasLimitBN.muln(0.95) + const saferGasLimitBN = BnMultiplyByFraction(blockGasLimitBN, 19, 20) txParams.gas = bnToHex(saferGasLimitBN) } // run tx, see if it will OOG @@ -143,3 +143,9 @@ function bnToHex (inputBn) { function hexToBn (inputHex) { return new BN(ethUtil.stripHexPrefix(inputHex), 16) } + +function BnMultiplyByFraction (targetBN, numerator, denominator) { + const numBN = new BN(numerator) + const denomBN = new BN(denominator) + return targetBN.mul(numBN).div(denomBN) +} From 611cb7ad930955bbc1691eafbe961a313557f17b Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Fri, 2 Jun 2017 11:08:59 -0700 Subject: [PATCH 341/372] Version 3.7.4 --- CHANGELOG.md | 5 +++++ app/manifest.json | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b48889f5d..0e7b292b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ ## Current Master +## 3.7.4 2017-6-2 + +- Fix bug with inflight cache that caused some block lookups to return bad values (affected OasisDex). +- Fixed bug with gas limit calculation that would sometimes create unsubmittable gas limits. + ## 3.7.3 2017-6-1 - Rebuilt to fix cache clearing bug. diff --git a/app/manifest.json b/app/manifest.json index 4dcd6df31..99a083f0f 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -1,7 +1,7 @@ { "name": "MetaMask", "short_name": "Metamask", - "version": "3.7.3", + "version": "3.7.4", "manifest_version": 2, "author": "https://metamask.io", "description": "Ethereum Browser Extension", From 2b7d8424981fbbd0f6306b5ee7abf8754f9f7092 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Fri, 2 Jun 2017 15:18:14 -0700 Subject: [PATCH 342/372] Update gasblocklimit params with every block. --- app/scripts/lib/eth-store.js | 2 ++ app/scripts/lib/tx-utils.js | 9 --------- ui/app/components/pending-tx.js | 11 +++++++++-- ui/app/conf-tx.js | 4 +++- 4 files changed, 14 insertions(+), 12 deletions(-) diff --git a/app/scripts/lib/eth-store.js b/app/scripts/lib/eth-store.js index 6f04a9dd6..ebba98f5c 100644 --- a/app/scripts/lib/eth-store.js +++ b/app/scripts/lib/eth-store.js @@ -21,6 +21,7 @@ class EthereumStore extends ObservableStore { transactions: {}, currentBlockNumber: '0', currentBlockHash: '', + currentBlockGasLimit: '', }) this._provider = opts.provider this._query = new EthQuery(this._provider) @@ -73,6 +74,7 @@ class EthereumStore extends ObservableStore { this._currentBlockNumber = blockNumber this.updateState({ currentBlockNumber: parseInt(blockNumber) }) this.updateState({ currentBlockHash: `0x${block.hash.toString('hex')}`}) + this.updateState({ currentBlockGasLimit: `0x${block.gasLimit.toString('hex')}` }) async.parallel([ this._updateAccounts.bind(this), this._updateTransactions.bind(this, blockNumber), diff --git a/app/scripts/lib/tx-utils.js b/app/scripts/lib/tx-utils.js index 658f3bedc..149d93102 100644 --- a/app/scripts/lib/tx-utils.js +++ b/app/scripts/lib/tx-utils.js @@ -21,21 +21,12 @@ module.exports = class txProviderUtils { this.query.getBlockByNumber('latest', true, (err, block) => { if (err) return cb(err) async.waterfall([ - self.setBlockGasLimit.bind(self, txMeta, block.gasLimit), self.estimateTxGas.bind(self, txMeta, block.gasLimit), self.setTxGas.bind(self, txMeta, block.gasLimit), ], cb) }) } - setBlockGasLimit (txMeta, blockGasLimitHex, cb) { - const blockGasLimitBN = hexToBn(blockGasLimitHex) - const saferGasLimitBN = BnMultiplyByFraction(blockGasLimitBN, 19, 20) - txMeta.blockGasLimit = bnToHex(saferGasLimitBN) - cb() - return - } - estimateTxGas (txMeta, blockGasLimitHex, cb) { const txParams = txMeta.txParams // check if gasLimit is already specified diff --git a/ui/app/components/pending-tx.js b/ui/app/components/pending-tx.js index b46f715bc..0847a8d4c 100644 --- a/ui/app/components/pending-tx.js +++ b/ui/app/components/pending-tx.js @@ -32,7 +32,7 @@ function PendingTx () { PendingTx.prototype.render = function () { const props = this.props - const { currentCurrency } = props + const { currentCurrency, blockGasLimit } = props const conversionRate = props.conversionRate const txMeta = this.gatherTxMeta() @@ -47,7 +47,8 @@ PendingTx.prototype.render = function () { // Gas const gas = txParams.gas const gasBn = hexToBn(gas) - const safeGasLimit = parseInt(txMeta.blockGasLimit) + const gasLimit = new BN(parseInt(blockGasLimit)) + const safeGasLimit = this.bnMultiplyByFraction(gasLimit, 19, 20).toString(10) // Gas Price const gasPrice = txParams.gasPrice || MIN_GAS_PRICE_BN.toString(16) @@ -434,6 +435,12 @@ PendingTx.prototype._notZeroOrEmptyString = function (obj) { return obj !== '' && obj !== '0x0' } +PendingTx.prototype.bnMultiplyByFraction = function (targetBN, numerator, denominator) { + const numBN = new BN(numerator) + const denomBN = new BN(denominator) + return targetBN.mul(numBN).div(denomBN) +} + function forwardCarrat () { return ( h('img', { diff --git a/ui/app/conf-tx.js b/ui/app/conf-tx.js index 008627ce6..c002019e2 100644 --- a/ui/app/conf-tx.js +++ b/ui/app/conf-tx.js @@ -29,6 +29,7 @@ function mapStateToProps (state) { provider: state.metamask.provider, conversionRate: state.metamask.conversionRate, currentCurrency: state.metamask.currentCurrency, + blockGasLimit: state.metamask.currentBlockGasLimit, } } @@ -40,7 +41,7 @@ function ConfirmTxScreen () { ConfirmTxScreen.prototype.render = function () { const props = this.props const { network, provider, unapprovedTxs, currentCurrency, - unapprovedMsgs, unapprovedPersonalMsgs, conversionRate } = props + unapprovedMsgs, unapprovedPersonalMsgs, conversionRate, blockGasLimit } = props var unconfTxList = txHelper(unapprovedTxs, unapprovedMsgs, unapprovedPersonalMsgs, network) @@ -106,6 +107,7 @@ ConfirmTxScreen.prototype.render = function () { identities: props.identities, conversionRate, currentCurrency, + blockGasLimit, // Actions buyEth: this.buyEth.bind(this, txParams.from || props.selectedAddress), sendTransaction: this.sendTransaction.bind(this), From 8d8eb0d8adb5edbfb9f34ed9bacd28ffd03b1e1d Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Fri, 2 Jun 2017 15:20:41 -0700 Subject: [PATCH 343/372] bump changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e7b292b5..7f8b8ad0b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Current Master +- Update gasLimit params with every new block seen. + ## 3.7.4 2017-6-2 - Fix bug with inflight cache that caused some block lookups to return bad values (affected OasisDex). From ec097c8e3473826f29d988bb6e754345f494913e Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Sun, 4 Jun 2017 22:13:28 -0700 Subject: [PATCH 344/372] Add copy links to mini tx panels --- ui/app/components/pending-tx.js | 48 ++++++++++++++++++++++++++------- 1 file changed, 38 insertions(+), 10 deletions(-) diff --git a/ui/app/components/pending-tx.js b/ui/app/components/pending-tx.js index b46f715bc..4a62746d6 100644 --- a/ui/app/components/pending-tx.js +++ b/ui/app/components/pending-tx.js @@ -9,6 +9,8 @@ const BN = ethUtil.BN const hexToBn = require('../../../app/scripts/lib/hex-to-bn') const MiniAccountPanel = require('./mini-account-panel') +const Tooltip = require('./tooltip') +const copyToClipboard = require('copy-to-clipboard') const EthBalance = require('./eth-balance') const util = require('../util') const addressSummary = util.addressSummary @@ -93,11 +95,23 @@ PendingTx.prototype.render = function () { fontFamily: 'Montserrat Bold, Montserrat, sans-serif', }, }, identity.name), - h('span.font-small', { - style: { - fontFamily: 'Montserrat Light, Montserrat, sans-serif', - }, - }, addressSummary(address, 6, 4, false)), + + h(Tooltip, { + title: 'Copy address', + position: 'bottom', + }, [ + h('span.font-small', { + onClick: (event) => { + event.preventDefault() + event.stopPropagation() + copyToClipboard(ethUtil.toChecksumAddress(address)) + }, + style: { + cursor: 'pointer', + fontFamily: 'Montserrat Light, Montserrat, sans-serif', + }, + }, addressSummary(address, 6, 4, false)), + ]), h('span.font-small', { style: { @@ -322,16 +336,30 @@ PendingTx.prototype.miniAccountPanelForRecipient = function () { imageSeed: txParams.to, picOrder: 'left', }, [ + h('span.font-small', { style: { fontFamily: 'Montserrat Bold, Montserrat, sans-serif', }, }, nameForAddress(txParams.to, props.identities)), - h('span.font-small', { - style: { - fontFamily: 'Montserrat Light, Montserrat, sans-serif', - }, - }, addressSummary(txParams.to, 6, 4, false)), + + h(Tooltip, { + title: 'Copy address', + position: 'bottom', + }, [ + h('span.font-small', { + onClick: (event) => { + event.preventDefault() + event.stopPropagation() + copyToClipboard(ethUtil.toChecksumAddress(txParams.to)) + }, + style: { + cursor: 'pointer', + fontFamily: 'Montserrat Light, Montserrat, sans-serif', + }, + }, addressSummary(txParams.to, 6, 4, false)), + ]), + ]) } else { return h(MiniAccountPanel, { From 773b36b0de5613f1f6bda1caba08ee240a14ab32 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Sun, 4 Jun 2017 22:21:37 -0700 Subject: [PATCH 345/372] Move address copying into reusable component "copyable" component allows any elements to be wrapped to include: - a tool tip that changes/debounces its label when clicked. - a customizable copyable value. Fixes #1539 --- ui/app/components/copyable.js | 49 +++++++++++++++++++++++++++++++++ ui/app/components/pending-tx.js | 25 ++++------------- 2 files changed, 54 insertions(+), 20 deletions(-) create mode 100644 ui/app/components/copyable.js diff --git a/ui/app/components/copyable.js b/ui/app/components/copyable.js new file mode 100644 index 000000000..9b785a77e --- /dev/null +++ b/ui/app/components/copyable.js @@ -0,0 +1,49 @@ +const Component = require('react').Component +const h = require('react-hyperscript') +const inherits = require('util').inherits + +const Tooltip = require('./tooltip') +const copyToClipboard = require('copy-to-clipboard') + +module.exports = Copyable + +inherits(Copyable, Component) +function Copyable () { + Component.call(this) + this.state = { + copied: false, + } +} + +Copyable.prototype.render = function () { + const props = this.props + const state = this.state + const { value, children } = props + const { copied } = state + + return h(Tooltip, { + title: copied ? 'Copied!' : 'Copy', + position: 'bottom', + style: { + cursor: 'pointer', + }, + }, h('span', { + style: { + cursor: 'pointer', + }, + onClick: (event) => { + event.preventDefault() + event.stopPropagation() + copyToClipboard(value) + this.debounceRestore() + }, + }, children)) +} + +Copyable.prototype.debounceRestore = function () { + this.setState({ copied: true }) + clearTimeout(this.timeout) + this.timeout = setTimeout(() => { + this.setState({ copied: false }) + }, 850) +} diff --git a/ui/app/components/pending-tx.js b/ui/app/components/pending-tx.js index 4a62746d6..4961db5de 100644 --- a/ui/app/components/pending-tx.js +++ b/ui/app/components/pending-tx.js @@ -9,8 +9,7 @@ const BN = ethUtil.BN const hexToBn = require('../../../app/scripts/lib/hex-to-bn') const MiniAccountPanel = require('./mini-account-panel') -const Tooltip = require('./tooltip') -const copyToClipboard = require('copy-to-clipboard') +const Copyable = require('./copyable') const EthBalance = require('./eth-balance') const util = require('../util') const addressSummary = util.addressSummary @@ -96,18 +95,11 @@ PendingTx.prototype.render = function () { }, }, identity.name), - h(Tooltip, { - title: 'Copy address', - position: 'bottom', + h(Copyable, { + value: ethUtil.toChecksumAddress(address), }, [ h('span.font-small', { - onClick: (event) => { - event.preventDefault() - event.stopPropagation() - copyToClipboard(ethUtil.toChecksumAddress(address)) - }, style: { - cursor: 'pointer', fontFamily: 'Montserrat Light, Montserrat, sans-serif', }, }, addressSummary(address, 6, 4, false)), @@ -343,18 +335,11 @@ PendingTx.prototype.miniAccountPanelForRecipient = function () { }, }, nameForAddress(txParams.to, props.identities)), - h(Tooltip, { - title: 'Copy address', - position: 'bottom', + h(Copyable, { + value: ethUtil.toChecksumAddress(txParams.to), }, [ h('span.font-small', { - onClick: (event) => { - event.preventDefault() - event.stopPropagation() - copyToClipboard(ethUtil.toChecksumAddress(txParams.to)) - }, style: { - cursor: 'pointer', fontFamily: 'Montserrat Light, Montserrat, sans-serif', }, }, addressSummary(txParams.to, 6, 4, false)), From 8dc6aa9c4c4f11e08eee0688c210324b313b710b Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Sun, 4 Jun 2017 22:26:32 -0700 Subject: [PATCH 346/372] Remove dead style code --- ui/app/components/copyable.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/ui/app/components/copyable.js b/ui/app/components/copyable.js index 9b785a77e..a4f6f4bc6 100644 --- a/ui/app/components/copyable.js +++ b/ui/app/components/copyable.js @@ -24,9 +24,6 @@ Copyable.prototype.render = function () { return h(Tooltip, { title: copied ? 'Copied!' : 'Copy', position: 'bottom', - style: { - cursor: 'pointer', - }, }, h('span', { style: { cursor: 'pointer', From bb6e41963d42a91ecc34a728b7c0c18d26e6cd9f Mon Sep 17 00:00:00 2001 From: frankiebee Date: Mon, 5 Jun 2017 11:40:20 -0700 Subject: [PATCH 347/372] Dissallow transactions to be sent to 0x0000000000000000000000000000000000000000 --- ui/app/components/ens-input.js | 1 + ui/app/components/pending-tx.js | 14 +++++++++++++- ui/app/conf-tx.js | 1 + ui/app/send.js | 2 +- 4 files changed, 16 insertions(+), 2 deletions(-) diff --git a/ui/app/components/ens-input.js b/ui/app/components/ens-input.js index 3e44d83af..11e0fb36d 100644 --- a/ui/app/components/ens-input.js +++ b/ui/app/components/ens-input.js @@ -95,6 +95,7 @@ EnsInput.prototype.lookupEnsName = function () { log.info(`ENS attempting to resolve name: ${recipient}`) this.ens.lookup(recipient.trim()) .then((address) => { + if (address === '0x0000000000000000000000000000000000000000') throw new Error('No address has been set for this name.') if (address !== ensResolution) { this.setState({ loadingEns: false, diff --git a/ui/app/components/pending-tx.js b/ui/app/components/pending-tx.js index b46f715bc..e8bf32d92 100644 --- a/ui/app/components/pending-tx.js +++ b/ui/app/components/pending-tx.js @@ -44,6 +44,9 @@ PendingTx.prototype.render = function () { const account = props.accounts[address] const balance = account ? account.balance : '0x0' + // recipient check + const isValidAddress = !(txParams.to === '0x0000000000000000000000000000000000000000') + // Gas const gas = txParams.gas const gasBn = hexToBn(gas) @@ -261,6 +264,15 @@ PendingTx.prototype.render = function () { }, 'Transaction Error. Exception thrown in contract code.') : null, + !isValidAddress ? + h('.error', { + style: { + marginLeft: 50, + fontSize: '0.9em', + }, + }, 'Recipient address is invalid sending this transaction will result in a loss of ETH.') + : null, + insufficientBalance ? h('span.error', { style: { @@ -298,7 +310,7 @@ PendingTx.prototype.render = function () { type: 'submit', value: 'ACCEPT', style: { marginLeft: '10px' }, - disabled: insufficientBalance || !this.state.valid, + disabled: insufficientBalance || !this.state.valid || !isValidAddress, }), h('button.cancel.btn-red', { diff --git a/ui/app/conf-tx.js b/ui/app/conf-tx.js index 008627ce6..4ae81f35f 100644 --- a/ui/app/conf-tx.js +++ b/ui/app/conf-tx.js @@ -48,6 +48,7 @@ ConfirmTxScreen.prototype.render = function () { var txParams = txData.params || {} var isNotification = isPopupOrNotification() === 'notification' + log.info(`rendering a combined ${unconfTxList.length} unconf msg & txs`) if (unconfTxList.length === 0) return h(Loading, { isLoading: true }) diff --git a/ui/app/send.js b/ui/app/send.js index fd6994145..e0896035e 100644 --- a/ui/app/send.js +++ b/ui/app/send.js @@ -262,7 +262,7 @@ SendTransactionScreen.prototype.onSubmit = function () { return this.props.dispatch(actions.displayWarning(message)) } - if ((!util.isValidAddress(recipient) && !txData) || (!recipient && !txData)) { + if ((!util.isValidAddress(recipient) && !txData) || (!recipient && !txData) || (recipient === '0x0000000000000000000000000000000000000000')) { message = 'Recipient address is invalid.' return this.props.dispatch(actions.displayWarning(message)) } From 37fd32025f9cb5dffb601011e2442efee59e3595 Mon Sep 17 00:00:00 2001 From: frankiebee Date: Mon, 5 Jun 2017 12:00:01 -0700 Subject: [PATCH 348/372] Fix punctuation --- ui/app/components/pending-tx.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/app/components/pending-tx.js b/ui/app/components/pending-tx.js index e8bf32d92..2d4dc26a2 100644 --- a/ui/app/components/pending-tx.js +++ b/ui/app/components/pending-tx.js @@ -270,7 +270,7 @@ PendingTx.prototype.render = function () { marginLeft: 50, fontSize: '0.9em', }, - }, 'Recipient address is invalid sending this transaction will result in a loss of ETH.') + }, 'Recipient address is invalid. Sending this transaction will result in a loss of ETH.') : null, insufficientBalance ? From 653319be1055b8ff0a36cb334c93ac7435f1fc5c Mon Sep 17 00:00:00 2001 From: frankiebee Date: Mon, 5 Jun 2017 12:09:19 -0700 Subject: [PATCH 349/372] move address check to util.isValidAddress --- ui/app/components/pending-tx.js | 4 ++-- ui/app/send.js | 2 +- ui/app/util.js | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/ui/app/components/pending-tx.js b/ui/app/components/pending-tx.js index 2d4dc26a2..56c466506 100644 --- a/ui/app/components/pending-tx.js +++ b/ui/app/components/pending-tx.js @@ -7,7 +7,7 @@ const clone = require('clone') const ethUtil = require('ethereumjs-util') const BN = ethUtil.BN const hexToBn = require('../../../app/scripts/lib/hex-to-bn') - +const util = require('../util') const MiniAccountPanel = require('./mini-account-panel') const EthBalance = require('./eth-balance') const util = require('../util') @@ -45,7 +45,7 @@ PendingTx.prototype.render = function () { const balance = account ? account.balance : '0x0' // recipient check - const isValidAddress = !(txParams.to === '0x0000000000000000000000000000000000000000') + const isValidAddress = util.isValidAddress(txParams.to) // Gas const gas = txParams.gas diff --git a/ui/app/send.js b/ui/app/send.js index e0896035e..75a600dee 100644 --- a/ui/app/send.js +++ b/ui/app/send.js @@ -262,7 +262,7 @@ SendTransactionScreen.prototype.onSubmit = function () { return this.props.dispatch(actions.displayWarning(message)) } - if ((!util.isValidAddress(recipient) && !txData) || (!recipient && !txData) || (recipient === '0x0000000000000000000000000000000000000000')) { + if ((!util.isValidAddress(recipient) && !txData) || (!recipient && !txData) { message = 'Recipient address is invalid.' return this.props.dispatch(actions.displayWarning(message)) } diff --git a/ui/app/util.js b/ui/app/util.js index 7a56bf6a0..ac3f42c6b 100644 --- a/ui/app/util.js +++ b/ui/app/util.js @@ -61,6 +61,7 @@ function miniAddressSummary (address) { function isValidAddress (address) { var prefixed = ethUtil.addHexPrefix(address) + if (address === '0x0000000000000000000000000000000000000000') return false return (isAllOneCase(prefixed) && ethUtil.isValidAddress(prefixed)) || ethUtil.isValidChecksumAddress(prefixed) } From 0f69a09823928ec6aaf5189cf4d4f50c52c9debb Mon Sep 17 00:00:00 2001 From: frankiebee Date: Mon, 5 Jun 2017 12:22:02 -0700 Subject: [PATCH 350/372] Fix linting error --- ui/app/components/pending-tx.js | 1 - ui/app/send.js | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/ui/app/components/pending-tx.js b/ui/app/components/pending-tx.js index 56c466506..18c378781 100644 --- a/ui/app/components/pending-tx.js +++ b/ui/app/components/pending-tx.js @@ -10,7 +10,6 @@ const hexToBn = require('../../../app/scripts/lib/hex-to-bn') const util = require('../util') const MiniAccountPanel = require('./mini-account-panel') const EthBalance = require('./eth-balance') -const util = require('../util') const addressSummary = util.addressSummary const nameForAddress = require('../../lib/contract-namer') const BNInput = require('./bn-as-decimal-input') diff --git a/ui/app/send.js b/ui/app/send.js index 75a600dee..fd6994145 100644 --- a/ui/app/send.js +++ b/ui/app/send.js @@ -262,7 +262,7 @@ SendTransactionScreen.prototype.onSubmit = function () { return this.props.dispatch(actions.displayWarning(message)) } - if ((!util.isValidAddress(recipient) && !txData) || (!recipient && !txData) { + if ((!util.isValidAddress(recipient) && !txData) || (!recipient && !txData)) { message = 'Recipient address is invalid.' return this.props.dispatch(actions.displayWarning(message)) } From ec99bfd5531922e7d153709c1a77f1c40cae9e99 Mon Sep 17 00:00:00 2001 From: frankiebee Date: Mon, 5 Jun 2017 12:37:29 -0700 Subject: [PATCH 351/372] set the ensResolution to an invalid address if an error ocurs durring look up --- ui/app/components/ens-input.js | 1 + 1 file changed, 1 insertion(+) diff --git a/ui/app/components/ens-input.js b/ui/app/components/ens-input.js index 11e0fb36d..16a3a684c 100644 --- a/ui/app/components/ens-input.js +++ b/ui/app/components/ens-input.js @@ -109,6 +109,7 @@ EnsInput.prototype.lookupEnsName = function () { log.error(reason) return this.setState({ loadingEns: false, + ensResolution: '0x0000000000000000000000000000000000000000', ensFailure: true, hoverText: reason.message, }) From 94fedd1fc9d44054670b9f60ae6f92b409e7b25e Mon Sep 17 00:00:00 2001 From: frankiebee Date: Mon, 5 Jun 2017 13:00:15 -0700 Subject: [PATCH 352/372] Fix for quick switch on ENS names --- ui/app/components/ens-input.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ui/app/components/ens-input.js b/ui/app/components/ens-input.js index 16a3a684c..43bb7ab22 100644 --- a/ui/app/components/ens-input.js +++ b/ui/app/components/ens-input.js @@ -21,6 +21,7 @@ EnsInput.prototype.render = function () { const opts = extend(props, { list: 'addresses', onChange: () => { + this.setState({ ensResolution: '0x0000000000000000000000000000000000000000' }) const network = this.props.network const networkHasEnsSupport = getNetworkEnsSupport(network) if (!networkHasEnsSupport) return @@ -102,6 +103,7 @@ EnsInput.prototype.lookupEnsName = function () { ensResolution: address, nickname: recipient.trim(), hoverText: address + '\nClick to Copy', + ensFailure: false, }) } }) From c92afef91dc08982ab12b19fcc81c14439aaa808 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Mon, 5 Jun 2017 13:40:26 -0700 Subject: [PATCH 353/372] Version 3.7.5 --- CHANGELOG.md | 6 ++++++ app/manifest.json | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e7b292b5..14a6ef3a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ ## Current Master +## 3.7.5 2017-6-5 + +- Prevent users from sending to the `0x0` address. +- Provide useful errors when entering bad characters in ENS name. +- Add ability to copy addresses from transaction confirmation view. + ## 3.7.4 2017-6-2 - Fix bug with inflight cache that caused some block lookups to return bad values (affected OasisDex). diff --git a/app/manifest.json b/app/manifest.json index 99a083f0f..acdb3795e 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -1,7 +1,7 @@ { "name": "MetaMask", "short_name": "Metamask", - "version": "3.7.4", + "version": "3.7.5", "manifest_version": 2, "author": "https://metamask.io", "description": "Ethereum Browser Extension", From e99ce3763ab81ed94eb22db66aadef343c183a00 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Mon, 5 Jun 2017 13:42:41 -0700 Subject: [PATCH 354/372] Add publishing guide to readme --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 821e1cdfd..7997cb52b 100644 --- a/README.md +++ b/README.md @@ -193,3 +193,8 @@ You will need: + An RPC Endpoint url + An explorer link + CSS for the display icon + +## Other Guides + +- [Publishing Guide](./docs/publishing.md) + From 7f991e5574b7247f7b2f4c0f14e3277200c3e526 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Mon, 5 Jun 2017 13:46:18 -0700 Subject: [PATCH 355/372] Add publishing guide --- docs/publishing.md | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 docs/publishing.md diff --git a/docs/publishing.md b/docs/publishing.md new file mode 100644 index 000000000..e546f327b --- /dev/null +++ b/docs/publishing.md @@ -0,0 +1,10 @@ +# Publishing Guide + +When publishing a new version of MetaMask, we follow this procedure: + +1. `npm run dist` to generate the latest build. +2. Publish to chrome store. +3. Publish to firefox addon marketplace. +4. Post on Github releases page. +5. `npm run announce`, post that announcement in our public places. + From d0144c285308da1dfe04dc07d0be377a81b094cc Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Mon, 5 Jun 2017 13:55:48 -0700 Subject: [PATCH 356/372] Break docs up into individual files --- README.md | 168 +++--------------------------- docs/add-to-chrome.md | 14 +++ docs/add-to-firef.md | 14 +++ docs/adding-new-networks.md | 25 +++++ docs/developing-on-deps.md | 10 ++ docs/development-visualization.md | 35 +++++++ docs/notices.md | 15 +++ docs/publishing.md | 9 ++ docs/ui-dev-mode.md | 6 ++ docs/ui-mock-mode.md | 8 ++ 10 files changed, 153 insertions(+), 151 deletions(-) create mode 100644 docs/add-to-chrome.md create mode 100644 docs/add-to-firef.md create mode 100644 docs/adding-new-networks.md create mode 100644 docs/developing-on-deps.md create mode 100644 docs/development-visualization.md create mode 100644 docs/notices.md create mode 100644 docs/ui-dev-mode.md create mode 100644 docs/ui-mock-mode.md diff --git a/README.md b/README.md index 7997cb52b..afeb96ae5 100644 --- a/README.md +++ b/README.md @@ -18,11 +18,15 @@ If you're a web dapp developer, we've got two types of guides for you: Uncompressed builds can be found in `/dist`, compressed builds can be found in `/builds` once they're built. -## Installing Local Builds on Chrome +### Running Tests -To install your locally built extension on Chrome, [follow this guide](http://stackoverflow.com/a/24577660/272576). +Requires `mocha` installed. Run `npm install -g mocha`. -The built extension is stored in `./dist/chrome/`. +Then just run `npm test`. + +You can also test with a continuously watching process, via `npm run watch`. + +You can run the linter by itself with `gulp lint`. ## Architecture @@ -41,160 +45,22 @@ npm start npm run dist ``` -#### In Chrome - -Open `Settings` > `Extensions`. - -Check "Developer mode". - -At the top, click `Load Unpacked Extension`. - -Navigate to your `metamask-plugin/dist/chrome` folder. - -Click `Select`. - -You now have the plugin, and can click 'inspect views: background plugin' to view its dev console. - -#### In Firefox - -Go to the url `about:debugging`. - -Click the button `Load Temporary Add-On`. - -Select the file `dist/firefox/manifest.json`. - -You can optionally enable debugging, and click `Debug`, for a console window that logs all of Metamask's processes to a single console. - -If you have problems debugging, try connecting to the IRC channel `#webextensions` on `irc.mozilla.org`. - -For longer questions, use the StackOverfow tag `firefox-addons`. - -### Developing on UI Only - -You can run `npm run ui`, and your browser should open a live-reloading demo version of the plugin UI. - -Some actions will crash the app, so this is only for tuning aesthetics, but it allows live-reloading styles, which is a much faster feedback loop than reloading the full extension. - -### Developing on UI with Mocked Background Process - -You can run `npm run mock` and your browser should open a live-reloading demo version of the plugin UI, just like the `npm run ui`, except that it tries to actually perform all normal operations. - -It does not yet connect to a real blockchain (this could be a good test feature later, connecting to a test blockchain), so only local operations work. - -You can reset the mock ui at any time with the `Reset` button at the top of the screen. - -### Developing on Dependencies - -To enjoy the live-reloading that `gulp dev` offers while working on the `web3-provider-engine` or other dependencies: - - 1. Clone the dependency locally. - 2. `npm install` in its folder. - 3. Run `npm link` in its folder. - 4. Run `npm link $DEP_NAME` in this project folder. - 5. Next time you `npm start` it will watch the dependency for changes as well! - -### Running Tests - -Requires `mocha` installed. Run `npm install -g mocha`. - -Then just run `npm test`. - -You can also test with a continuously watching process, via `npm run watch`. - -You can run the linter by itself with `gulp lint`. - #### Writing Browser Tests To write tests that will be run in the browser using QUnit, add your test files to `test/integration/lib`. -### Deploying the UI +## Other Docs - You must be authorized already on the MetaMask plugin. - - 0. Update the version in `app/manifest.json` and the Changelog in `CHANGELOG.md`. - 1. Visit [the chrome developer dashboard](https://chrome.google.com/webstore/developer/dashboard?authuser=2). - 2. Run `gulp dist` (or `gulp zip` if you've already built) - 3. Upload the latest zip file from `builds/metamask-$PLATFORM-$VERSION.zip` as the updated package. +- [How to add custom build to Chrome](./docs/add-to-chrome.md) +- [How to add custom build to Firefox](./docs/add-to-firefox.md) +- [How to develop a live-reloading UI](./docs/ui-dev-mode.md) +- [Publishing Guide](./docs/publishing.md) +- [How to develop an in-browser mocked UI](./docs/ui-mock-mode.md) +- [How to live reload on local dependency changes](./docs/developing-on-deps.md) +- [How to add new networks to the Provider Menu](./docs/adding-new-networks.md) +- [How to manage notices that appear when the app starts up](./docs/notices.md) +- [How to generate a visualization of this repository's development](./docs/development-visualization.md) [1]: http://www.nomnoml.com/#view/%5B%3Cactor%3Euser%5D%0A%0A%5Bmetamask-ui%7C%0A%20%20%20%5Btools%7C%0A%20%20%20%20%20react%0A%20%20%20%20%20redux%0A%20%20%20%20%20thunk%0A%20%20%20%20%20ethUtils%0A%20%20%20%20%20jazzicon%0A%20%20%20%5D%0A%20%20%20%5Bcomponents%7C%0A%20%20%20%20%20app%0A%20%20%20%20%20account-detail%0A%20%20%20%20%20accounts%0A%20%20%20%20%20locked-screen%0A%20%20%20%20%20restore-vault%0A%20%20%20%20%20identicon%0A%20%20%20%20%20config%0A%20%20%20%20%20info%0A%20%20%20%5D%0A%20%20%20%5Breducers%7C%0A%20%20%20%20%20app%0A%20%20%20%20%20metamask%0A%20%20%20%20%20identities%0A%20%20%20%5D%0A%20%20%20%5Bactions%7C%0A%20%20%20%20%20%5BaccountManager%5D%0A%20%20%20%5D%0A%20%20%20%5Bcomponents%5D%3A-%3E%5Bactions%5D%0A%20%20%20%5Bactions%5D%3A-%3E%5Breducers%5D%0A%20%20%20%5Breducers%5D%3A-%3E%5Bcomponents%5D%0A%5D%0A%0A%5Bweb%20dapp%7C%0A%20%20%5Bui%20code%5D%0A%20%20%5Bweb3%5D%0A%20%20%5Bmetamask-inpage%5D%0A%20%20%0A%20%20%5B%3Cactor%3Eui%20developer%5D%0A%20%20%5Bui%20developer%5D-%3E%5Bui%20code%5D%0A%20%20%5Bui%20code%5D%3C-%3E%5Bweb3%5D%0A%20%20%5Bweb3%5D%3C-%3E%5Bmetamask-inpage%5D%0A%5D%0A%0A%5Bmetamask-background%7C%0A%20%20%5Bprovider-engine%5D%0A%20%20%5Bhooked%20wallet%20subprovider%5D%0A%20%20%5Bid%20store%5D%0A%20%20%0A%20%20%5Bprovider-engine%5D%3C-%3E%5Bhooked%20wallet%20subprovider%5D%0A%20%20%5Bhooked%20wallet%20subprovider%5D%3C-%3E%5Bid%20store%5D%0A%20%20%5Bconfig%20manager%7C%0A%20%20%20%20%5Brpc%20configuration%5D%0A%20%20%20%20%5Bencrypted%20keys%5D%0A%20%20%20%20%5Bwallet%20nicknames%5D%0A%20%20%5D%0A%20%20%0A%20%20%5Bprovider-engine%5D%3C-%5Bconfig%20manager%5D%0A%20%20%5Bid%20store%5D%3C-%3E%5Bconfig%20manager%5D%0A%5D%0A%0A%5Buser%5D%3C-%3E%5Bmetamask-ui%5D%0A%0A%5Buser%5D%3C%3A--%3A%3E%5Bweb%20dapp%5D%0A%0A%5Bmetamask-contentscript%7C%0A%20%20%5Bplugin%20restart%20detector%5D%0A%20%20%5Brpc%20passthrough%5D%0A%5D%0A%0A%5Brpc%20%7C%0A%20%20%5Bethereum%20blockchain%20%7C%0A%20%20%20%20%5Bcontracts%5D%0A%20%20%20%20%5Baccounts%5D%0A%20%20%5D%0A%5D%0A%0A%5Bweb%20dapp%5D%3C%3A--%3A%3E%5Bmetamask-contentscript%5D%0A%5Bmetamask-contentscript%5D%3C-%3E%5Bmetamask-background%5D%0A%5Bmetamask-background%5D%3C-%3E%5Bmetamask-ui%5D%0A%5Bmetamask-background%5D%3C-%3E%5Brpc%5D%0A -### Generate Development Visualization - -This will generate a video of the repo commit history. - -Install preqs: -``` -brew install gource -brew install ffmpeg -``` - -From the repo dir, pipe `gource` into `ffmpeg`: -``` -gource \ - --seconds-per-day .1 \ - --user-scale 1.5 \ - --default-user-image "./images/icon-512.png" \ - --viewport 1280x720 \ - --auto-skip-seconds .1 \ - --multi-sampling \ - --stop-at-end \ - --highlight-users \ - --hide mouse,progress \ - --file-idle-time 0 \ - --max-files 0 \ - --background-colour 000000 \ - --font-size 18 \ - --date-format "%b %d, %Y" \ - --highlight-dirs \ - --user-friction 0.1 \ - --title "MetaMask Development History" \ - --output-ppm-stream - \ - --output-framerate 30 \ - | ffmpeg -y -r 30 -f image2pipe -vcodec ppm -i - -b 65536K metamask-dev-history.mp4 -``` - -## Generating Notices - -To add a notice: -``` -npm run generateNotice -``` -Enter the body of your notice into the text editor that pops up, without including the body. Be sure to save the file before closing the window! -Afterwards, enter the title of the notice in the command line and press enter. Afterwards, add and commit the new changes made. - -To delete a notice: -``` -npm run deleteNotice -``` -A list of active notices will pop up. Enter the corresponding id in the command line prompt and add and commit the new changes afterwards. - -## Adding Custom Networks - -To add another network to our dropdown menu, make sure the following files are adjusted properly: - -``` -app/scripts/config.js -app/scripts/lib/buy-eth-url.js -app/scripts/lib/config-manager.js -ui/app/app.js -ui/app/components/buy-button-subview.js -ui/app/components/drop-menu-item.js -ui/app/components/network.js -ui/app/components/transaction-list-item.js -ui/app/config.js -ui/app/css/lib.css -ui/lib/account-link.js -ui/lib/explorer-link.js -``` - -You will need: -+ The network ID -+ An RPC Endpoint url -+ An explorer link -+ CSS for the display icon - -## Other Guides - -- [Publishing Guide](./docs/publishing.md) - diff --git a/docs/add-to-chrome.md b/docs/add-to-chrome.md new file mode 100644 index 000000000..ea5213182 --- /dev/null +++ b/docs/add-to-chrome.md @@ -0,0 +1,14 @@ +## Add Custom Build to Chrome + +Open `Settings` > `Extensions`. + +Check "Developer mode". + +At the top, click `Load Unpacked Extension`. + +Navigate to your `metamask-plugin/dist/chrome` folder. + +Click `Select`. + +You now have the plugin, and can click 'inspect views: background plugin' to view its dev console. + diff --git a/docs/add-to-firef.md b/docs/add-to-firef.md new file mode 100644 index 000000000..593d06170 --- /dev/null +++ b/docs/add-to-firef.md @@ -0,0 +1,14 @@ +# Add Custom Build to Firefox + +Go to the url `about:debugging`. + +Click the button `Load Temporary Add-On`. + +Select the file `dist/firefox/manifest.json`. + +You can optionally enable debugging, and click `Debug`, for a console window that logs all of Metamask's processes to a single console. + +If you have problems debugging, try connecting to the IRC channel `#webextensions` on `irc.mozilla.org`. + +For longer questions, use the StackOverfow tag `firefox-addons`. + diff --git a/docs/adding-new-networks.md b/docs/adding-new-networks.md new file mode 100644 index 000000000..ea1453c21 --- /dev/null +++ b/docs/adding-new-networks.md @@ -0,0 +1,25 @@ +## Adding Custom Networks + +To add another network to our dropdown menu, make sure the following files are adjusted properly: + +``` +app/scripts/config.js +app/scripts/lib/buy-eth-url.js +app/scripts/lib/config-manager.js +ui/app/app.js +ui/app/components/buy-button-subview.js +ui/app/components/drop-menu-item.js +ui/app/components/network.js +ui/app/components/transaction-list-item.js +ui/app/config.js +ui/app/css/lib.css +ui/lib/account-link.js +ui/lib/explorer-link.js +``` + +You will need: ++ The network ID ++ An RPC Endpoint url ++ An explorer link ++ CSS for the display icon + diff --git a/docs/developing-on-deps.md b/docs/developing-on-deps.md new file mode 100644 index 000000000..7de3f67a8 --- /dev/null +++ b/docs/developing-on-deps.md @@ -0,0 +1,10 @@ +### Developing on Dependencies + +To enjoy the live-reloading that `gulp dev` offers while working on the `web3-provider-engine` or other dependencies: + + 1. Clone the dependency locally. + 2. `npm install` in its folder. + 3. Run `npm link` in its folder. + 4. Run `npm link $DEP_NAME` in this project folder. + 5. Next time you `npm start` it will watch the dependency for changes as well! + diff --git a/docs/development-visualization.md b/docs/development-visualization.md new file mode 100644 index 000000000..95847300d --- /dev/null +++ b/docs/development-visualization.md @@ -0,0 +1,35 @@ +### Generate Development Visualization + +This will generate a video of the repo commit history. + +Install preqs: +``` +brew install gource +brew install ffmpeg +``` + +From the repo dir, pipe `gource` into `ffmpeg`: +``` +gource \ + --seconds-per-day .1 \ + --user-scale 1.5 \ + --default-user-image "./images/icon-512.png" \ + --viewport 1280x720 \ + --auto-skip-seconds .1 \ + --multi-sampling \ + --stop-at-end \ + --highlight-users \ + --hide mouse,progress \ + --file-idle-time 0 \ + --max-files 0 \ + --background-colour 000000 \ + --font-size 18 \ + --date-format "%b %d, %Y" \ + --highlight-dirs \ + --user-friction 0.1 \ + --title "MetaMask Development History" \ + --output-ppm-stream - \ + --output-framerate 30 \ + | ffmpeg -y -r 30 -f image2pipe -vcodec ppm -i - -b 65536K metamask-dev-history.mp4 +``` + diff --git a/docs/notices.md b/docs/notices.md new file mode 100644 index 000000000..826e6e84e --- /dev/null +++ b/docs/notices.md @@ -0,0 +1,15 @@ +## Generating Notices + +To add a notice: +``` +npm run generateNotice +``` +Enter the body of your notice into the text editor that pops up, without including the body. Be sure to save the file before closing the window! +Afterwards, enter the title of the notice in the command line and press enter. Afterwards, add and commit the new changes made. + +To delete a notice: +``` +npm run deleteNotice +``` +A list of active notices will pop up. Enter the corresponding id in the command line prompt and add and commit the new changes afterwards. + diff --git a/docs/publishing.md b/docs/publishing.md index e546f327b..00369acf9 100644 --- a/docs/publishing.md +++ b/docs/publishing.md @@ -2,6 +2,15 @@ When publishing a new version of MetaMask, we follow this procedure: +## Incrementing Version & Changelog + + You must be authorized already on the MetaMask plugin. + +1. Update the version in `app/manifest.json` and the Changelog in `CHANGELOG.md`. +2. Visit [the chrome developer dashboard](https://chrome.google.com/webstore/developer/dashboard?authuser=2). + +## Publishing + 1. `npm run dist` to generate the latest build. 2. Publish to chrome store. 3. Publish to firefox addon marketplace. diff --git a/docs/ui-dev-mode.md b/docs/ui-dev-mode.md new file mode 100644 index 000000000..df49d8b04 --- /dev/null +++ b/docs/ui-dev-mode.md @@ -0,0 +1,6 @@ +# Running UI Dev Mode + +You can run `npm run ui`, and your browser should open a live-reloading demo version of the plugin UI. + +Some actions will crash the app, so this is only for tuning aesthetics, but it allows live-reloading styles, which is a much faster feedback loop than reloading the full extension. + diff --git a/docs/ui-mock-mode.md b/docs/ui-mock-mode.md new file mode 100644 index 000000000..bb54dc471 --- /dev/null +++ b/docs/ui-mock-mode.md @@ -0,0 +1,8 @@ +### Developing on UI with Mocked Background Process + +You can run `npm run mock` and your browser should open a live-reloading demo version of the plugin UI, just like the `npm run ui`, except that it tries to actually perform all normal operations. + +It does not yet connect to a real blockchain (this could be a good test feature later, connecting to a test blockchain), so only local operations work. + +You can reset the mock ui at any time with the `Reset` button at the top of the screen. + From c8f0802a8e63ca29c643d08ebc1b777520645246 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Mon, 5 Jun 2017 15:35:52 -0700 Subject: [PATCH 357/372] Fix bug that prevented publishing contracts --- CHANGELOG.md | 2 ++ ui/app/components/pending-tx.js | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 14a6ef3a7..6b0f27e72 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Current Master +- Fix bug that prevented publishing contracts. + ## 3.7.5 2017-6-5 - Prevent users from sending to the `0x0` address. diff --git a/ui/app/components/pending-tx.js b/ui/app/components/pending-tx.js index a106f245b..e3b307b0b 100644 --- a/ui/app/components/pending-tx.js +++ b/ui/app/components/pending-tx.js @@ -45,7 +45,7 @@ PendingTx.prototype.render = function () { const balance = account ? account.balance : '0x0' // recipient check - const isValidAddress = util.isValidAddress(txParams.to) + const isValidAddress = !txParams.to || util.isValidAddress(txParams.to) // Gas const gas = txParams.gas From 838ffb62ee59a36d1dbfdafca0dc6727aeb6985d Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Mon, 5 Jun 2017 15:36:18 -0700 Subject: [PATCH 358/372] Version 3.7.6 --- CHANGELOG.md | 2 ++ app/manifest.json | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b0f27e72..cbab1c71a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Current Master +## 3.7.6 2017-6-5 + - Fix bug that prevented publishing contracts. ## 3.7.5 2017-6-5 diff --git a/app/manifest.json b/app/manifest.json index acdb3795e..2d321e862 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -1,7 +1,7 @@ { "name": "MetaMask", "short_name": "Metamask", - "version": "3.7.5", + "version": "3.7.6", "manifest_version": 2, "author": "https://metamask.io", "description": "Ethereum Browser Extension", From 203a573f3fa8cf636c846a0a467318f6767d05fe Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Mon, 5 Jun 2017 16:23:56 -0700 Subject: [PATCH 359/372] Use new URL for currency API from cryptonator. --- app/manifest.json | 2 +- app/scripts/controllers/currency.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/manifest.json b/app/manifest.json index 99a083f0f..01ee173a6 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -58,7 +58,7 @@ "storage", "clipboardWrite", "http://localhost:8545/", - "https://www.cryptonator.com/" + "https://api.cryptonator.com/" ], "web_accessible_resources": [ "scripts/inpage.js" diff --git a/app/scripts/controllers/currency.js b/app/scripts/controllers/currency.js index fb130ed76..1f20dc005 100644 --- a/app/scripts/controllers/currency.js +++ b/app/scripts/controllers/currency.js @@ -45,7 +45,7 @@ class CurrencyController { updateConversionRate () { const currentCurrency = this.getCurrentCurrency() - return fetch(`https://www.cryptonator.com/api/ticker/eth-${currentCurrency}`) + return fetch(`https://api.cryptonator.com/api/ticker/eth-${currentCurrency}`) .then(response => response.json()) .then((parsedResponse) => { this.setConversionRate(Number(parsedResponse.ticker.price)) From f7773538ebb583905b5385096abea48c58e7673c Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Mon, 5 Jun 2017 16:24:32 -0700 Subject: [PATCH 360/372] Modify tests for new api mock.' --- test/unit/currency-controller-test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/unit/currency-controller-test.js b/test/unit/currency-controller-test.js index cfbce7fb3..5eeaf9bcc 100644 --- a/test/unit/currency-controller-test.js +++ b/test/unit/currency-controller-test.js @@ -36,7 +36,7 @@ describe('currency-controller', function () { describe('#updateConversionRate', function () { it('should retrieve an update for ETH to USD and set it in memory', function (done) { this.timeout(15000) - nock('https://www.cryptonator.com') + nock('https://api.cryptonator.com') .get('/api/ticker/eth-USD') .reply(200, '{"ticker":{"base":"ETH","target":"USD","price":"11.02456145","volume":"44948.91745289","change":"-0.01472534"},"timestamp":1472072136,"success":true,"error":""}') @@ -57,7 +57,7 @@ describe('currency-controller', function () { this.timeout(15000) assert.equal(currencyController.getConversionRate(), 0) - nock('https://www.cryptonator.com') + nock('https://api.cryptonator.com') .get('/api/ticker/eth-JPY') .reply(200, '{"ticker":{"base":"ETH","target":"JPY","price":"11.02456145","volume":"44948.91745289","change":"-0.01472534"},"timestamp":1472072136,"success":true,"error":""}') From 0fb38632befc8a09dc12de8f609edfb0ffab9d35 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Mon, 5 Jun 2017 16:25:02 -0700 Subject: [PATCH 361/372] Bump changelog. --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e7b292b5..52d187ccf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Current Master +- Fix currency API URL from cryptonator. + ## 3.7.4 2017-6-2 - Fix bug with inflight cache that caused some block lookups to return bad values (affected OasisDex). From f788ba7244d78a60233b9516423524ebd931fbb6 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Mon, 5 Jun 2017 16:27:15 -0700 Subject: [PATCH 362/372] Resolve changelog merging. --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d9de93d3..7bfa441b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Current Master - Fix currency API URL from cryptonator. + ## 3.7.6 2017-6-5 - Fix bug that prevented publishing contracts. From a37c8f3ed07d62948f26444ddce6ac148ed6e458 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Thu, 8 Jun 2017 16:02:23 -0700 Subject: [PATCH 363/372] Modify FAQ to be more visible. --- ui/app/app.js | 2 +- ui/app/info.js | 19 +++++++++---------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/ui/app/app.js b/ui/app/app.js index 6e5aa57cd..53dbc3354 100644 --- a/ui/app/app.js +++ b/ui/app/app.js @@ -341,7 +341,7 @@ App.prototype.renderDropdown = function () { }), h(DropMenuItem, { - label: 'Info', + label: 'Info/Help', closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }), action: () => this.props.dispatch(actions.showInfoPage()), icon: h('i.fa.fa-question.fa-lg'), diff --git a/ui/app/info.js b/ui/app/info.js index a6fdeb315..aa4503b62 100644 --- a/ui/app/info.js +++ b/ui/app/info.js @@ -52,7 +52,7 @@ InfoScreen.prototype.render = function () { h('div', { style: { - marginBottom: '10px', + marginBottom: '5px', }}, [ h('div', [ @@ -87,7 +87,7 @@ InfoScreen.prototype.render = function () { h('hr', { style: { - margin: '20px 0 ', + margin: '10px 0 ', width: '7em', }, }), @@ -97,6 +97,13 @@ InfoScreen.prototype.render = function () { paddingLeft: '30px', }}, [ + h('div.fa.fa-github', [ + h('a.info', { + href: 'https://github.com/MetaMask/faq', + target: '_blank', + onClick (event) { this.navigateTo(event.target.href) }, + }, 'Need Help? Read our FAQ!'), + ]), h('div', [ h('a', { href: 'https://metamask.io/', @@ -138,14 +145,6 @@ InfoScreen.prototype.render = function () { onClick () { this.navigateTo('mailto:help@metamask.io?subject=Feedback') }, }, 'Email us!'), ]), - - h('div.fa.fa-github', [ - h('a.info', { - href: 'https://github.com/MetaMask/metamask-plugin/issues', - target: '_blank', - onClick (event) { this.navigateTo(event.target.href) }, - }, 'Start a thread on GitHub'), - ]), ]), ]), ]), From 57a7fc4425fbd87a89d4bc5c61eac3dc1ffa250e Mon Sep 17 00:00:00 2001 From: kumavis Date: Thu, 8 Jun 2017 16:07:05 -0700 Subject: [PATCH 364/372] deps - bump provider engine for warp feature --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9efba3866..c6c3add8e 100644 --- a/package.json +++ b/package.json @@ -123,7 +123,7 @@ "valid-url": "^1.0.9", "vreme": "^3.0.2", "web3": "0.18.2", - "web3-provider-engine": "^12.1.0", + "web3-provider-engine": "^12.2.3", "web3-stream-provider": "^2.0.6", "xtend": "^4.0.1" }, From f994461763057a15a51b69b805faacd891e08db4 Mon Sep 17 00:00:00 2001 From: kumavis Date: Thu, 8 Jun 2017 16:21:16 -0700 Subject: [PATCH 365/372] changelog - note on block-tracker warp --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cbab1c71a..1e98b17d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Current Master +- Fix bug where metamask would show old data after computer being asleep or disconnected from the internet. + ## 3.7.6 2017-6-5 - Fix bug that prevented publishing contracts. From 017c7c4c006d3e6041d33ae815d83faf43ec21bb Mon Sep 17 00:00:00 2001 From: kumavis Date: Thu, 8 Jun 2017 16:42:00 -0700 Subject: [PATCH 366/372] 3.7.7 --- app/manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/manifest.json b/app/manifest.json index 2d321e862..a0d1500c2 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -1,7 +1,7 @@ { "name": "MetaMask", "short_name": "Metamask", - "version": "3.7.6", + "version": "3.7.7", "manifest_version": 2, "author": "https://metamask.io", "description": "Ethereum Browser Extension", From e9c693eae4d5ea9c44117b26260c7e05261b3eb9 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Thu, 8 Jun 2017 16:44:11 -0700 Subject: [PATCH 367/372] Version bump changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e98b17d8..b1fa344f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Current Master +## 3.7.7 2017-6-8 + - Fix bug where metamask would show old data after computer being asleep or disconnected from the internet. ## 3.7.6 2017-6-5 From 1a70141e8bbfce0881aebd3afe431b3d38880167 Mon Sep 17 00:00:00 2001 From: frankiebee Date: Thu, 8 Jun 2017 17:32:50 -0700 Subject: [PATCH 368/372] clean up code --- mascara/src/background.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/mascara/src/background.js b/mascara/src/background.js index dff5e6a7c..d9dbf593a 100644 --- a/mascara/src/background.js +++ b/mascara/src/background.js @@ -33,11 +33,6 @@ self.addEventListener('install', function(event) { }) self.addEventListener('activate', function(event) { event.waitUntil(self.clients.claim()) - self.clients.matchAll() - .then((clients) => { - if (connectedClientCount < clients.length) sendMessageToAllClients('reconnect') - }) - }) console.log('inside:open') From 4941b5ab118e4ab99ae94b2a714c7fceab24adcf Mon Sep 17 00:00:00 2001 From: frankiebee Date: Thu, 8 Jun 2017 17:33:27 -0700 Subject: [PATCH 369/372] bump cswready event --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9efba3866..c58202ef0 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "bluebird": "^3.5.0", "browser-passworder": "^2.0.3", "browserify-derequire": "^0.9.4", - "client-sw-ready-event": "^3.0.3", + "client-sw-ready-event": "^3.3.0", "clone": "^1.0.2", "copy-to-clipboard": "^2.0.0", "debounce": "^1.0.0", From a0a19468a805994f3897a63489c344e5f3f89dc9 Mon Sep 17 00:00:00 2001 From: frankiebee Date: Thu, 8 Jun 2017 17:34:51 -0700 Subject: [PATCH 370/372] reload the page when switching networks --- mascara/src/mascara.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/mascara/src/mascara.js b/mascara/src/mascara.js index 0fc2868e1..1655d1f64 100644 --- a/mascara/src/mascara.js +++ b/mascara/src/mascara.js @@ -1,6 +1,6 @@ const Web3 = require('web3') const setupProvider = require('./lib/setup-provider.js') - +const setupDappAutoReload = require('../../app/scripts/lib/auto-reload.js') const MASCARA_ORIGIN = process.env.MASCARA_ORIGIN || 'http://localhost:9001' console.log('MASCARA_ORIGIN:', MASCARA_ORIGIN) @@ -14,8 +14,7 @@ const provider = setupProvider({ instrumentForUserInteractionTriggers(provider) const web3 = new Web3(provider) -global.web3 = web3 - +setupDappAutoReload(web3, provider.publicConfigStore) // // ui stuff // From 3d1d38a2c0cd04d637b878d0a0be7f505f406a97 Mon Sep 17 00:00:00 2001 From: frankiebee Date: Thu, 8 Jun 2017 17:35:21 -0700 Subject: [PATCH 371/372] reload page if ui is not present --- mascara/src/ui.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/mascara/src/ui.js b/mascara/src/ui.js index e798847a7..5f9be542f 100644 --- a/mascara/src/ui.js +++ b/mascara/src/ui.js @@ -40,7 +40,6 @@ const connectApp = function (readSw) { }) }) } - background.on('ready', (sw) => { background.removeListener('updatefound', connectApp) connectApp(sw) @@ -48,5 +47,10 @@ background.on('ready', (sw) => { background.on('updatefound', () => window.location.reload()) background.startWorker() -// background.startWorker() +.then(() => { + setTimeout(() => { + const appContent = document.getElementById(`app-content`) + if (!appContent.children.length) window.location.reload() + }, 2000) +}) console.log('hello from MetaMascara ui!') From 2fcf3d843985e0675fc9780043b0331d45ba7190 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Fri, 9 Jun 2017 10:48:28 -0700 Subject: [PATCH 372/372] Modify wording to new accept. --- mascara/test/lib/first-time.js | 2 +- test/integration/lib/first-time.js | 2 +- ui/app/components/notice.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/mascara/test/lib/first-time.js b/mascara/test/lib/first-time.js index 76a4545bf..e42c9e39d 100644 --- a/mascara/test/lib/first-time.js +++ b/mascara/test/lib/first-time.js @@ -10,7 +10,7 @@ QUnit.test('render init screen', function (assert) { app = $('#app-content').contents() const recurseNotices = function () { let button = app.find('button') - if (button.html() === 'Agree') { + if (button.html() === 'Accept') { let termsPage = app.find('.markdown')[0] termsPage.scrollTop = termsPage.scrollHeight return wait().then(() => { diff --git a/test/integration/lib/first-time.js b/test/integration/lib/first-time.js index f0fa4ee3f..6c8cedbac 100644 --- a/test/integration/lib/first-time.js +++ b/test/integration/lib/first-time.js @@ -11,7 +11,7 @@ QUnit.test('render init screen', function (assert) { const recurseNotices = function () { let button = app.find('button') - if (button.html() === 'Agree') { + if (button.html() === 'Accept') { let termsPage = app.find('.markdown')[0] termsPage.scrollTop = termsPage.scrollHeight return wait().then(() => { diff --git a/ui/app/components/notice.js b/ui/app/components/notice.js index 7fe41fa88..d9f0067cd 100644 --- a/ui/app/components/notice.js +++ b/ui/app/components/notice.js @@ -107,7 +107,7 @@ Notice.prototype.render = function () { style: { marginTop: '18px', }, - }, 'Agree'), + }, 'Accept'), ]) ) }