Merge branch 'master' into mascara-deploy

This commit is contained in:
kumavis 2018-02-27 19:25:29 +00:00 committed by GitHub
commit 3fefccd372
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
580 changed files with 67166 additions and 12086 deletions

View File

@ -1 +1,4 @@
{ "presets": ["es2015"] }
{
"presets": ["es2015", "stage-0", "react"],
"plugins": ["transform-runtime", "transform-async-to-generator"]
}

View File

@ -1 +1,6 @@
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
ui/lib/blockies.js

View File

@ -1,7 +1,8 @@
{
"parser": "babel-eslint",
"parserOptions": {
"sourceType": "module",
"ecmaVersion": 6,
"ecmaVersion": 2017,
"ecmaFeatures": {
"experimentalObjectRestSpread": true,
"impliedStrict": true,
@ -10,17 +11,25 @@
"arrowFunctions": true,
"objectLiteralShorthandMethods": true,
"objectLiteralShorthandProperties": true,
"templateStrings": true
"templateStrings": true,
"classes": true,
"jsx": true
},
},
"extends": ["plugin:react/recommended"],
"env": {
"es6": true,
"node": true,
"browser": true
"browser": true,
"mocha" : true
},
"plugins": [
"mocha",
"chai",
"react"
],
"globals": {
@ -47,8 +56,8 @@
"eqeqeq": [2, "allow-null"],
"generator-star-spacing": [2, { "before": true, "after": true }],
"handle-callback-err": [1, "^(err|error)$" ],
"indent": [2, 2, { "SwitchCase": 1 }],
"jsx-quotes": [2, "prefer-single"],
"indent": "off",
"jsx-quotes": [2, "prefer-double"],
"key-spacing": 1,
"keyword-spacing": [2, { "before": true, "after": true }],
"new-cap": [2, { "newIsCap": true, "capIsNew": false }],
@ -130,7 +139,7 @@
"no-with": 2,
"one-var": [2, { "initialized": "never" }],
"operator-linebreak": [1, "after", { "overrides": { "?": "ignore", ":": "ignore" } }],
"padded-blocks": [1, "never"],
"padded-blocks": "off",
"quotes": [2, "single", {"avoidEscape": true, "allowTemplateLiterals": true}],
"semi": [2, "never"],
"semi-spacing": [2, { "before": false, "after": true }],

33
.gitignore vendored
View File

@ -1,18 +1,35 @@
dist
npm-debug.log
node_modules
temp
.tmp
.sass-cache
package-lock.json
app/bower_components
test/bower_components
package
.idea
temp
.tmp
.sass-cache
.DS_Store
app/.DS_Store
dist
builds/
disc/
notes.txt
app/.DS_Store
development/bundle.js
builds.zip
test/integration/bundle.js
development/bundle.js
development/states.js
test/integration/bundle.js
test/background.js
test/bundle.js
test/test-bundle.js
#ignore css output and sourcemaps
ui/app/css/output/
notes.txt
.coveralls.yml
.nyc_output

10
.stylelintignore Normal file
View File

@ -0,0 +1,10 @@
app/
development/
dist/
docs/
fonts/
images/
mascara/
node_modules/
notices/
test/

50
.stylelintrc Normal file
View File

@ -0,0 +1,50 @@
{
"extends": "stylelint-config-standard",
"rules": {
"color-named": "never",
"font-family-name-quotes": "always-where-recommended",
"font-weight-notation": "numeric",
"function-url-quotes": "always",
"number-leading-zero": "never",
"value-no-vendor-prefix": true,
"value-list-comma-newline-before": "never-multi-line",
"custom-property-empty-line-before": "never",
"property-no-unknown": [
true,
{
"ignoreProperties": [
"composes",
"all",
"-webkit-appearance"
]
}
],
"declaration-block-semicolon-newline-after": "always",
"block-opening-brace-newline-after": "always",
"selector-attribute-quotes": "always",
"selector-max-specificity": "0,5,2",
"selector-pseudo-class-no-unknown": [
true,
{
"ignorePseudoClasses": ["local", "global"]
}
],
"at-rule-empty-line-before": [
"always",
{
"ignore": [
"after-comment",
]
}
],
"indentation": [
2,
{
"indentInsideParens": "once-at-root-twice-in-block"
}
],
"max-nesting-depth": 3,
"no-duplicate-selectors": true,
"no-unknown-animations": true
}
}

View File

@ -2,8 +2,405 @@
## Current Master
## 4.1.0 2018-2-27
- Report failed txs to Sentry with more specific message
- Fix internal feature flags being sometimes undefined
- Standardized license to MIT
## 4.0.0 2018-2-22
- Introduce new MetaMask user interface.
## 3.14.2 2018-2-15
- Fix bug where log subscriptions would break when switching network.
- Fix bug where storage values were cached across blocks.
- Add MetaMask light client [testing container](https://github.com/MetaMask/mesh-testing)
## 3.14.1 2018-2-1
- Further fix scrolling for Firefox.
## 3.14.0 2018-2-1
- Removed unneeded data from storage
- Add a "reset account" feature to Settings
- Add warning for importing some kinds of files.
- Scrollable Setting view for Firefox.
## 3.13.8 2018-1-29
- Fix provider for Kovan network.
- Bump limit for EventEmitter listeners before warning.
- Display Error when empty string is entered as a token address.
## 3.13.7 2018-1-22
- Add ability to bypass gas estimation loading indicator.
- Forward failed transactions to Sentry error reporting service
- Re-add changes from 3.13.5
## 3.13.6 2017-1-18
- Roll back changes to 3.13.4 to fix some issues with the new Infura REST provider.
## 3.13.5 2018-1-16
- Estimating gas limit for simple ether sends now faster & cheaper, by avoiding VM usage on recipients with no code.
- Add an extra px to address for Firefox clipping.
- Fix Firefox scrollbar.
- Open metamask popup for transaction confirmation before gas estimation finishes and add a loading screen over transaction confirmation.
- Fix bug that prevented eth_signTypedData from signing bytes.
- Further improve gas price estimation.
## 3.13.4 2018-1-9
- Remove recipient field if application initializes a tx with an empty string, or 0x, and tx data. Throw an error with the same condition, but without tx data.
- Improve gas price suggestion to be closer to the lowest that will be accepted.
- Throw an error if a application tries to submit a tx whose value is a decimal, and inform that it should be in wei.
- Fix bug that prevented updating custom token details.
- No longer mark long-pending transactions as failed, since we now have button to retry with higher gas.
- Fix rounding error when specifying an ether amount that has too much precision.
- Fix bug where incorrectly inputting seed phrase would prevent any future attempts from succeeding.
## 3.13.3 2017-12-14
- Show tokens that are held that have no balance.
- Reduce load on Infura by using a new block polling endpoint.
## 3.13.2 2017-12-9
- Reduce new block polling interval to 8000 ms, to ease server load.
## 3.13.1 2017-12-7
- Allow Dapps to specify a transaction nonce, allowing dapps to propose resubmit and force-cancel transactions.
## 3.13.0 2017-12-7
- Allow resubmitting transactions that are taking long to complete.
## 3.12.1 2017-11-29
- Fix bug where a user could be shown two different seed phrases.
- Detect when multiple web3 extensions are active, and provide useful error.
- Adds notice about seed phrase backup.
## 3.12.0 2017-10-25
- Add support for alternative ENS TLDs (Ethereum Name Service Top-Level Domains).
- Lower minimum gas price to 0.1 GWEI.
- Remove web3 injection message from production (thanks to @ChainsawBaby)
- Add additional debugging info to our state logs, specifically OS version and browser version.
## 3.11.2 2017-10-21
- Fix bug where reject button would sometimes not work.
- Fixed bug where sometimes MetaMask's connection to a page would be unreliable.
## 3.11.1 2017-10-20
- Fix bug where log filters were not populated correctly
- Fix bug where web3 API was sometimes injected after the page loaded.
- Fix bug where first account was sometimes not selected correctly after creating or restoring a vault.
- Fix bug where imported accounts could not use new eth_signTypedData method.
## 3.11.0 2017-10-11
- Add support for new eth_signTypedData method per EIP 712.
- Fix bug where some transactions would be shown as pending forever, even after successfully mined.
- Fix bug where a transaction might be shown as pending forever if another tx with the same nonce was mined.
- Fix link to support article on token addresses.
## 3.10.9 2017-10-5
- Only rebrodcast transactions for a day not a days worth of blocks
- Remove Slack link from info page, since it is a big phishing target.
- Stop computing balance based on pending transactions, to avoid edge case where users are unable to send transactions.
## 3.10.8 2017-9-28
- Fixed usage of new currency fetching API.
## 3.10.7 2017-9-28
- Fixed bug where sometimes the current account was not correctly set and exposed to web apps.
- Added AUD, HKD, SGD, IDR, PHP to currency conversion list
## 3.10.6 2017-9-27
- Fix bug where newly created accounts were not selected.
- Fix bug where selected account was not persisted between lockings.
## 3.10.5 2017-9-27
- Fix block gas limit estimation.
## 3.10.4 2017-9-27
- Fix bug that could mis-render token balances when very small. (Not actually included in 3.9.9)
- Fix memory leak warning.
- Fix bug where new event filters would not include historical events.
## 3.10.3 2017-9-21
- Fix bug where metamask-dapp connections are lost on rpc error
- Fix bug that would sometimes display transactions as failed that could be successfully mined.
## 3.10.2 2017-9-18
rollback to 3.10.0 due to bug
## 3.10.1 2017-9-18
- Add ability to export private keys as a file.
- Add ability to export seed words as a file.
- Changed state logs to a file download than a clipboard copy.
- Add specific error for failed recipient address checksum.
- Fixed a long standing memory leak associated with filters installed by dapps
- Fix link to support center.
- Fixed tooltip icon locations to avoid overflow.
- Warn users when a dapp proposes a high gas limit (90% of blockGasLimit or higher
- Sort currencies by currency name (thanks to strelok1: https://github.com/strelok1).
## 3.10.0 2017-9-11
- Readded loose keyring label back into the account list.
- Remove cryptonator from chrome permissions.
- Add info on token contract addresses.
- Add validation preventing users from inputting their own addresses as token tracking addresses.
- Added button to reject all transactions (thanks to davidp94! https://github.com/davidp94)
## 3.9.13 2017-9-8
- Changed the way we initialize the inpage provider to fix a bug affecting some developers.
## 3.9.12 2017-9-6
- Fix bug that prevented Web3 1.0 compatibility
- Make eth_sign deprecation warning less noisy
- Add useful link to eth_sign deprecation warning.
- Fix bug with network version serialization over synchronous RPC
- Add MetaMask version to state logs.
- Add the total amount of tokens when multiple tokens are added under the token list
- Use HTTPS links for Etherscan.
- Update Support center link to new one with HTTPS.
- Make web3 deprecation notice more useful by linking to a descriptive article.
## 3.9.11 2017-8-24
- Fix nonce calculation bug that would sometimes generate very wrong nonces.
- Give up resubmitting a transaction after 3500 blocks.
## 3.9.10 2017-8-23
- Improve nonce calculation, to prevent bug where people are unable to send transactions reliably.
- Remove link to eth-tx-viz from identicons in tx history.
## 3.9.9 2017-8-18
- Fix bug where some transaction submission errors would show an empty screen.
- Fix bug that could mis-render token balances when very small.
- Fix formatting of eth_sign "Sign Message" view.
- Add deprecation warning to eth_sign "Sign Message" view.
## 3.9.8 2017-8-16
- Reenable token list.
- Remove default tokens.
## 3.9.7 2017-8-15
- hotfix - disable token list
- Added a deprecation warning for web3 https://github.com/ethereum/mist/releases/tag/v0.9.0
## 3.9.6 2017-8-09
- Replace account screen with an account drop-down menu.
- Replace account buttons with a new account-specific drop-down menu.
## 3.9.5 2017-8-04
- Improved phishing detection configuration update rate
## 3.9.4 2017-8-03
- Fixed bug that prevented transactions from being rejected.
## 3.9.3 2017-8-03
- Add support for EGO ujo token
- Continuously update blacklist for known phishing sites in background.
- Automatically detect suspicious URLs too similar to common phishing targets, and blacklist them.
## 3.9.2 2017-7-26
- Fix bugs that could sometimes result in failed transactions after switching networks.
- Include stack traces in txMeta's to better understand the life cycle of transactions
- Enhance blacklister functionality to include levenshtein logic. (credit to @sogoiii and @409H for their help!)
## 3.9.1 2017-7-19
- No longer automatically request 1 ropsten ether for the first account in a new vault.
- Now redirects from known malicious sites faster.
- Added a link to our new support page to the help screen.
- Fixed bug where a new transaction would be shown over the current transaction, creating a possible timing attack against user confirmation.
- Fixed bug in nonce tracker where an incorrect nonce would be calculated.
- Lowered minimum gas price to 1 Gwei.
## 3.9.0 2017-7-12
- Now detects and blocks known phishing sites.
## 3.8.6 2017-7-11
- Make transaction resubmission more resilient.
- No longer validate nonce client-side in retry loop.
- Fix bug where insufficient balance error was sometimes shown on successful transactions.
## 3.8.5 2017-7-7
- Fix transaction resubmit logic to fail slightly less eagerly.
## 3.8.4 2017-7-7
- Improve transaction resubmit logic to fail more eagerly when a user would expect it to.
## 3.8.3 2017-7-6
- Re-enable default token list.
- Add origin header to dapp-bound requests to allow providers to throttle sites.
- Fix bug that could sometimes resubmit a transaction that had been stalled due to low balance after balance was restored.
## 3.8.2 2017-7-3
- No longer show network loading indication on config screen, to allow selecting custom RPCs.
- Visually indicate that network spinner is a menu.
- Indicate what network is being searched for when disconnected.
## 3.8.1 2017-6-30
- Temporarily disabled loading popular tokens by default to improve performance.
- Remove SEND token button until a better token sending form can be built, due to some precision issues.
- Fix precision bug in token balances.
- Cache token symbol and precisions to reduce network load.
- Transpile some newer JavaScript, restores compatibility with some older browsers.
## 3.8.0 2017-6-28
- No longer stop rebroadcasting transactions
- Add list of popular tokens held to the account detail view.
- Add ability to add Tokens to token list.
- Add a warning to JSON file import.
- Add "send" link to token list, which goes to TokenFactory.
- Fix bug where slowly mined txs would sometimes be incorrectly marked as failed.
- Fix bug where badge count did not reflect personal_sign pending messages.
- Seed word confirmation wording is now scarier.
- Fix error for invalid seed words.
- Prevent users from submitting two duplicate transactions by disabling submit.
- Allow Dapps to specify gas price as hex string.
- Add button for copying state logs to clipboard.
## 3.7.8 2017-6-12
- Add an `ethereum:` prefix to the QR code address
- The default network on installation is now MainNet
- Fix currency API URL from cryptonator.
- Update gasLimit params with every new block seen.
- Fix ENS resolver symbol UI.
## 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
@ -64,7 +461,7 @@
- Add ability to import accounts in JSON file format (used by Mist, Geth, MyEtherWallet, and more!)
- Fix unapproved messages not being included in extension badge.
- Fix rendering bug where the Confirm transaction view would lets you approve transactions when the account has insufficient balance.
- Fix rendering bug where the Confirm transaction view would let you approve transactions when the account has insufficient balance.
## 3.1.2 2017-1-24
@ -87,8 +484,8 @@
## 3.0.0 2017-1-16
- Fix seed word account generation (https://medium.com/metamask/metamask-3-migration-guide-914b79533cdd#.t4i1qmmsz).
- Fix Bug where you see a empty transaction flash by on the confirm transaction view.
- Create visible difference in transaction history between a approved but not yet included in a block transaction and a transaction who has been confirmed.
- Fix Bug where you see an empty transaction flash by on the confirm transaction view.
- Create visible difference in transaction history between an approved but not yet included in a block transaction and a transaction who has been confirmed.
- Fix memory leak in RPC Cache
- Override RPC commands eth_syncing and web3_clientVersion
- Remove certain non-essential permissions from certain builds.
@ -143,7 +540,7 @@
- Fix bug where gas estimate would sometimes be very high.
- Increased our gas estimate from 100k gas to 20% of estimate.
- Fix github link on info page to point at current repository.
- Fix GitHub link on info page to point at current repository.
## 2.13.6 2016-10-26
@ -219,7 +616,7 @@ popup notification opens up.
- Block negative values from transactions.
- Fixed a memory leak.
- MetaMask logo now renders as super lightweight SVG, improving compatibility and performance.
- Now showing loading indication during vault unlocking, to clarify behavior for users who are experience slow unlocks.
- Now showing loading indication during vault unlocking, to clarify behavior for users who are experiencing slow unlocks.
- Now only initially creates one wallet when restoring a vault, to reduce some users' confusion.
## 2.10.2 2016-09-02
@ -251,7 +648,7 @@ popup notification opens up.
- Added info link on account screen that visits Etherscan.
- Fixed bug where a message signing request would be lost if the vault was locked.
- Added shortcut to open MetaMask (Ctrl+Alt+M or Cmd+Opt/Alt+M)
- Prevent API calls in tests.
- Prevent API calls in tests.
- Fixed bug where sign message confirmation would sometimes render blank.
## 2.9.0 2016-08-22

View File

@ -1,4 +1,4 @@
FROM node:6
FROM node:7
MAINTAINER kumavis
# setup app dir

15
ISSUE_TEMPLATE Normal file
View File

@ -0,0 +1,15 @@
<!--
BEFORE SUBMITTING, please make sure your question hasn't been answered in our support center: https://support.metamask.io
Common questions such as "Where is my ether?" or "Where did my tokens go?" are answered there.
Bug Reports:
Briefly describe the issue you've encountered
* Expected Behavior
* Actual Behavior
* Browser Used
* Operating System Used
Screenshots are very helpful and will expedite your issue being resolved!
-->

40
LICENSE
View File

@ -1,34 +1,20 @@
Copyright (c) 2016 MetaMask
MIT License
The Ethereum Project Contributor Asset Distribution Terms ( MIT + Share-alike )
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
associated documentation files (the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge, publish, distribute,
sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
Copyright (c) 2018 MetaMask
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial
portions of the Software.
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR
THE USE OR OTHER DEALINGS IN THE SOFTWARE.
These licence terms have been adapted from the MIT licence.
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE

151
README.md
View File

@ -1,10 +1,22 @@
# MetaMask Plugin [![Build Status](https://circleci.com/gh/MetaMask/metamask-plugin.svg?style=shield&circle-token=a1ddcf3cd38e29267f254c9c59d556d513e3a1fd)](https://circleci.com/gh/MetaMask/metamask-plugin)
# MetaMask Browser Extension
[![Build Status](https://circleci.com/gh/MetaMask/metamask-extension.svg?style=shield&circle-token=a1ddcf3cd38e29267f254c9c59d556d513e3a1fd)](https://circleci.com/gh/MetaMask/metamask-extension) [![Coverage Status](https://coveralls.io/repos/github/MetaMask/metamask-extension/badge.svg?branch=master)](https://coveralls.io/github/MetaMask/metamask-extension?branch=master) [![Greenkeeper badge](https://badges.greenkeeper.io/MetaMask/metamask-extension.svg)](https://greenkeeper.io/) [![Stories in Ready](https://badge.waffle.io/MetaMask/metamask-extension.png?label=in%20progress&title=waffle.io)](http://waffle.io/MetaMask/metamask-extension)
## Support
If you're a user seeking support, [here is our support site](https://metamask.helpscoutdocs.com/).
## Developing Compatible Dapps
If you're a web dapp developer, we've got two types of guides for you:
- If you've never built a Dapp before, we've got a gentle introduction on [Developing Dapps with Truffle and MetaMask](https://blog.metamask.io/developing-for-metamask-with-truffle/).
### New Dapp Developers
- We recommend this [Learning Solidity](https://karl.tech/learning-solidity-part-1-deploy-a-contract/) tutorial series by Karl Floersch.
- We wrote a (slightly outdated now) gentle introduction on [Developing Dapps with Truffle and MetaMask](https://medium.com/metamask/developing-ethereum-dapps-with-truffle-and-metamask-aa8ad7e363ba).
### Current Dapp Developers
- If you have a Dapp, and you want to ensure compatibility, [here is our guide on building MetaMask-compatible Dapps](https://github.com/MetaMask/faq/blob/master/DEVELOPERS.md)
## Building locally
@ -18,11 +30,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 +57,23 @@ 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 port MetaMask to a new platform](./docs/porting_to_new_environment.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
```

View File

@ -0,0 +1,10 @@
{
"appName": {
"message": "MetaMask",
"description": "The name of the application"
},
"appDescription": {
"message": "이더리움 계좌 관리",
"description": "The description of the application"
}
}

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
app/fonts/Lato/Lato-Black.ttf Executable file

Binary file not shown.

Binary file not shown.

BIN
app/fonts/Lato/Lato-Bold.ttf Executable file

Binary file not shown.

Binary file not shown.

BIN
app/fonts/Lato/Lato-Hairline.ttf Executable file

Binary file not shown.

Binary file not shown.

BIN
app/fonts/Lato/Lato-Italic.ttf Executable file

Binary file not shown.

BIN
app/fonts/Lato/Lato-Light.ttf Executable file

Binary file not shown.

Binary file not shown.

BIN
app/fonts/Lato/Lato-Regular.ttf Executable file

Binary file not shown.

93
app/fonts/Lato/OFL.txt Executable file
View File

@ -0,0 +1,93 @@
Copyright (c) 2010-2014 by tyPoland Lukasz Dziedzic (team@latofonts.com) with Reserved Font Name "Lato"
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

12
app/home.html Normal file
View File

@ -0,0 +1,12 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1 user-scalable=no">
<title>MetaMask Plugin</title>
</head>
<body>
<div id="app-content"></div>
<script src="./scripts/popup.js" type="text/javascript" charset="utf-8"></script>
</body>
</html>

BIN
app/images/.DS_Store vendored

Binary file not shown.

View File

@ -0,0 +1,76 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 1000 1000" style="enable-background:new 0 0 1000 1000;" xml:space="preserve">
<style type="text/css">
.st0{fill:#231F20;}
.st1{fill:none;stroke:#000000;stroke-width:35;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
.st2{fill:none;stroke:#000000;stroke-width:35;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:25,61;}
.st3{display:none;}
.st4{display:inline;}
.st5{fill:#EC008C;}
.st6{display:inline;fill:#FFF200;}
</style>
<g id="Layer_4">
</g>
<g id="Layer_1">
<g>
<g>
<g>
<g>
<g>
<g>
<g>
<g>
<g>
<g>
<g>
<g>
<g>
<g>
<g>
<g>
<g>
<g>
<g>
<g>
<g>
<g>
<g>
<g>
<g>
<g>
<path class="st0" d="M380.4,756.7c-4.5,0-9-1.7-12.4-5.1c-6.8-6.8-6.8-17.9,0-24.7L594.9,500L368,273.2
c-6.8-6.8-6.8-17.9,0-24.7c6.8-6.8,17.9-6.8,24.7,0L632,487.6c6.8,6.8,6.8,17.9,0,24.7L392.8,751.6
C389.3,755,384.9,756.7,380.4,756.7z"/>
</g>
</g>
</g>
</g>
</g>
</g>
</g>
</g>
</g>
</g>
</g>
</g>
</g>
</g>
</g>
</g>
</g>
</g>
</g>
</g>
</g>
</g>
</g>
</g>
</g>
</g>
</g>
<g id="Layer_2" class="st3">
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="16px" height="13px" viewBox="0 0 16 13" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 47 (45396) - http://www.bohemiancoding.com/sketch -->
<title>check-white</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="MetaMascara-v2" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="account-dropdown-top-bar-IXD" transform="translate(-17.000000, -80.000000)" fill-rule="nonzero" fill="#FFFFFF">
<g id="Group-11" transform="translate(18.000000, 74.000000)">
<polygon id="check-white" points="4.2 15.5712828 0.714212839 12.0143571 -0.714212839 13.4142143 4.2 18.4287172 14.7142128 7.69992858 13.2857872 6.30007142"></polygon>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 872 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

11
app/images/eth_logo.svg Normal file
View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="256px" height="417px" viewBox="0 0 256 417" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid">
<g>
<polygon fill="#343434" points="127.9611 0 125.1661 9.5 125.1661 285.168 127.9611 287.958 255.9231 212.32"/>
<polygon fill="#8C8C8C" points="127.962 0 0 212.32 127.962 287.959 127.962 154.158"/>
<polygon fill="#3C3C3B" points="127.9611 312.1866 126.3861 314.1066 126.3861 412.3056 127.9611 416.9066 255.9991 236.5866"/>
<polygon fill="#8C8C8C" points="127.962 416.9052 127.962 312.1852 0 236.5852"/>
<polygon fill="#141414" points="127.9611 287.9577 255.9211 212.3207 127.9611 154.1587"/>
<polygon fill="#393939" points="0.0009 212.3208 127.9609 287.9578 127.9609 154.1588"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 854 B

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="15px" height="15px" viewBox="0 0 15 15" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 47 (45396) - http://www.bohemiancoding.com/sketch -->
<title>import-account</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="MetaMascara-v2" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="account-dropdown-top-bar-IXD" transform="translate(-25.000000, -718.000000)">
<g id="Group-6" transform="translate(4.000000, 646.000000)">
<g id="import-account" transform="translate(21.000000, 72.000000)">
<rect id="Rectangle-49" fill="#FFFFFF" x="0" y="13.1721326" width="14.4893459" height="1.08397642"></rect>
<rect id="Rectangle" fill="#FFFFFF" x="6.5860663" y="0" width="1.08397642" height="10.5377061"></rect>
<polyline id="Path-12" stroke="#FFFFFF" points="2.63442652 6.5860663 7.24467293 10.5377061 11.8549193 6.5860663"></polyline>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
app/images/info-logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

128
app/images/metamask-fox.svg Normal file
View File

@ -0,0 +1,128 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns:ev="http://www.w3.org/2001/xml-events"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 318.6 318.6"
style="enable-background:new 0 0 318.6 318.6;" xml:space="preserve">
<style type="text/css">
.st0{fill:#161616;stroke:#161616;}
.st1{fill:#E4761B;stroke:#E4761B;stroke-linecap:round;stroke-linejoin:round;}
.st2{fill:#763D16;stroke:#763D16;stroke-linecap:round;stroke-linejoin:round;}
.st3{fill:#F6851B;stroke:#F6851B;stroke-linecap:round;stroke-linejoin:round;}
.st4{fill:#E2761B;stroke:#E2761B;stroke-linecap:round;stroke-linejoin:round;}
.st5{fill:#CD6116;stroke:#CD6116;stroke-linecap:round;stroke-linejoin:round;}
.st6{fill:#C0AD9E;stroke:#C0AD9E;stroke-linecap:round;stroke-linejoin:round;}
.st7{fill:#D7C1B3;stroke:#D7C1B3;stroke-linecap:round;stroke-linejoin:round;}
.st8{fill:#E4751F;stroke:#E4751F;stroke-linecap:round;stroke-linejoin:round;}
.st9{fill:#233447;stroke:#233447;stroke-linecap:round;stroke-linejoin:round;}
.st10{fill:#161616;stroke:#161616;stroke-linecap:round;stroke-linejoin:round;}
</style>
<polygon class="st0" points="277.3,145.6 272.3,142 280.3,134.7 274.2,129.9 282.2,123.8 276.9,119.8 285.3,79 272.7,41.1
191.6,71.4 124.1,71.4 43,41.1 30.4,79 38.9,119.8 33.5,123.8 41.5,129.9 35.4,134.7 43.4,142 38.4,145.6 49.9,159.1 32.5,213.3
48.6,268.6 105.3,253 116.3,262 138.7,277.5 177,277.5 199.4,262 210.4,253 267.1,268.6 283.3,213.3 265.8,159.1 "/>
<g>
<polygon class="st1" points="105.3,253 48.6,268.6 32.5,213.3 "/>
<polygon class="st1" points="283.3,213.3 267.1,268.6 210.4,253 "/>
<polygon class="st2" points="265.8,159.1 213.5,143.8 231.8,139 "/>
<polygon class="st2" points="49.9,159.1 84,139 102.2,143.8 "/>
<polygon class="st2" points="43.4,142 41.5,129.9 84,139 "/>
<polygon class="st2" points="272.3,142 231.8,139 274.2,129.9 "/>
<polygon class="st2" points="272.3,142 265.8,159.1 231.8,139 "/>
<polygon class="st2" points="43.4,142 84,139 49.9,159.1 "/>
<polygon class="st2" points="231.8,139 276.9,119.8 274.2,129.9 "/>
<polygon class="st2" points="84,139 41.5,129.9 38.9,119.8 "/>
<polygon class="st3" points="124.1,71.4 191.6,71.4 176.5,112.5 "/>
<polygon class="st3" points="176.5,112.5 139.2,112.5 124.1,71.4 "/>
<polygon class="st2" points="276.9,119.8 231.8,139 231,87.4 "/>
<polygon class="st2" points="102.2,143.8 84,139 84.7,87.4 "/>
<polygon class="st2" points="84.7,87.4 84,139 38.9,119.8 "/>
<polygon class="st2" points="231,87.4 231.8,139 213.5,143.8 "/>
<polygon class="st1" points="139.2,112.5 43,41.1 124.1,71.4 "/>
<polygon class="st4" points="272.7,41.1 176.5,112.5 191.6,71.4 "/>
<polygon class="st1" points="210.4,253 236.9,213.3 283.3,213.3 "/>
<polygon class="st1" points="32.5,213.3 78.9,213.3 105.3,253 "/>
<polygon class="st3" points="229.3,167.7 283.3,213.3 236.9,213.3 "/>
<polygon class="st3" points="86.4,167.7 32.5,213.3 49.9,159.1 "/>
<polygon class="st3" points="78.9,213.3 32.5,213.3 86.4,167.7 "/>
<polygon class="st3" points="229.3,167.7 265.8,159.1 283.3,213.3 "/>
<polygon class="st2" points="84.7,87.4 139.2,112.5 102.2,143.8 "/>
<polygon class="st2" points="213.5,143.8 176.5,112.5 231,87.4 "/>
<polygon class="st2" points="265.8,159.1 272.3,142 277.3,145.6 "/>
<polygon class="st2" points="49.9,159.1 38.4,145.6 43.4,142 "/>
<polygon class="st2" points="272.3,142 274.2,129.9 280.3,134.7 "/>
<polygon class="st2" points="43.4,142 35.4,134.7 41.5,129.9 "/>
<polygon class="st2" points="33.5,123.8 38.9,119.8 41.5,129.9 "/>
<polygon class="st2" points="282.2,123.8 274.2,129.9 276.9,119.8 "/>
<polygon class="st3" points="49.9,159.1 102.2,143.8 86.4,167.7 "/>
<polygon class="st3" points="265.8,159.1 229.3,167.7 213.5,143.8 "/>
<polygon class="st2" points="38.9,119.8 30.4,79 84.7,87.4 "/>
<polygon class="st2" points="231,87.4 285.3,79 276.9,119.8 "/>
<polygon class="st1" points="102.2,143.8 139.2,112.5 142.6,170.2 "/>
<polygon class="st1" points="213.5,143.8 229.3,167.7 173.1,170.2 "/>
<polygon class="st1" points="173.1,170.2 176.5,112.5 213.5,143.8 "/>
<polygon class="st1" points="142.6,170.2 86.4,167.7 102.2,143.8 "/>
<polygon class="st2" points="272.7,41.1 285.3,79 231,87.4 "/>
<polygon class="st2" points="43,41.1 139.2,112.5 84.7,87.4 "/>
<polygon class="st2" points="231,87.4 176.5,112.5 272.7,41.1 "/>
<polygon class="st2" points="84.7,87.4 30.4,79 43,41.1 "/>
<polygon class="st5" points="105.3,253 78.9,213.3 110,213.7 "/>
<polygon class="st5" points="210.4,253 205.7,213.7 236.9,213.3 "/>
<polygon class="st3" points="173.1,170.2 142.6,170.2 139.2,112.5 "/>
<polygon class="st3" points="139.2,112.5 176.5,112.5 173.1,170.2 "/>
<polygon class="st6" points="116.3,262 105.3,253 136.8,267.9 "/>
<polygon class="st6" points="178.9,267.9 210.4,253 199.4,262 "/>
<polygon class="st7" points="136.6,258.6 136.8,267.9 105.3,253 "/>
<polygon class="st7" points="179.2,258.6 210.4,253 178.9,267.9 "/>
<polygon class="st3" points="86.4,167.7 110,213.7 78.9,213.3 "/>
<polygon class="st3" points="236.9,213.3 205.7,213.7 229.3,167.7 "/>
<polygon class="st8" points="86.4,167.7 109.2,190.8 110,213.7 "/>
<polygon class="st8" points="229.3,167.7 205.7,213.7 206.6,190.8 "/>
<polygon class="st7" points="105.3,253 139.2,236.5 136.6,258.6 "/>
<polygon class="st7" points="210.4,253 179.2,258.6 176.5,236.5 "/>
<polygon class="st1" points="139.2,236.5 105.3,253 110,213.7 "/>
<polygon class="st1" points="176.5,236.5 205.7,213.7 210.4,253 "/>
<polygon class="st5" points="173.1,170.2 229.3,167.7 206.6,190.8 "/>
<polygon class="st5" points="109.2,190.8 86.4,167.7 142.6,170.2 "/>
<polygon class="st5" points="142.6,170.2 129.1,181.7 109.2,190.8 "/>
<polygon class="st5" points="206.6,190.8 186.6,181.7 173.1,170.2 "/>
<polygon class="st3" points="205.7,213.7 178.3,199.1 206.6,190.8 "/>
<polygon class="st3" points="110,213.7 109.2,190.8 137.4,199.1 "/>
<polygon class="st9" points="137.4,199.1 109.2,190.8 129.1,181.7 "/>
<polygon class="st9" points="178.3,199.1 186.6,181.7 206.6,190.8 "/>
<polygon class="st5" points="186.6,181.7 178.3,199.1 173.1,170.2 "/>
<polygon class="st5" points="129.1,181.7 142.6,170.2 137.4,199.1 "/>
<polygon class="st6" points="199.4,262 177,277.5 178.9,267.9 "/>
<polygon class="st6" points="136.8,267.9 138.7,277.5 116.3,262 "/>
<polygon class="st4" points="178.3,199.1 171.8,188.4 173.1,170.2 "/>
<polygon class="st8" points="137.4,199.1 142.6,170.2 143.9,188.4 "/>
<polygon class="st3" points="173.1,170.2 171.8,188.4 143.9,188.4 "/>
<polygon class="st3" points="143.9,188.4 142.6,170.2 173.1,170.2 "/>
<polygon class="st3" points="178.3,199.1 205.7,213.7 176.5,236.5 "/>
<polygon class="st3" points="139.2,236.5 110,213.7 137.4,199.1 "/>
<polygon class="st3" points="137.4,199.1 144,233.2 139.2,236.5 "/>
<polygon class="st3" points="176.5,236.5 171.7,233.2 178.3,199.1 "/>
<polygon class="st8" points="171.8,188.4 178.3,199.1 171.7,233.2 "/>
<polygon class="st8" points="143.9,188.4 144,233.2 137.4,199.1 "/>
<polygon class="st3" points="143.9,188.4 171.8,188.4 171.7,233.2 "/>
<polygon class="st3" points="171.7,233.2 144,233.2 143.9,188.4 "/>
<polygon class="st6" points="179.2,258.6 178.9,267.9 177,277.5 "/>
<polygon class="st6" points="138.7,277.5 136.8,267.9 136.6,258.6 "/>
<polygon class="st6" points="136.6,258.6 139,256.4 138.7,277.5 "/>
<polygon class="st6" points="177,277.5 176.7,256.4 179.2,258.6 "/>
<polygon class="st6" points="138.7,277.5 139,256.4 176.7,256.4 "/>
<polygon class="st6" points="176.7,256.4 177,277.5 138.7,277.5 "/>
<polygon class="st10" points="176.5,236.5 179.2,258.6 176.7,256.4 "/>
<polygon class="st10" points="139,256.4 136.6,258.6 139.2,236.5 "/>
<polygon class="st10" points="139.2,236.5 140.7,241.2 139,256.4 "/>
<polygon class="st10" points="176.7,256.4 175,241.2 176.5,236.5 "/>
<polygon class="st10" points="143.7,237.7 140.7,241.2 139.2,236.5 "/>
<polygon class="st10" points="176.5,236.5 175,241.2 172,237.7 "/>
<polygon class="st10" points="172,237.7 171.7,233.2 176.5,236.5 "/>
<polygon class="st10" points="139.2,236.5 144,233.2 143.7,237.7 "/>
<polygon class="st10" points="171.7,233.2 172,237.7 143.7,237.7 "/>
<polygon class="st10" points="143.7,237.7 144,233.2 171.7,233.2 "/>
<polygon class="st10" points="140.7,241.2 175,241.2 176.7,256.4 "/>
<polygon class="st10" points="176.7,256.4 139,256.4 140.7,241.2 "/>
<polygon class="st10" points="140.7,241.2 143.7,237.7 172,237.7 "/>
<polygon class="st10" points="172,237.7 175,241.2 140.7,241.2 "/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 8.6 KiB

11
app/images/mm-bolt.svg Normal file
View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 21.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 252 251.7" style="enable-background:new 0 0 252 251.7;" xml:space="preserve">
<style type="text/css">
.st0{fill:#757575;}
</style>
<path class="st0" d="M211.3,103.9h-60.7c-2,0-3.6-1.6-3.6-3.6V3.6c0-3.5-4.5-5-6.6-2.2l-102.7,140c-1.8,2.4,0,5.8,2.9,5.8h60.7
c2,0,3.6,1.6,3.6,3.6v96.6c0,3.5,4.5,5,6.6,2.2l102.7-140C216,107.3,214.3,103.9,211.3,103.9z"/>
</svg>

After

Width:  |  Height:  |  Size: 732 B

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 21.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 10 10" style="enable-background:new 0 0 10 10;" xml:space="preserve">
<style type="text/css">
.st0{fill:#B8B8B8;}
</style>
<path class="st0" d="M5,0C2.2,0,0,2.2,0,5s2.2,5,5,5s5-2.2,5-5S7.8,0,5,0z M5,2c0.4,0,0.7,0.3,0.7,0.7c0,0.4-0.3,0.7-0.7,0.7
S4.3,3.2,4.3,2.8C4.3,2.4,4.6,2,5,2z M5.7,8H4.3V4.3h1.5V8z"/>
</svg>

After

Width:  |  Height:  |  Size: 689 B

15
app/images/open.svg Normal file
View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="28px" height="28px" viewBox="0 0 28 28" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 47.1 (45422) - http://www.bohemiancoding.com/sketch -->
<title>open</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="Mobile-screens" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="MetaMascara-Mobile---structured" transform="translate(-329.000000, -93.000000)">
<g id="open" transform="translate(330.000000, 94.000000)">
<path d="M26,13 C26,20.1799 20.1799,26 13,26 C5.8201,26 0,20.1799 0,13 C0,5.8201 5.8201,0 13,0 C20.1799,0 26,5.8201 26,13 Z" id="Stroke-3" stroke="#4A4A4A"></path>
<path d="M6,17 C6,17 7.78735344,10.8360387 13.7616996,10.8360387 L13.7616996,8 L19,12.3733433 L13.7616996,17 L13.7616996,14.1639613 C13.7616996,14.1639613 9.54083576,13.4629933 6,17" id="Fill-5" fill="#4A4A4A"></path>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 47 (45396) - http://www.bohemiancoding.com/sketch -->
<title>plus-btn-white</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="MetaMascara-v2" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="account-dropdown-top-bar-IXD" transform="translate(-24.000000, -669.000000)" fill="#FFFFFF">
<g id="Group-6" transform="translate(4.000000, 646.000000)">
<g id="plus-btn-white" transform="translate(20.000000, 23.000000)">
<rect id="Rectangle-48" x="7.38461538" y="0" width="1.23076923" height="16"></rect>
<rect id="Rectangle-48" transform="translate(8.000000, 8.000000) rotate(-90.000000) translate(-8.000000, -8.000000) " x="7.38461538" y="0" width="1.23076923" height="16"></rect>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

21
app/images/popout.svg Normal file
View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="22px" height="22px" viewBox="0 0 22 22" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 48.2 (47327) - http://www.bohemiancoding.com/sketch -->
<title>popout</title>
<desc>Created with Sketch.</desc>
<defs>
<polygon id="path-1" points="-0.00035 0 10.9999 0 10.9999 10.9997 -0.00035 10.9997"></polygon>
</defs>
<g id="MetaMascara-Mobile---structured-TOKEN" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" transform="translate(-327.000000, -96.000000)">
<g id="popout" transform="translate(327.000000, 96.000000)">
<g id="Group-3" transform="translate(11.000000, 0.000000)">
<mask id="mask-2" fill="white">
<use xlink:href="#path-1"></use>
</mask>
<g id="Clip-2"></g>
<path d="M10.9229,0.6177 C10.8209,0.3737 10.6269,0.1787 10.3819,0.0767 C10.2599,0.0267 10.1309,-0.0003 9.9999,-0.0003 L3.9999,-0.0003 C3.4479,-0.0003 2.9999,0.4477 2.9999,0.9997 C2.9999,1.5527 3.4479,1.9997 3.9999,1.9997 L7.5859,1.9997 L0.2929,9.2927 C-0.0981,9.6837 -0.0981,10.3167 0.2929,10.7067 C0.4879,10.9027 0.7439,10.9997 0.9999,10.9997 C1.2559,10.9997 1.5119,10.9027 1.7069,10.7067 L8.9999,3.4137 L8.9999,6.9997 C8.9999,7.5527 9.4479,7.9997 9.9999,7.9997 C10.5519,7.9997 10.9999,7.5527 10.9999,6.9997 L10.9999,0.9997 C10.9999,0.8697 10.9739,0.7407 10.9229,0.6177" id="Fill-1" fill="#4A4A4A" mask="url(#mask-2)"></path>
</g>
<path d="M19,10 C18.448,10 18,10.448 18,11 L18,19 C18,19.551 17.551,20 17,20 L3,20 C2.449,20 2,19.551 2,19 L2,5 C2,4.449 2.449,4 3,4 L11,4 C11.552,4 12,3.552 12,3 C12,2.448 11.552,2 11,2 L3,2 C1.346,2 0,3.346 0,5 L0,19 C0,20.654 1.346,22 3,22 L17,22 C18.654,22 20,20.654 20,19 L20,11 C20,10.448 19.552,10 19,10" id="Fill-4" fill="#4A4A4A"></path>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -1,24 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="24.088px" height="24px" viewBox="0 0 24.088 24" enable-background="new 0 0 24.088 24" xml:space="preserve">
<path d="M21.525,10.147c-0.41-0.059-0.847-0.428-0.974-0.82l-0.608-1.481c-0.191-0.365-0.146-0.935,0.1-1.264l0.99-1.318
c0.246-0.33,0.227-0.854-0.047-1.162l-1.084-1.086c-0.31-0.272-0.832-0.293-1.164-0.045l-1.316,0.988
c-0.33,0.248-0.898,0.293-1.264,0.101l-1.48-0.609c-0.395-0.126-0.764-0.562-0.82-0.971l-0.233-1.629
c-0.058-0.409-0.44-0.778-0.851-0.822c0,0-0.254-0.026-0.77-0.026c-0.514,0-0.77,0.026-0.77,0.026
c-0.41,0.044-0.793,0.413-0.852,0.822L10.15,2.48c-0.059,0.409-0.428,0.845-0.82,0.971L7.85,4.06
C7.484,4.251,6.916,4.207,6.586,3.959L5.268,2.97c-0.33-0.248-0.854-0.228-1.162,0.045L3.021,4.101
C2.749,4.41,2.727,4.933,2.975,5.263l0.988,1.318c0.249,0.33,0.293,0.899,0.102,1.264l-0.61,1.482
c-0.125,0.393-0.562,0.762-0.972,0.82l-1.629,0.231c-0.408,0.059-0.776,0.442-0.82,0.853c0,0-0.026,0.255-0.026,0.77
c0,0.516,0.026,0.77,0.026,0.77c0.044,0.412,0.412,0.793,0.82,0.853l1.629,0.231c0.408,0.06,0.847,0.429,0.972,0.82l0.61,1.48
c0.191,0.365,0.146,0.936-0.102,1.264l-0.988,1.318c-0.248,0.33-0.308,0.779-0.132,0.994c0.175,0.217,0.677,0.752,0.679,0.754
c0,0.002,0.17,0.156,0.375,0.344c0.203,0.188,1.041,0.449,1.371,0.203l1.317-0.99c0.33-0.246,0.897-0.293,1.265-0.1l1.479,0.608
c0.394,0.125,0.763,0.562,0.819,0.972l0.233,1.629c0.058,0.408,0.44,0.779,0.853,0.822c0,0,0.254,0.026,0.769,0.026
s0.771-0.026,0.771-0.026c0.408-0.043,0.793-0.414,0.85-0.822l0.234-1.629c0.057-0.408,0.426-0.847,0.819-0.972l1.479-0.61
c0.365-0.191,0.935-0.146,1.265,0.102l1.317,0.99c0.332,0.246,0.854,0.227,1.164-0.047l1.082-1.084
c0.273-0.312,0.293-0.834,0.047-1.164l-0.989-1.318c-0.246-0.328-0.291-0.898-0.101-1.264l0.609-1.48
c0.127-0.393,0.562-0.762,0.973-0.82l1.627-0.231c0.41-0.06,0.779-0.44,0.822-0.853c0,0,0.027-0.254,0.027-0.77
c0-0.515-0.027-0.77-0.027-0.77c-0.043-0.41-0.412-0.794-0.822-0.853L21.525,10.147z M12.004,15.001c-1.657,0-3-1.344-3-3
c0-1.657,1.343-3,3-3s3,1.344,3,3S13.66,15.001,12.004,15.001z"/>
</svg>
<?xml version="1.0" encoding="UTF-8"?>
<svg width="20px" height="20px" viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 47 (45396) - http://www.bohemiancoding.com/sketch -->
<title>settings</title>
<desc>Created with Sketch.</desc>
<defs>
<polygon id="path-1" points="20 10 20 19.9998 0 19.9998 0 10 0 0.0002 20 0.0002"></polygon>
</defs>
<g id="MetaMascara-v2" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="account-dropdown-top-bar-IXD" transform="translate(-25.000000, -826.000000)">
<g id="Group-6" transform="translate(4.000000, 646.000000)">
<g id="settings" transform="translate(21.000000, 180.000000)">
<mask id="mask-2" fill="white">
<use xlink:href="#path-1"></use>
</mask>
<g id="Clip-2"></g>
<path d="M10,13.6602 C7.979,13.6602 6.34,12.0212 6.34,10.0002 C6.34,7.9782 7.979,6.3402 10,6.3402 C12.021,6.3402 13.66,7.9782 13.66,10.0002 C13.66,12.0212 12.021,13.6602 10,13.6602 L10,13.6602 Z M19.157,11.8112 C19.53,11.8112 19.878,11.5092 19.929,11.1392 C19.929,11.1392 20,10.6182 20,10.0002 C20,9.3822 19.929,8.8622 19.929,8.8622 C19.878,8.4922 19.53,8.1892 19.157,8.1892 L17.228,8.1892 C16.854,8.1892 16.466,7.9512 16.365,7.6602 C16.265,7.3682 16.127,6.4352 16.391,6.1712 L17.755,4.8072 C18.019,4.5432 18.039,4.0922 17.8,3.8052 L16.195,2.2002 C15.908,1.9602 15.458,1.9812 15.193,2.2452 L13.829,3.6092 C13.565,3.8732 13.125,3.9802 12.852,3.8462 C12.578,3.7122 11.812,3.1462 11.812,2.7732 L11.812,0.8432 C11.812,0.4702 11.509,0.1222 11.139,0.0722 C11.139,0.0722 10.619,0.0002 10,0.0002 C9.382,0.0002 8.862,0.0722 8.862,0.0722 C8.492,0.1222 8.189,0.4702 8.189,0.8432 L8.189,2.7732 C8.189,3.1462 7.951,3.5352 7.66,3.6352 C7.369,3.7352 6.435,3.8732 6.171,3.6092 L4.807,2.2452 C4.542,1.9812 4.092,1.9612 3.805,2.2002 L2.2,3.8052 C1.96,4.0922 1.981,4.5432 2.245,4.8072 L3.609,6.1712 C3.873,6.4352 3.98,6.8752 3.846,7.1482 C3.711,7.4222 3.146,8.1892 2.773,8.1892 L0.843,8.1892 C0.47,8.1892 0.123,8.4922 0.072,8.8622 C0.072,8.8622 0,9.3822 0,10.0002 C0,10.6182 0.072,11.1392 0.072,11.1392 C0.123,11.5092 0.47,11.8112 0.843,11.8112 L2.773,11.8112 C3.146,11.8112 3.535,12.0502 3.635,12.3412 C3.735,12.6322 3.874,13.5642 3.609,13.8292 L2.246,15.1932 C1.981,15.4572 1.961,15.9082 2.2,16.1952 L3.805,17.8002 C4.092,18.0392 4.542,18.0192 4.807,17.7552 L6.171,16.3902 C6.435,16.1272 6.875,16.0202 7.148,16.1542 C7.422,16.2882 8.189,16.8532 8.189,17.2272 L8.189,19.1572 C8.189,19.5302 8.492,19.8782 8.862,19.9292 C8.862,19.9292 9.382,20.0002 10,20.0002 C10.619,20.0002 11.139,19.9292 11.139,19.9292 C11.509,19.8772 11.812,19.5302 11.812,19.1572 L11.812,17.2272 C11.812,16.8532 12.05,16.4662 12.341,16.3652 C12.632,16.2642 13.565,16.1272 13.829,16.3902 L15.193,17.7552 C15.458,18.0182 15.908,18.0392 16.195,17.8002 L17.8,16.1952 C18.039,15.9082 18.02,15.4582 17.755,15.1932 L16.391,13.8292 C16.127,13.5652 16.021,13.1252 16.154,12.8512 C16.288,12.5782 16.854,11.8112 17.228,11.8112 L19.157,11.8112 Z" id="Fill-1" fill="#B3B3B3" mask="url(#mask-2)"></path>
</g>
</g>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

@ -1,7 +1,7 @@
{
"name": "MetaMask",
"short_name": "Metamask",
"version": "3.5.2",
"version": "4.1.0",
"manifest_version": 2,
"author": "https://metamask.io",
"description": "Ethereum Browser Extension",
@ -58,8 +58,8 @@
"storage",
"clipboardWrite",
"http://localhost:8545/",
"https://www.cryptonator.com/"
],
"https://*.infura.io/"
],
"web_accessible_resources": [
"scripts/inpage.js"
],

View File

@ -1,5 +1,5 @@
<!doctype html>
<html>
<html style="height:600px;">
<head>
<meta charset="utf-8">
<title>MetaMask Notification</title>
@ -9,7 +9,7 @@
}
</style>
</head>
<body>
<body class="notification" style="height:600px;">
<div id="app-content"></div>
<script src="./scripts/popup.js" type="text/javascript" charset="utf-8"></script>
</body>

View File

@ -1,11 +1,12 @@
<!doctype html>
<html>
<html style="width:357px; height:600px;">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1 user-scalable=no">
<title>MetaMask Plugin</title>
</head>
<body>
<body style="width:357px; height:600px;">
<div id="app-content"></div>
<script src="./scripts/popup.js" type="text/javascript" charset="utf-8"></script>
</body>
</html>
</html>

View File

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

View File

@ -1,22 +1,24 @@
const urlUtil = require('url')
const endOfStream = require('end-of-stream')
const asyncQ = require('async-q')
const pipe = require('pump')
const pump = require('pump')
const log = require('loglevel')
const extension = require('extensionizer')
const LocalStorageStore = require('obs-store/lib/localStorage')
const storeTransform = require('obs-store/lib/transform')
const asStream = require('obs-store/lib/asStream')
const ExtensionPlatform = require('./platforms/extension')
const Migrator = require('./lib/migrator/')
const migrations = require('./migrations/')
const PortStream = require('./lib/port-stream.js')
const NotificationManager = require('./lib/notification-manager.js')
const MetamaskController = require('./metamask-controller')
const extension = require('extensionizer')
const firstTimeState = require('./first-time-state')
const setupRaven = require('./setupRaven')
const setupMetamaskMeshMetrics = require('./lib/setupMetamaskMeshMetrics')
const STORAGE_KEY = 'metamask-config'
const METAMASK_DEBUG = 'GULP_METAMASK_DEBUG'
const log = require('loglevel')
window.log = log
log.setDefaultLevel(METAMASK_DEBUG ? 'debug' : 'warn')
@ -24,44 +26,46 @@ const platform = new ExtensionPlatform()
const notificationManager = new NotificationManager()
global.METAMASK_NOTIFIER = notificationManager
// setup sentry error reporting
const release = platform.getVersion()
const raven = setupRaven({ release })
let popupIsOpen = false
let openMetamaskTabsIDs = {}
// 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(log.error)
// setup metamask mesh testing container
setupMetamaskMeshMetrics()
async function initialize () {
const initState = await loadStateFromPersistence()
await setupController(initState)
log.debug('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
//
@ -78,15 +82,26 @@ function setupController (initState) {
})
global.metamaskController = controller
// report failed transactions to Sentry
controller.txController.on(`tx:status-update`, (txId, status) => {
if (status !== 'failed') return
const txMeta = controller.txController.txStateManager.getTx(txId)
const errorMessage = `Transaction Failed: ${txMeta.err.message}`
raven.captureMessage(errorMessage, {
// "extra" key is required by Sentry
extra: txMeta,
})
})
// setup state persistence
pipe(
controller.store,
pump(
asStream(controller.store),
storeTransform(versionifyData),
diskStore
asStream(diskStore)
)
function versionifyData(state) {
let versionedData = diskStore.getState()
function versionifyData (state) {
const versionedData = diskStore.getState()
versionedData.data = state
return versionedData
}
@ -97,21 +112,27 @@ function setupController (initState) {
extension.runtime.onConnect.addListener(connectRemote)
function connectRemote (remotePort) {
var isMetaMaskInternalProcess = remotePort.name === 'popup' || remotePort.name === 'notification'
var portStream = new PortStream(remotePort)
const isMetaMaskInternalProcess = remotePort.name === 'popup' || remotePort.name === 'notification'
const portStream = new PortStream(remotePort)
if (isMetaMaskInternalProcess) {
// communication with popup
popupIsOpen = popupIsOpen || (remotePort.name === 'popup')
controller.setupTrustedCommunication(portStream, 'MetaMask', remotePort.name)
controller.setupTrustedCommunication(portStream, 'MetaMask')
// record popup as closed
if (remotePort.sender.url.match(/home.html$/)) {
openMetamaskTabsIDs[remotePort.sender.tab.id] = true
}
if (remotePort.name === 'popup') {
endOfStream(portStream, () => {
popupIsOpen = false
if (remotePort.sender.url.match(/home.html$/)) {
openMetamaskTabsIDs[remotePort.sender.tab.id] = false
}
})
}
} else {
// communication with page
var originDomain = urlUtil.parse(remotePort.sender.url).hostname
const originDomain = urlUtil.parse(remotePort.sender.url).hostname
controller.setupUntrustedCommunication(portStream, originDomain)
}
}
@ -121,15 +142,18 @@ function setupController (initState) {
//
updateBadge()
controller.txManager.on('updateBadge', updateBadge)
controller.txController.on('update:badge', updateBadge)
controller.messageManager.on('updateBadge', updateBadge)
controller.personalMessageManager.on('updateBadge', updateBadge)
// plugin badge text
function updateBadge () {
var label = ''
var unapprovedTxCount = controller.txManager.unapprovedTxCount
var unapprovedTxCount = controller.txController.getUnapprovedTxCount()
var unapprovedMsgCount = controller.messageManager.unapprovedMsgCount
var count = unapprovedTxCount + unapprovedMsgCount
var unapprovedPersonalMsgs = controller.personalMessageManager.unapprovedPersonalMsgCount
var unapprovedTypedMsgs = controller.typedMessageManager.unapprovedTypedMessagesCount
var count = unapprovedTxCount + unapprovedMsgCount + unapprovedPersonalMsgs + unapprovedTypedMsgs
if (count) {
label = String(count)
}
@ -138,7 +162,6 @@ function setupController (initState) {
}
return Promise.resolve()
}
//
@ -147,7 +170,10 @@ function setupController (initState) {
// popup trigger
function triggerUi () {
if (!popupIsOpen) notificationManager.showPopup()
extension.tabs.query({ active: true }, (tabs) => {
const currentlyActiveMetamaskTab = tabs.find(tab => openMetamaskTabsIDs[tab.id])
if (!popupIsOpen && !currentlyActiveMetamaskTab) notificationManager.showPopup()
})
}
// On first install, open a window to MetaMask website to how-it-works.

View File

@ -1,16 +1,44 @@
const MAINET_RPC_URL = 'https://mainnet.infura.io/metamask'
const TESTNET_RPC_URL = 'https://ropsten.infura.io/metamask'
const ROPSTEN_RPC_URL = 'https://ropsten.infura.io/metamask'
const KOVAN_RPC_URL = 'https://kovan.infura.io/metamask'
const DEFAULT_RPC_URL = TESTNET_RPC_URL
const RINKEBY_RPC_URL = 'https://rinkeby.infura.io/metamask'
const LOCALHOST_RPC_URL = 'http://localhost:8545'
const MAINET_RPC_URL_BETA = 'https://mainnet.infura.io/metamask2'
const ROPSTEN_RPC_URL_BETA = 'https://ropsten.infura.io/metamask2'
const KOVAN_RPC_URL_BETA = 'https://kovan.infura.io/metamask2'
const RINKEBY_RPC_URL_BETA = 'https://rinkeby.infura.io/metamask2'
const DEFAULT_RPC = 'rinkeby'
const OLD_UI_NETWORK_TYPE = 'network'
const BETA_UI_NETWORK_TYPE = 'networkBeta'
global.METAMASK_DEBUG = 'GULP_METAMASK_DEBUG'
module.exports = {
network: {
default: DEFAULT_RPC_URL,
localhost: LOCALHOST_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,
},
// Used for beta UI
networkBeta: {
localhost: LOCALHOST_RPC_URL,
mainnet: MAINET_RPC_URL_BETA,
ropsten: ROPSTEN_RPC_URL_BETA,
kovan: KOVAN_RPC_URL_BETA,
rinkeby: RINKEBY_RPC_URL_BETA,
},
networkNames: {
3: 'Ropsten',
4: 'Rinkeby',
42: 'Kovan',
},
enums: {
DEFAULT_RPC,
OLD_UI_NETWORK_TYPE,
BETA_UI_NETWORK_TYPE,
},
}

View File

@ -1,12 +1,15 @@
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('extensionizer')
const fs = require('fs')
const path = require('path')
const inpageText = fs.readFileSync(path.join(__dirname, 'inpage.js')).toString()
const pump = require('pump')
const LocalMessageDuplexStream = require('post-message-stream')
const PongStream = require('ping-pong-stream/pong')
const ObjectMultiplex = require('obj-multiplex')
const extension = require('extensionizer')
const PortStream = require('./lib/port-stream.js')
const inpageContent = fs.readFileSync(path.join(__dirname, '..', '..', 'dist', 'chrome', 'scripts', 'inpage.js')).toString()
const inpageSuffix = '//# sourceURL=' + extension.extension.getURL('scripts/inpage.js') + '\n'
const inpageBundle = inpageContent + inpageSuffix
// Eventually this streaming injection could be replaced with:
// https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Language_Bindings/Components.utils.exportFunction
@ -24,8 +27,7 @@ function setupInjection () {
try {
// inject in-page script
var scriptTag = document.createElement('script')
scriptTag.src = extension.extension.getURL('scripts/inpage.js')
scriptTag.textContent = inpageText
scriptTag.textContent = inpageBundle
scriptTag.onload = function () { this.parentNode.removeChild(this) }
var container = document.head || document.documentElement
// append as first child
@ -37,35 +39,64 @@ function setupInjection () {
function setupStreams () {
// setup communication to page and plugin
var pageStream = new LocalMessageDuplexStream({
const pageStream = new LocalMessageDuplexStream({
name: 'contentscript',
target: 'inpage',
})
pageStream.on('error', console.error)
var pluginPort = extension.runtime.connect({name: 'contentscript'})
var pluginStream = new PortStream(pluginPort)
pluginStream.on('error', console.error)
const pluginPort = extension.runtime.connect({ name: 'contentscript' })
const pluginStream = new PortStream(pluginPort)
// forward communication plugin->inpage
pageStream.pipe(pluginStream).pipe(pageStream)
pump(
pageStream,
pluginStream,
pageStream,
(err) => logStreamDisconnectWarning('MetaMask Contentscript Forwarding', err)
)
// setup local multistream channels
var mx = ObjectMultiplex()
mx.on('error', console.error)
mx.pipe(pageStream).pipe(mx)
const mux = new ObjectMultiplex()
mux.setMaxListeners(25)
pump(
mux,
pageStream,
mux,
(err) => logStreamDisconnectWarning('MetaMask Inpage', err)
)
pump(
mux,
pluginStream,
mux,
(err) => logStreamDisconnectWarning('MetaMask Background', err)
)
// connect ping stream
var pongStream = new PongStream({ objectMode: true })
pongStream.pipe(mx.createStream('pingpong')).pipe(pongStream)
const pongStream = new PongStream({ objectMode: true })
pump(
mux,
pongStream,
mux,
(err) => logStreamDisconnectWarning('MetaMask PingPongStream', err)
)
// ignore unused channels (handled by background)
mx.ignoreStream('provider')
mx.ignoreStream('publicConfig')
mx.ignoreStream('reload')
// connect phishing warning stream
const phishingStream = mux.createStream('phishing')
phishingStream.once('data', redirectToPhishingWarning)
// ignore unused channels (handled by background, inpage)
mux.ignoreStream('provider')
mux.ignoreStream('publicConfig')
}
function logStreamDisconnectWarning (remoteLabel, err) {
let warningMsg = `MetamaskContentscript - lost connection to ${remoteLabel}`
if (err) warningMsg += '\n' + err.stack
console.warn(warningMsg)
}
function shouldInjectWeb3 () {
return doctypeCheck() || suffixCheck()
return doctypeCheck() && suffixCheck() && documentElementCheck()
}
function doctypeCheck () {
@ -73,19 +104,32 @@ function doctypeCheck () {
if (doctype) {
return doctype.name === 'html'
} else {
return false
return true
}
}
function suffixCheck() {
function suffixCheck () {
var prohibitedTypes = ['xml', 'pdf']
var currentUrl = window.location.href
var currentRegex
for (let i = 0; i < prohibitedTypes.length; i++) {
currentRegex = new RegExp(`\.${prohibitedTypes[i]}$`)
currentRegex = new RegExp(`\\.${prohibitedTypes[i]}$`)
if (currentRegex.test(currentUrl)) {
return false
}
}
return true
}
function documentElementCheck () {
var documentElement = document.documentElement.nodeName
if (documentElement) {
return documentElement.toLowerCase() === 'html'
}
return true
}
function redirectToPhishingWarning () {
console.log('MetaMask - redirecting to phishing warning')
window.location.href = 'https://metamask.io/phishing.html'
}

View File

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

View File

@ -0,0 +1,80 @@
const ObservableStore = require('obs-store')
const PendingBalanceCalculator = require('../lib/pending-balance-calculator')
const BN = require('ethereumjs-util').BN
class BalanceController {
constructor (opts = {}) {
this._validateParams(opts)
const { address, accountTracker, txController, blockTracker } = opts
this.address = address
this.accountTracker = accountTracker
this.txController = txController
this.blockTracker = blockTracker
const initState = {
ethBalance: undefined,
}
this.store = new ObservableStore(initState)
this.balanceCalc = new PendingBalanceCalculator({
getBalance: () => this._getBalance(),
getPendingTransactions: this._getPendingTransactions.bind(this),
})
this._registerUpdates()
}
async updateBalance () {
const balance = await this.balanceCalc.getBalance()
this.store.updateState({
ethBalance: balance,
})
}
_registerUpdates () {
const update = this.updateBalance.bind(this)
this.txController.on('tx:status-update', (txId, status) => {
switch (status) {
case 'submitted':
case 'confirmed':
case 'failed':
update()
return
default:
return
}
})
this.accountTracker.store.subscribe(update)
this.blockTracker.on('block', update)
}
async _getBalance () {
const { accounts } = this.accountTracker.store.getState()
const entry = accounts[this.address]
const balance = entry.balance
return balance ? new BN(balance.substring(2), 16) : undefined
}
async _getPendingTransactions () {
const pending = this.txController.getFilteredTxList({
from: this.address,
status: 'submitted',
err: undefined,
})
return pending
}
_validateParams (opts) {
const { address, accountTracker, txController, blockTracker } = opts
if (!address || !accountTracker || !txController || !blockTracker) {
const error = 'Cannot construct a balance checker without address, accountTracker, txController, and blockTracker.'
throw new Error(error)
}
}
}
module.exports = BalanceController

View File

@ -0,0 +1,60 @@
const ObservableStore = require('obs-store')
const extend = require('xtend')
const PhishingDetector = require('eth-phishing-detect/src/detector')
// compute phishing lists
const PHISHING_DETECTION_CONFIG = require('eth-phishing-detect/src/config.json')
// every four minutes
const POLLING_INTERVAL = 4 * 60 * 1000
class BlacklistController {
constructor (opts = {}) {
const initState = extend({
phishing: PHISHING_DETECTION_CONFIG,
}, opts.initState)
this.store = new ObservableStore(initState)
// phishing detector
this._phishingDetector = null
this._setupPhishingDetector(initState.phishing)
// polling references
this._phishingUpdateIntervalRef = null
}
//
// PUBLIC METHODS
//
checkForPhishing (hostname) {
if (!hostname) return false
const { result } = this._phishingDetector.check(hostname)
return result
}
async updatePhishingList () {
const response = await fetch('https://api.infura.io/v2/blacklist')
const phishing = await response.json()
this.store.updateState({ phishing })
this._setupPhishingDetector(phishing)
return phishing
}
scheduleUpdates () {
if (this._phishingUpdateIntervalRef) return
this.updatePhishingList()
this._phishingUpdateIntervalRef = setInterval(() => {
this.updatePhishingList()
}, POLLING_INTERVAL)
}
//
// PRIVATE METHODS
//
_setupPhishingDetector (config) {
this._phishingDetector = new PhishingDetector(config)
}
}
module.exports = BlacklistController

View File

@ -0,0 +1,77 @@
const ObservableStore = require('obs-store')
const extend = require('xtend')
const BalanceController = require('./balance')
class ComputedbalancesController {
constructor (opts = {}) {
const { accountTracker, txController, blockTracker } = opts
this.accountTracker = accountTracker
this.txController = txController
this.blockTracker = blockTracker
const initState = extend({
computedBalances: {},
}, opts.initState)
this.store = new ObservableStore(initState)
this.balances = {}
this._initBalanceUpdating()
}
updateAllBalances () {
Object.keys(this.balances).forEach((balance) => {
const address = balance.address
this.balances[address].updateBalance()
})
}
_initBalanceUpdating () {
const store = this.accountTracker.store.getState()
this.syncAllAccountsFromStore(store)
this.accountTracker.store.subscribe(this.syncAllAccountsFromStore.bind(this))
}
syncAllAccountsFromStore (store) {
const upstream = Object.keys(store.accounts)
const balances = Object.keys(this.balances)
.map(address => this.balances[address])
// Follow new addresses
for (const address in balances) {
this.trackAddressIfNotAlready(address)
}
// Unfollow old ones
balances.forEach(({ address }) => {
if (!upstream.includes(address)) {
delete this.balances[address]
}
})
}
trackAddressIfNotAlready (address) {
const state = this.store.getState()
if (!(address in state.computedBalances)) {
this.trackAddress(address)
}
}
trackAddress (address) {
const updater = new BalanceController({
address,
accountTracker: this.accountTracker,
txController: this.txController,
blockTracker: this.blockTracker,
})
updater.store.subscribe((accountBalance) => {
const newState = this.store.getState()
newState.computedBalances[address] = accountBalance
this.store.updateState(newState)
})
this.balances[address] = updater
updater.updateBalance()
}
}
module.exports = ComputedbalancesController

View File

@ -8,7 +8,7 @@ class CurrencyController {
constructor (opts = {}) {
const initState = extend({
currentCurrency: 'USD',
currentCurrency: 'usd',
conversionRate: 0,
conversionDate: 'N/A',
}, opts.initState)
@ -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.infura.io/v1/ticker/eth${currentCurrency.toLowerCase()}`)
.then(response => response.json())
.then((parsedResponse) => {
this.setConversionRate(Number(parsedResponse.ticker.price))
this.setConversionRate(Number(parsedResponse.bid))
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')
}
})
}

View File

@ -0,0 +1,43 @@
const ObservableStore = require('obs-store')
const extend = require('xtend')
// every ten minutes
const POLLING_INTERVAL = 10 * 60 * 1000
class InfuraController {
constructor (opts = {}) {
const initState = extend({
infuraNetworkStatus: {},
}, opts.initState)
this.store = new ObservableStore(initState)
}
//
// PUBLIC METHODS
//
// Responsible for retrieving the status of Infura's nodes. Can return either
// ok, degraded, or down.
checkInfuraNetworkStatus () {
return fetch('https://api.infura.io/v1/status/metamask')
.then(response => response.json())
.then((parsedResponse) => {
this.store.updateState({
infuraNetworkStatus: parsedResponse,
})
return parsedResponse
})
}
scheduleInfuraNetworkCheck () {
if (this.conversionInterval) {
clearInterval(this.conversionInterval)
}
this.conversionInterval = setInterval(() => {
this.checkInfuraNetworkStatus()
}, POLLING_INTERVAL)
}
}
module.exports = InfuraController

View File

@ -0,0 +1,214 @@
const assert = require('assert')
const EventEmitter = require('events')
const createMetamaskProvider = require('web3-provider-engine/zero.js')
const SubproviderFromProvider = require('web3-provider-engine/subproviders/web3.js')
const createInfuraProvider = require('eth-json-rpc-infura/src/createProvider')
const ObservableStore = require('obs-store')
const ComposedStore = require('obs-store/lib/composed')
const extend = require('xtend')
const EthQuery = require('eth-query')
const createEventEmitterProxy = require('../lib/events-proxy.js')
const networkConfig = require('../config.js')
const { OLD_UI_NETWORK_TYPE, DEFAULT_RPC } = networkConfig.enums
const INFURA_PROVIDER_TYPES = ['ropsten', 'rinkeby', 'kovan', 'mainnet']
module.exports = class NetworkController extends EventEmitter {
constructor (config) {
super()
this._networkEndpointVersion = OLD_UI_NETWORK_TYPE
this._networkEndpoints = this.getNetworkEndpoints(OLD_UI_NETWORK_TYPE)
this._defaultRpc = this._networkEndpoints[DEFAULT_RPC]
config.provider.rpcTarget = this.getRpcAddressForType(config.provider.type, config.provider)
this.networkStore = new ObservableStore('loading')
this.providerStore = new ObservableStore(config.provider)
this.store = new ComposedStore({ provider: this.providerStore, network: this.networkStore })
this._proxy = createEventEmitterProxy()
this.on('networkDidChange', this.lookupNetwork)
}
async setNetworkEndpoints (version) {
if (version === this._networkEndpointVersion) {
return
}
this._networkEndpointVersion = version
this._networkEndpoints = this.getNetworkEndpoints(version)
this._defaultRpc = this._networkEndpoints[DEFAULT_RPC]
const { type } = this.getProviderConfig()
return this.setProviderType(type, true)
}
getNetworkEndpoints (version = OLD_UI_NETWORK_TYPE) {
return networkConfig[version]
}
initializeProvider (_providerParams) {
this._baseProviderParams = _providerParams
const { type, rpcTarget } = this.providerStore.getState()
// map rpcTarget to rpcUrl
const opts = {
type,
rpcUrl: rpcTarget,
}
this._configureProvider(opts)
this._proxy.on('block', this._logBlock.bind(this))
this._proxy.on('error', this.verifyNetwork.bind(this))
this.ethQuery = new EthQuery(this._proxy)
this.lookupNetwork()
return this._proxy
}
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 () {
// Prevent firing when provider is not defined.
if (!this.ethQuery || !this.ethQuery.sendAsync) {
return log.warn('NetworkController - lookupNetwork aborted due to missing ethQuery')
}
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,
})
this._switchNetwork({ rpcUrl })
}
getCurrentRpcAddress () {
const provider = this.getProviderConfig()
if (!provider) return null
return this.getRpcAddressForType(provider.type)
}
async setProviderType (type, forceUpdate = false) {
assert(type !== 'rpc', `NetworkController.setProviderType - cannot connect by type "rpc"`)
// skip if type already matches
if (type === this.getProviderConfig().type && !forceUpdate) {
return
}
const rpcTarget = this.getRpcAddressForType(type)
assert(rpcTarget, `NetworkController - unknown rpc address for type "${type}"`)
this.providerStore.updateState({ type, rpcTarget })
this._switchNetwork({ type })
}
getProviderConfig () {
return this.providerStore.getState()
}
getRpcAddressForType (type, provider = this.getProviderConfig()) {
if (this._networkEndpoints[type]) {
return this._networkEndpoints[type]
}
return provider && provider.rpcTarget ? provider.rpcTarget : this._defaultRpc
}
//
// Private
//
_switchNetwork (opts) {
this.setNetworkState('loading')
this._configureProvider(opts)
this.emit('networkDidChange')
}
_configureProvider (opts) {
// type-based rpc endpoints
const { type } = opts
if (type) {
// type-based infura rpc endpoints
const isInfura = INFURA_PROVIDER_TYPES.includes(type)
opts.rpcUrl = this.getRpcAddressForType(type)
if (isInfura) {
this._configureInfuraProvider(opts)
// other type-based rpc endpoints
} else {
this._configureStandardProvider(opts)
}
// url-based rpc endpoints
} else {
this._configureStandardProvider(opts)
}
}
_configureInfuraProvider (opts) {
log.info('_configureInfuraProvider', opts)
const infuraProvider = createInfuraProvider({
network: opts.type,
})
const infuraSubprovider = new SubproviderFromProvider(infuraProvider)
const providerParams = extend(this._baseProviderParams, {
rpcUrl: opts.rpcUrl,
engineParams: {
pollingInterval: 8000,
blockTrackerProvider: infuraProvider,
},
dataSubprovider: infuraSubprovider,
})
const provider = createMetamaskProvider(providerParams)
this._setProvider(provider)
}
_configureStandardProvider ({ rpcUrl }) {
const providerParams = extend(this._baseProviderParams, {
rpcUrl,
engineParams: {
pollingInterval: 8000,
},
})
const provider = createMetamaskProvider(providerParams)
this._setProvider(provider)
}
_setProvider (provider) {
// collect old block tracker events
const oldProvider = this._provider
let blockTrackerHandlers
if (oldProvider) {
// capture old block handlers
blockTrackerHandlers = oldProvider._blockTracker.proxyEventHandlers
// tear down
oldProvider.removeAllListeners()
oldProvider.stop()
}
// override block tracler
provider._blockTracker = createEventEmitterProxy(provider._blockTracker, blockTrackerHandlers)
// set as new provider
this._provider = provider
this._proxy.setTarget(provider)
}
_logBlock (block) {
log.info(`BLOCK CHANGED: #${block.number.toString('hex')} 0x${block.hash.toString('hex')}`)
this.verifyNetwork()
}
}

View File

@ -7,13 +7,22 @@ class PreferencesController {
constructor (opts = {}) {
const initState = extend({
frequentRpcList: [],
currentAccountTab: 'history',
tokens: [],
useBlockie: false,
featureFlags: {},
}, opts.initState)
this.store = new ObservableStore(initState)
}
// PUBLIC METHODS
//
// PUBLIC METHODS
//
setUseBlockie (val) {
this.store.updateState({ useBlockie: val })
}
getUseBlockie () {
return this.store.getState().useBlockie
}
setSelectedAddress (_address) {
return new Promise((resolve, reject) => {
@ -23,10 +32,44 @@ class PreferencesController {
})
}
getSelectedAddress (_address) {
getSelectedAddress () {
return this.store.getState().selectedAddress
}
async addToken (rawAddress, symbol, decimals) {
const address = normalizeAddress(rawAddress)
const newEntry = { address, symbol, decimals }
const tokens = this.store.getState().tokens
const previousEntry = tokens.find((token, index) => {
return token.address === address
})
const previousIndex = tokens.indexOf(previousEntry)
if (previousEntry) {
tokens[previousIndex] = newEntry
} else {
tokens.push(newEntry)
}
this.store.updateState({ tokens })
return Promise.resolve(tokens)
}
removeToken (rawAddress) {
const tokens = this.store.getState().tokens
const updatedTokens = tokens.filter(token => token.address !== rawAddress)
this.store.updateState({ tokens: updatedTokens })
return Promise.resolve(updatedTokens)
}
getTokens () {
return this.store.getState().tokens
}
updateFrequentRpcList (_url) {
return this.addToFrequentRpcList(_url)
.then((rpcList) => {
@ -35,9 +78,16 @@ class PreferencesController {
})
}
setCurrentAccountTab (currentAccountTab) {
return new Promise((resolve, reject) => {
this.store.updateState({ currentAccountTab })
resolve()
})
}
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)
}
@ -54,12 +104,24 @@ class PreferencesController {
return this.store.getState().frequentRpcList
}
setFeatureFlag (feature, activated) {
const currentFeatureFlags = this.store.getState().featureFlags
const updatedFeatureFlags = {
...currentFeatureFlags,
[feature]: activated,
}
this.store.updateState({ featureFlags: updatedFeatureFlags })
return Promise.resolve(updatedFeatureFlags)
}
getFeatureFlags () {
return this.store.getState().featureFlags
}
//
// PRIVATE METHODS
//
}
module.exports = PreferencesController

View File

@ -0,0 +1,110 @@
const ObservableStore = require('obs-store')
const extend = require('xtend')
const BN = require('ethereumjs-util').BN
const EthQuery = require('eth-query')
class RecentBlocksController {
constructor (opts = {}) {
const { blockTracker, provider } = opts
this.blockTracker = blockTracker
this.ethQuery = new EthQuery(provider)
this.historyLength = opts.historyLength || 40
const initState = extend({
recentBlocks: [],
}, opts.initState)
this.store = new ObservableStore(initState)
this.blockTracker.on('block', this.processBlock.bind(this))
this.backfill()
}
resetState () {
this.store.updateState({
recentBlocks: [],
})
}
processBlock (newBlock) {
const block = this.mapTransactionsToPrices(newBlock)
const state = this.store.getState()
state.recentBlocks.push(block)
while (state.recentBlocks.length > this.historyLength) {
state.recentBlocks.shift()
}
this.store.updateState(state)
}
backfillBlock (newBlock) {
const block = this.mapTransactionsToPrices(newBlock)
const state = this.store.getState()
if (state.recentBlocks.length < this.historyLength) {
state.recentBlocks.unshift(block)
}
this.store.updateState(state)
}
mapTransactionsToPrices (newBlock) {
const block = extend(newBlock, {
gasPrices: newBlock.transactions.map((tx) => {
return tx.gasPrice
}),
})
delete block.transactions
return block
}
async backfill() {
this.blockTracker.once('block', async (block) => {
let blockNum = block.number
let recentBlocks
let state = this.store.getState()
recentBlocks = state.recentBlocks
while (recentBlocks.length < this.historyLength) {
try {
let blockNumBn = new BN(blockNum.substr(2), 16)
const newNum = blockNumBn.subn(1).toString(10)
const newBlock = await this.getBlockByNumber(newNum)
if (newBlock) {
this.backfillBlock(newBlock)
blockNum = newBlock.number
}
state = this.store.getState()
recentBlocks = state.recentBlocks
} catch (e) {
log.error(e)
}
await this.wait()
}
})
}
async wait () {
return new Promise((resolve) => {
setTimeout(resolve, 100)
})
}
async getBlockByNumber (number) {
const bn = new BN(number)
return new Promise((resolve, reject) => {
this.ethQuery.getBlockByNumber('0x' + bn.toString(16), true, (err, block) => {
if (err) reject(err)
resolve(block)
})
})
}
}
module.exports = RecentBlocksController

View File

@ -0,0 +1,325 @@
const EventEmitter = require('events')
const ObservableStore = require('obs-store')
const ethUtil = require('ethereumjs-util')
const Transaction = require('ethereumjs-tx')
const EthQuery = require('ethjs-query')
const TransactionStateManger = require('../lib/tx-state-manager')
const TxGasUtil = require('../lib/tx-gas-utils')
const PendingTransactionTracker = require('../lib/pending-tx-tracker')
const createId = require('../lib/random-id')
const NonceTracker = require('../lib/nonce-tracker')
/*
Transaction Controller is an aggregate of sub-controllers and trackers
composing them in a way to be exposed to the metamask controller
- txStateManager
responsible for the state of a transaction and
storing the transaction
- pendingTxTracker
watching blocks for transactions to be include
and emitting confirmed events
- txGasUtil
gas calculations and safety buffering
- nonceTracker
calculating nonces
*/
module.exports = class TransactionController extends EventEmitter {
constructor (opts) {
super()
this.networkStore = opts.networkStore || new ObservableStore({})
this.preferencesStore = opts.preferencesStore || new ObservableStore({})
this.provider = opts.provider
this.blockTracker = opts.blockTracker
this.signEthTx = opts.signTransaction
this.getGasPrice = opts.getGasPrice
this.memStore = new ObservableStore({})
this.query = new EthQuery(this.provider)
this.txGasUtil = new TxGasUtil(this.provider)
this.txStateManager = new TransactionStateManger({
initState: opts.initState,
txHistoryLimit: opts.txHistoryLimit,
getNetwork: this.getNetwork.bind(this),
})
this.txStateManager.getFilteredTxList({
status: 'unapproved',
loadingDefaults: true,
}).forEach((tx) => {
this.addTxDefaults(tx)
.then((txMeta) => {
txMeta.loadingDefaults = false
this.txStateManager.updateTx(txMeta, 'transactions: gas estimation for tx on boot')
}).catch((error) => {
this.txStateManager.setTxStatusFailed(tx.id, error)
})
})
this.txStateManager.getFilteredTxList({
status: 'approved',
}).forEach((txMeta) => {
const txSignError = new Error('Transaction found as "approved" during boot - possibly stuck during signing')
this.txStateManager.setTxStatusFailed(txMeta.id, txSignError)
})
this.store = this.txStateManager.store
this.txStateManager.on('tx:status-update', this.emit.bind(this, 'tx:status-update'))
this.nonceTracker = new NonceTracker({
provider: this.provider,
getPendingTransactions: this.txStateManager.getPendingTransactions.bind(this.txStateManager),
getConfirmedTransactions: (address) => {
return this.txStateManager.getFilteredTxList({
from: address,
status: 'confirmed',
err: undefined,
})
},
})
this.pendingTxTracker = new PendingTransactionTracker({
provider: this.provider,
nonceTracker: this.nonceTracker,
publishTransaction: (rawTx) => this.query.sendRawTransaction(rawTx),
getPendingTransactions: this.txStateManager.getPendingTransactions.bind(this.txStateManager),
getCompletedTransactions: this.txStateManager.getConfirmedTransactions.bind(this.txStateManager),
})
this.txStateManager.store.subscribe(() => this.emit('update:badge'))
this.pendingTxTracker.on('tx:warning', (txMeta) => {
this.txStateManager.updateTx(txMeta, 'transactions/pending-tx-tracker#event: tx:warning')
})
this.pendingTxTracker.on('tx:failed', this.txStateManager.setTxStatusFailed.bind(this.txStateManager))
this.pendingTxTracker.on('tx:confirmed', this.txStateManager.setTxStatusConfirmed.bind(this.txStateManager))
this.pendingTxTracker.on('tx:block-update', (txMeta, latestBlockNumber) => {
if (!txMeta.firstRetryBlockNumber) {
txMeta.firstRetryBlockNumber = latestBlockNumber
this.txStateManager.updateTx(txMeta, 'transactions/pending-tx-tracker#event: tx:block-update')
}
})
this.pendingTxTracker.on('tx:retry', (txMeta) => {
if (!('retryCount' in txMeta)) txMeta.retryCount = 0
txMeta.retryCount++
this.txStateManager.updateTx(txMeta, 'transactions/pending-tx-tracker#event: tx:retry')
})
this.blockTracker.on('block', this.pendingTxTracker.checkForTxInBlock.bind(this.pendingTxTracker))
// this is a little messy but until ethstore has been either
// removed or redone this is to guard against the race condition
this.blockTracker.on('latest', this.pendingTxTracker.resubmitPendingTxs.bind(this.pendingTxTracker))
this.blockTracker.on('sync', this.pendingTxTracker.queryPendingTxs.bind(this.pendingTxTracker))
// memstore is computed from a few different stores
this._updateMemstore()
this.txStateManager.store.subscribe(() => this._updateMemstore())
this.networkStore.subscribe(() => this._updateMemstore())
this.preferencesStore.subscribe(() => this._updateMemstore())
}
getState () {
return this.memStore.getState()
}
getNetwork () {
return this.networkStore.getState()
}
getSelectedAddress () {
return this.preferencesStore.getState().selectedAddress
}
getUnapprovedTxCount () {
return Object.keys(this.txStateManager.getUnapprovedTxList()).length
}
getPendingTxCount (account) {
return this.txStateManager.getPendingTransactions(account).length
}
getFilteredTxList (opts) {
return this.txStateManager.getFilteredTxList(opts)
}
getChainId () {
const networkState = this.networkStore.getState()
const getChainId = parseInt(networkState)
if (Number.isNaN(getChainId)) {
return 0
} else {
return getChainId
}
}
wipeTransactions (address) {
this.txStateManager.wipeTransactions(address)
}
// Adds a tx to the txlist
addTx (txMeta) {
this.txStateManager.addTx(txMeta)
this.emit(`${txMeta.id}:unapproved`, txMeta)
}
async newUnapprovedTransaction (txParams) {
log.debug(`MetaMaskController newUnapprovedTransaction ${JSON.stringify(txParams)}`)
const initialTxMeta = await this.addUnapprovedTransaction(txParams)
// listen for tx completion (success, fail)
return new Promise((resolve, reject) => {
this.txStateManager.once(`${initialTxMeta.id}:finished`, (finishedTxMeta) => {
switch (finishedTxMeta.status) {
case 'submitted':
return resolve(finishedTxMeta.hash)
case 'rejected':
return reject(new Error('MetaMask Tx Signature: User denied transaction signature.'))
case 'failed':
return reject(new Error(finishedTxMeta.err.message))
default:
return reject(new Error(`MetaMask Tx Signature: Unknown problem: ${JSON.stringify(finishedTxMeta.txParams)}`))
}
})
})
}
async addUnapprovedTransaction (txParams) {
// validate
await this.txGasUtil.validateTxParams(txParams)
// construct txMeta
const txMeta = {
id: createId(),
time: (new Date()).getTime(),
status: 'unapproved',
metamaskNetworkId: this.getNetwork(),
txParams: txParams,
loadingDefaults: true,
}
this.addTx(txMeta)
this.emit('newUnapprovedTx', txMeta)
// add default tx params
try {
await this.addTxDefaults(txMeta)
} catch (error) {
console.log(error)
this.txStateManager.setTxStatusFailed(txMeta.id, error)
throw error
}
txMeta.loadingDefaults = false
// save txMeta
this.txStateManager.updateTx(txMeta)
return txMeta
}
async addTxDefaults (txMeta) {
const txParams = txMeta.txParams
// ensure value
txMeta.gasPriceSpecified = Boolean(txParams.gasPrice)
txMeta.nonceSpecified = Boolean(txParams.nonce)
let gasPrice = txParams.gasPrice
if (!gasPrice) {
gasPrice = this.getGasPrice ? this.getGasPrice() : await this.query.gasPrice()
}
txParams.gasPrice = ethUtil.addHexPrefix(gasPrice.toString(16))
txParams.value = txParams.value || '0x0'
// set gasLimit
return await this.txGasUtil.analyzeGasUsage(txMeta)
}
async retryTransaction (txId) {
this.txStateManager.setTxStatusUnapproved(txId)
const txMeta = this.txStateManager.getTx(txId)
txMeta.lastGasPrice = txMeta.txParams.gasPrice
this.txStateManager.updateTx(txMeta, 'retryTransaction: manual retry')
}
async updateTransaction (txMeta) {
this.txStateManager.updateTx(txMeta, 'confTx: user updated transaction')
}
async updateAndApproveTransaction (txMeta) {
this.txStateManager.updateTx(txMeta, 'confTx: user approved transaction')
await this.approveTransaction(txMeta.id)
}
async approveTransaction (txId) {
let nonceLock
try {
// approve
this.txStateManager.setTxStatusApproved(txId)
// get next nonce
const txMeta = this.txStateManager.getTx(txId)
const fromAddress = txMeta.txParams.from
// wait for a nonce
nonceLock = await this.nonceTracker.getNonceLock(fromAddress)
// add nonce to txParams
const nonce = txMeta.nonceSpecified ? txMeta.txParams.nonce : nonceLock.nextNonce
if (nonce > nonceLock.nextNonce) {
const message = `Specified nonce may not be larger than account's next valid nonce.`
throw new Error(message)
}
txMeta.txParams.nonce = ethUtil.addHexPrefix(nonce.toString(16))
// add nonce debugging information to txMeta
txMeta.nonceDetails = nonceLock.nonceDetails
this.txStateManager.updateTx(txMeta, 'transactions#approveTransaction')
// sign transaction
const rawTx = await this.signTransaction(txId)
await this.publishTransaction(txId, rawTx)
// must set transaction to submitted/failed before releasing lock
nonceLock.releaseLock()
} catch (err) {
this.txStateManager.setTxStatusFailed(txId, err)
// must set transaction to submitted/failed before releasing lock
if (nonceLock) nonceLock.releaseLock()
// continue with error chain
throw err
}
}
async signTransaction (txId) {
const txMeta = this.txStateManager.getTx(txId)
const txParams = txMeta.txParams
const fromAddress = txParams.from
// add network/chain id
txParams.chainId = ethUtil.addHexPrefix(this.getChainId().toString(16))
const ethTx = new Transaction(txParams)
await this.signEthTx(ethTx, fromAddress)
this.txStateManager.setTxStatusSigned(txMeta.id)
const rawTx = ethUtil.bufferToHex(ethTx.serialize())
return rawTx
}
async publishTransaction (txId, rawTx) {
const txMeta = this.txStateManager.getTx(txId)
txMeta.rawTx = rawTx
this.txStateManager.updateTx(txMeta, 'transactions#publishTransaction')
const txHash = await this.query.sendRawTransaction(rawTx)
this.setTxHash(txId, txHash)
this.txStateManager.setTxStatusSubmitted(txId)
}
async cancelTransaction (txId) {
this.txStateManager.setTxStatusRejected(txId)
}
// receives a txHash records the tx as signed
setTxHash (txId, txHash) {
// Add the tx hash to the persisted meta-tx object
const txMeta = this.txStateManager.getTx(txId)
txMeta.hash = txHash
this.txStateManager.updateTx(txMeta, 'transactions#setTxHash')
}
//
// PRIVATE METHODS
//
_updateMemstore () {
const unapprovedTxs = this.txStateManager.getUnapprovedTxList()
const selectedAddressTxList = this.txStateManager.getFilteredTxList({
from: this.getSelectedAddress(),
metamaskNetworkId: this.getNetwork(),
})
this.memStore.updateState({ unapprovedTxs, selectedAddressTxList })
}
}

View File

@ -1,11 +1,15 @@
// test and development environment variables
const env = process.env.METAMASK_ENV
const METAMASK_DEBUG = 'GULP_METAMASK_DEBUG'
//
// The default state of MetaMask
//
module.exports = {
config: {
config: {},
NetworkController: {
provider: {
type: 'testnet',
type: (METAMASK_DEBUG || env === 'test') ? 'rinkeby' : 'mainnet',
},
},
}
}

View File

@ -1,6 +1,7 @@
/*global Web3*/
cleanContextForImports()
require('web3/dist/web3.min.js')
const log = require('loglevel')
const LocalMessageDuplexStream = require('post-message-stream')
// const PingStream = require('ping-pong-stream/ping')
// const endOfStream = require('end-of-stream')
@ -8,6 +9,10 @@ const setupDappAutoReload = require('./lib/auto-reload.js')
const MetamaskInpageProvider = require('./lib/inpage-provider.js')
restoreContextAfterImports()
const METAMASK_DEBUG = 'GULP_METAMASK_DEBUG'
window.log = log
log.setDefaultLevel(METAMASK_DEBUG ? 'debug' : 'warn')
//
// setup plugin communication
@ -26,31 +31,23 @@ var inpageProvider = new MetamaskInpageProvider(metamaskStream)
// setup web3
//
if (typeof window.web3 !== 'undefined') {
throw new Error(`MetaMask detected another web3.
MetaMask will not work reliably with another web3 extension.
This usually happens if you have two MetaMasks installed,
or MetaMask and another web3 extension. Please remove one
and try again.`)
}
var web3 = new Web3(inpageProvider)
web3.setProvider = function () {
console.log('MetaMask - overrode web3.setProvider')
log.debug('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)
log.debug('MetaMask - injected web3')
// export global web3, with usage-detection
setupDappAutoReload(web3, inpageProvider.publicConfigStore)
// set web3 defaultAccount
inpageProvider.publicConfigStore.subscribe(function (state) {
web3.eth.defaultAccount = state.selectedAddress
})

View File

@ -1,594 +0,0 @@
const ethUtil = require('ethereumjs-util')
const BN = ethUtil.BN
const bip39 = require('bip39')
const EventEmitter = require('events').EventEmitter
const ObservableStore = require('obs-store')
const filter = require('promise-filter')
const encryptor = require('browser-passworder')
const sigUtil = require('eth-sig-util')
const normalizeAddress = sigUtil.normalize
// Keyrings:
const SimpleKeyring = require('eth-simple-keyring')
const HdKeyring = require('eth-hd-keyring')
const keyringTypes = [
SimpleKeyring,
HdKeyring,
]
class KeyringController extends EventEmitter {
// PUBLIC METHODS
//
// THE FIRST SECTION OF METHODS ARE PUBLIC-FACING,
// MEANING THEY ARE USED BY CONSUMERS OF THIS CLASS.
//
// THEIR SURFACE AREA SHOULD BE CHANGED WITH GREAT CARE.
constructor (opts) {
super()
const initState = opts.initState || {}
this.keyringTypes = keyringTypes
this.store = new ObservableStore(initState)
this.memStore = new ObservableStore({
isUnlocked: false,
keyringTypes: this.keyringTypes.map(krt => krt.type),
keyrings: [],
identities: {},
})
this.ethStore = opts.ethStore
this.encryptor = encryptor
this.keyrings = []
this.getNetwork = opts.getNetwork
}
// Full Update
// returns Promise( @object state )
//
// Emits the `update` event and
// returns a Promise that resolves to the current state.
//
// Frequently used to end asynchronous chains in this class,
// indicating consumers can often either listen for updates,
// or accept a state-resolving promise to consume their results.
//
// Not all methods end with this, that might be a nice refactor.
fullUpdate () {
this.emit('update')
return Promise.resolve(this.memStore.getState())
}
// Create New Vault And Keychain
// @string password - The password to encrypt the vault with
//
// returns Promise( @object state )
//
// Destroys any old encrypted storage,
// creates a new encrypted store with the given password,
// randomly creates a new HD wallet with 1 account,
// faucets that account on the testnet.
createNewVaultAndKeychain (password) {
return this.persistAllKeyrings(password)
.then(this.createFirstKeyTree.bind(this))
.then(this.fullUpdate.bind(this))
}
// CreateNewVaultAndRestore
// @string password - The password to encrypt the vault with
// @string seed - The BIP44-compliant seed phrase.
//
// returns Promise( @object state )
//
// Destroys any old encrypted storage,
// creates a new encrypted store with the given password,
// creates a new HD wallet from the given seed with 1 account.
createNewVaultAndRestore (password, seed) {
if (typeof password !== 'string') {
return Promise.reject('Password must be text.')
}
if (!bip39.validateMnemonic(seed)) {
return Promise.reject('Seed phrase is invalid.')
}
this.clearKeyrings()
return this.persistAllKeyrings(password)
.then(() => {
return this.addNewKeyring('HD Key Tree', {
mnemonic: seed,
numberOfAccounts: 1,
})
})
.then((firstKeyring) => {
return firstKeyring.getAccounts()
})
.then((accounts) => {
const firstAccount = accounts[0]
if (!firstAccount) throw new Error('KeyringController - First Account not found.')
const hexAccount = normalizeAddress(firstAccount)
this.emit('newAccount', hexAccount)
return this.setupAccounts(accounts)
})
.then(this.persistAllKeyrings.bind(this, password))
.then(this.fullUpdate.bind(this))
}
// Set Locked
// returns Promise( @object state )
//
// This method deallocates all secrets, and effectively locks metamask.
setLocked () {
// set locked
this.password = null
this.memStore.updateState({ isUnlocked: false })
// remove keyrings
this.keyrings = []
this._updateMemStoreKeyrings()
return this.fullUpdate()
}
// Submit Password
// @string password
//
// returns Promise( @object state )
//
// Attempts to decrypt the current vault and load its keyrings
// into memory.
//
// Temporarily also migrates any old-style vaults first, as well.
// (Pre MetaMask 3.0.0)
submitPassword (password) {
return this.unlockKeyrings(password)
.then((keyrings) => {
this.keyrings = keyrings
return this.fullUpdate()
})
}
// Add New Keyring
// @string type
// @object opts
//
// returns Promise( @Keyring keyring )
//
// Adds a new Keyring of the given `type` to the vault
// and the current decrypted Keyrings array.
//
// All Keyring classes implement a unique `type` string,
// and this is used to retrieve them from the keyringTypes array.
addNewKeyring (type, opts) {
const Keyring = this.getKeyringClassForType(type)
const keyring = new Keyring(opts)
return keyring.deserialize(opts)
.then(() => {
return keyring.getAccounts()
})
.then((accounts) => {
return this.checkForDuplicate(type, accounts)
})
.then((checkedAccounts) => {
this.keyrings.push(keyring)
return this.setupAccounts(checkedAccounts)
})
.then(() => this.persistAllKeyrings())
.then(() => this.fullUpdate())
.then(() => {
this._updateMemStoreKeyrings()
return keyring
})
}
// For now just checks for simple key pairs
// but in the future
// should possibly add HD and other types
//
checkForDuplicate (type, newAccount) {
return this.getAccounts()
.then((accounts) => {
switch (type) {
case 'Simple Key Pair':
let 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)
}
})
}
// Add New Account
// @number keyRingNum
//
// returns Promise( @object state )
//
// Calls the `addAccounts` method on the Keyring
// in the kryings array at index `keyringNum`,
// and then saves those changes.
addNewAccount (selectedKeyring) {
return selectedKeyring.addAccounts(1)
.then(this.setupAccounts.bind(this))
.then(this.persistAllKeyrings.bind(this))
.then(this.fullUpdate.bind(this))
}
// Save Account Label
// @string account
// @string label
//
// returns Promise( @string label )
//
// Persists a nickname equal to `label` for the specified account.
saveAccountLabel (account, label) {
try {
const hexAddress = normalizeAddress(account)
// update state on diskStore
const state = this.store.getState()
const walletNicknames = state.walletNicknames || {}
walletNicknames[hexAddress] = label
this.store.updateState({ walletNicknames })
// update state on memStore
const identities = this.memStore.getState().identities
identities[hexAddress].name = label
this.memStore.updateState({ identities })
return Promise.resolve(label)
} catch (err) {
return Promise.reject(err)
}
}
// Export Account
// @string address
//
// returns Promise( @string privateKey )
//
// Requests the private key from the keyring controlling
// the specified address.
//
// Returns a Promise that may resolve with the private key string.
exportAccount (address) {
try {
return this.getKeyringForAccount(address)
.then((keyring) => {
return keyring.exportAccount(normalizeAddress(address))
})
} catch (e) {
return Promise.reject(e)
}
}
// SIGNING METHODS
//
// This method signs tx and returns a promise for
// TX Manager to update the state after signing
signTransaction (ethTx, _fromAddress) {
const fromAddress = normalizeAddress(_fromAddress)
return this.getKeyringForAccount(fromAddress)
.then((keyring) => {
return keyring.signTransaction(fromAddress, ethTx)
})
}
// Sign Message
// @object msgParams
//
// returns Promise(@buffer rawSig)
//
// Attempts to sign the provided @object msgParams.
signMessage (msgParams) {
const address = normalizeAddress(msgParams.from)
return this.getKeyringForAccount(address)
.then((keyring) => {
return keyring.signMessage(address, msgParams.data)
})
}
// Sign Personal Message
// @object msgParams
//
// returns Promise(@buffer rawSig)
//
// Attempts to sign the provided @object msgParams.
// Prefixes the hash before signing as per the new geth behavior.
signPersonalMessage (msgParams) {
const address = normalizeAddress(msgParams.from)
return this.getKeyringForAccount(address)
.then((keyring) => {
return keyring.signPersonalMessage(address, msgParams.data)
})
}
// PRIVATE METHODS
//
// THESE METHODS ARE ONLY USED INTERNALLY TO THE KEYRING-CONTROLLER
// AND SO MAY BE CHANGED MORE LIBERALLY THAN THE ABOVE METHODS.
// Create First Key Tree
// returns @Promise
//
// Clears the vault,
// creates a new one,
// creates a random new HD Keyring with 1 account,
// makes that account the selected account,
// faucets that account on testnet,
// puts the current seed words into the state tree.
createFirstKeyTree () {
this.clearKeyrings()
return this.addNewKeyring('HD Key Tree', { numberOfAccounts: 1 })
.then((keyring) => {
return keyring.getAccounts()
})
.then((accounts) => {
const firstAccount = accounts[0]
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))
}
// Setup Accounts
// @array accounts
//
// returns @Promise(@object account)
//
// Initializes the provided account array
// Gives them numerically incremented nicknames,
// and adds them to the ethStore for regular balance checking.
setupAccounts (accounts) {
return this.getAccounts()
.then((loadedAccounts) => {
const arr = accounts || loadedAccounts
return Promise.all(arr.map((account) => {
return this.getBalanceAndNickname(account)
}))
})
}
// Get Balance And Nickname
// @string account
//
// returns Promise( @string label )
//
// Takes an account address and an iterator representing
// the current number of named accounts.
getBalanceAndNickname (account) {
if (!account) {
throw new Error('Problem loading account.')
}
const address = normalizeAddress(account)
this.ethStore.addAccount(address)
return this.createNickname(address)
}
// Create Nickname
// @string address
//
// returns Promise( @string label )
//
// Takes an address, and assigns it an incremented nickname, persisting it.
createNickname (address) {
const hexAddress = normalizeAddress(address)
const identities = this.memStore.getState().identities
const currentIdentityCount = Object.keys(identities).length + 1
const nicknames = this.store.getState().walletNicknames || {}
const existingNickname = nicknames[hexAddress]
const name = existingNickname || `Account ${currentIdentityCount}`
identities[hexAddress] = {
address: hexAddress,
name,
}
this.memStore.updateState({ identities })
return this.saveAccountLabel(hexAddress, name)
}
// Persist All Keyrings
// @password string
//
// returns Promise
//
// Iterates the current `keyrings` array,
// serializes each one into a serialized array,
// encrypts that array with the provided `password`,
// and persists that encrypted string to storage.
persistAllKeyrings (password = this.password) {
if (typeof password === 'string') {
this.password = password
this.memStore.updateState({ isUnlocked: true })
}
return Promise.all(this.keyrings.map((keyring) => {
return Promise.all([keyring.type, keyring.serialize()])
.then((serializedKeyringArray) => {
// Label the output values on each serialized Keyring:
return {
type: serializedKeyringArray[0],
data: serializedKeyringArray[1],
}
})
}))
.then((serializedKeyrings) => {
return this.encryptor.encrypt(this.password, serializedKeyrings)
})
.then((encryptedString) => {
this.store.updateState({ vault: encryptedString })
return true
})
}
// Unlock Keyrings
// @string password
//
// returns Promise( @array keyrings )
//
// Attempts to unlock the persisted encrypted storage,
// initializing the persisted keyrings to RAM.
unlockKeyrings (password) {
const encryptedVault = this.store.getState().vault
if (!encryptedVault) {
throw new Error('Cannot unlock without a previous vault.')
}
return this.encryptor.decrypt(password, encryptedVault)
.then((vault) => {
this.password = password
this.memStore.updateState({ isUnlocked: true })
vault.forEach(this.restoreKeyring.bind(this))
return this.keyrings
})
}
// Restore Keyring
// @object serialized
//
// returns Promise( @Keyring deserialized )
//
// Attempts to initialize a new keyring from the provided
// serialized payload.
//
// On success, returns the resulting @Keyring instance.
restoreKeyring (serialized) {
const { type, data } = serialized
const Keyring = this.getKeyringClassForType(type)
const keyring = new Keyring()
return keyring.deserialize(data)
.then(() => {
return keyring.getAccounts()
})
.then((accounts) => {
return this.setupAccounts(accounts)
})
.then(() => {
this.keyrings.push(keyring)
this._updateMemStoreKeyrings()
return keyring
})
}
// Get Keyring Class For Type
// @string type
//
// Returns @class Keyring
//
// Searches the current `keyringTypes` array
// for a Keyring class whose unique `type` property
// matches the provided `type`,
// returning it if it exists.
getKeyringClassForType (type) {
return this.keyringTypes.find(kr => kr.type === type)
}
getKeyringsByType (type) {
return this.keyrings.filter((keyring) => keyring.type === type)
}
// Get Accounts
// returns Promise( @Array[ @string accounts ] )
//
// Returns the public addresses of all current accounts
// managed by all currently unlocked keyrings.
getAccounts () {
const keyrings = this.keyrings || []
return Promise.all(keyrings.map(kr => kr.getAccounts()))
.then((keyringArrays) => {
return keyringArrays.reduce((res, arr) => {
return res.concat(arr)
}, [])
})
}
// Get Keyring For Account
// @string address
//
// returns Promise(@Keyring keyring)
//
// Returns the currently initialized keyring that manages
// the specified `address` if one exists.
getKeyringForAccount (address) {
const hexed = normalizeAddress(address)
log.debug(`KeyringController - getKeyringForAccount: ${hexed}`)
return Promise.all(this.keyrings.map((keyring) => {
return Promise.all([
keyring,
keyring.getAccounts(),
])
}))
.then(filter((candidate) => {
const accounts = candidate[1].map(normalizeAddress)
return accounts.includes(hexed)
}))
.then((winners) => {
if (winners && winners.length > 0) {
return winners[0][0]
} else {
throw new Error('No keyring found for the requested account.')
}
})
}
// Display For Keyring
// @Keyring keyring
//
// returns Promise( @Object { type:String, accounts:Array } )
//
// Is used for adding the current keyrings to the state object.
displayForKeyring (keyring) {
return keyring.getAccounts()
.then((accounts) => {
return {
type: keyring.type,
accounts: accounts,
}
})
}
// Add Gas Buffer
// @string gas (as hexadecimal value)
//
// returns @string bufferedGas (as hexadecimal value)
//
// Adds a healthy buffer of gas to an initial gas estimate.
addGasBuffer (gas) {
const gasBuffer = new BN('100000', 10)
const bnGas = new BN(ethUtil.stripHexPrefix(gas), 16)
const correct = bnGas.add(gasBuffer)
return ethUtil.addHexPrefix(correct.toString(16))
}
// Clear Keyrings
//
// Deallocates all currently managed keyrings and accounts.
// Used before initializing a new vault.
clearKeyrings () {
let accounts
try {
accounts = Object.keys(this.ethStore.getState())
} catch (e) {
accounts = []
}
accounts.forEach((address) => {
this.ethStore.removeAccount(address)
})
// clear keyrings from memory
this.keyrings = []
this.memStore.updateState({
keyrings: [],
identities: {},
})
}
_updateMemStoreKeyrings() {
Promise.all(this.keyrings.map(this.displayForKeyring))
.then((keyrings) => {
this.memStore.updateState({ keyrings })
})
}
}
module.exports = KeyringController

View File

@ -0,0 +1,125 @@
/* Account Tracker
*
* This module is responsible for tracking any number of accounts
* and caching their current balances & transaction counts.
*
* It also tracks transaction hashes, and checks their inclusion status
* on each new block.
*/
const async = require('async')
const EthQuery = require('eth-query')
const ObservableStore = require('obs-store')
const EventEmitter = require('events').EventEmitter
function noop () {}
class AccountTracker extends EventEmitter {
constructor (opts = {}) {
super()
const initState = {
accounts: {},
currentBlockGasLimit: '',
}
this.store = new ObservableStore(initState)
this._provider = opts.provider
this._query = new EthQuery(this._provider)
this._blockTracker = opts.blockTracker
// subscribe to latest block
this._blockTracker.on('block', this._updateForBlock.bind(this))
// blockTracker.currentBlock may be null
this._currentBlockNumber = this._blockTracker.currentBlock
}
//
// public
//
syncWithAddresses (addresses) {
const accounts = this.store.getState().accounts
const locals = Object.keys(accounts)
const toAdd = []
addresses.forEach((upstream) => {
if (!locals.includes(upstream)) {
toAdd.push(upstream)
}
})
const toRemove = []
locals.forEach((local) => {
if (!addresses.includes(local)) {
toRemove.push(local)
}
})
toAdd.forEach(upstream => this.addAccount(upstream))
toRemove.forEach(local => this.removeAccount(local))
this._updateAccounts()
}
addAccount (address) {
const accounts = this.store.getState().accounts
accounts[address] = {}
this.store.updateState({ accounts })
if (!this._currentBlockNumber) return
this._updateAccount(address)
}
removeAccount (address) {
const accounts = this.store.getState().accounts
delete accounts[address]
this.store.updateState({ accounts })
}
//
// private
//
_updateForBlock (block) {
this._currentBlockNumber = block.number
const currentBlockGasLimit = block.gasLimit
this.store.updateState({ currentBlockGasLimit })
async.parallel([
this._updateAccounts.bind(this),
], (err) => {
if (err) return console.error(err)
this.emit('block', this.store.getState())
})
}
_updateAccounts (cb = noop) {
const accounts = this.store.getState().accounts
const addresses = Object.keys(accounts)
async.each(addresses, this._updateAccount.bind(this), cb)
}
_updateAccount (address, cb = noop) {
this._getAccount(address, (err, result) => {
if (err) return cb(err)
result.address = address
const accounts = this.store.getState().accounts
// only populate if the entry is still present
if (accounts[address]) {
accounts[address] = result
this.store.updateState({ accounts })
}
cb(null, result)
})
}
_getAccount (address, cb = noop) {
const query = this._query
async.parallel({
balance: query.getBalance.bind(query, address),
}, cb)
}
}
module.exports = AccountTracker

View File

@ -1,20 +0,0 @@
const uri = 'https://faucet.metamask.io/'
const METAMASK_DEBUG = 'GULP_METAMASK_DEBUG'
const env = process.env.METAMASK_ENV
module.exports = function (address) {
// 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)
})
}

View File

@ -1,30 +1,58 @@
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
}))
let hasBeenWarned = false
let reloadInProgress = false
let lastTimeUsed
let lastSeenNetwork
return handleResetRequest
global.web3 = new Proxy(web3, {
get: (_web3, key) => {
// show warning once on web3 access
if (!hasBeenWarned && key !== 'currentProvider') {
console.warn('MetaMask: web3 will be deprecated in the near future in favor of the ethereumProvider \nhttps://github.com/MetaMask/faq/blob/master/detecting_metamask.md#web3-deprecation')
hasBeenWarned = true
}
// get the time of use
lastTimeUsed = Date.now()
// return value normally
return _web3[key]
},
set: (_web3, key, value) => {
// set value normally
_web3[key] = value
},
})
function handleResetRequest () {
resetWasRequested = true
// ignore if web3 was not used
if (!pageIsUsingWeb3) return
// reload after short timeout
setTimeout(triggerReset, 500)
}
observable.subscribe(function (state) {
// if reload in progress, no need to check reload logic
if (reloadInProgress) return
const currentNetwork = state.networkVersion
// set the initial network
if (!lastSeenNetwork) {
lastSeenNetwork = currentNetwork
return
}
// skip reload logic if web3 not used
if (!lastTimeUsed) return
// if network did not change, exit
if (currentNetwork === lastSeenNetwork) return
// initiate page reload
reloadInProgress = true
const timeSinceUse = Date.now() - lastTimeUsed
// if web3 was recently used then delay the reloading of the page
if (timeSinceUse > 500) {
triggerReset()
} else {
setTimeout(triggerReset, 500)
}
})
}
// reload the page

View File

@ -1,6 +1,6 @@
module.exports = getBuyEthUrl
function getBuyEthUrl({ network, amount, address }){
function getBuyEthUrl ({ network, amount, address }) {
let url
switch (network) {
case '1':
@ -11,9 +11,13 @@ function getBuyEthUrl({ network, amount, address }){
url = 'https://faucet.metamask.io/'
break
case '4':
url = 'https://www.rinkeby.io/'
break
case '42':
url = 'https://github.com/kovan-testnet/faucet'
break
}
return url
}
}

View File

@ -1,11 +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.
@ -33,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)
}
@ -71,6 +42,17 @@ ConfigManager.prototype.getData = function () {
return this.store.getState()
}
ConfigManager.prototype.setPasswordForgotten = function (passwordForgottenState) {
const data = this.getData()
data.forgottenPassword = passwordForgottenState
this.setData(data)
}
ConfigManager.prototype.getPasswordForgotten = function (passwordForgottenState) {
const data = this.getData()
return data.forgottenPassword
}
ConfigManager.prototype.setWallet = function (wallet) {
var data = this.getData()
data.wallet = wallet
@ -136,6 +118,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()
@ -145,17 +156,17 @@ ConfigManager.prototype.getCurrentRpcAddress = function () {
case 'mainnet':
return MAINNET_RPC
case 'testnet':
return TESTNET_RPC
case 'morden':
return MORDEN_RPC
case 'ropsten':
return ROPSTEN_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
}
}

View File

@ -0,0 +1,15 @@
// log rpc activity
module.exports = createLoggerMiddleware
function createLoggerMiddleware ({ origin }) {
return function loggerMiddleware (req, res, next, end) {
next((cb) => {
if (res.error) {
log.error('Error in RPC response:\n', res)
}
if (req.isMetamaskInternal) return
log.info(`RPC (${origin}):`, req, '->', res)
cb()
})
}
}

View File

@ -0,0 +1,9 @@
// append dapp origin domain to request
module.exports = createOriginMiddleware
function createOriginMiddleware ({ origin }) {
return function originMiddleware (req, res, next, end) {
req.origin = origin
next()
}
}

View File

@ -0,0 +1,12 @@
module.exports = createProviderMiddleware
// forward requests to provider
function createProviderMiddleware ({ provider }) {
return (req, res, next, end) => {
provider.sendAsync(req, (err, _res) => {
if (err) return end(err)
res.result = _res.result
end()
})
}
}

View File

@ -0,0 +1,10 @@
module.exports = function environmentType () {
const url = window.location.href
if (url.match(/popup.html$/)) {
return 'popup'
} else if (url.match(/home.html$/)) {
return 'responsive'
} else {
return 'notification'
}
}

View File

@ -1,136 +0,0 @@
/* Ethereum Store
*
* This module is responsible for tracking any number of accounts
* and caching their current balances & transaction counts.
*
* It also tracks transaction hashes, and checks their inclusion status
* on each new block.
*/
const async = require('async')
const EthQuery = require('eth-query')
const ObservableStore = require('obs-store')
function noop() {}
class EthereumStore extends ObservableStore {
constructor (opts = {}) {
super({
accounts: {},
transactions: {},
currentBlockNumber: '0',
currentBlockHash: '',
})
this._provider = opts.provider
this._query = new EthQuery(this._provider)
this._blockTracker = opts.blockTracker
// subscribe to latest block
this._blockTracker.on('block', this._updateForBlock.bind(this))
// blockTracker.currentBlock may be null
this._currentBlockNumber = this._blockTracker.currentBlock
}
//
// public
//
addAccount (address) {
const accounts = this.getState().accounts
accounts[address] = {}
this.updateState({ accounts })
if (!this._currentBlockNumber) return
this._updateAccount(address)
}
removeAccount (address) {
const accounts = this.getState().accounts
delete accounts[address]
this.updateState({ accounts })
}
addTransaction (txHash) {
const transactions = this.getState().transactions
transactions[txHash] = {}
this.updateState({ transactions })
if (!this._currentBlockNumber) return
this._updateTransaction(this._currentBlockNumber, txHash, noop)
}
removeTransaction (txHash) {
const transactions = this.getState().transactions
delete transactions[txHash]
this.updateState({ transactions })
}
//
// private
//
_updateForBlock (block) {
const blockNumber = '0x' + block.number.toString('hex')
this._currentBlockNumber = blockNumber
this.updateState({ currentBlockNumber: parseInt(blockNumber) })
this.updateState({ currentBlockHash: `0x${block.hash.toString('hex')}`})
async.parallel([
this._updateAccounts.bind(this),
this._updateTransactions.bind(this, blockNumber),
], (err) => {
if (err) return console.error(err)
this.emit('block', this.getState())
})
}
_updateAccounts (cb = noop) {
const accounts = this.getState().accounts
const addresses = Object.keys(accounts)
async.each(addresses, this._updateAccount.bind(this), cb)
}
_updateAccount (address, cb = noop) {
const accounts = this.getState().accounts
this._getAccount(address, (err, result) => {
if (err) return cb(err)
result.address = address
// only populate if the entry is still present
if (accounts[address]) {
accounts[address] = result
this.updateState({ accounts })
}
cb(null, result)
})
}
_updateTransactions (block, cb = noop) {
const transactions = this.getState().transactions
const txHashes = Object.keys(transactions)
async.each(txHashes, this._updateTransaction.bind(this, block), cb)
}
_updateTransaction (block, txHash, cb = noop) {
// would use the block here to determine how many confirmations the tx has
const transactions = this.getState().transactions
this._query.getTransaction(txHash, (err, result) => {
if (err) return cb(err)
// only populate if the entry is still present
if (transactions[txHash]) {
transactions[txHash] = result
this.updateState({ transactions })
}
cb(null, result)
})
}
_getAccount (address, cb = noop) {
const query = this._query
async.parallel({
balance: query.getBalance.bind(query, address),
nonce: query.getTransactionCount.bind(query, address),
code: query.getCode.bind(query, address),
}, cb)
}
}
module.exports = EthereumStore

Some files were not shown because too many files have changed in this diff Show More