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/.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/.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 diff --git a/.eslintrc b/.eslintrc index 8bbfe13c7..a57a93cdc 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,7 +1,7 @@ { "parserOptions": { "sourceType": "module", - "ecmaVersion": 6, + "ecmaVersion": 2017, "ecmaFeatures": { "experimentalObjectRestSpread": true, "impliedStrict": true, @@ -17,10 +17,13 @@ "env": { "es6": true, "node": true, - "browser": true + "browser": true, + "mocha" : true }, "plugins": [ + "mocha", + "chai" ], "globals": { diff --git a/.gitignore b/.gitignore index c61847aab..cc0ba4fd1 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,6 @@ development/bundle.js builds.zip test/integration/bundle.js development/states.js +test/background.js +test/bundle.js +test/test-bundle.js diff --git a/CHANGELOG.md b/CHANGELOG.md index f75f4eb54..38aebcbd4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,9 +3,124 @@ ## Current Master - The default network on installation is now MainNet +- Fix currency API URL from cryptonator. +- Update gasLimit params with every new block seen. + +## 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 + +- Fix bug that prevented publishing contracts. + +## 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). +- 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. + +## 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. + +## 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. +- Continually resubmit pending txs for a period of time to ensure successful broadcast. +- 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 + +- Fix bug where edited gas parameters would not take effect. +- Trim currency list. +- Enable decimals in our gas prices. +- Fix reset button. +- 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 + +- Fix main-net ENS resolution. + +## 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. +- 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 + +- Made fox less nosy. +- Fix bug where error was reported in debugger console when Chrome opened a new window. + +## 3.6.0 2017-4-26 + +- 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. + +## 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 + +- Fix bug where gas estimate totals were sometimes wrong. +- Add link to Kovan Test Faucet instructions on buy view. +- 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. + +## 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. - 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. +- 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) +- 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 diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..be0a328fe --- /dev/null +++ b/Dockerfile @@ -0,0 +1,22 @@ +FROM node:7 +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/README.md b/README.md index aa79f4564..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,126 +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 -``` -To delete a notice: -``` -npm run deleteNotice -``` 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/app/manifest.json b/app/manifest.json index 910a5701e..a610d9e75 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -1,7 +1,7 @@ { "name": "MetaMask", "short_name": "Metamask", - "version": "3.4.0", + "version": "3.7.7", "manifest_version": 2, "author": "https://metamask.io", "description": "Ethereum Browser Extension", @@ -51,13 +51,14 @@ "scripts/contentscript.js" ], "run_at": "document_start", - "all_frames": false + "all_frames": true } ], "permissions": [ "storage", "clipboardWrite", - "http://localhost:8545/" + "http://localhost:8545/", + "https://api.cryptonator.com/" ], "web_accessible_resources": [ "scripts/inpage.js" 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 254737dec..63c8a7252 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -1,15 +1,15 @@ 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') +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,44 +19,42 @@ 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 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 - let migrator = new Migrator({ migrations }) - let 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), - ]) + const migrator = new Migrator({ migrations }) + // 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) { - // // MetaMask Controller // @@ -68,6 +66,8 @@ function setupController (initState) { showUnapprovedTx: triggerUi, // initial state initState, + // platform specific api + platform, }) global.metamaskController = controller @@ -78,8 +78,8 @@ function setupController (initState) { diskStore ) - function versionifyData(state) { - let versionedData = diskStore.getState() + function versionifyData (state) { + const versionedData = diskStore.getState() versionedData.data = state return versionedData } @@ -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) { @@ -131,7 +131,6 @@ function setupController (initState) { } return Promise.resolve() - } // @@ -140,7 +139,7 @@ function setupController (initState) { // popup trigger function triggerUi () { - if (!popupIsOpen) notification.show() + if (!popupIsOpen) notificationManager.showPopup() } // On first install, open a window to MetaMask website to how-it-works. diff --git a/app/scripts/config.js b/app/scripts/config.js index b4541a04a..8e28db80e 100644 --- a/app/scripts/config.js +++ b/app/scripts/config.js @@ -1,14 +1,15 @@ const MAINET_RPC_URL = 'https://mainnet.infura.io/metamask' -const TESTNET_RPC_URL = 'https://ropsten.infura.io/metamask' -const DEFAULT_RPC_URL = TESTNET_RPC_URL +const ROPSTEN_RPC_URL = 'https://ropsten.infura.io/metamask' +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, + ropsten: ROPSTEN_RPC_URL, + kovan: KOVAN_RPC_URL, + rinkeby: RINKEBY_RPC_URL, }, } diff --git a/app/scripts/contentscript.js b/app/scripts/contentscript.js index ab64dc9fa..291b922e8 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') @@ -61,14 +61,22 @@ function setupStreams () { // ignore unused channels (handled by background) mx.ignoreStream('provider') mx.ignoreStream('publicConfig') - mx.ignoreStream('reload') } 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 false + } +} + +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..1f20dc005 100644 --- a/app/scripts/controllers/currency.js +++ b/app/scripts/controllers/currency.js @@ -45,15 +45,17 @@ 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)) 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/network.js b/app/scripts/controllers/network.js new file mode 100644 index 000000000..c07f13b8d --- /dev/null +++ b/app/scripts/controllers/network.js @@ -0,0 +1,129 @@ +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 (config) { + super() + 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.on('networkDidChange', this.lookupNetwork) + this.providerStore.subscribe((state) => this.switchNetwork({rpcUrl: state.rpcTarget})) + } + + get provider () { + return this._proxy + } + + set provider (provider) { + this._provider = provider + } + + initializeProvider (opts) { + this.providerInit = 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 this.provider + } + + switchNetwork (providerInit) { + this.setNetworkState('loading') + const newInit = extend(this.providerInit, providerInit) + 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) => { + this._providerListeners[key].forEach((handler) => this._provider.addListener(key, handler)) + }) + this.emit('networkDidChange') + } + + + verifyNetwork () { + // Check network when restoring connectivity: + if (this.isNetworkLoading()) this.lookupNetwork() + } + + getNetworkState () { + return this.networkStore.getState() + } + + setNetworkState (network) { + return this.networkStore.putState(network) + } + + isNetworkLoading () { + return this.getNetworkState() === 'loading' + } + + lookupNetwork () { + 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({ + type: 'rpc', + rpcTarget: rpcUrl, + }) + } + + getCurrentRpcAddress () { + const provider = this.getProviderConfig() + if (!provider) return null + return this.getRpcAddressForType(provider.type) + } + + setProviderType (type) { + if (type === this.getProviderConfig().type) return + const rpcTarget = this.getRpcAddressForType(type) + this.providerStore.updateState({type, rpcTarget}) + } + + getProviderConfig () { + return this.providerStore.getState() + } + + 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) { + log.info(`BLOCK CHANGED: #${block.number.toString('hex')} 0x${block.hash.toString('hex')}`) + this.verifyNetwork() + } + + _on (event, handler) { + if (!this._providerListeners[event]) this._providerListeners[event] = [] + this._providerListeners[event].push(handler) + this._provider.on(event, handler) + } +} 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/transaction-manager.js b/app/scripts/controllers/transactions.js similarity index 67% rename from app/scripts/transaction-manager.js rename to app/scripts/controllers/transactions.js index c6cfdf11d..faccf1ab1 100644 --- a/app/scripts/transaction-manager.js +++ b/app/scripts/controllers/transactions.js @@ -4,11 +4,14 @@ 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') +const TxProviderUtil = require('../lib/tx-utils') +const createId = require('../lib/random-id') +const denodeify = require('denodeify') -module.exports = class TransactionManager extends EventEmitter { +const RETRY_LIMIT = 200 +const RESUBMIT_INTERVAL = 10000 // Ten seconds + +module.exports = class TransactionController extends EventEmitter { constructor (opts) { super() this.store = new ObservableStore(extend({ @@ -20,16 +23,19 @@ module.exports = class TransactionManager extends EventEmitter { this.txHistoryLimit = opts.txHistoryLimit this.provider = opts.provider this.blockTracker = opts.blockTracker - this.txProviderUtils = new TxProviderUtil(this.provider) + this.query = opts.ethQuery + this.txProviderUtils = new TxProviderUtil(this.query) this.blockTracker.on('block', this.checkForTxInBlock.bind(this)) this.signEthTx = opts.signTransaction this.nonceLock = Semaphore(1) // 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()) + + this.continuallyResubmitPendingTxs() } getState () { @@ -37,7 +43,7 @@ module.exports = class TransactionManager extends EventEmitter { } getNetwork () { - return this.networkStore.getState().network + return this.networkStore.getState() } getSelectedAddress () { @@ -46,28 +52,41 @@ module.exports = class TransactionManager extends EventEmitter { // Returns the tx list getTxList () { - let network = this.getNetwork() - let fullTxList = this.store.getState().transactions + const network = this.getNetwork() + const 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 + const txCount = this.getTxCount() + const network = this.getNetwork() + const fullTxList = this.getFullTxList() + const 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(fullTxList) + this.emit('update') - this._saveTxList(txList) this.once(`${txMeta.id}:signed`, function (txId) { this.removeAllListeners(`${txMeta.id}:rejected`) }) @@ -89,7 +108,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) @@ -109,44 +128,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 () { @@ -172,7 +185,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() @@ -186,7 +202,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) @@ -194,11 +210,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())) @@ -207,7 +235,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) @@ -219,7 +251,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) } @@ -291,7 +323,10 @@ module.exports = class TransactionManager extends EventEmitter { this._setTxStatus(txId, 'confirmed') } - setTxStatusFailed (txId) { + setTxStatusFailed (txId, reason) { + const txMeta = this.getTx(txId) + txMeta.err = reason + this.updateTx(txMeta) this._setTxStatus(txId, 'failed') } @@ -312,14 +347,13 @@ module.exports = class TransactionManager extends EventEmitter { var txHash = txMeta.hash var txId = txMeta.id if (!txHash) { - txMeta.err = { + const 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) => { + this.query.getTransactionByHash(txHash, (err, txParams) => { if (err || !txParams) { if (!txParams) return txMeta.err = { @@ -328,7 +362,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) @@ -354,6 +388,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') @@ -373,7 +408,47 @@ 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) + } + } -const warn = () => console.warn('warn was used no cb provided') +const warn = () => log.warn('warn was used no cb provided') diff --git a/app/scripts/first-time-state.js b/app/scripts/first-time-state.js index 46fcde998..5d89a0a21 100644 --- a/app/scripts/first-time-state.js +++ b/app/scripts/first-time-state.js @@ -6,9 +6,11 @@ const METAMASK_DEBUG = 'GULP_METAMASK_DEBUG' // The default state of MetaMask // module.exports = { - config: { + config: {}, + NetworkController: { provider: { - type: (METAMASK_DEBUG || env === 'test') ? 'testnet' : 'mainnet', + type: (METAMASK_DEBUG || env === 'test') ? 'rinkeby' : 'mainnet', + type: 'rinkeby', }, }, } 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/keyring-controller.js b/app/scripts/keyring-controller.js index 72f613641..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) @@ -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)) @@ -581,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/auto-faucet.js b/app/scripts/lib/auto-faucet.js index 1e86f735e..38d54ba5e 100644 --- a/app/scripts/lib/auto-faucet.js +++ b/app/scripts/lib/auto-faucet.js @@ -3,10 +3,18 @@ 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 - var http = new XMLHttpRequest() - var data = address - http.open('POST', uri, true) - http.setRequestHeader('Content-type', 'application/rawdata') - http.send(data) + // Don't faucet in development or test + if (METAMASK_DEBUG === true || 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', + headers, + body: data, + }) + .catch((err) => { + console.error(err) + }) } diff --git a/app/scripts/lib/auto-reload.js b/app/scripts/lib/auto-reload.js index 1302df35f..534047330 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 timeSinceUse = Date.now() - web3._used + // if web3 was recently used then delay the reloading of the page + timeSinceUse > 500 ? triggerReset() : setTimeout(triggerReset, 500) + // prevent reentry into if statement if state updates again before + // reload + networkVersion = curentNetVersion + } + }) } // reload the page diff --git a/app/scripts/lib/buy-eth-url.js b/app/scripts/lib/buy-eth-url.js new file mode 100644 index 000000000..b9dde3c28 --- /dev/null +++ b/app/scripts/lib/buy-eth-url.js @@ -0,0 +1,23 @@ +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 '4': + url = 'https://www.rinkeby.io/' + break + + case '42': + url = 'https://github.com/kovan-testnet/faucet' + break + } + return url +} diff --git a/app/scripts/lib/config-manager.js b/app/scripts/lib/config-manager.js index 6868637e5..9c0dffe9c 100644 --- a/app/scripts/lib/config-manager.js +++ b/app/scripts/lib/config-manager.js @@ -1,10 +1,12 @@ -const MetamaskConfig = require('../config.js') const ethUtil = require('ethereumjs-util') const normalize = require('eth-sig-util').normalize +const MetamaskConfig = require('../config.js') + -const TESTNET_RPC = MetamaskConfig.network.testnet const MAINNET_RPC = MetamaskConfig.network.mainnet -const MORDEN_RPC = MetamaskConfig.network.morden +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. @@ -32,36 +34,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) } @@ -135,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() @@ -144,14 +145,17 @@ ConfigManager.prototype.getCurrentRpcAddress = function () { case 'mainnet': return MAINNET_RPC - case 'testnet': - return TESTNET_RPC + case 'ropsten': + return ROPSTEN_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 + return provider && provider.rpcTarget ? provider.rpcTarget : RINKEBY_RPC } } diff --git a/app/scripts/lib/eth-store.js b/app/scripts/lib/eth-store.js index 8812a507b..ebba98f5c 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 { @@ -19,6 +19,9 @@ class EthereumStore extends ObservableStore { super({ accounts: {}, transactions: {}, + currentBlockNumber: '0', + currentBlockHash: '', + currentBlockGasLimit: '', }) this._provider = opts.provider this._query = new EthQuery(this._provider) @@ -69,6 +72,9 @@ 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')}`}) + this.updateState({ currentBlockGasLimit: `0x${block.gasLimit.toString('hex')}` }) async.parallel([ this._updateAccounts.bind(this), this._updateTransactions.bind(this, blockNumber), @@ -129,4 +135,4 @@ class EthereumStore extends ObservableStore { } -module.exports = EthereumStore \ No newline at end of file +module.exports = EthereumStore 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 4b670490f..000000000 --- a/app/scripts/lib/extension.js +++ /dev/null @@ -1,14 +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') -module.exports = new Extension() 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/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/lib/inpage-provider.js b/app/scripts/lib/inpage-provider.js index 92936de2f..8b8623974 100644 --- a/app/scripts/lib/inpage-provider.js +++ b/app/scripts/lib/inpage-provider.js @@ -34,6 +34,7 @@ function MetamaskInpageProvider (connectionStream) { asyncProvider, (err) => logStreamDisconnectWarning('MetaMask RpcProvider', err) ) + // start and stop polling to unblock first block lock self.idMap = {} // handle sendAsync requests via asyncProvider @@ -85,7 +86,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 +126,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..de6f5d5cd 100644 --- a/app/scripts/lib/migrator/index.js +++ b/app/scripts/lib/migrator/index.js @@ -1,42 +1,35 @@ -const asyncQ = require('async-q') - class Migrator { constructor (opts = {}) { - let migrations = opts.migrations || [] + const migrations = opts.migrations || [] + // sort migrations by version this.migrations = migrations.sort((a, b) => a.version - b.version) - let lastMigration = this.migrations.slice(-1)[0] + // 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()) { - let remaining = this.migrations.filter(migrationIsPending) - - return ( - asyncQ.eachSeries(remaining, (migration) => this.runMigration(versionedData, migration)) - .then(() => versionedData) - ) + async migrateData (versionedData = this.generateInitialState()) { + const pendingMigrations = this.migrations.filter(migrationIsPending) - // migration is "pending" if hit has a higher + 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 && versionedData.meta.version !== migration.version) throw new Error('Migrator - Migration did not update version number correctly') + } + + return versionedData + + // migration is "pending" if it has a higher // version number than currentVersion - function migrationIsPending(migration) { + function migrationIsPending (migration) { return migration.version > versionedData.meta.version } } - 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) - }) - ) - } - generateInitialState (initState) { return { meta: { diff --git a/app/scripts/lib/notification-manager.js b/app/scripts/lib/notification-manager.js new file mode 100644 index 000000000..7846ef7f0 --- /dev/null +++ b/app/scripts/lib/notification-manager.js @@ -0,0 +1,71 @@ +const extension = require('extensionizer') +const height = 520 +const width = 360 + + +class NotificationManager { + + // + // Public + // + + showPopup () { + 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, + }) + } + }) + } + + closePopup () { + this._getPopup((err, popup) => { + if (err) throw err + if (!popup) return + extension.windows.remove(popup.id, console.error) + }) + } + + // + // 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) { + 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 diff --git a/app/scripts/lib/notifications.js b/app/scripts/lib/notifications.js deleted file mode 100644 index 3db1ac6b5..000000000 --- a/app/scripts/lib/notifications.js +++ /dev/null @@ -1,65 +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', - focused: true, - width, - height, - }) - } - }) -} - -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/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 c6814c05f..149d93102 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,53 +6,55 @@ 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 (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) { - txParams.gas = blockGasLimitHex + if (!txMeta.gasLimitSpecified) { + const blockGasLimitBN = hexToBn(blockGasLimitHex) + const saferGasLimitBN = BnMultiplyByFraction(blockGasLimitBN, 19, 20) + txParams.gas = bnToHex(saferGasLimitBN) } // 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 @@ -62,25 +63,26 @@ 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) { - 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) @@ -90,16 +92,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.gasLimit = normalize(txParams.gasLimit || txParams.gas) + 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)}`) @@ -124,14 +123,20 @@ 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) -} \ No newline at end of file +} + +function BnMultiplyByFraction (targetBN, numerator, denominator) { + const numBN = new BN(numerator) + const denomBN = new BN(denominator) + return targetBN.mul(numBN).div(denomBN) +} diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 25f9d9e5d..a7eb3d056 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -4,13 +4,12 @@ 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') -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') @@ -18,13 +17,12 @@ 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 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 getBuyEthUrl = require('./lib/buy-eth-url') const version = require('../manifest.json').version @@ -33,14 +31,17 @@ 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 // observable state store 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, @@ -60,8 +61,6 @@ 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)) // eth data query tools this.ethQuery = new EthQuery(this.provider) @@ -74,10 +73,12 @@ 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) + }) + this.keyringController.on('newVault', (address) => { autoFaucet(address) }) @@ -87,15 +88,16 @@ module.exports = class MetamaskController extends EventEmitter { }, this.keyringController) // tx mgmt - this.txManager = new TxManager({ - initState: initState.TransactionManager, - networkStore: this.networkStore, + this.txController = new TransactionController({ + initState: initState.TransactionController || initState.TransactionManager, + 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, + ethQuery: this.ethQuery, }) // notices @@ -110,19 +112,14 @@ module.exports = class MetamaskController extends EventEmitter { initState: initState.ShapeShiftController, }) - this.lookupNetwork() + this.networkController.lookupNetwork() this.messageManager = new MessageManager() 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 }) + this.txController.store.subscribe((state) => { + this.store.updateState({ TransactionController: state }) }) this.keyringController.store.subscribe((state) => { this.store.updateState({ KeyringController: state }) @@ -142,11 +139,14 @@ module.exports = class MetamaskController extends EventEmitter { this.shapeshiftController.store.subscribe((state) => { this.store.updateState({ ShapeShiftController: state }) }) + this.networkController.store.subscribe((state) => { + this.store.updateState({ NetworkController: state }) + }) // manual mem state subscriptions - this.networkStore.subscribe(this.sendUpdate.bind(this)) + this.networkController.store.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)) @@ -162,17 +162,21 @@ module.exports = class MetamaskController extends EventEmitter { // initializeProvider () { - - let provider = MetaMaskProvider({ + return this.networkController.initializeProvider({ static: { eth_syncing: false, web3_clientVersion: `MetaMask/v${version}`, }, - rpcUrl: this.configManager.getCurrentRpcAddress(), + rpcUrl: this.networkController.getCurrentRpcAddress(), // account mgmt getAccounts: (cb) => { - let selectedAddress = this.preferencesController.getSelectedAddress() - let result = selectedAddress ? [selectedAddress] : [] + const isUnlocked = this.keyringController.memStore.getState().isUnlocked + const result = [] + const selectedAddress = this.preferencesController.getSelectedAddress() + // only show address if account is unlocked + if (isUnlocked && selectedAddress) { + result.push(selectedAddress) + } cb(null, result) }, // tx signing @@ -183,26 +187,23 @@ module.exports = class MetamaskController extends EventEmitter { // new style msg signing processPersonalMessage: this.newUnsignedPersonalMessage.bind(this), }) - return provider } initPublicConfigStore () { // get init state const publicConfigStore = new ObservableStore() - // 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 } @@ -214,7 +215,6 @@ module.exports = class MetamaskController extends EventEmitter { // getState () { - const wallet = this.configManager.getWallet() const vault = this.keyringController.store.getState().vault const isInitialized = (!!wallet || !!vault) @@ -222,9 +222,9 @@ module.exports = class MetamaskController extends EventEmitter { { isInitialized, }, - this.networkStore.getState(), + this.networkController.store.getState(), this.ethStore.getState(), - this.txManager.memStore.getState(), + this.txController.memStore.getState(), this.messageManager.memStore.getState(), this.personalMessageManager.memStore.getState(), this.keyringController.memStore.getState(), @@ -249,62 +249,61 @@ 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 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.networkController.setProviderType.bind(this.networkController), + 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), + // txController + approveTransaction: txController.approveTransaction.bind(txController), + cancelTransaction: txController.cancelTransaction.bind(txController), 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), } } @@ -344,9 +343,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) } } @@ -366,8 +363,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) }) } @@ -393,7 +389,7 @@ module.exports = class MetamaskController extends EventEmitter { .then((serialized) => { const seedWords = serialized.mnemonic this.configManager.setSeedWords(seedWords) - cb() + cb(null, seedWords) }) } @@ -425,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) @@ -444,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) => { @@ -464,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) => { @@ -479,11 +475,11 @@ 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) - txManager.approveTransaction(txMeta.id, cb) + const txController = this.txController + txController.updateTx(txMeta) + txController.approveTransaction(txMeta.id, cb) } signMessage (msgParams, cb) { @@ -505,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') { @@ -515,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) => { @@ -548,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') { @@ -562,42 +558,13 @@ 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) { + 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)) @@ -623,12 +590,6 @@ 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')}`) - } - this.verifyNetwork() - } setCurrentCurrency (currencyCode, cb) { try { @@ -647,91 +608,29 @@ 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 - } - - if (url) extension.tabs.create({ url }) + const network = this.networkController.getNetworkState() + const url = getBuyEthUrl({ network, address, amount }) + if (url) this.platform.openWindow({ url }) } 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') - extension.runtime.reload() - this.lookupNetwork() + this.networkController.setRpcTarget('http://localhost:8545') return Promise.resolve('http://localhost:8545') } setCustomRpc (rpcTarget, rpcList) { - this.configManager.setRpcTarget(rpcTarget) + this.networkController.setRpcTarget(rpcTarget) + return this.preferencesController.updateFrequentRpcList(rpcTarget) - .then(() => { - extension.runtime.reload() - this.lookupNetwork() - return Promise.resolve(rpcTarget) - }) - } - - setProviderType (type) { - this.configManager.setProviderType(type) - extension.runtime.reload() - this.lookupNetwork() - } - - useEtherscanProvider () { - this.configManager.useEtherscanProvider() - extension.runtime.reload() - } - - 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) { - this.setNetworkState('loading') - return - } - if (global.METAMASK_DEBUG) { - console.log('web3.getNetwork returned ' + network) - } - this.setNetworkState(network) + .then(() => { + return Promise.resolve(rpcTarget) }) } + } 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 new file mode 100644 index 000000000..f69ccbb02 --- /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) { + 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.NoticeController.noticesList.forEach((notice) => { + if (notice.read) { + notice.body = '' + } + }) + return newState +} 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/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/_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/migrations/index.js b/app/scripts/migrations/index.js index a3dd48c17..fb1ad7863 100644 --- a/app/scripts/migrations/index.js +++ b/app/scripts/migrations/index.js @@ -22,4 +22,7 @@ module.exports = [ require('./009'), require('./010'), require('./011'), + require('./012'), + require('./013'), + require('./014'), ] 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) diff --git a/app/scripts/platforms/extension.js b/app/scripts/platforms/extension.js new file mode 100644 index 000000000..00c2aa275 --- /dev/null +++ b/app/scripts/platforms/extension.js @@ -0,0 +1,23 @@ +const extension = require('extensionizer') + +class ExtensionPlatform { + + // + // Public + // + + reload () { + extension.runtime.reload() + } + + openWindow ({ url }) { + extension.tabs.create({ url }) + } + + getVersion () { + return extension.runtime.getManifest().version + } + +} + +module.exports = ExtensionPlatform 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/app/scripts/popup-core.js b/app/scripts/popup-core.js index b1e521a7a..f1eb394d7 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 EthQuery = require('eth-query') +const launchMetamaskUi = require('../../ui') const StreamProvider = require('web3-stream-provider') const setupMultiplex = require('./lib/stream-utils.js').setupMultiplex @@ -9,9 +10,12 @@ 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) { @@ -28,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) { @@ -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 62db68c10..5f17f0651 100644 --- a/app/scripts/popup.js +++ b/app/scripts/popup.js @@ -3,23 +3,47 @@ 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 ExtensionPlatform = require('./platforms/extension') +const NotificationManager = require('./lib/notification-manager') +const notificationManager = new NotificationManager() -var css = MetaMaskUiCss() +// create platform global +global.platform = new ExtensionPlatform() + +// 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) -var pluginPort = extension.runtime.connect({ name }) -var portStream = new PortStream(pluginPort) +// setup stream to background +const extensionPort = extension.runtime.connect({ name: windowType }) +const connectionStream = new PortStream(extensionPort) -startPopup(portStream) +// 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() + }) +}) -function closePopupIfOpen (name) { - if (name !== 'notification') { - notification.closePopup() + +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/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" 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) 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/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": {} +} 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/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 new file mode 100644 index 000000000..00369acf9 --- /dev/null +++ b/docs/publishing.md @@ -0,0 +1,19 @@ +# Publishing Guide + +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. +4. Post on Github releases page. +5. `npm run announce`, post that announcement in our public places. + 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. + diff --git a/gulpfile.js b/gulpfile.js index fe223adf1..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') @@ -182,7 +192,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}`))) @@ -296,8 +306,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 diff --git a/library/README.md b/library/README.md deleted file mode 100644 index 6a6574dbd..000000000 --- a/library/README.md +++ /dev/null @@ -1,24 +0,0 @@ -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 -- 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) diff --git a/library/controller.js b/library/controller.js deleted file mode 100644 index 5823287cc..000000000 --- a/library/controller.js +++ /dev/null @@ -1,159 +0,0 @@ -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/index.js b/library/index.js deleted file mode 100644 index b5f4f6637..000000000 --- a/library/index.js +++ /dev/null @@ -1,43 +0,0 @@ -const Web3 = require('web3') -const setupProvider = require('./lib/setup-provider.js') - -// -// setup web3 -// -var provider = setupProvider() -hijackProvider(provider) -var web3 = new Web3(provider) -web3.setProvider = function(){ - console.log('MetaMask - overrode web3.setProvider') -} -console.log('metamask lib hijacked provider') - -// -// export web3 -// - -global.web3 = web3 - -// -// ui stuff -// - -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') - console.log('opening window...') -}) - - -function hijackProvider(provider){ - var _super = provider.sendAsync.bind(provider) - provider.sendAsync = function(payload, cb){ - if (payload.method === 'eth_sendTransaction') { - console.log('saw send') - shouldPop = true - } - _super(payload, cb) - } -} diff --git a/library/popup.js b/library/popup.js deleted file mode 100644 index 667b13371..000000000 --- a/library/popup.js +++ /dev/null @@ -1,19 +0,0 @@ -const injectCss = require('inject-css') -const MetaMaskUiCss = require('../ui/css') -const startPopup = require('../app/scripts/popup-core') -const setupIframe = require('./lib/setup-iframe.js') - - -var css = MetaMaskUiCss() -injectCss(css) - -var name = 'popup' -window.METAMASK_UI_TYPE = name - -var iframeStream = setupIframe({ - zeroClientProvider: 'http://127.0.0.1:9001', - sandboxAttributes: ['allow-scripts', 'allow-popups', 'allow-same-origin'], - container: document.body, -}) - -startPopup(iframeStream) diff --git a/library/server.js b/library/server.js deleted file mode 100644 index 797ad8a77..000000000 --- a/library/server.js +++ /dev/null @@ -1,96 +0,0 @@ -const express = require('express') -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 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) -}) - -// 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/README.md b/mascara/README.md new file mode 100644 index 000000000..6e3bfe96b --- /dev/null +++ b/mascara/README.md @@ -0,0 +1,33 @@ +start the dual servers (dapp + mascara) +``` +npm run mascara +``` + +### First time use: + +- navigate to: http://localhost:9001 +- Create an Account +- 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 + + +### Deploy: + +Will build and deploy mascara via docker + +``` +docker-compose build && docker-compose stop && docker-compose up -d && docker-compose logs --tail 200 -f +``` \ No newline at end of file diff --git a/library/example/index.js b/mascara/example/app.js similarity index 88% rename from library/example/index.js rename to mascara/example/app.js index 4a107df6a..aae7ccd19 100644 --- a/library/example/index.js +++ b/mascara/example/app.js @@ -1,5 +1,5 @@ - window.addEventListener('load', web3Detect) +window.addEventListener('message', console.warn) function web3Detect() { if (global.web3) { @@ -13,10 +13,10 @@ 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]) primaryAccount = addresses[0] }) @@ -24,6 +24,7 @@ function startApp(){ document.querySelector('.action-button-1').addEventListener('click', function(){ console.log('saw click') console.log('sending tx') + primaryAccount web3.eth.sendTransaction({ from: primaryAccount, to: primaryAccount, @@ -53,4 +54,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/example/index.html b/mascara/example/app/index.html similarity index 81% rename from library/example/index.html rename to mascara/example/app/index.html index 47d6da34f..02323e5f9 100644 --- a/library/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/library/server/index.html b/mascara/proxy/index.html similarity index 90% rename from library/server/index.html rename to mascara/proxy/index.html index 2308dd98b..b83fc41af 100644 --- a/library/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/index.js b/mascara/server/index.js new file mode 100644 index 000000000..14e3fa18e --- /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(__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() + // 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/background.js b/mascara/src/background.js new file mode 100644 index 000000000..d9dbf593a --- /dev/null +++ b/mascara/src/background.js @@ -0,0 +1,155 @@ +global.window = global +const self = global +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 DbController = require('idb-global') + +const SwPlatform = require('../../app/scripts/platforms/sw') +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 STORAGE_KEY = 'metamask-config' +// const METAMASK_DEBUG = 'GULP_METAMASK_DEBUG' +const METAMASK_DEBUG = true +let popupIsOpen = false +let connectedClientCount = 0 + +const log = require('loglevel') +global.log = log +log.setDefaultLevel(METAMASK_DEBUG ? 'debug' : 'warn') + +self.addEventListener('install', function(event) { + event.waitUntil(self.skipWaiting()) +}) +self.addEventListener('activate', function(event) { + event.waitUntil(self.clients.claim()) +}) + +console.log('inside:open') + + +// // state persistence +let diskStore +const dbController = new DbController({ + key: STORAGE_KEY, +}) +loadStateFromPersistence() +.then((initState) => setupController(initState)) +.then(() => console.log('MetaMask initialization complete.')) +.catch((err) => console.error('WHILE SETTING UP:', 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((versionedData) => migrator.migrateData(versionedData)) + .then((versionedData) => { + dbController.put(versionedData) + return Promise.resolve(versionedData) + }) + .then((versionedData) => Promise.resolve(versionedData.data)) +} + +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, + showUnapprovedTx: noop, + // initial state + initState, + }) + global.metamaskController = controller + + controller.store.subscribe((state) => { + versionifyData(state) + .then((versionedData) => dbController.put(versionedData)) + .catch((err) => {console.error(err)}) + }) + function versionifyData(state) { + return dbController.get() + .then((rawData) => { + return Promise.resolve({ + data: state, + meta: rawData.meta, + })} + ) + } + + // + // connect to other contexts + // + + connectionListener.on('remote', (portStream, messageEvent) => { + console.log('REMOTE CONECTION FOUND***********') + connectedClientCount += 1 + connectRemote(portStream, messageEvent.data.context) + }) + + function connectRemote (connectionStream, context) { + var isMetaMaskInternalProcess = (context === 'popup') + if (isMetaMaskInternalProcess) { + // communication with popup + controller.setupTrustedCommunication(connectionStream, 'MetaMask') + popupIsOpen = true + } else { + // communication with page + setupUntrustedCommunication(connectionStream, context) + } + } + + 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() + +} + +function sendMessageToAllClients (message) { + self.clients.matchAll().then(function(clients) { + clients.forEach(function(client) { + client.postMessage(message) + }) + }) +} +function noop () {} diff --git a/library/lib/setup-iframe.js b/mascara/src/lib/setup-iframe.js similarity index 100% rename from library/lib/setup-iframe.js rename to mascara/src/lib/setup-iframe.js diff --git a/library/lib/setup-provider.js b/mascara/src/lib/setup-provider.js similarity index 76% rename from library/lib/setup-provider.js rename to mascara/src/lib/setup-provider.js index 9efd209cb..62335b18d 100644 --- a/library/lib/setup-provider.js +++ b/mascara/src/lib/setup-provider.js @@ -1,19 +1,17 @@ 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 -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://127.0.0.1:9001', + zeroClientProvider: opts.mascaraUrl, sandboxAttributes: ['allow-scripts', 'allow-popups', 'allow-same-origin'], container: document.body, }) @@ -22,4 +20,3 @@ function getProvider(){ return inpageProvider } - diff --git a/mascara/src/mascara.js b/mascara/src/mascara.js new file mode 100644 index 000000000..1655d1f64 --- /dev/null +++ b/mascara/src/mascara.js @@ -0,0 +1,47 @@ +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) + +// +// setup web3 +// + +const provider = setupProvider({ + mascaraUrl: MASCARA_ORIGIN + '/proxy/', +}) +instrumentForUserInteractionTriggers(provider) + +const web3 = new Web3(provider) +setupDappAutoReload(web3, provider.publicConfigStore) +// +// ui stuff +// + +let shouldPop = false +window.addEventListener('click', maybeTriggerPopup) + +// +// util +// + +function maybeTriggerPopup(){ + if (!shouldPop) return + shouldPop = false + window.open(MASCARA_ORIGIN, '', 'width=360 height=500') + console.log('opening window...') +} + +function instrumentForUserInteractionTriggers(provider){ + const _super = provider.sendAsync.bind(provider) + provider.sendAsync = function(payload, cb){ + if (payload.method === 'eth_sendTransaction') { + console.log('saw send') + shouldPop = true + } + _super(payload, cb) + } +} + + diff --git a/mascara/src/proxy.js b/mascara/src/proxy.js new file mode 100644 index 000000000..eabc547b4 --- /dev/null +++ b/mascara/src/proxy.js @@ -0,0 +1,26 @@ +const ParentStream = require('iframe-stream').ParentStream +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() * (30000 - 1000)) + 1000 +const background = new SWcontroller({ + fileName: '/background.js', + letBeIdle: false, + wakeUpInterval: 30000, + intervalDelay, +}) + +const pageStream = new ParentStream() +background.on('ready', (_) => { + let swStream = SwStream({ + serviceWorker: background.controller, + context: 'dapp', + }) + 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 new file mode 100644 index 000000000..5f9be542f --- /dev/null +++ b/mascara/src/ui.js @@ -0,0 +1,56 @@ +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 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') + +var name = 'popup' +window.METAMASK_UI_TYPE = name + +let intervalDelay = Math.floor(Math.random() * (30000 - 1000)) + 1000 + +const background = new SWcontroller({ + fileName: '/background.js', + letBeIdle: false, + intervalDelay, + wakeUpInterval: 20000 +}) +// Setup listener for when the service worker is read +const connectApp = function (readSw) { + 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() + }) + }) +} +background.on('ready', (sw) => { + background.removeListener('updatefound', connectApp) + connectApp(sw) +}) +background.on('updatefound', () => window.location.reload()) + +background.startWorker() +.then(() => { + setTimeout(() => { + const appContent = document.getElementById(`app-content`) + if (!appContent.children.length) window.location.reload() + }, 2000) +}) +console.log('hello from MetaMascara ui!') 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..6495c2cfc --- /dev/null +++ b/mascara/test/index.html @@ -0,0 +1,21 @@ + + + + + + QUnit Example + + + +
+
+ + + + + + +
+ + + diff --git a/mascara/test/index.js b/mascara/test/index.js new file mode 100644 index 000000000..0134cdf00 --- /dev/null +++ b/mascara/test/index.js @@ -0,0 +1,22 @@ +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) { + console.error(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(" + + \ No newline at end of file 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 d00491fd7..e440e5c84 100644 --- a/notices/notice-nonce.json +++ b/notices/notice-nonce.json @@ -1 +1 @@ -1 +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 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 + } + } +} diff --git a/package.json b/package.json index 96d58fa1b..341af49a7 100644 --- a/package.json +++ b/package.json @@ -5,23 +5,28 @@ "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\"", + "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", + "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" + "deleteNotice": "node notices/notice-delete.js", + "mascara": "node ./mascara/example/server", + "buildMascaraCi": "browserify mascara/test/window-load.js -o mascara/test/bundle.js", + "buildMascaraSWCi": "browserify mascara/src/background.js -o mascara/test/background.js", + "mascaraCi": "npm run buildMascaraCi && npm run buildMascaraSWCi && node mascara/test/index.js", + "testMascara": "cd mascara/test && npm run mascaraCi && testem ci -P 3" }, "browserify": { "transform": [ @@ -29,43 +34,49 @@ "babelify", { "presets": [ - "es2015" + "es2015", + "stage-0" ] } ], + "envify", "brfs" ] }, "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", "browserify-derequire": "^0.9.4", + "client-sw-ready-event": "^3.3.0", "clone": "^1.0.2", "copy-to-clipboard": "^2.0.0", "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", "ensnare": "^1.0.0", "eth-bin-to-ops": "^1.0.1", + "eth-contract-metadata": "^1.0.0", "eth-hd-keyring": "^1.1.1", - "eth-lightwallet": "^2.3.3", - "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.0.0", + "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", "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", @@ -77,6 +88,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", @@ -99,23 +111,26 @@ "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", "semaphore": "^1.0.5", + "sw-stream": "^2.0.0", "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": "^12.2.3", "web3-stream-provider": "^2.0.6", "xtend": "^4.0.1" }, "devDependencies": { "babel-eslint": "^6.0.5", + "babel-plugin-transform-runtime": "^6.23.0", + "babel-preset-stage-0": "^6.24.1", "babel-register": "^6.7.2", "babelify": "^7.2.0", "beefy": "^2.1.5", @@ -125,6 +140,10 @@ "clone": "^1.0.2", "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", "gulp": "github:gulpjs/gulp#4.0", "gulp-if": "^2.0.1", @@ -149,6 +168,10 @@ "prompt": "^1.0.0", "qs": "^6.2.0", "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", "testem": "^1.10.3", diff --git a/test/helper.js b/test/helper.js index a01ea1e53..1c5934a89 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') + + // modified from https://github.com/mochajs/mocha/issues/1926#issuecomment-180842722 + + // 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!') + } +} 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/integration/lib/first-time.js b/test/integration/lib/first-time.js index dbb88a3da..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() === 'Continue') { + if (button.html() === 'Accept') { let termsPage = app.find('.markdown')[0] termsPage.scrollTop = termsPage.scrollHeight return wait().then(() => { 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/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 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/lib/mock-config-manager.js b/test/lib/mock-config-manager.js index 72be86ed1..0cc6953bb 100644 --- a/test/lib/mock-config-manager.js +++ b/test/lib/mock-config-manager.js @@ -2,9 +2,8 @@ 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() { - 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/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/account-link-test.js b/test/unit/account-link-test.js index 4ea12e002..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 morden prefix to morden test network', function() { - var result = linkGen('account', '2') - assert.notEqual(result.indexOf('morden'), -1, 'testnet included') - assert.notEqual(result.indexOf('account'), -1, 'account included') - }) - - it('adds testnet 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('testnet'), -1, 'testnet included') + 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 () { + 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() { +describe('tx confirmation screen', function () { + beforeEach(function () { + this.sinon = sinon.sandbox.create() + }) - 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..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 = { @@ -7,21 +6,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/components/bn-as-decimal-input-test.js b/test/unit/components/bn-as-decimal-input-test.js new file mode 100644 index 000000000..b3365b6f9 --- /dev/null +++ b/test/unit/components/bn-as-decimal-input-test.js @@ -0,0 +1,51 @@ +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('BnInput', function () { + it('can tolerate a gas decimal number at a high precision', function (done) { + const renderer = ReactTestUtils.createRenderer() + + let valueStr = '20' + while (valueStr.length < 20) { + valueStr += '0' + } + const value = new BN(valueStr, 10) + + let inputStr = '2.3' + + let targetStr = '23' + while (targetStr.length < 19) { + targetStr += '0' + } + const target = new BN(targetStr, 10) + + const precision = 18 // ether precision + const scale = 18 + + const props = { + value, + scale, + precision, + onChange: (newBn) => { + assert.equal(newBn.toString(), target.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 new file mode 100644 index 000000000..52e5e5910 --- /dev/null +++ b/test/unit/components/pending-tx-test.js @@ -0,0 +1,80 @@ +const assert = require('assert') +const additions = require('react-testutils-additions') +const h = require('react-hyperscript') +const PendingTx = require('../../../ui/app/components/pending-tx') +const ReactTestUtils = require('react-addons-test-utils') +const ethUtil = require('ethereumjs-util') + +describe('PendingTx', function () { + 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 renderer = ReactTestUtils.createRenderer() + const newGasPrice = '0x77359400' + + const props = { + identities, + 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.') + done() + }, + } + + const pendingTxComponent = h(PendingTx, props) + const component = additions.renderIntoDocument(pendingTxComponent) + renderer.render(pendingTxComponent) + const result = renderer.getRenderOutput() + assert.equal(result.type, 'div', 'should create a div') + + try { + const input = additions.find(component, '.cell.row input[type="number"]')[1] + ReactTestUtils.Simulate.change(input, { + target: { + value: 2, + checkValidity() { return true }, + }, + }) + + const 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') + console.error(e) + } + }) +}) + diff --git a/test/unit/config-manager-test.js b/test/unit/config-manager-test.js index 05324e741..b710e2dfb 100644 --- a/test/unit/config-manager-test.js +++ b/test/unit/config-manager-test.js @@ -2,26 +2,22 @@ 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() { +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 +26,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 +54,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 +75,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 +90,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..5eeaf9bcc 100644 --- a/test/unit/currency-controller-test.js +++ b/test/unit/currency-controller-test.js @@ -2,26 +2,23 @@ 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') -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 +26,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://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":""}') 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://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":""}') @@ -69,19 +65,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 8aa58bff9..e672b36ed 100644 --- a/test/unit/explorer-link-test.js +++ b/test/unit/explorer-link-test.js @@ -1,11 +1,15 @@ var assert = require('assert') var linkGen = require('../../ui/lib/explorer-link') -describe('explorer-link', function() { - - it('adds testnet prefix to morden 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('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') }) }) 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/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() - }) - }) - }) -}) diff --git a/test/unit/keyring-controller-test.js b/test/unit/keyring-controller-test.js index aae4cdfd6..2d9a53723 100644 --- a/test/unit/keyring-controller-test.js +++ b/test/unit/keyring-controller-test.js @@ -3,21 +3,18 @@ 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() { +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 +22,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 +35,7 @@ describe('KeyringController', function() { keyringController.createNewVaultAndKeychain(password) .then(function (newState) { - state = newState + newState done() }) .catch((err) => { @@ -46,7 +43,7 @@ describe('KeyringController', function() { }) }) - afterEach(function() { + afterEach(function () { // Cleanup mocks this.sinon.restore() }) @@ -54,7 +51,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 +66,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 +96,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 +106,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 +130,30 @@ describe('KeyringController', function() { }) }) - describe('#getAccounts', function() { - it('returns the result of getAccounts for each keyring', function() { + 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..30cb4f067 100644 --- a/test/unit/message-manager-test.js +++ b/test/unit/message-manager-test.js @@ -1,29 +1,26 @@ const assert = require('assert') -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 +30,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 +42,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 +54,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 +64,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..5ee0a6c84 100644 --- a/test/unit/metamask-controller-test.js +++ b/test/unit/metamask-controller-test.js @@ -4,11 +4,9 @@ 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() { +describe('MetaMaskController', function () { const noop = () => {} - let controller = new MetaMaskController({ + const metamaskController = new MetaMaskController({ showUnconfirmedMessage: noop, unlockAccountMessage: noop, showUnapprovedTx: noop, @@ -16,14 +14,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 d2a83be77..5bad25a45 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')) @@ -15,13 +15,15 @@ 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 migration13 = require(path.join('..', '..', 'app', 'scripts', 'migrations', '013')) + const oldTestRpc = 'https://rawtestrpc.metamask.io/' 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) @@ -91,7 +93,16 @@ 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.') + + 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.') + }) }) }) 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) + }) +}) 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/network-contoller-test.js b/test/unit/network-contoller-test.js new file mode 100644 index 000000000..76452b303 --- /dev/null +++ b/test/unit/network-contoller-test.js @@ -0,0 +1,77 @@ +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 Proxy({}, { + get: (obj, name) => { + return () => {} + }, + }) + 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/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 73fdb2f2e..09eeda15c 100644 --- a/test/unit/notice-controller-test.js +++ b/test/unit/notice-controller-test.js @@ -1,116 +1,114 @@ 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() { +describe('notice-controller', function () { var noticeController - beforeEach(function() { + beforeEach(function () { // simple localStorage polyfill - window.localStorage = {} - if (window.localStorage.clear) window.localStorage.clear() - 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() { - 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() { - it('should set data appropriately', 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 assert.equal(testListId, 0) + done() }) }) - describe('#updateNoticeslist', function() { - it('should integrate the latest changes from the source', function() { + 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(() => { 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" + 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, - title:"Futuristic Notice" + id: 0, + read: false, + title: 'Futuristic Notice', }] noticeController.setNoticesList(testList) 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"}, - {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() 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"}, - {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() assert.ok(!latestUnread) + done() }) }) }) - }) diff --git a/test/unit/personal-message-manager-test.js b/test/unit/personal-message-manager-test.js index f2c01392c..ec2f9a4d1 100644 --- a/test/unit/personal-message-manager-test.js +++ b/test/unit/personal-message-manager-test.js @@ -1,29 +1,27 @@ const assert = require('assert') -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 +31,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 +43,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 +55,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 +65,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 +85,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-controller-test.js b/test/unit/tx-controller-test.js new file mode 100644 index 000000000..711e1ea79 --- /dev/null +++ b/test/unit/tx-controller-test.js @@ -0,0 +1,324 @@ +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') +const TransactionController = require('../../app/scripts/controllers/transactions') +const noop = () => true +const currentNetworkId = 42 +const otherNetworkId = 36 +const privKey = new Buffer('8718b9618a37d1fc78c436511fc6df3c8258d3250635bba617f33003270ec03e', 'hex') + +describe('Transaction Controller', function () { + let txController + + beforeEach(function () { + txController = new TransactionController({ + networkStore: new ObservableStore(currentNetworkId), + txHistoryLimit: 10, + blockTracker: new EventEmitter(), + ethQuery: new EthQuery(new EventEmitter()), + signTransaction: (ethTx) => new Promise((resolve) => { + ethTx.sign(privKey) + resolve() + }), + }) + }) + + describe('#validateTxParams', function () { + it('returns null for positive values', function () { + var sample = { + value: '0x01', + } + txController.txProviderUtils.validateTxParams(sample, (err) => { + assert.equal(err, null, 'no error') + }) + }) + + it('returns error for negative values', function () { + var sample = { + value: '-0x01', + } + txController.txProviderUtils.validateTxParams(sample, (err) => { + assert.ok(err, 'error') + }) + }) + }) + + describe('#getTxList', function () { + it('when new should return empty array', function () { + var result = txController.getTxList() + assert.ok(Array.isArray(result)) + assert.equal(result.length, 0) + }) + it('should also return transactions from local storage if any', function () { + + }) + }) + + describe('#addTx', function () { + it('adds a tx returned in getTxList', function () { + var tx = { id: 1, status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} } + txController.addTx(tx, noop) + var result = txController.getTxList() + assert.ok(Array.isArray(result)) + assert.equal(result.length, 1) + assert.equal(result[0].id, 1) + }) + + 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: {} } + 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 = txController.txHistoryLimit + for (let i = 0; i < limit + 1; i++) { + const tx = { id: i, time: new Date(), status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} } + txController.addTx(tx, noop) + } + 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 = txController.txHistoryLimit + for (let i = 0; i < limit + 1; i++) { + const tx = { id: i, time: new Date(), status: 'rejected', metamaskNetworkId: currentNetworkId, txParams: {} } + txController.addTx(tx, noop) + } + 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: {} } + 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: {} } + txController.addTx(tx, noop) + } + 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') + assert.equal(result[1].id, 2, 'early txs truncted') + }) + }) + + describe('#setTxStatusSigned', function () { + it('sets the tx status to signed', function () { + var tx = { id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} } + 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') + }) + + 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: {} } + const noop = function () { + assert(true, 'event listener has been triggered and noop executed') + done() + } + 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: {} } + 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') + }) + + 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: {} } + txController.addTx(tx) + const noop = function (err, txId) { + assert(true, 'event listener has been triggered and noop executed') + done() + } + txController.on('1:rejected', noop) + txController.setTxStatusRejected(1) + }) + }) + + describe('#updateTx', function () { + it('replaces the tx with the same id', function () { + 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') + }) + + it('updates gas price', function () { + const originalGasPrice = '0x01' + const desiredGasPrice = '0x02' + + const txMeta = { + id: '1', + status: 'unapproved', + metamaskNetworkId: currentNetworkId, + txParams: { + gasPrice: originalGasPrice, + }, + } + + const updatedMeta = clone(txMeta) + + txController.addTx(txMeta) + updatedMeta.txParams.gasPrice = desiredGasPrice + txController.updateTx(updatedMeta) + var result = txController.getTx('1') + assert.equal(result.txParams.gasPrice, desiredGasPrice, 'gas price updated') + }) + }) + + describe('#getUnapprovedTxList', function () { + it('returns unapproved txs in a hash', function () { + 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) + }) + }) + + describe('#getTx', function () { + it('returns a tx with the requested id', function () { + 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') + }) + }) + + 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 }, + ] + txMetas.forEach((txMeta) => txController.addTx(txMeta, noop)) + let filterParams + + filterParams = { status: 'unapproved', from: '0xaa' } + assert.equal(txController.getFilteredTxList(filterParams).length, 3, `getFilteredTxList - ${JSON.stringify(filterParams)}`) + filterParams = { status: 'unapproved', to: '0xaa' } + assert.equal(txController.getFilteredTxList(filterParams).length, 2, `getFilteredTxList - ${JSON.stringify(filterParams)}`) + filterParams = { status: 'confirmed', from: '0xbb' } + assert.equal(txController.getFilteredTxList(filterParams).length, 3, `getFilteredTxList - ${JSON.stringify(filterParams)}`) + filterParams = { status: 'confirmed' } + assert.equal(txController.getFilteredTxList(filterParams).length, 5, `getFilteredTxList - ${JSON.stringify(filterParams)}`) + filterParams = { from: '0xaa' } + assert.equal(txController.getFilteredTxList(filterParams).length, 5, `getFilteredTxList - ${JSON.stringify(filterParams)}`) + filterParams = { to: '0xaa' } + assert.equal(txController.getFilteredTxList(filterParams).length, 5, `getFilteredTxList - ${JSON.stringify(filterParams)}`) + }) + }) + + 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) + 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) + }) + }) + }) + +}) diff --git a/test/unit/tx-manager-test.js b/test/unit/tx-manager-test.js deleted file mode 100644 index f64f048e3..000000000 --- a/test/unit/tx-manager-test.js +++ /dev/null @@ -1,211 +0,0 @@ -const assert = require('assert') -const extend = require('xtend') -const EventEmitter = require('events') -const ObservableStore = require('obs-store') -const STORAGE_KEY = 'metamask-persistance-key' -const TransactionManager = require('../../app/scripts/transaction-manager') -const noop = () => true - -describe('Transaction Manager', function() { - let txManager - - beforeEach(function() { - txManager = new TransactionManager({ - networkStore: new ObservableStore({ network: 'unit test' }), - txHistoryLimit: 10, - blockTracker: new EventEmitter(), - }) - }) - - describe('#validateTxParams', function () { - it('returns null for positive values', function() { - var sample = { - value: '0x01' - } - var res = txManager.txProviderUtils.validateTxParams(sample, (err) => { - assert.equal(err, null, 'no error') - }) - }) - - - it('returns error for negative values', function() { - var sample = { - value: '-0x01' - } - var res = txManager.txProviderUtils.validateTxParams(sample, (err) => { - assert.ok(err, 'error') - }) - }) - }) - - 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() { - - }) - }) - - describe('#addTx', function() { - it('adds a tx returned in getTxList', function() { - var tx = { id: 1, status: 'confirmed', metamaskNetworkId: 'unit test', txParams: {} } - txManager.addTx(tx, noop) - var result = txManager.getTxList() - assert.ok(Array.isArray(result)) - assert.equal(result.length, 1) - assert.equal(result[0].id, 1) - }) - - 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: {} } - txManager.addTx(tx, noop) - } - var result = txManager.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 - for (let i = 0; i < limit + 1; i++) { - let tx = { id: i, time: new Date(), status: 'rejected', metamaskNetworkId: 'unit test', txParams: {} } - txManager.addTx(tx, noop) - } - var result = txManager.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: 'unit test', 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: {} } - txManager.addTx(tx, noop) - } - var result = txManager.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') - assert.equal(result[1].id, 2, 'early txs truncted') - }) - }) - - describe('#setTxStatusSigned', function() { - it('sets the tx status to signed', function() { - var tx = { id: 1, status: 'unapproved', metamaskNetworkId: 'unit test', txParams: {} } - txManager.addTx(tx, noop) - txManager.setTxStatusSigned(1) - var result = txManager.getTxList() - assert.ok(Array.isArray(result)) - assert.equal(result.length, 1) - assert.equal(result[0].status, 'signed') - }) - - 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: {} } - let noop = function () { - assert(true, 'event listener has been triggered and noop executed') - done() - } - txManager.addTx(tx) - txManager.on('1:signed', noop) - txManager.setTxStatusSigned(1) - }) - }) - - describe('#setTxStatusRejected', function() { - it('sets the tx status to rejected', function() { - var tx = { id: 1, status: 'unapproved', metamaskNetworkId: 'unit test', txParams: {} } - txManager.addTx(tx) - txManager.setTxStatusRejected(1) - var result = txManager.getTxList() - assert.ok(Array.isArray(result)) - assert.equal(result.length, 1) - assert.equal(result[0].status, 'rejected') - }) - - 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: {} } - txManager.addTx(tx) - let 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() { - 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: {} }) - var result = txManager.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: 'unit test', txParams: {} }, noop) - txManager.addTx({ id: '2', status: 'confirmed', metamaskNetworkId: 'unit test', txParams: {} }, noop) - let 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() { - txManager.addTx({ id: '1', status: 'unapproved', metamaskNetworkId: 'unit test', txParams: {} }, noop) - txManager.addTx({ id: '2', status: 'confirmed', metamaskNetworkId: 'unit test', txParams: {} }, noop) - assert.equal(txManager.getTx('1').status, 'unapproved') - assert.equal(txManager.getTx('2').status, 'confirmed') - }) - }) - - 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' }, - ] - txMetas.forEach((txMeta) => txManager.addTx(txMeta, noop)) - let filterParams - - filterParams = { status: 'unapproved', from: '0xaa' } - assert.equal(txManager.getFilteredTxList(filterParams).length, 3, `getFilteredTxList - ${JSON.stringify(filterParams)}`) - filterParams = { status: 'unapproved', to: '0xaa' } - assert.equal(txManager.getFilteredTxList(filterParams).length, 2, `getFilteredTxList - ${JSON.stringify(filterParams)}`) - filterParams = { status: 'confirmed', from: '0xbb' } - assert.equal(txManager.getFilteredTxList(filterParams).length, 3, `getFilteredTxList - ${JSON.stringify(filterParams)}`) - filterParams = { status: 'confirmed' } - assert.equal(txManager.getFilteredTxList(filterParams).length, 5, `getFilteredTxList - ${JSON.stringify(filterParams)}`) - filterParams = { from: '0xaa' } - assert.equal(txManager.getFilteredTxList(filterParams).length, 5, `getFilteredTxList - ${JSON.stringify(filterParams)}`) - filterParams = { to: '0xaa' } - assert.equal(txManager.getFilteredTxList(filterParams).length, 5, `getFilteredTxList - ${JSON.stringify(filterParams)}`) - }) - }) - -}) diff --git a/test/unit/tx-utils-test.js b/test/unit/tx-utils-test.js index e57b25e83..7ace1f587 100644 --- a/test/unit/tx-utils-test.js +++ b/test/unit/tx-utils-test.js @@ -5,17 +5,38 @@ const BN = ethUtil.BN const TxUtils = require('../../app/scripts/lib/tx-utils') -describe('txUtils', function() { +describe('txUtils', function () { let txUtils - before(function() { - txUtils = new TxUtils() + before(function () { + txUtils = new TxUtils(new Proxy({}, { + get: (obj, name) => { + return () => {} + }, + })) }) - describe('addGasBuffer', function() { - it('multiplies by 1.5, when within block gas limit', function() { - // naive estimatedGas: 0x123fad (~1.2 mil) - const inputHex = '0x123fad' + 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) + const inputHex = '0x16e360' // dummy gas limit: 0x3d4c52 (4 mil) const blockGasLimitHex = '0x3d4c52' const output = txUtils.addGasBuffer(inputHex, blockGasLimitHex) @@ -24,35 +45,41 @@ 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() { - // naive estimatedGas: 0x123fad (~1.2 mil) - const inputHex = '0x123fad' + + 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 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') }) }) }) // util -function hexToBn(inputHex) { +function hexToBn (inputHex) { return new BN(ethUtil.stripHexPrefix(inputHex), 16) -} \ No newline at end of file +} + +function bnToHex (inputBn) { + return ethUtil.addHexPrefix(inputBn.toString(16)) +} 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) }) - }) }) }) diff --git a/ui/app/account-detail.js b/ui/app/account-detail.js index 514be7f25..7a78a360c 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) { @@ -30,6 +29,8 @@ function mapStateToProps (state) { unapprovedMsgs: valuesFor(state.metamask.unapprovedMsgs), 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 } = props + const { network, conversionRate, currentCurrency } = props return ( @@ -60,7 +61,7 @@ AccountDetailScreen.prototype.render = function () { // header - identicon + nav h('div', { style: { - marginTop: '20px', + paddingTop: '20px', display: 'flex', justifyContent: 'flex-start', alignItems: 'flex-start', @@ -183,6 +184,8 @@ AccountDetailScreen.prototype.render = function () { h(EthBalance, { value: account && account.balance, + conversionRate, + currentCurrency, style: { lineHeight: '7px', marginTop: '10px', @@ -238,19 +241,19 @@ 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() } } 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) => { @@ -262,15 +265,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)) - } -} diff --git a/ui/app/accounts/account-list-item.js b/ui/app/accounts/account-list-item.js index 2a3c13d05..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 } = 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,8 @@ AccountListItem.prototype.render = function () { }, checksumAddress), h(EthBalance, { value: account && account.balance, + currentCurrency, + conversionRate, style: { lineHeight: '7px', marginTop: '10px', 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/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 ])) } - diff --git a/ui/app/accounts/index.js b/ui/app/accounts/index.js index e236a4e85..ac2615cd7 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) @@ -23,6 +23,8 @@ function mapStateToProps (state) { scrollToBottom: state.appState.scrollToBottom, pending, keyrings: state.metamask.keyrings, + conversionRate: state.metamask.conversionRate, + currentCurrency: state.metamask.currentCurrency, } } @@ -33,7 +35,7 @@ function AccountsScreen () { AccountsScreen.prototype.render = function () { const props = this.props - const { keyrings } = props + const { keyrings, conversionRate, currentCurrency } = props const identityList = valuesFor(props.identities) const unapprovedTxList = valuesFor(props.unapprovedTxs) @@ -81,6 +83,8 @@ AccountsScreen.prototype.render = function () { key: `acct-panel-${identity.address}`, identity, selectedAddress: this.props.selectedAddress, + conversionRate, + currentCurrency, accounts: this.props.accounts, onShowDetail: this.onShowDetail.bind(this), pending, @@ -97,7 +101,7 @@ AccountsScreen.prototype.render = function () { style: { display: 'flex', height: '40px', - paddint: '10px', + padding: '10px', justifyContent: 'center', alignItems: 'center', }, diff --git a/ui/app/actions.js b/ui/app/actions.js index b09021577..1a3557cb4 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, @@ -132,10 +134,6 @@ var actions = { buyEth: buyEth, buyEthView: buyEthView, 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', @@ -269,11 +267,14 @@ function requestRevealSeed (password) { dispatch(actions.showLoadingIndication()) log.debug(`background.submitPassword`) background.submitPassword(password, (err) => { - if (err) return dispatch(actions.displayWarning(err.message)) + if (err) { + 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)) }) }) } @@ -296,10 +297,10 @@ 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) => { + dispatch(actions.hideLoadingIndication()) if (err) { return dispatch(actions.displayWarning(err.message)) } @@ -313,7 +314,7 @@ function importNewAccount (strategy, args) { } } -function navigateToNewAccountScreen() { +function navigateToNewAccountScreen () { return { type: this.NEW_ACCOUNT_SCREEN, } @@ -392,11 +393,10 @@ 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()) - dispatch(actions.goHome()) }) dispatch(this.showConfTxPage()) } @@ -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) @@ -664,7 +665,7 @@ function clearNotices () { } } -function markAccountsFound() { +function markAccountsFound () { log.debug(`background.markAccountsFound`) return callBackgroundThenUpdate(background.markAccountsFound) } @@ -698,7 +699,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 +773,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)) + }) }) } } @@ -822,10 +831,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, }) @@ -839,20 +848,6 @@ function buyEthView (address) { } } -function updateCoinBaseAmount (value) { - return { - type: actions.UPDATE_COINBASE_AMOUNT, - value, - } -} - -function updateBuyAddress (value) { - return { - type: actions.UPDATE_BUY_ADDRESS, - value, - } -} - function coinBaseSubview () { return { type: actions.COINBASE_SUBVIEW, @@ -983,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 9c1ba8a3a..53dbc3354 100644 --- a/ui/app/app.js +++ b/ui/app/app.js @@ -249,12 +249,30 @@ 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, }), + 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: 'Rinkeby Test Network', + closeMenu: () => this.setState({ isNetworkMenuOpen: false}), + action: () => props.dispatch(actions.setProviderType('rinkeby')), + icon: h('.menu-icon.golden-square'), + activeNetworkRender: props.network, + provider: props.provider, + }), + h(DropMenuItem, { label: 'Localhost 8545', closeMenu: () => this.setState({ isNetworkMenuOpen: false }), @@ -323,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'), @@ -543,5 +561,4 @@ App.prototype.renderCommonRpc = function (rpcList, provider) { }) } }) - } diff --git a/ui/app/components/account-export.js b/ui/app/components/account-export.js index 6d8b099a5..888196c5d 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,58 @@ 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', { + type: 'password', + 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 +119,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/components/account-info-link.js b/ui/app/components/account-info-link.js index 49c42e9ec..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('../../../app/scripts/lib/extension') 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/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/bn-as-decimal-input.js b/ui/app/components/bn-as-decimal-input.js new file mode 100644 index 000000000..f3ace4720 --- /dev/null +++ b/ui/app/components/bn-as-decimal-input.js @@ -0,0 +1,174 @@ +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 bn string value. + * + * On change, calls back its `onChange` function parameter + * and passes it an updated bn string. + */ + +BnAsDecimalInput.prototype.render = function () { + const props = this.props + const state = this.state + + const { value, scale, precision, onChange, min, max } = props + + const suffix = props.suffix + const style = props.style + const valueString = value.toString(10) + const newValue = this.downsize(valueString, scale, 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, + 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 = this.upsize(value, scale, precision) + const precisionBN = new BN(scaledNumber, 10) + onChange(precisionBN, event.target.checkValidity()) + }, + 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 +} + + +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)) + } +} + +BnAsDecimalInput.prototype.upsize = function (number, scale, precision) { + var stringArray = number.toString().split('.') + var decimalLength = stringArray[1] ? stringArray[1].length : 0 + 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' + } + return newString +} diff --git a/ui/app/components/buy-button-subview.js b/ui/app/components/buy-button-subview.js index 3074bd7cd..87084f92d 100644 --- a/ui/app/components/buy-button-subview.js +++ b/ui/app/components/buy-button-subview.js @@ -5,14 +5,16 @@ 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 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, @@ -32,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: { @@ -47,65 +53,87 @@ BuyButtonSubview.prototype.render = function () { left: '10px', }, }), - h('h2.page-subtitle', 'Buy Eth'), + h('h2.text-transform-uppercase.flex-center', { + style: { + width: '100vw', + background: 'rgb(235, 235, 235)', + color: 'rgb(174, 174, 174)', + paddingTop: '4px', + paddingBottom: '4px', + }, + }, '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', - }, - { - 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', - }, - ], - defaultTab: 'coinbase', - tabSelected: (key) => { - switch (key) { - case 'coinbase': - props.dispatch(actions.coinBaseSubview()) - break - case 'shapeshift': - props.dispatch(actions.shapeShiftSubview(props.provider.type)) - break - } + 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, + labels: [ + 'Coinbase', + 'ShapeShift', + ], + subtext: { + 'Coinbase': 'Crypto/FIAT (USA only)', + 'ShapeShift': 'Crypto', + }, + 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(), ]) ) } 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) { @@ -121,21 +149,34 @@ 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') ? h('button.text-transform-uppercase', { - onClick: () => this.props.dispatch(actions.buyEth()), + ((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', }, - }, 'Go To Test Faucet') : null, + }, '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: { + marginTop: '15px', + }, + }, 'Kovan Test Faucet') : null, ]) } } BuyButtonSubview.prototype.navigateTo = function (url) { - extension.tabs.create({ url }) + global.platform.openWindow({ url }) } BuyButtonSubview.prototype.backButtonContext = function () { @@ -145,3 +186,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 40f5719bb..f44d86045 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,105 +20,36 @@ 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', + marginTop: '35px', 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', { + 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'), ]), ]) } -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 () { - 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(address, 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)) - } +CoinbaseForm.prototype.toCoinbase = function () { + const props = this.props + const address = props.buyView.buyAddress + props.dispatch(actions.buyEth({ network: '1', address, amount: 0 })) } CoinbaseForm.prototype.renderLoading = function () { @@ -131,29 +61,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/copyable.js b/ui/app/components/copyable.js new file mode 100644 index 000000000..a4f6f4bc6 --- /dev/null +++ b/ui/app/components/copyable.js @@ -0,0 +1,46 @@ +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', + }, 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/custom-radio-list.js b/ui/app/components/custom-radio-list.js new file mode 100644 index 000000000..a4c525396 --- /dev/null +++ b/ui/app/components/custom-radio-list.js @@ -0,0 +1,60 @@ +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 + const activeClass = '.custom-radio-selected' + const inactiveClass = '.custom-radio-inactive' + const { + labels, + defaultFocus, + } = props + + + return ( + h('.flex-row', { + style: { + fontSize: '12px', + }, + }, [ + h('.flex-column.custom-radios', { + style: { + marginRight: '5px', + }, + }, + labels.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', {}, + labels.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) + } + }) + ), + ]) + ) +} + diff --git a/ui/app/components/drop-menu-item.js b/ui/app/components/drop-menu-item.js index 9f002234e..e42948209 100644 --- a/ui/app/components/drop-menu-item.js +++ b/ui/app/components/drop-menu-item.js @@ -42,7 +42,13 @@ DropMenuItem.prototype.activeNetworkRender = function () { if (providerType === 'mainnet') return h('.check', '✓') break case 'Ropsten Test Network': - if (provider.type === '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', '✓') break case 'Localhost 8545': if (activeNetwork === 'http://localhost:8545') return h('.check', '✓') diff --git a/ui/app/components/ens-input.js b/ui/app/components/ens-input.js index facf29d97..43bb7ab22 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 @@ -23,9 +21,10 @@ EnsInput.prototype.render = function () { const opts = extend(props, { list: 'addresses', onChange: () => { + this.setState({ ensResolution: '0x0000000000000000000000000000000000000000' }) const network = this.props.network - let 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) { @@ -52,7 +51,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, @@ -63,6 +62,7 @@ EnsInput.prototype.render = function () { return h('option', { value: identity.address, label: identity.name, + key: identity.address, }) }), ]), @@ -72,10 +72,10 @@ EnsInput.prototype.render = function () { EnsInput.prototype.componentDidMount = function () { const network = this.props.network - let resolverAddress = networkResolvers[network] + const networkHasEnsSupport = getNetworkEnsSupport(network) - if (resolverAddress) { - const provider = web3.currentProvider + if (networkHasEnsSupport) { + const provider = global.ethereumProvider this.ens = new ENS({ provider, network }) this.checkName = debounce(this.lookupEnsName.bind(this), 200) } @@ -96,12 +96,14 @@ 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, ensResolution: address, nickname: recipient.trim(), hoverText: address + '\nClick to Copy', + ensFailure: false, }) } }) @@ -109,6 +111,7 @@ EnsInput.prototype.lookupEnsName = function () { log.error(reason) return this.setState({ loadingEns: false, + ensResolution: '0x0000000000000000000000000000000000000000', ensFailure: true, hoverText: reason.message, }) @@ -168,3 +171,8 @@ EnsInput.prototype.ensIconContents = function (recipient) { }) } } + +function getNetworkEnsSupport (network) { + return Boolean(networkMap[network]) +} + diff --git a/ui/app/components/eth-balance.js b/ui/app/components/eth-balance.js index 57ca84564..4f538fd31 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, currentCurrency } = 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: props.value, conversionRate, currentCurrency }) : null, ])) ) } diff --git a/ui/app/components/fiat-value.js b/ui/app/components/fiat-value.js index 298809b30..8a64a1cfc 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,23 +12,23 @@ function FiatValue () { FiatValue.prototype.render = function () { const props = this.props + const { conversionRate, currentCurrency } = 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' fiatTooltipNumber = 'Unknown' } - var fiatSuffix = props.currentCurrency - - return fiatDisplay(fiatDisplayNumber, fiatSuffix) + return fiatDisplay(fiatDisplayNumber, currentCurrency) } function fiatDisplay (fiatDisplayNumber, fiatSuffix) { diff --git a/ui/app/components/hex-as-decimal-input.js b/ui/app/components/hex-as-decimal-input.js index c89ed0416..4a71e9585 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,51 +24,122 @@ function HexAsDecimalInput () { HexAsDecimalInput.prototype.render = function () { const props = this.props - const { value, onChange } = 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.ether-balance.ether-balance-amount', { - type: 'number', - 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('div', { + h('.flex-column', [ + h('.flex-row', { style: { - color: ' #AEAEAE', - fontSize: '12px', - marginLeft: '5px', - marginRight: '6px', - width: '20px', + alignItems: 'flex-end', + lineHeight: '13px', + fontFamily: 'Montserrat Light', + textRendering: 'geometricPrecision', }, - }, suffix), + }, [ + 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), + onBlur: (event) => { + this.updateValidity(event) + }, + onChange: (event) => { + this.updateValidity(event) + 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() + 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, ]) ) } +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() + if (valid) { + this.setState({ 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) + const hexBN = new BN(parseInt(decimalString), 10) return '0x' + hexBN.toString('hex') } 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) + } } diff --git a/ui/app/components/network.js b/ui/app/components/network.js index 77805fd57..31a8fc17c 100644 --- a/ui/app/components/network.js +++ b/ui/app/components/network.js @@ -34,12 +34,18 @@ 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) { hoverText = 'Ropsten Test Network' iconName = 'ropsten-test-network' + } else if (providerName === 'kovan') { + hoverText = 'Kovan Test Network' + iconName = 'kovan-test-network' + } else if (providerName === 'rinkeby') { + hoverText = 'Rinkeby Test Network' + iconName = 'rinkeby-test-network' } else { hoverText = 'Unknown Private Network' iconName = 'unknown-private-network' @@ -70,6 +76,24 @@ 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'), + ]) + case 'rinkeby-test-network': + return h('.network-indicator', [ + h('.menu-icon.golden-square'), + h('.network-name', { + style: { + color: '#e7a218', + }}, + 'Rinkeby Test Net'), + ]) default: return h('.network-indicator', [ h('i.fa.fa-question-circle.fa-lg', { diff --git a/ui/app/components/notice.js b/ui/app/components/notice.js index 23ded9d5d..d9f0067cd 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, }), @@ -99,11 +100,14 @@ Notice.prototype.render = function () { h('button', { disabled, - onClick: onConfirm, + onClick: () => { + this.setState({disclaimerDisabled: true}) + onConfirm() + }, style: { marginTop: '18px', }, - }, 'Continue'), + }, 'Accept'), ]) ) } @@ -111,6 +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}) + } } Notice.prototype.componentWillUnmount = function () { 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', + }, + }), ]), ]) diff --git a/ui/app/components/pending-tx-details.js b/ui/app/components/pending-tx-details.js deleted file mode 100644 index e92ce575f..000000000 --- a/ui/app/components/pending-tx-details.js +++ /dev/null @@ -1,344 +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') - -module.exports = PendingTxDetails - -inherits(PendingTxDetails, Component) -function PendingTxDetails () { - Component.call(this) -} - -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 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, { - value: 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, { - value: gasPrice, - suffix: 'WEI', - 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: txFee.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.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 || '0x4a817c800'), 16) - 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 - - this.setState({ - txFee: '0x' + txFee.toString('hex'), - maxCost: '0x' + maxCost.toString('hex'), - }) - - 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 2ab6f25a9..4b1a00eca 100644 --- a/ui/app/components/pending-tx.js +++ b/ui/app/components/pending-tx.js @@ -1,99 +1,478 @@ 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 clone = require('clone') -module.exports = connect(mapStateToProps)(PendingTx) +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 Copyable = require('./copyable') +const EthBalance = require('./eth-balance') +const addressSummary = util.addressSummary +const nameForAddress = require('../../lib/contract-namer') +const BNInput = require('./bn-as-decimal-input') -function mapStateToProps (state) { - return { - - } -} +const MIN_GAS_PRICE_GWEI_BN = new BN(2) +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 = PendingTx inherits(PendingTx, Component) function PendingTx () { Component.call(this) + this.state = { + valid: true, + txData: null, + } } PendingTx.prototype.render = function () { const props = this.props - const newProps = extend(props, {ref: 'details'}) - const txData = props.txData + const { currentCurrency, blockGasLimit } = props + + const conversionRate = props.conversionRate + 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' + + // recipient check + const isValidAddress = !txParams.to || util.isValidAddress(txParams.to) + + // Gas + const gas = txParams.gas + const gasBn = hexToBn(gas) + 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) + const gasPriceBn = hexToBn(gasPrice) + + const txFeeBn = gasBn.mul(gasPriceBn) + const valueBn = hexToBn(txParams.value) + const maxCost = txFeeBn.add(valueBn) + + const dataLength = txParams.data ? (txParams.data.length - 2) / 2 : 0 + + const balanceBn = hexToBn(balance) + const insufficientBalance = balanceBn.lt(maxCost) + + this.inputs = [] return ( h('div', { - key: txData.id, + key: txMeta.id, }, [ - // tx info - h(PendingTxDetails, newProps), + h('form#pending-tx-form', { + onSubmit: this.onSubmit.bind(this), - 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', - }, }, [ - props.insufficientBalance ? - h('button', { - onClick: props.buyEth, - }, 'Buy Ether') + // tx info + h('div', [ + + h('.flex-row.flex-center', { + style: { + maxWidth: '100%', + }, + }, [ + + h(MiniAccountPanel, { + imageSeed: address, + picOrder: 'right', + }, [ + h('span.font-small', { + style: { + fontFamily: 'Montserrat Bold, Montserrat, sans-serif', + }, + }, identity.name), + + h(Copyable, { + value: ethUtil.toChecksumAddress(address), + }, [ + 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, + conversionRate, + currentCurrency, + 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, currentCurrency, conversionRate }), + ]), + + // Gas Limit (customizable) + h('.cell.row', [ + h('.cell.label', 'Gas Limit'), + h('.cell.value', { + }, [ + h(BNInput, { + name: 'Gas Limit', + value: gasBn, + precision: 0, + scale: 0, + // The hard lower limit for gas. + min: MIN_GAS_LIMIT_BN.toString(10), + max: safeGasLimit, + suffix: 'UNITS', + style: { + position: 'relative', + top: '5px', + }, + onChange: this.gasLimitChanged.bind(this), + + ref: (hexInput) => { this.inputs.push(hexInput) }, + }), + ]), + ]), + + // Gas Price (customizable) + h('.cell.row', [ + h('.cell.label', 'Gas Price'), + h('.cell.value', { + }, [ + h(BNInput, { + name: 'Gas Price', + value: gasPriceBn, + precision: 9, + scale: 9, + suffix: 'GWEI', + min: MIN_GAS_PRICE_GWEI_BN.toString(10), + style: { + position: 'relative', + top: '5px', + }, + onChange: this.gasPriceChanged.bind(this), + ref: (hexInput) => { this.inputs.push(hexInput) }, + }), + ]), + ]), + + // Max Transaction Fee (calculated) + h('.cell.row', [ + h('.cell.label', 'Max Transaction Fee'), + h(EthBalance, { value: txFeeBn.toString(16), currentCurrency, conversionRate }), + ]), + + 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), + currentCurrency, + conversionRate, + 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 { + margin-left: 10px; + text-transform: uppercase; + } + `), + + txMeta.simulationFails ? + h('.error', { + style: { + marginLeft: 50, + fontSize: '0.9em', + }, + }, 'Transaction Error. Exception thrown in contract code.') : null, - h('button', { - onClick: () => { - this.refs.details.resetGasFields() - }, - }, 'Reset'), + !isValidAddress ? + h('.error', { + style: { + marginLeft: 50, + fontSize: '0.9em', + }, + }, 'Recipient address is invalid. Sending this transaction will result in a loss of ETH.') + : null, - 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')) - } - }, - }, 'Accept'), + insufficientBalance ? + h('span.error', { + style: { + marginLeft: 50, + fontSize: '0.9em', + }, + }, 'Insufficient balance for transaction') + : null, - h('button.cancel.btn-red', { - onClick: props.cancelTransaction, - }, 'Reject'), + // send + cancel + h('.flex-row.flex-space-around.conf-buttons', { + style: { + display: 'flex', + justifyContent: 'flex-end', + margin: '14px 25px', + }, + }, [ + + + insufficientBalance ? + h('button.btn-green', { + onClick: props.buyEth, + }, 'Buy Ether') + : null, + + h('button', { + onClick: (event) => { + this.resetGasFields() + event.preventDefault() + }, + }, 'Reset'), + + // Accept Button + h('input.confirm.btn-green', { + type: 'submit', + value: 'ACCEPT', + style: { marginLeft: '10px' }, + disabled: insufficientBalance || !this.state.valid || !isValidAddress, + }), + + h('button.cancel.btn-red', { + onClick: props.cancelTransaction, + }, 'Reject'), + ]), ]), ]) ) } + +PendingTx.prototype.miniAccountPanelForRecipient = function () { + const props = this.props + const txData = props.txData + const txParams = txData.txParams || {} + const isContractDeploy = !('to' in txParams) + + // If it's not a contract deploy, send to the account + if (!isContractDeploy) { + return h(MiniAccountPanel, { + imageSeed: txParams.to, + picOrder: 'left', + }, [ + + h('span.font-small', { + style: { + fontFamily: 'Montserrat Bold, Montserrat, sans-serif', + }, + }, nameForAddress(txParams.to, props.identities)), + + h(Copyable, { + value: ethUtil.toChecksumAddress(txParams.to), + }, [ + h('span.font-small', { + style: { + fontFamily: 'Montserrat Light, Montserrat, sans-serif', + }, + }, addressSummary(txParams.to, 6, 4, false)), + ]), + + ]) + } else { + return h(MiniAccountPanel, { + picOrder: 'left', + }, [ + + h('span.font-small', { + style: { + fontFamily: 'Montserrat Bold, Montserrat, sans-serif', + }, + }, 'New Contract'), + + ]) + } +} + +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), + valid, + }) +} + +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), + valid, + }) +} + +PendingTx.prototype.resetGasFields = function () { + log.debug(`pending-tx resetGasFields`) + + this.inputs.forEach((hexInput) => { + if (hexInput) { + hexInput.setValid() + } + }) + + this.setState({ + txData: null, + valid: true, + }) +} + +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 = this.getFormEl() + 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`) + const props = this.props + const state = this.state + const txData = clone(state.txData) || clone(props.txData) + + 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) + ) +} + +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', { + src: 'images/forward-carrat.svg', + style: { + padding: '5px 6px 0px 10px', + height: '37px', + }, + }) + ) +} diff --git a/ui/app/components/shapeshift-form.js b/ui/app/components/shapeshift-form.js index 8c9686035..e0a720426 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', { @@ -66,6 +70,7 @@ ShapeshiftForm.prototype.renderMain = function () { h('input#fromCoin.buy-inputs.ex-coins', { type: 'text', list: 'coinList', + autoFocus: true, dataset: { persistentFormId: 'input-coin', }, @@ -92,7 +97,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 +125,6 @@ ShapeshiftForm.prototype.renderMain = function () { }, }), ]), - - this.props.isSubLoading ? this.renderLoading() : null, h('.flex-column', { style: { alignItems: 'flex-start', @@ -138,17 +140,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 +147,7 @@ ShapeshiftForm.prototype.renderMain = function () { width: '100%', }, }, [ + h('div', `${coin} Address:`), h('input#fromCoinAddress.buy-inputs', { @@ -166,8 +158,8 @@ ShapeshiftForm.prototype.renderMain = function () { }, style: { boxSizing: 'border-box', - width: '278px', - height: '20px', + width: '227px', + height: '30px', padding: ' 5px ', }, }), @@ -177,7 +169,7 @@ ShapeshiftForm.prototype.renderMain = function () { fontSize: '12px', color: '#F7861C', position: 'relative', - bottom: '5px', + bottom: '10px', right: '11px', }, }), @@ -190,6 +182,8 @@ ShapeshiftForm.prototype.renderMain = function () { onClick: this.shift.bind(this), style: { marginTop: '10px', + position: 'relative', + bottom: '40px', }, }, 'Submit'), @@ -266,8 +260,6 @@ ShapeshiftForm.prototype.renderInfo = function () { return h('span', { style: { - marginTop: '10px', - marginBottom: '15px', }, }, [ h('h3.flex-row.text-transform-uppercase', { @@ -286,10 +278,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/components/shift-list-item.js b/ui/app/components/shift-list-item.js index e0243e247..32bfbeda4 100644 --- a/ui/app/components/shift-list-item.js +++ b/ui/app/components/shift-list-item.js @@ -4,19 +4,21 @@ 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 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') module.exports = connect(mapStateToProps)(ShiftListItem) function mapStateToProps (state) { - return {} + return { + conversionRate: state.metamask.conversionRate, + currentCurrency: state.metamask.currentCurrency, + } } inherits(ShiftListItem, Component) @@ -65,6 +67,7 @@ function formatDate (date) { ShiftListItem.prototype.renderUtilComponents = function () { var props = this.props + const { conversionRate, currentCurrency } = props switch (props.response.status) { case 'no_deposits': @@ -95,8 +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, @@ -172,9 +177,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/components/transaction-list-item-icon.js b/ui/app/components/transaction-list-item-icon.js index ca2781451..431054340 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') @@ -15,7 +16,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', { @@ -32,11 +33,16 @@ 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) { diff --git a/ui/app/components/transaction-list-item.js b/ui/app/components/transaction-list-item.js index 44d2dc587..dbda66a31 100644 --- a/ui/app/components/transaction-list-item.js +++ b/ui/app/components/transaction-list-item.js @@ -2,13 +2,13 @@ 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') const vreme = new (require('vreme')) -const extension = require('../../../app/scripts/lib/extension') const Tooltip = require('./tooltip') +const numberToBN = require('number-to-bn') const TransactionIcon = require('./transaction-list-item-icon') const ShiftListItem = require('./shift-list-item') @@ -20,7 +20,7 @@ function TransactionListItem () { } TransactionListItem.prototype.render = function () { - const { transaction, network } = this.props + const { transaction, network, conversionRate, currentCurrency } = this.props if (transaction.key === 'shapeshift') { if (network === '1') return h(ShiftListItem, transaction) } @@ -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 === 4 || numericNet === 42 var isMsg = ('msgParams' in transaction) var isTx = ('txParams' in transaction) @@ -40,6 +40,8 @@ TransactionListItem.prototype.render = function () { txParams = transaction.msgParams } + const nonce = txParams.nonce ? numberToBN(txParams.nonce).toString(10) : '' + const isClickable = ('hash' in transaction && isLinkable) || isPending return ( h(`.transaction-list-item.flex-row.flex-space-between${isClickable ? '.pointer' : ''}`, { @@ -50,7 +52,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,13 +65,29 @@ 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 }), ]), ]), + 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), @@ -79,8 +97,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, @@ -135,7 +155,6 @@ function failIfFailed (transaction) { return h('span.error', ' (Rejected)') } if (transaction.err) { - return h(Tooltip, { title: transaction.err.message, position: 'bottom', @@ -143,5 +162,4 @@ function failIfFailed (transaction) { h('span.error', ' (Failed)'), ]) } - } 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 07985094c..747d3ce2b 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') @@ -29,6 +27,9 @@ function mapStateToProps (state) { warning: state.appState.warning, network: state.metamask.network, provider: state.metamask.provider, + conversionRate: state.metamask.conversionRate, + currentCurrency: state.metamask.currentCurrency, + blockGasLimit: state.metamask.currentBlockGasLimit, } } @@ -39,15 +40,16 @@ function ConfirmTxScreen () { ConfirmTxScreen.prototype.render = function () { const props = this.props - const { network, provider, unapprovedTxs, - unapprovedMsgs, unapprovedPersonalMsgs } = props + const { network, provider, unapprovedTxs, currentCurrency, + unapprovedMsgs, unapprovedPersonalMsgs, conversionRate, blockGasLimit } = 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' + log.info(`rendering a combined ${unconfTxList.length} unconf msg & txs`) if (unconfTxList.length === 0) return h(Loading, { isLoading: true }) @@ -104,12 +106,12 @@ ConfirmTxScreen.prototype.render = function () { selectedAddress: props.selectedAddress, accounts: props.accounts, identities: props.identities, - insufficientBalance: this.checkBalanceAgainstTx(txData), - // State actions - onTxChange: this.onTxChange.bind(this), + conversionRate, + currentCurrency, + blockGasLimit, // 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), @@ -130,14 +132,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) @@ -145,41 +145,19 @@ 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) { - event.stopPropagation() + event.preventDefault() 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) { - event.stopPropagation() - const state = this.state || {} - const txMeta = state.txData - this.props.dispatch(actions.updateAndApproveTx(txMeta || txData)) + this.stopPropagation(event) + this.props.dispatch(actions.updateAndApproveTx(txData)) } ConfirmTxScreen.prototype.cancelTransaction = function (txData, event) { - event.stopPropagation() + this.stopPropagation(event) + event.preventDefault() this.props.dispatch(actions.cancelTx(txData)) } @@ -187,32 +165,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/config.js b/ui/app/config.js index 3f0507f48..d7be26757 100644 --- a/ui/app/config.js +++ b/ui/app/config.js @@ -156,11 +156,21 @@ function currentProviderDisplay (metamaskState) { value = 'Main Ethereum Network' break - case 'testnet': + case 'ropsten': title = 'Current Network' value = 'Ropsten Test Network' break + case 'kovan': + title = 'Current Network' + value = 'Kovan Test Network' + break + + case 'rinkeby': + title = 'Current Network' + value = 'Rinkeby Test Network' + break + default: title = 'Current RPC' value = metamaskState.provider.rpcTarget diff --git a/ui/app/conversion.json b/ui/app/conversion.json index eeca164ce..155ffc4fc 100644 --- a/ui/app/conversion.json +++ b/ui/app/conversion.json @@ -1,5730 +1,207 @@ -{ - "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" + ] + } + ] +} diff --git a/ui/app/css/index.css b/ui/app/css/index.css index 8c6ff29d3..808aafb4c 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); } @@ -148,7 +148,6 @@ h2.page-subtitle { } textarea.twelve-word-phrase { - margin-top: 20px; padding: 12px; width: 300px; height: 140px; @@ -266,8 +265,9 @@ app sections } .sizing-input{ - font-size: 1em; + font-size: 14px; height: 30px; + padding-left: 5px; } .editable-label{ display: flex; @@ -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/css/lib.css b/ui/app/css/lib.css index 99c6f1b9d..910a24ee2 100644 --- a/ui/app/css/lib.css +++ b/ui/app/css/lib.css @@ -188,7 +188,11 @@ hr.horizontal-line { .hollow-diamond { transform: rotate(45deg); - border: 1px solid #038789; + border: 3px solid #690496; +} + +.golden-square { + background: #EBB33F; } .pending-dot { diff --git a/ui/app/info.js b/ui/app/info.js index e79580be4..aa4503b62 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('../../app/scripts/lib/extension') 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,12 +47,12 @@ InfoScreen.prototype.render = function () { style: { marginBottom: '10px', }, - }, `Version: ${manifest.version}`), + }, `Version: ${version}`), ]), h('div', { style: { - marginBottom: '10px', + marginBottom: '5px', }}, [ h('div', [ @@ -93,7 +87,7 @@ InfoScreen.prototype.render = function () { h('hr', { style: { - margin: '20px 0 ', + margin: '10px 0 ', width: '7em', }, }), @@ -103,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/', @@ -110,10 +111,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,17 +142,9 @@ 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!'), ]), - - 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'), - ]), ]), ]), ]), @@ -158,5 +153,5 @@ InfoScreen.prototype.render = function () { } InfoScreen.prototype.navigateTo = function (url) { - extension.tabs.create({ url }) + global.platform.openWindow({ url }) } 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, 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.js b/ui/app/reducers.js index 4d10e2b39..11efca529 100644 --- a/ui/app/reducers.js +++ b/ui/app/reducers.js @@ -42,6 +42,11 @@ 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) + return stateString +} + +function removeSeedWords (key, value) { + return key === 'seedWords' ? undefined : value } diff --git a/ui/app/reducers/app.js b/ui/app/reducers/app.js index 7ea1e1d7c..deacad0a7 100644 --- a/ui/app/reducers/app.js +++ b/ui/app/reducers/app.js @@ -1,20 +1,20 @@ const extend = require('xtend') const actions = require('../actions') const txHelper = require('../../lib/tx-helper') -const notification = require('../../../app/scripts/lib/notifications') module.exports = reduceApp + 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' } @@ -32,7 +32,9 @@ function reduceApp (state, action) { seedWords, } + // default state var appState = extend({ + shouldClose: false, menuOpen: false, currentView: seedWords ? seedConfView : defaultView, accountDetail: { @@ -302,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, @@ -312,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 + const otherUnconfActions = getUnconfActionList(state) + .filter(tx => tx.id !== action.value) + const hasOtherUnconfActions = otherUnconfActions.length > 0 - var unconfTxList = txHelper(unapprovedTxs, unapprovedMsgs, unapprovedPersonalMsgs, network) - .filter(tx => tx.id !== action.value ) - - pendingTxs = unconfTxList.length > 0 - - if (pendingTxs) { + if (hasOtherUnconfActions) { log.debug('reducer detected txs - rendering confTx view') return extend(appState, { transForward: false, @@ -332,9 +330,9 @@ function reduceApp (state, action) { }) } else { log.debug('attempting to close popup') - notification.closePopup() - return extend(appState, { + // indicate notification should close + shouldClose: true, transForward: false, warning: null, currentView: { @@ -426,6 +424,7 @@ function reduceApp (state, action) { case actions.DISPLAY_WARNING: return extend(appState, { warning: action.value, + isLoading: false, }) case actions.HIDE_WARNING: @@ -470,8 +469,9 @@ function reduceApp (state, action) { name: 'buyEth', context: appState.currentView.name, }, + identity: state.metamask.identities[action.value], buyView: { - subview: 'buyForm', + subview: 'Coinbase', amount: '15.00', buyAddress: action.value, formView: { @@ -481,36 +481,10 @@ function reduceApp (state, action) { }, }) - case actions.UPDATE_BUY_ADDRESS: - return extend(appState, { - buyView: { - subview: 'buyForm', - 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: { - subview: 'buyForm', - formView: { - coinbase: true, - shapeshift: false, - }, - buyAddress: appState.buyView.buyAddress, - amount: action.value, - }, - }) - case actions.COINBASE_SUBVIEW: return extend(appState, { buyView: { - subview: 'buyForm', + subview: 'Coinbase', formView: { coinbase: true, shapeshift: false, @@ -523,7 +497,7 @@ function reduceApp (state, action) { case actions.SHAPESHIFT_SUBVIEW: return extend(appState, { buyView: { - subview: 'buyForm', + subview: 'ShapeShift', formView: { coinbase: false, shapeshift: true, @@ -538,7 +512,7 @@ function reduceApp (state, action) { case actions.PAIR_UPDATE: return extend(appState, { buyView: { - subview: 'buyForm', + subview: 'ShapeShift', formView: { coinbase: false, shapeshift: true, @@ -579,25 +553,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 network = state.metamask.network - var unconfTxList = txHelper(unapprovedTxs, unapprovedMsgs, 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 } 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: diff --git a/ui/app/send.js b/ui/app/send.js index eb32d5e06..fd6994145 100644 --- a/ui/app/send.js +++ b/ui/app/send.js @@ -21,6 +21,8 @@ function mapStateToProps (state) { warning: state.appState.warning, network: state.metamask.network, addressBook: state.metamask.addressBook, + conversionRate: state.metamask.conversionRate, + currentCurrency: state.metamask.currentCurrency, } result.error = result.warning && result.warning.split('.')[0] @@ -40,13 +42,17 @@ 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 + const props = this.props + const { + address, + account, + identity, + network, + identities, + addressBook, + conversionRate, + currentCurrency, + } = props return ( @@ -125,6 +131,8 @@ SendTransactionScreen.prototype.render = function () { h(EthBalance, { value: account && account.balance, + conversionRate, + currentCurrency, }), ]), @@ -147,7 +155,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', [ 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) } diff --git a/ui/index.js b/ui/index.js index 1a65f813c..a729138d3 100644 --- a/ui/index.js +++ b/ui/index.js @@ -4,27 +4,27 @@ 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 +37,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 +53,6 @@ function startApp (metamaskState, accountManager, opts) { store: store, } ), opts.container) + + return store } diff --git a/ui/lib/account-link.js b/ui/lib/account-link.js index 77db0851d..d061d0ad1 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}` @@ -10,7 +9,13 @@ 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 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 default: link = '' diff --git a/ui/lib/contract-namer.js b/ui/lib/contract-namer.js index a94c62b62..f05e770cc 100644 --- a/ui/lib/contract-namer.js +++ b/ui/lib/contract-namer.js @@ -5,14 +5,18 @@ * otherwise returns null. */ -// 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 (contractMap[checksummed] && contractMap[checksummed].name) { + 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) { diff --git a/ui/lib/explorer-link.js b/ui/lib/explorer-link.js index dc6be2984..e11249551 100644 --- a/ui/lib/explorer-link.js +++ b/ui/lib/explorer-link.js @@ -5,8 +5,14 @@ module.exports = function (hash, network) { case 1: // main net prefix = '' break - case 3: // morden test net - prefix = 'testnet.' + case 3: // ropsten test net + prefix = 'ropsten.' + break + case 4: // rinkeby test net + prefix = 'rinkeby.' + break + case 42: // kovan test net + prefix = 'kovan.' break default: prefix = '' diff --git a/ui/lib/icon-factory.js b/ui/lib/icon-factory.js index 82cc839d6..45be47b7a 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 contractMap = require('eth-contract-metadata') 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,23 @@ IconFactory.prototype.generateNewIdenticon = function (address, diameter) { // util +function iconExistsFor (address) { + return (contractMap.address) && isValidAddress(address) && (contractMap[address].logo) +} + +function imageElFor (address) { + const contract = contractMap[address] + const fileName = contract.logo + 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) -} diff --git a/ui/lib/tx-helper.js b/ui/lib/tx-helper.js index 2eefdff68..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.txParams.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) }