1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-12-23 09:52:26 +01:00

Merge branch 'kumavis-patch-1' of github.com:MetaMask/metamask-plugin into kumavis-patch-1

This commit is contained in:
Dan Finlay 2017-02-15 16:09:16 -08:00
commit 6d103dc1e7
222 changed files with 16422 additions and 3374 deletions

View File

@ -1,2 +1 @@
app/scripts/lib/extension-instance.js
ui/app/conversion-util.js

View File

@ -1,5 +1,6 @@
{
"parserOptions": {
"sourceType": "module",
"ecmaVersion": 6,
"ecmaFeatures": {
"experimentalObjectRestSpread": true,
@ -44,10 +45,10 @@
"eol-last": 1,
"eqeqeq": [2, "allow-null"],
"generator-star-spacing": [2, { "before": true, "after": true }],
"handle-callback-err": [2, "^(err|error)$" ],
"handle-callback-err": [1, "^(err|error)$" ],
"indent": [2, 2, { "SwitchCase": 1 }],
"jsx-quotes": [2, "prefer-single"],
"key-spacing": [2, { "beforeColon": false, "afterColon": true }],
"key-spacing": 1,
"keyword-spacing": [2, { "before": true, "after": true }],
"new-cap": [2, { "newIsCap": true, "capIsNew": false }],
"new-parens": 2,
@ -127,14 +128,14 @@
"no-whitespace-before-property": 2,
"no-with": 2,
"one-var": [2, { "initialized": "never" }],
"operator-linebreak": [2, "after", { "overrides": { "?": "before", ":": "before" } }],
"operator-linebreak": [1, "after", { "overrides": { "?": "ignore", ":": "ignore" } }],
"padded-blocks": [1, "never"],
"quotes": [2, "single", "avoid-escape"],
"quotes": [2, "single", {"avoidEscape": true, "allowTemplateLiterals": true}],
"semi": [2, "never"],
"semi-spacing": [2, { "before": false, "after": true }],
"space-before-blocks": [1, "always"],
"space-before-function-paren": [1, "always"],
"space-in-parens": [2, "never"],
"space-in-parens": [1, "never"],
"space-infix-ops": 2,
"space-unary-ops": [2, { "words": true, "nonwords": false }],
"spaced-comment": [2, "always", { "markers": ["global", "globals", "eslint", "eslint-disable", "*package", "!", ","] }],
@ -145,6 +146,6 @@
"wrap-iife": [2, "any"],
"yield-star-spacing": [2, "both"],
"yoda": [2, "never"],
"prefer-const": 1
"prefer-const": 1,
}
}

6
.gitignore vendored
View File

@ -1,5 +1,5 @@
dist
npm-debug.log
node_modules
temp
.tmp
@ -7,10 +7,12 @@ temp
app/bower_components
test/bower_components
package
.DS_Store
builds/
disc/
notes.txt
app/.DS_Store
development/bundle.js
builds.zip
test/integration/bundle.js
development/states.js

View File

@ -1,12 +1,204 @@
# Changelog
## Current Master
- net_version has been made synchronous.
- Test suite for migrations expanded.
- Improve test coverage of eth.sign behavior, including a code example of verifying a signature.
## 3.2.2 2017-2-8
- Revert eth.sign behavior to the previous one with a big warning. We will be gradually implementing the new behavior over the coming time. https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_sign
## 3.2.1 2017-2-8
- Revert back to old style message signing.
- Fixed some build errors that were causing a variety of bugs.
## 3.2.0 2017-2-8
- 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.
## 3.1.2 2017-1-24
- Fix "New Account" default keychain
## 3.1.1 2017-1-20
- Fix HD wallet seed export
## 3.1.0 2017-1-18
- Add ability to import accounts by private key.
- Fixed bug that returned the wrong transaction hashes on private networks that had not implemented EIP 155 replay protection (like TestRPC).
## 3.0.1 2017-1-17
- Fixed bug that prevented eth.sign from working.
- Fix the displaying of transactions that have been submitted to the network in Transaction History
## 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 memory leak in RPC Cache
- Override RPC commands eth_syncing and web3_clientVersion
- Remove certain non-essential permissions from certain builds.
- Add a check for when a tx is included in a block.
- Fix bug where browser-solidity would sometimes warn of a contract creation error when there was none.
- Minor modifications to network display.
- Network now displays properly for pending transactions.
- Implement replay attack protections allowed by EIP 155.
- Fix bug where sometimes loading account data would fail by querying a future block.
## 2.14.1 2016-12-20
- Update Coinbase info. and increase the buy amount to $15
- Fixed ropsten transaction links
- Temporarily disable extension reload detection causing infinite reload bug.
- Implemented basic checking for valid RPC URIs.
## 2.14.0 2016-12-16
- Removed Morden testnet provider from provider menu.
- Add support for notices.
- Fix broken reload detection.
- Fix transaction forever cached-as-pending bug.
## 2.13.11 2016-11-23
- Add support for synchronous RPC method "eth_uninstallFilter".
- Forgotten password prompts now send users directly to seed word restoration.
## 2.13.10 2016-11-22
- Improve gas calculation logic.
- Default to Dapp-specified gas limits for transactions.
- Ropsten networks now properly point to the faucet when attempting to buy ether.
- Ropsten transactions now link to etherscan correctly.
## 2.13.9 2016-11-21
- Add support for the new, default Ropsten Test Network.
- Fix bug that would cause MetaMask to occasionally lose its StreamProvider connection and drop requests.
- Fix bug that would cause the Custom RPC menu item to not appear when Localhost 8545 was selected.
- Point ropsten faucet button to actual faucet.
- Phase out ethereumjs-util from our encryptor module.
## 2.13.8 2016-11-16
- Show a warning when a transaction fails during simulation.
- Fix bug where 20% of gas estimate was not being added properly.
- Render error messages in confirmation screen more gracefully.
## 2.13.7 2016-11-8
- 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.
## 2.13.6 2016-10-26
- Add a check for improper Transaction data.
- Inject up to date version of web3.js
- Now nicknaming new accounts "Account #" instead of "Wallet #" for clarity.
- Fix bug where custom provider selection could show duplicate items.
- Fix bug where connecting to a local morden node would make two providers appear selected.
- Fix bug that was sometimes preventing transactions from being sent.
## 2.13.5 2016-10-18
- Increase default max gas to `100000` over the RPC's `estimateGas` response.
- Fix bug where slow-loading dapps would sometimes trigger infinite reload loops.
## 2.13.4 2016-10-17
- Add custom transaction fee field to send form.
- Fix bug where web3 was being injected into XML files.
- Fix bug where changing network would not reload current Dapps.
## 2.13.3 2016-10-4
- Fix bug where log queries were filtered out.
- Decreased vault confirmation button font size to help some Linux users who could not see it.
- Made popup a little taller because it would sometimes cut off buttons.
- Fix bug where long account lists would get scrunched instead of scrolling.
- Add legal information to relevant pages.
- Rename UI elements to be more consistent with one another.
- Updated Terms of Service and Usage.
- Prompt users to re-agree to the Terms of Service when they are updated.
## 2.13.2 2016-10-4
- Fix bug where chosen FIAT exchange rate does no persist when switching networks
- Fix additional parameters that made MetaMask sometimes receive errors from Parity.
- Fix bug where invalid transactions would still open the MetaMask popup.
- Removed hex prefix from private key export, to increase compatibility with Geth, MyEtherWallet, and Jaxx.
## 2.13.1 2016-09-23
- Fix a bug with estimating gas on Parity
- Show loading indication when selecting ShapeShift as purchasing method.
## 2.13.0 2016-09-18
- Add Parity compatibility, fixing Geth dependency issues.
- Add a link to the transaction in history that goes to https://metamask.github.io/eth-tx-viz
too help visualize transactions and to where they are going.
- Show "Buy Ether" button and warning on tx confirmation when sender balance is insufficient
## 2.12.1 2016-09-14
- Fixed bug where if you send a transaction from within MetaMask extension the
popup notification opens up.
- Fixed bug where some tx errors would block subsequent txs until the plugin was refreshed.
## 2.12.0 2016-09-14
- Add a QR button to the Account detail screen
- Fixed bug where opening MetaMask could close a non-metamask popup.
- Fixed memory leak that caused occasional crashes.
## 2.11.1 2016-09-12
- Fix bug that prevented caches from being cleared in Opera.
## 2.11.0 2016-09-12
- Fix bug where pending transactions from Test net (or other networks) show up In Main net.
- Add fiat conversion values to more views.
- On fresh install, open a new tab with the MetaMask Introduction video. Does not open on update.
- 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 only initially creates one wallet when restoring a vault, to reduce some users' confusion.
## 2.10.2 2016-09-02
- Fix bug where notification popup would not display.
## 2.10.1 2016-09-02
- Fix bug where provider menu did not allow switching to custom network from a custom network.
- Sending a transaction from within MetaMask no longer triggers a popup.
- The ability to build without livereload features (such as for production) can be enabled with the gulp --disableLiveReload flag.
- Fix Ethereum JSON RPC Filters bug.
## 2.10.0 2016-08-29
- Changed transaction approval from notifications system to popup system.
- Add a back button to locked screen to allow restoring vault from seed words when password is forgotten.
- Forms now retain their values even when closing the popup and reopening it.
- Fixed a spelling error in provider menu.
## 2.9.2 2016-08-24
- Fixed shortcut bug from preventing installation.
## 2.9.1 2016-08-24
- Added static image as fallback for when WebGL isn't supported.
@ -27,7 +219,7 @@
## 2.8.0 2016-08-15
- Integrate ShapeShift
- Add a for for Coinbase to specify amount to buy
- Add a form for Coinbase to specify amount to buy
- Fix various typos.
- Make dapp-metamask connection more reliable
- Remove Ethereum Classic from provider menu.

View File

@ -1,5 +1,12 @@
# MetaMask Plugin [![Build Status](https://circleci.com/gh/MetaMask/metamask-plugin.svg?style=shield&circle-token=a1ddcf3cd38e29267f254c9c59d556d513e3a1fd)](https://circleci.com/gh/MetaMask/metamask-plugin)
## 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/).
- 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
- Install [Node.js](https://nodejs.org/en/) version 6.3.1 or later.
@ -11,6 +18,12 @@
Uncompressed builds can be found in `/dist`, compressed builds can be found in `/builds` once they're built.
## Installing Local Builds on Chrome
To install your locally built extension on Chrome, [follow this guide](http://stackoverflow.com/a/24577660/272576).
The built extension is stored in `./dist/chrome/`.
## Architecture
[![Architecture Diagram](./docs/architecture.png)][1]
@ -19,6 +32,13 @@
```bash
npm install
npm start
```
## Build for Publishing
```bash
npm run dist
```
#### In Chrome
@ -71,7 +91,7 @@ To enjoy the live-reloading that `gulp dev` offers while working on the `web3-pr
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 `gulp dev` it will watch the dependency for changes as well!
5. Next time you `npm start` it will watch the dependency for changes as well!
### Running Tests
@ -83,6 +103,10 @@ 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
You must be authorized already on the MetaMask plugin.
@ -93,3 +117,50 @@ You can run the linter by itself with `gulp lint`.
3. Upload the latest zip file from `builds/metamask-$PLATFORM-$VERSION.zip` as the updated package.
[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

@ -1,55 +1,179 @@
# Disclaimer
# Terms of Use #
**METAMASK is still Beta software. Do not rely on METAMASK to manage large amounts of Ether.**
**THIS AGREEMENT IS SUBJECT TO BINDING ARBITRATION AND A WAIVER OF CLASS ACTION RIGHTS AS DETAILED IN SECTION 13. PLEASE READ THE AGREEMENT CAREFULLY.**
## SECURITY WARNINGS
_Our Terms of Use have been updated as of September 5, 2016_
**You are responsible for your own computer's security. If your machine is compromised, you could lose your ether and access to your accounts and contracts.**
## 1. Acceptance of Terms ##
You are responsible for your own actions.
MetaMask provides a platform for managing Ethereum (or "ETH") accounts, and allowing ordinary websites to interact with the Ethereum blockchain, while keeping the user in control over what transactions they approve, through our website located at[ ](http://metamask.io)[https://metamask.io/](https://metamask.io/) and browser plugin (the "Site") — which includes text, images, audio, code and other materials (collectively, the “Content”) and all of the features, and services provided. The Site, and any other features, tools, materials, or other services offered from time to time by MetaMask are referred to here as the “Service.” Please read these Terms of Use (the “Terms” or “Terms of Use”) carefully before using the Service. By using or otherwise accessing the Services, or clicking to accept or agree to these Terms where that option is made available, you (1) accept and agree to these Terms (2) consent to the collection, use, disclosure and other handling of information as described in our Privacy Policy and (3) any additional terms, rules and conditions of participation issued by MetaMask from time to time. If you do not agree to the Terms, then you may not access or use the Content or Services.
The user acknowledges the following serious risks to any use of METAMASK and ETH and expressly agrees to not hold liable MetaMask or the MetaMask Team should any of these risks occur:
## 2. Modification of Terms of Use ##
**METMASK AT THIS POINT IN TIME IS MEANT FOR DEVELOPERS OR PEOPLE WITH A UNDERSTANDING OF THE ETHEREUM PROTOCOL. DUE TO THE NATURE OF METAMASK, THE METAMASK TEAM DOES NOT ADVISE STORING LARGE AMOUNTS OF ETHER IN METAMASK VAULTS. THE METAMASK TEAM DOES NOT TAKE RESPONSIBILITY FOR LOST OR STOLEN ETHER. BY CLICKING AGREE AND USING METAMASK THE USER UNDERSTANDS THE RISKS OF USING EXPERIMENTAL SOFTWARE AND ACKNOWLEDGES THE FACT THAT THERE MAY BE BUGS OR SECURITY RISKS IN METAMASK THAT MAY RESULT IN LOSS OF ETHER AND THE METAMASK TEAM IS NOT HELD RESPONSIBLE FOR REIMBURSEMENT OF LOST FUNDS.**
Except for Section 13, providing for binding arbitration and waiver of class action rights, MetaMask reserves the right, at its sole discretion, to modify or replace the Terms of Use at any time. The most current version of these Terms will be posted on our Site. You shall be responsible for reviewing and becoming familiar with any such modifications. Use of the Services by you after any modification to the Terms constitutes your acceptance of the Terms of Use as modified.
### Risk of Regulatory Actions in One or More Jurisdictions
METAMASK and ETH could be impacted by one or more regulatory inquiries or regulatory action, which could impede or limit the ability of METAMASK to continue to develop, or which could impede or limit the ability of User to use Ethereum Platform or ETH.
### Risk that METAMASK—As Developed—Will Not Meet the Expectations of User
## 3. Eligibility ##
The User recognizes that METAMASK is under development and may undergo significant changes. User acknowledges that any expectations regarding the form and functionality of METAMASK held by the User may not be met upon release of METAMASK, for any number of reasons including a change in the design and implementation plans and execution of the implementation of METAMASK.
You hereby represent and warrant that you are fully able and competent to enter into the terms, conditions, obligations, affirmations, representations and warranties set forth in these Terms and to abide by and comply with these Terms.
### Risk of Weaknesses or Exploitable Breakthroughs in the Field of Cryptography
MetaMask is a global platform and by accessing the Content or Services, you are representing and warranting that, you are of the legal age of majority in your jurisdiction as is required to access such Services and Content and enter into arrangements as provided by the Service. You further represent that you are otherwise legally permitted to use the service in your jurisdiction including owning cryptographic tokens of value, and interacting with the Services or Content in any way. You further represent you are responsible for ensuring compliance with the laws of your jurisdiction and acknowledge that MetaMask is not liable for your compliance with such laws.
Cryptography is an art, not a science; the state of the art can advance over time. Advances in code cracking or technical advances such as the development of quantum computers may present risks to cryptocurrencies and METAMASK, which could result in the theft or loss of ETH. To the extent possible, METAMASK intends to update the protocol underlying METAMASK to account for any advances in cryptography and to incorporate additional security measures, but cannot it cannot predict the future of cryptography or the success of any future security updates.
## 4 Account Password and Security ##
### Warning of the Volatility of Crypto Currencies
When setting up an account within MetaMask, you will be responsible for keeping your own account secrets, which may be a twelve-word seed phrase, an account file, or other locally stored secret information. MetaMask encrypts this information locally with a password you provide, that we never send to our servers. You agree to (a) never use the same password for MetaMask that you have ever used outside of this service; (b) keep your secret information and password confidential and do not share them with anyone else; (c) immediately notify MetaMask of any unauthorized use of your account or breach of security. MetaMask cannot and will not be liable for any loss or damage arising from your failure to comply with this section.
ETHEREUM is a new enough technology that the value of its core token and network fee currency is highly volatile, due to a combination of adoption and speculation.
## 5. Representations, Warranties, and Risks ##
METAMASK cannot be held responsible for increases in the cost of network resources that affect the viability of any business activities that may take place on the blockchain.
### 5.1. Warranty Disclaimer ###
### Warning of Possible Accidental Flaws in Ethereum Apps
You expressly understand and agree that your use of the Service is at your sole risk. The Service (including the Service and the Content) are provided on an "AS IS" and "as available" basis, without warranties of any kind, either express or implied, including, without limitation, implied warranties of merchantability, fitness for a particular purpose or non-infringement. You acknowledge that MetaMask has no control over, and no duty to take any action regarding: which users gain access to or use the Service; what effects the Content may have on you; how you may interpret or use the Content; or what actions you may take as a result of having been exposed to the Content. You release MetaMask from all liability for you having acquired or not acquired Content through the Service. MetaMask makes no representations concerning any Content contained in or accessed through the Service, and MetaMask will not be responsible or liable for the accuracy, copyright compliance, legality or decency of material contained in or accessed through the Service.
ETHEREUM applications are only as good as the code they are written with. METAMASK cannot be held responsible if you interact with a flawed contract. ETHEREUM applications are a very new kind of software, and people are still learning how to write fully bulletproof software, so you are responsible for evaluating the trustworthiness of the websites you are willing to submit Ethereum transactions to.
### 5.2 Sophistication and Risk of Cryptographic Systems ###
### Warning of Possible Malicious Contracts in Ethereum Apps
By utilizing the Service or interacting with the Content or platform in any way, you represent that you understand the inherent risks associated with cryptographic systems; and warrant that you have an understanding of the usage and intricacies of native cryptographic tokens, like Ether (ETH) and Bitcoin (BTC), smart contract based tokens such as those that follow the Ethereum Token Standard (https://github.com/ethereum/EIPs/issues/20), and blockchain-based software systems.
ETHEREUM applications can be written maliciously. METAMASK cannot be held liable if you choose to interact with a contract that does not perform as expected. A contract may keep funds sent to it, or even impersonate the person making a request of it. METAMASK developers will do everything they can to warn you of potential threats, but they cannot be considered omniscient, and so all liabilities of interacting with malevolent contracts must lie on the user.
### 5.3 Risk of Regulatory Actions in One or More Jurisdictions ###
### Acknowledgment, Acceptance of all Risks and Disclaimer of Warranties and Liabilities
MetaMask and ETH could be impacted by one or more regulatory inquiries or regulatory action, which could impede or limit the ability of MetaMask to continue to develop, or which could impede or limit your ability to access or use the Service or Ethereum blockchain.
THE USER EXPRESSLY KNOWS AND AGREES THAT THE USER IS USING METAMASK AND METAMASK AT THE USERS SOLE RISK. THE USER REPRESENTS THAT THE USER HAS AN ADEQUATE UNDERSTANDING OF THE RISKS, USAGE AND INTRICACIES OF CRYPTOGRAPHIC TOKENS AND BLOCKCHAIN-BASED OPEN SOURCE SOFTWARE, ETH PLATFORM AND ETH. THE USER ACKNOWLEDGES AND AGREES THAT, TO THE FULLEST EXTENT PERMITTED BY ANY APPLICABLE LAW, THE DISCLAIMERS OF LIABILITY CONTAINED HEREIN APPLY TO ANY AND ALL DAMAGES OR INJURY WHATSOEVER CAUSED BY OR RELATED TO RISKS OF, USE OF, OR INABILITY TO USE, ETH OR METAMASK UNDER ANY CAUSE OR ACTION WHATSOEVER OF ANY KIND IN ANY JURISDICTION, INCLUDING, WITHOUT LIMITATION, ACTIONS FOR BREACH OF WARRANTY, BREACH OF CONTRACT OR TORT (INCLUDING NEGLIGENCE) AND THAT NEITHER METAMASK NOR THE METAMASK TEAM WILL BE LIABLE FOR ANY INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES, INCLUDING FOR LOSS OF PROFITS, GOODWILL OR DATA.
### 5.4 Risk of Weaknesses or Exploits in the Field of Cryptography ###
### Acknowledgement of User's Duty to All Laws and Regulations
You acknowledge and understand that Cryptography is a progressing field. Advances in code cracking or technical advances such as the development of quantum computers may present risks to cryptocurrencies and Services of Content, which could result in the theft or loss of your cryptographic tokens or property. To the extent possible, MetaMask intends to update the protocol underlying Services to account for any advances in cryptography and to incorporate additional security measures, but does not guarantee or otherwise represent full security of the system. By using the Service or accessing Content, you acknowledge these inherent risks.
The USER is responsible for their own actions with the use of the METAMASK software. Like any tool, it could be conceivably be used in ways not legal in some jurisdictions.
### 5.5 Volatility of Crypto Currencies ###
THE USER agrees to adhere to all binding laws and regulations for the conditions in which they use METAMASK.
You understand that Ethereum and other blockchain technologies and associated currencies or tokens are highly volatile due to many factors including but not limited to adoption, speculation, technology and security risks. You also acknowledge that the cost of transacting on such technologies is variable and may increase at any time causing impact to any activities taking place on the Ethereum blockchain. You acknowledge these risks and represent that MetaMask cannot be held liable for such fluctuations or increased costs.
### Risk of Temporary Network Incoherence
### 5.6 Application Security ###
METAMASK is not liable for unavoidable casualty, delays in delivery of materials, embargoes, government orders, acts of civil or military authorities, lack of energy, sickness or other ill health, injurious dance parties, rancid bacon, acts of Gods, Nymphs, Angels (fallen or otherwise) or Djinn, or any similarly unforeseen events that render misfortune.
You acknowledge that Ethereum applications are code subject to flaws and acknowledge that you are solely responsible for evaluating any code provided by the Services or Content and the trustworthiness of any third-party websites, products, smart-contracts, or Content you access or use through the Service. You further expressly acknowledge and represent that Ethereum applications can be written maliciously or negligently, that MetaMask cannot be held liable for your interaction with such applications and that such applications may cause the loss of property or even identity. This warning and others later provided by MetaMask in no way evidence or represent an on-going duty to alert you to all of the potential risks of utilizing the Service or Content.
The METAMASK DEVELOPERS wish the very best luck in creating a better life for yourself, those you love, and your greater community, the world.
## 6. Indemnity ##
You agree to release and to indemnify, defend and hold harmless MetaMask and its parents, subsidiaries, affiliates and agencies, as well as the officers, directors, employees, shareholders and representatives of any of the foregoing entities, from and against any and all losses, liabilities, expenses, damages, costs (including attorneys fees and court costs) claims or actions of any kind whatsoever arising or resulting from your use of the Service, your violation of these Terms of Use, and any of your acts or omissions that implicate publicity rights, defamation or invasion of privacy. MetaMask reserves the right, at its own expense, to assume exclusive defense and control of any matter otherwise subject to indemnification by you and, in such case, you agree to cooperate with MetaMask in the defense of such matter.
## 7. Limitation on liability ##
YOU ACKNOWLEDGE AND AGREE THAT YOU ASSUME FULL RESPONSIBILITY FOR YOUR USE OF THE SITE AND SERVICE. YOU ACKNOWLEDGE AND AGREE THAT ANY INFORMATION YOU SEND OR RECEIVE DURING YOUR USE OF THE SITE AND SERVICE MAY NOT BE SECURE AND MAY BE INTERCEPTED OR LATER ACQUIRED BY UNAUTHORIZED PARTIES. YOU ACKNOWLEDGE AND AGREE THAT YOUR USE OF THE SITE AND SERVICE IS AT YOUR OWN RISK. RECOGNIZING SUCH, YOU UNDERSTAND AND AGREE THAT, TO THE FULLEST EXTENT PERMITTED BY APPLICABLE LAW, NEITHER METAMASK NOR ITS SUPPLIERS OR LICENSORS WILL BE LIABLE TO YOU FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY OR OTHER DAMAGES OF ANY KIND, INCLUDING WITHOUT LIMITATION DAMAGES FOR LOSS OF PROFITS, GOODWILL, USE, DATA OR OTHER TANGIBLE OR INTANGIBLE LOSSES OR ANY OTHER DAMAGES BASED ON CONTRACT, TORT, STRICT LIABILITY OR ANY OTHER THEORY (EVEN IF METAMASK HAD BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES), RESULTING FROM THE SITE OR SERVICE; THE USE OR THE INABILITY TO USE THE SITE OR SERVICE; UNAUTHORIZED ACCESS TO OR ALTERATION OF YOUR TRANSMISSIONS OR DATA; STATEMENTS OR CONDUCT OF ANY THIRD PARTY ON THE SITE OR SERVICE; ANY ACTIONS WE TAKE OR FAIL TO TAKE AS A RESULT OF COMMUNICATIONS YOU SEND TO US; HUMAN ERRORS; TECHNICAL MALFUNCTIONS; FAILURES, INCLUDING PUBLIC UTILITY OR TELEPHONE OUTAGES; OMISSIONS, INTERRUPTIONS, LATENCY, DELETIONS OR DEFECTS OF ANY DEVICE OR NETWORK, PROVIDERS, OR SOFTWARE (INCLUDING, BUT NOT LIMITED TO, THOSE THAT DO NOT PERMIT PARTICIPATION IN THE SERVICE); ANY INJURY OR DAMAGE TO COMPUTER EQUIPMENT; INABILITY TO FULLY ACCESS THE SITE OR SERVICE OR ANY OTHER WEBSITE; THEFT, TAMPERING, DESTRUCTION, OR UNAUTHORIZED ACCESS TO, IMAGES OR OTHER CONTENT OF ANY KIND; DATA THAT IS PROCESSED LATE OR INCORRECTLY OR IS INCOMPLETE OR LOST; TYPOGRAPHICAL, PRINTING OR OTHER ERRORS, OR ANY COMBINATION THEREOF; OR ANY OTHER MATTER RELATING TO THE SITE OR SERVICE.
SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF CERTAIN WARRANTIES OR THE LIMITATION OR EXCLUSION OF LIABILITY FOR INCIDENTAL OR CONSEQUENTIAL DAMAGES. ACCORDINGLY, SOME OF THE ABOVE LIMITATIONS MAY NOT APPLY TO YOU.
## 8. Our Proprietary Rights ##
All title, ownership and intellectual property rights in and to the Service are owned by MetaMask or its licensors. You acknowledge and agree that the Service contains proprietary and confidential information that is protected by applicable intellectual property and other laws. Except as expressly authorized by MetaMask, you agree not to copy, modify, rent, lease, loan, sell, distribute, perform, display or create derivative works based on the Service, in whole or in part. MetaMask issues a license for MetaMask, found [here](https://github.com/MetaMask/metamask-plugin/blob/master/LICENSE). For information on other licenses utilized in the development of MetaMask, please see our attribution page at: [https://metamask.io/attributions.html](https://metamask.io/attributions.html)
## 9. Links ##
The Service provides, or third parties may provide, links to other World Wide Web or accessible sites, applications or resources. Because MetaMask has no control over such sites, applications and resources, you acknowledge and agree that MetaMask is not responsible for the availability of such external sites, applications or resources, and does not endorse and is not responsible or liable for any content, advertising, products or other materials on or available from such sites or resources. You further acknowledge and agree that MetaMask shall not be responsible or liable, directly or indirectly, for any damage or loss caused or alleged to be caused by or in connection with use of or reliance on any such content, goods or services available on or through any such site or resource.
## 10. Termination and Suspension ##
MetaMask may terminate or suspend all or part of the Service and your MetaMask access immediately, without prior notice or liability, if you breach any of the terms or conditions of the Terms. Upon termination of your access, your right to use the Service will immediately cease.
The following provisions of the Terms survive any termination of these Terms: INDEMNITY; WARRANTY DISCLAIMERS; LIMITATION ON LIABILITY; OUR PROPRIETARY RIGHTS; LINKS; TERMINATION; NO THIRD PARTY BENEFICIARIES; BINDING ARBITRATION AND CLASS ACTION WAIVER; GENERAL INFORMATION.
## 11. No Third Party Beneficiaries ##
You agree that, except as otherwise expressly provided in these Terms, there shall be no third party beneficiaries to the Terms.
## 12. Notice and Procedure For Making Claims of Copyright Infringement ##
If you believe that your copyright or the copyright of a person on whose behalf you are authorized to act has been infringed, please provide MetaMasks Copyright Agent a written Notice containing the following information:
· an electronic or physical signature of the person authorized to act on behalf of the owner of the copyright or other intellectual property interest;
· a description of the copyrighted work or other intellectual property that you claim has been infringed;
· a description of where the material that you claim is infringing is located on the Service;
· your address, telephone number, and email address;
· a statement by you that you have a good faith belief that the disputed use is not authorized by the copyright owner, its agent, or the law;
· a statement by you, made under penalty of perjury, that the above information in your Notice is accurate and that you are the copyright or intellectual property owner or authorized to act on the copyright or intellectual property owner's behalf.
MetaMasks Copyright Agent can be reached at:
Email: copyright [at] metamask [dot] io
Mail:
Attention:
MetaMask Copyright ℅ ConsenSys
49 Bogart Street
Brooklyn, NY 11206
## 13. Binding Arbitration and Class Action Waiver ##
PLEASE READ THIS SECTION CAREFULLY IT MAY SIGNIFICANTLY AFFECT YOUR LEGAL RIGHTS, INCLUDING YOUR RIGHT TO FILE A LAWSUIT IN COURT
### 13.1 Initial Dispute Resolution ###
The parties shall use their best efforts to engage directly to settle any dispute, claim, question, or disagreement and engage in good faith negotiations which shall be a condition to either party initiating a lawsuit or arbitration.
### 13.2 Binding Arbitration ###
If the parties do not reach an agreed upon solution within a period of 30 days from the time informal dispute resolution under the Initial Dispute Resolution provision begins, then either party may initiate binding arbitration as the sole means to resolve claims, subject to the terms set forth below. Specifically, all claims arising out of or relating to these Terms (including their formation, performance and breach), the parties relationship with each other and/or your use of the Service shall be finally settled by binding arbitration administered by the American Arbitration Association in accordance with the provisions of its Commercial Arbitration Rules and the supplementary procedures for consumer related disputes of the American Arbitration Association (the "AAA"), excluding any rules or procedures governing or permitting class actions.
The arbitrator, and not any federal, state or local court or agency, shall have exclusive authority to resolve all disputes arising out of or relating to the interpretation, applicability, enforceability or formation of these Terms, including, but not limited to any claim that all or any part of these Terms are void or voidable, or whether a claim is subject to arbitration. The arbitrator shall be empowered to grant whatever relief would be available in a court under law or in equity. The arbitrators award shall be written, and binding on the parties and may be entered as a judgment in any court of competent jurisdiction.
The parties understand that, absent this mandatory provision, they would have the right to sue in court and have a jury trial. They further understand that, in some instances, the costs of arbitration could exceed the costs of litigation and the right to discovery may be more limited in arbitration than in court.
### 13.3 Location ###
Binding arbitration shall take place in New York. You agree to submit to the personal jurisdiction of any federal or state court in New York County, New York, in order to compel arbitration, to stay proceedings pending arbitration, or to confirm, modify, vacate or enter judgment on the award entered by the arbitrator.
### 13.4 Class Action Waiver ###
The parties further agree that any arbitration shall be conducted in their individual capacities only and not as a class action or other representative action, and the parties expressly waive their right to file a class action or seek relief on a class basis. YOU AND METAMASK AGREE THAT EACH MAY BRING CLAIMS AGAINST THE OTHER ONLY IN YOUR OR ITS INDIVIDUAL CAPACITY, AND NOT AS A PLAINTIFF OR CLASS MEMBER IN ANY PURPORTED CLASS OR REPRESENTATIVE PROCEEDING. If any court or arbitrator determines that the class action waiver set forth in this paragraph is void or unenforceable for any reason or that an arbitration can proceed on a class basis, then the arbitration provision set forth above shall be deemed null and void in its entirety and the parties shall be deemed to have not agreed to arbitrate disputes.
### 13.5 Exception - Litigation of Intellectual Property and Small Claims Court Claims ###
Notwithstanding the parties' decision to resolve all disputes through arbitration, either party may bring an action in state or federal court to protect its intellectual property rights ("intellectual property rights" means patents, copyrights, moral rights, trademarks, and trade secrets, but not privacy or publicity rights). Either party may also seek relief in a small claims court for disputes or claims within the scope of that courts jurisdiction.
### 13.6 30-Day Right to Opt Out ###
You have the right to opt-out and not be bound by the arbitration and class action waiver provisions set forth above by sending written notice of your decision to opt-out to the following address: MetaMask ℅ ConsenSys, 49 Bogart Street, Brooklyn NY 11206 and via email at legal-opt@metamask.io. The notice must be sent within 30 days of September 6, 2016 or your first use of the Service, whichever is later, otherwise you shall be bound to arbitrate disputes in accordance with the terms of those paragraphs. If you opt-out of these arbitration provisions, MetaMask also will not be bound by them.
### 13.7 Changes to This Section ###
MetaMask will provide 60-days notice of any changes to this section. Changes will become effective on the 60th day, and will apply prospectively only to any claims arising after the 60th day.
For any dispute not subject to arbitration you and MetaMask agree to submit to the personal and exclusive jurisdiction of and venue in the federal and state courts located in New York, New York. You further agree to accept service of process by mail, and hereby waive any and all jurisdictional and venue defenses otherwise available.
The Terms and the relationship between you and MetaMask shall be governed by the laws of the State of New York without regard to conflict of law provisions.
## 14. General Information ##
### 14.1 Entire Agreement ###
These Terms (and any additional terms, rules and conditions of participation that MetaMask may post on the Service) constitute the entire agreement between you and MetaMask with respect to the Service and supersedes any prior agreements, oral or written, between you and MetaMask. In the event of a conflict between these Terms and the additional terms, rules and conditions of participation, the latter will prevail over the Terms to the extent of the conflict.
### 14.2 Waiver and Severability of Terms ###
The failure of MetaMask to exercise or enforce any right or provision of the Terms shall not constitute a waiver of such right or provision. If any provision of the Terms is found by an arbitrator or court of competent jurisdiction to be invalid, the parties nevertheless agree that the arbitrator or court should endeavor to give effect to the parties' intentions as reflected in the provision, and the other provisions of the Terms remain in full force and effect.
### 14.3 Statute of Limitations ###
You agree that regardless of any statute or law to the contrary, any claim or cause of action arising out of or related to the use of the Service or the Terms must be filed within one (1) year after such claim or cause of action arose or be forever barred.
### 14.4 Section Titles ###
The section titles in the Terms are for convenience only and have no legal or contractual effect.
### 14.5 Communications ###
Users with questions, complaints or claims with respect to the Service may contact us using the relevant contact information set forth above and at communications@metamask.io.
## 15 Related Links ##
**[Terms of Use](https://metamask.io/terms.html)**
**[Privacy](https://metamask.io/privacy.html)**
**[Attributions](https://metamask.io/attributions.html)**

BIN
app/images/icon-32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
app/images/icon-64.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@ -1,15 +1,16 @@
{
"name": "MetaMask",
"short_name": "Metamask",
"version": "2.9.2",
"version": "3.2.2",
"manifest_version": 2,
"author": "https://metamask.io",
"description": "Ethereum Browser Extension",
"commands": {
"_execute_browser_action": {
"suggested_key": {
"windows": "Alt+Shift+M",
"mac": "Alt+Shift+M",
"chromeos": "Search+M",
"chromeos": "Alt+Shift+M",
"linux": "Alt+Shift+M"
}
}
@ -28,7 +29,8 @@
"scripts": [
"scripts/chromereload.js",
"scripts/background.js"
]
],
"persistent": true
},
"browser_action": {
"default_icon": {
@ -53,9 +55,8 @@
}
],
"permissions": [
"notifications",
"storage",
"tabs",
"clipboardWrite",
"http://localhost:8545/"
],
"web_accessible_resources": [

16
app/notification.html Normal file
View File

@ -0,0 +1,16 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>MetaMask Notification</title>
<style>
body {
overflow: hidden;
}
</style>
</head>
<body>
<div id="app-content"></div>
<script src="./scripts/popup.js" type="text/javascript" charset="utf-8"></script>
</body>
</html>

View File

@ -0,0 +1,45 @@
const Wallet = require('ethereumjs-wallet')
const importers = require('ethereumjs-wallet/thirdparty')
const ethUtil = require('ethereumjs-util')
const accountImporter = {
importAccount(strategy, args) {
try {
const importer = this.strategies[strategy]
const privateKeyHex = importer.apply(null, args)
return Promise.resolve(privateKeyHex)
} catch (e) {
return Promise.reject(e)
}
},
strategies: {
'Private Key': (privateKey) => {
const stripped = ethUtil.stripHexPrefix(privateKey)
return stripped
},
'JSON File': (input, password) => {
let wallet
try {
wallet = importers.fromEtherWallet(input, password)
} catch (e) {
console.log('Attempt to import as EtherWallet format failed, trying V3...')
}
if (!wallet) {
wallet = Wallet.fromV3(input, password, true)
}
return walletToPrivateKey(wallet)
},
},
}
function walletToPrivateKey (wallet) {
const privateKeyBuffer = wallet.getPrivateKey()
return ethUtil.bufferToHex(privateKeyBuffer)
}
module.exports = accountImporter

View File

@ -1,192 +1,147 @@
const urlUtil = require('url')
const extend = require('xtend')
const Dnode = require('dnode')
const eos = require('end-of-stream')
const endOfStream = require('end-of-stream')
const asyncQ = require('async-q')
const pipe = require('pump')
const LocalStorageStore = require('obs-store/lib/localStorage')
const storeTransform = require('obs-store/lib/transform')
const Migrator = require('./lib/migrator/')
const migrations = require('./migrations/')
const PortStream = require('./lib/port-stream.js')
const createUnlockRequestNotification = require('./lib/notifications.js').createUnlockRequestNotification
const createTxNotification = require('./lib/notifications.js').createTxNotification
const createMsgNotification = require('./lib/notifications.js').createMsgNotification
const messageManager = require('./lib/message-manager')
const setupMultiplex = require('./lib/stream-utils.js').setupMultiplex
const notification = require('./lib/notifications.js')
const MetamaskController = require('./metamask-controller')
const extension = require('./lib/extension')
const firstTimeState = require('./first-time-state')
const STORAGE_KEY = 'metamask-config'
const METAMASK_DEBUG = 'GULP_METAMASK_DEBUG'
let popupIsOpen = false
const controller = new MetamaskController({
// User confirmation callbacks:
showUnconfirmedMessage,
unlockAccountMessage,
showUnconfirmedTx,
// Persistence Methods:
setData,
loadData,
// 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) })
//
// State and Persistence
//
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),
])
}
function setupController (initState) {
//
// MetaMask Controller
//
const controller = new MetamaskController({
// User confirmation callbacks:
showUnconfirmedMessage: triggerUi,
unlockAccountMessage: triggerUi,
showUnapprovedTx: triggerUi,
// initial state
initState,
})
global.metamaskController = controller
// setup state persistence
pipe(
controller.store,
storeTransform(versionifyData),
diskStore
)
function versionifyData(state) {
let versionedData = diskStore.getState()
versionedData.data = state
return versionedData
}
//
// connect to other contexts
//
extension.runtime.onConnect.addListener(connectRemote)
function connectRemote (remotePort) {
var isMetaMaskInternalProcess = remotePort.name === 'popup' || remotePort.name === 'notification'
var portStream = new PortStream(remotePort)
if (isMetaMaskInternalProcess) {
// communication with popup
popupIsOpen = popupIsOpen || (remotePort.name === 'popup')
controller.setupTrustedCommunication(portStream, 'MetaMask', remotePort.name)
// record popup as closed
if (remotePort.name === 'popup') {
endOfStream(portStream, () => {
popupIsOpen = false
})
}
} else {
// communication with page
var originDomain = urlUtil.parse(remotePort.sender.url).hostname
controller.setupUntrustedCommunication(portStream, originDomain)
}
}
//
// User Interface setup
//
updateBadge()
controller.txManager.on('updateBadge', updateBadge)
controller.messageManager.on('updateBadge', updateBadge)
// plugin badge text
function updateBadge () {
var label = ''
var unapprovedTxCount = controller.txManager.unapprovedTxCount
var unapprovedMsgCount = controller.messageManager.unapprovedMsgCount
var count = unapprovedTxCount + unapprovedMsgCount
if (count) {
label = String(count)
}
extension.browserAction.setBadgeText({ text: label })
extension.browserAction.setBadgeBackgroundColor({ color: '#506F8B' })
}
return Promise.resolve()
}
//
// Etc...
//
// popup trigger
function triggerUi () {
if (!popupIsOpen) notification.show()
}
// On first install, open a window to MetaMask website to how-it-works.
extension.runtime.onInstalled.addListener(function (details) {
if ((details.reason === 'install') && (!METAMASK_DEBUG)) {
extension.tabs.create({url: 'https://metamask.io/#how-it-works'})
}
})
const idStore = controller.idStore
function unlockAccountMessage () {
createUnlockRequestNotification({
title: 'Account Unlock Request',
})
}
function showUnconfirmedMessage (msgParams, msgId) {
var controllerState = controller.getState()
createMsgNotification({
imageifyIdenticons: false,
txData: {
msgParams: msgParams,
time: (new Date()).getTime(),
},
identities: controllerState.identities,
accounts: controllerState.accounts,
onConfirm: idStore.approveMessage.bind(idStore, msgId, noop),
onCancel: idStore.cancelMessage.bind(idStore, msgId),
})
}
function showUnconfirmedTx (txParams, txData, onTxDoneCb) {
var controllerState = controller.getState()
createTxNotification({
imageifyIdenticons: false,
txData: {
txParams: txParams,
time: (new Date()).getTime(),
},
identities: controllerState.identities,
accounts: controllerState.accounts,
onConfirm: idStore.approveTransaction.bind(idStore, txData.id, noop),
onCancel: idStore.cancelTransaction.bind(idStore, txData.id),
})
}
//
// connect to other contexts
//
extension.runtime.onConnect.addListener(connectRemote)
function connectRemote (remotePort) {
var isMetaMaskInternalProcess = (remotePort.name === 'popup')
var portStream = new PortStream(remotePort)
if (isMetaMaskInternalProcess) {
// communication with popup
setupTrustedCommunication(portStream, 'MetaMask')
} else {
// communication with page
var originDomain = urlUtil.parse(remotePort.sender.url).hostname
setupUntrustedCommunication(portStream, originDomain)
}
}
function setupUntrustedCommunication (connectionStream, originDomain) {
// setup multiplexing
var mx = setupMultiplex(connectionStream)
// connect features
controller.setupProviderConnection(mx.createStream('provider'), originDomain)
controller.setupPublicConfig(mx.createStream('publicConfig'))
}
function setupTrustedCommunication (connectionStream, originDomain) {
// setup multiplexing
var mx = setupMultiplex(connectionStream)
// connect features
setupControllerConnection(mx.createStream('controller'))
controller.setupProviderConnection(mx.createStream('provider'), originDomain)
}
//
// remote features
//
function setupControllerConnection (stream) {
controller.stream = stream
var api = controller.getApi()
var dnode = Dnode(api)
stream.pipe(dnode).pipe(stream)
dnode.on('remote', (remote) => {
// push updates to popup
controller.ethStore.on('update', controller.sendUpdate.bind(controller))
controller.remote = remote
idStore.on('update', controller.sendUpdate.bind(controller))
// teardown on disconnect
eos(stream, () => {
controller.ethStore.removeListener('update', controller.sendUpdate.bind(controller))
})
})
}
//
// plugin badge text
//
idStore.on('update', updateBadge)
function updateBadge (state) {
var label = ''
var unconfTxs = controller.configManager.unconfirmedTxs()
var unconfTxLen = Object.keys(unconfTxs).length
var unconfMsgs = messageManager.unconfirmedMsgs()
var unconfMsgLen = Object.keys(unconfMsgs).length
var count = unconfTxLen + unconfMsgLen
if (count) {
label = String(count)
}
extension.browserAction.setBadgeText({ text: label })
extension.browserAction.setBadgeBackgroundColor({ color: '#506F8B' })
}
function loadData () {
var oldData = getOldStyleData()
var newData
try {
newData = JSON.parse(window.localStorage[STORAGE_KEY])
} catch (e) {}
var data = extend({
meta: {
version: 0,
},
data: {
config: {
provider: {
type: 'testnet',
},
},
},
}, oldData || null, newData || null)
return data
}
function getOldStyleData () {
var config, wallet, seedWords
var result = {
meta: { version: 0 },
data: {},
}
try {
config = JSON.parse(window.localStorage['config'])
result.data.config = config
} catch (e) {}
try {
wallet = JSON.parse(window.localStorage['lightwallet'])
result.data.wallet = wallet
} catch (e) {}
try {
seedWords = window.localStorage['seedWords']
result.data.seedWords = seedWords
} catch (e) {}
return result
}
function setData (data) {
window.localStorage[STORAGE_KEY] = JSON.stringify(data)
}
function noop () {}

View File

@ -324,13 +324,13 @@ window.LiveReloadOptions = { host: 'localhost' };
this.pluginIdentifiers = {}
this.console = this.window.console && this.window.console.log && this.window.console.error ? this.window.location.href.match(/LR-verbose/) ? this.window.console : {
log: function () {},
error: this.window.console.error.bind(this.window.console),
error: console.error,
} : {
log: function () {},
error: function () {},
}
if (!(this.WebSocket = this.window.WebSocket || this.window.MozWebSocket)) {
this.console.error('LiveReload disabled because the browser does not seem to support web sockets')
console.error('LiveReload disabled because the browser does not seem to support web sockets')
return
}
if ('LiveReloadOptions' in window) {
@ -344,7 +344,7 @@ window.LiveReloadOptions = { host: 'localhost' };
} else {
this.options = Options.extract(this.window.document)
if (!this.options) {
this.console.error('LiveReload disabled because it could not find its own <SCRIPT> tag')
console.error('LiveReload disabled because it could not find its own <SCRIPT> tag')
return
}
}

View File

@ -1,13 +1,14 @@
const MAINET_RPC_URL = 'https://mainnet.infura.io/'
const TESTNET_RPC_URL = 'https://morden.infura.io/'
const MAINET_RPC_URL = 'https://mainnet.infura.io/metamask'
const TESTNET_RPC_URL = 'https://ropsten.infura.io/metamask'
const DEFAULT_RPC_URL = TESTNET_RPC_URL
global.METAMASK_DEBUG = false
global.METAMASK_DEBUG = 'GULP_METAMASK_DEBUG'
module.exports = {
network: {
default: DEFAULT_RPC_URL,
mainnet: MAINET_RPC_URL,
testnet: TESTNET_RPC_URL,
morden: TESTNET_RPC_URL,
},
}

View File

@ -1,11 +1,12 @@
const LocalMessageDuplexStream = require('post-message-stream')
const PongStream = require('ping-pong-stream/pong')
const PortStream = require('./lib/port-stream.js')
const ObjectMultiplex = require('./lib/obj-multiplex')
const extension = require('./lib/extension')
const fs = require('fs')
const path = require('path')
const inpageText = fs.readFileSync(path.join(__dirname + '/inpage.js')).toString()
const inpageText = fs.readFileSync(path.join(__dirname, 'inpage.js')).toString()
// Eventually this streaming injection could be replaced with:
// https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Language_Bindings/Components.utils.exportFunction
@ -19,9 +20,8 @@ if (shouldInjectWeb3()) {
setupStreams()
}
function setupInjection(){
function setupInjection () {
try {
// inject in-page script
var scriptTag = document.createElement('script')
scriptTag.src = extension.extension.getURL('scripts/inpage.js')
@ -30,41 +30,53 @@ function setupInjection(){
var container = document.head || document.documentElement
// append as first child
container.insertBefore(scriptTag, container.children[0])
} catch (e) {
console.error('Metamask injection failed.', e)
}
}
function setupStreams(){
function setupStreams () {
// setup communication to page and plugin
var pageStream = new LocalMessageDuplexStream({
name: 'contentscript',
target: 'inpage',
})
pageStream.on('error', console.error.bind(console))
pageStream.on('error', console.error)
var pluginPort = extension.runtime.connect({name: 'contentscript'})
var pluginStream = new PortStream(pluginPort)
pluginStream.on('error', console.error.bind(console))
pluginStream.on('error', console.error)
// forward communication plugin->inpage
pageStream.pipe(pluginStream).pipe(pageStream)
// connect contentscript->inpage reload stream
// setup local multistream channels
var mx = ObjectMultiplex()
mx.on('error', console.error.bind(console))
mx.pipe(pageStream)
var reloadStream = mx.createStream('reload')
reloadStream.on('error', console.error.bind(console))
mx.on('error', console.error)
mx.pipe(pageStream).pipe(mx)
// if we lose connection with the plugin, trigger tab refresh
pluginStream.on('close', function () {
reloadStream.write({ method: 'reset' })
})
// connect ping stream
var pongStream = new PongStream({ objectMode: true })
pongStream.pipe(mx.createStream('pingpong')).pipe(pongStream)
// ignore unused channels (handled by background)
mx.ignoreStream('provider')
mx.ignoreStream('publicConfig')
mx.ignoreStream('reload')
}
function shouldInjectWeb3(){
var shouldInject = (window.location.href.indexOf('.pdf') === -1)
return shouldInject
function shouldInjectWeb3 () {
return isAllowedSuffix(window.location.href)
}
function isAllowedSuffix (testCase) {
var prohibitedTypes = ['xml', 'pdf']
var currentUrl = window.location.href
var currentRegex
for (let i = 0; i < prohibitedTypes.length; i++) {
currentRegex = new RegExp(`\.${prohibitedTypes[i]}$`)
if (currentRegex.test(currentUrl)) {
return false
}
}
return true
}

View File

@ -0,0 +1,11 @@
//
// The default state of MetaMask
//
module.exports = {
config: {
provider: {
type: 'testnet',
},
},
}

View File

@ -2,6 +2,8 @@
cleanContextForImports()
require('web3/dist/web3.min.js')
const LocalMessageDuplexStream = require('post-message-stream')
// const PingStream = require('ping-pong-stream/ping')
// const endOfStream = require('end-of-stream')
const setupDappAutoReload = require('./lib/auto-reload.js')
const MetamaskInpageProvider = require('./lib/inpage-provider.js')
restoreContextAfterImports()
@ -29,15 +31,26 @@ web3.setProvider = function () {
console.log('MetaMask - overrode web3.setProvider')
}
console.log('MetaMask - injected web3')
// export global web3, with usage-detection reload fn
var triggerReload = setupDappAutoReload(web3)
//
// export global web3 with auto dapp reload
//
// listen for reset requests from metamask
var reloadStream = inpageProvider.multiStream.createStream('reload')
setupDappAutoReload(web3, reloadStream)
reloadStream.once('data', triggerReload)
// set web3 defaultAcount
// 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)
// set web3 defaultAccount
inpageProvider.publicConfigStore.subscribe(function (state) {
web3.eth.defaultAccount = state.selectedAddress
})

View File

@ -0,0 +1,554 @@
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 normalizeAddress = require('./lib/sig-util').normalize
// Keyrings:
const SimpleKeyring = require('./keyrings/simple')
const HdKeyring = require('./keyrings/hd')
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) => {
this.keyrings.push(keyring)
return this.setupAccounts(accounts)
})
.then(() => this.persistAllKeyrings())
.then(() => this.fullUpdate())
.then(() => {
return keyring
})
}
// 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)
})
}
// 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)
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)
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

125
app/scripts/keyrings/hd.js Normal file
View File

@ -0,0 +1,125 @@
const EventEmitter = require('events').EventEmitter
const hdkey = require('ethereumjs-wallet/hdkey')
const bip39 = require('bip39')
const ethUtil = require('ethereumjs-util')
// *Internal Deps
const sigUtil = require('../lib/sig-util')
// Options:
const hdPathString = `m/44'/60'/0'/0`
const type = 'HD Key Tree'
class HdKeyring extends EventEmitter {
/* PUBLIC METHODS */
constructor (opts = {}) {
super()
this.type = type
this.deserialize(opts)
}
serialize () {
return Promise.resolve({
mnemonic: this.mnemonic,
numberOfAccounts: this.wallets.length,
})
}
deserialize (opts = {}) {
this.opts = opts || {}
this.wallets = []
this.mnemonic = null
this.root = null
if (opts.mnemonic) {
this._initFromMnemonic(opts.mnemonic)
}
if (opts.numberOfAccounts) {
return this.addAccounts(opts.numberOfAccounts)
}
return Promise.resolve([])
}
addAccounts (numberOfAccounts = 1) {
if (!this.root) {
this._initFromMnemonic(bip39.generateMnemonic())
}
const oldLen = this.wallets.length
const newWallets = []
for (let i = oldLen; i < numberOfAccounts + oldLen; i++) {
const child = this.root.deriveChild(i)
const wallet = child.getWallet()
newWallets.push(wallet)
this.wallets.push(wallet)
}
const hexWallets = newWallets.map(w => w.getAddress().toString('hex'))
return Promise.resolve(hexWallets)
}
getAccounts () {
return Promise.resolve(this.wallets.map(w => w.getAddress().toString('hex')))
}
// tx is an instance of the ethereumjs-transaction class.
signTransaction (address, tx) {
const wallet = this._getWalletForAccount(address)
var privKey = wallet.getPrivateKey()
tx.sign(privKey)
return Promise.resolve(tx)
}
// For eth_sign, we need to sign transactions:
// hd
signMessage (withAccount, data) {
const wallet = this._getWalletForAccount(withAccount)
const message = ethUtil.stripHexPrefix(data)
var privKey = wallet.getPrivateKey()
var msgSig = ethUtil.ecsign(new Buffer(message, 'hex'), privKey)
var rawMsgSig = ethUtil.bufferToHex(sigUtil.concatSig(msgSig.v, msgSig.r, msgSig.s))
return Promise.resolve(rawMsgSig)
}
// For eth_sign, we need to sign transactions:
newGethSignMessage (withAccount, msgHex) {
const wallet = this._getWalletForAccount(withAccount)
const privKey = wallet.getPrivateKey()
const msgBuffer = ethUtil.toBuffer(msgHex)
const msgHash = ethUtil.hashPersonalMessage(msgBuffer)
const msgSig = ethUtil.ecsign(msgHash, privKey)
const rawMsgSig = ethUtil.bufferToHex(sigUtil.concatSig(msgSig.v, msgSig.r, msgSig.s))
return Promise.resolve(rawMsgSig)
}
exportAccount (address) {
const wallet = this._getWalletForAccount(address)
return Promise.resolve(wallet.getPrivateKey().toString('hex'))
}
/* PRIVATE METHODS */
_initFromMnemonic (mnemonic) {
this.mnemonic = mnemonic
const seed = bip39.mnemonicToSeed(mnemonic)
this.hdWallet = hdkey.fromMasterSeed(seed)
this.root = this.hdWallet.derivePath(hdPathString)
}
_getWalletForAccount (account) {
const targetAddress = sigUtil.normalize(account)
return this.wallets.find((w) => {
const address = w.getAddress().toString('hex')
return ((address === targetAddress) ||
(sigUtil.normalize(address) === targetAddress))
})
}
}
HdKeyring.type = type
module.exports = HdKeyring

View File

@ -0,0 +1,100 @@
const EventEmitter = require('events').EventEmitter
const Wallet = require('ethereumjs-wallet')
const ethUtil = require('ethereumjs-util')
const type = 'Simple Key Pair'
const sigUtil = require('../lib/sig-util')
class SimpleKeyring extends EventEmitter {
/* PUBLIC METHODS */
constructor (opts) {
super()
this.type = type
this.opts = opts || {}
this.wallets = []
}
serialize () {
return Promise.resolve(this.wallets.map(w => w.getPrivateKey().toString('hex')))
}
deserialize (privateKeys = []) {
return new Promise((resolve, reject) => {
try {
this.wallets = privateKeys.map((privateKey) => {
const stripped = ethUtil.stripHexPrefix(privateKey)
const buffer = new Buffer(stripped, 'hex')
const wallet = Wallet.fromPrivateKey(buffer)
return wallet
})
} catch (e) {
reject(e)
}
resolve()
})
}
addAccounts (n = 1) {
var newWallets = []
for (var i = 0; i < n; i++) {
newWallets.push(Wallet.generate())
}
this.wallets = this.wallets.concat(newWallets)
const hexWallets = newWallets.map(w => ethUtil.bufferToHex(w.getAddress()))
return Promise.resolve(hexWallets)
}
getAccounts () {
return Promise.resolve(this.wallets.map(w => ethUtil.bufferToHex(w.getAddress())))
}
// tx is an instance of the ethereumjs-transaction class.
signTransaction (address, tx) {
const wallet = this._getWalletForAccount(address)
var privKey = wallet.getPrivateKey()
tx.sign(privKey)
return Promise.resolve(tx)
}
// For eth_sign, we need to sign transactions:
signMessage (withAccount, data) {
const wallet = this._getWalletForAccount(withAccount)
const message = ethUtil.stripHexPrefix(data)
var privKey = wallet.getPrivateKey()
var msgSig = ethUtil.ecsign(new Buffer(message, 'hex'), privKey)
var rawMsgSig = ethUtil.bufferToHex(sigUtil.concatSig(msgSig.v, msgSig.r, msgSig.s))
return Promise.resolve(rawMsgSig)
}
// For eth_sign, we need to sign transactions:
newGethSignMessage (withAccount, msgHex) {
const wallet = this._getWalletForAccount(withAccount)
const privKey = wallet.getPrivateKey()
const msgBuffer = ethUtil.toBuffer(msgHex)
const msgHash = ethUtil.hashPersonalMessage(msgBuffer)
const msgSig = ethUtil.ecsign(msgHash, privKey)
const rawMsgSig = ethUtil.bufferToHex(sigUtil.concatSig(msgSig.v, msgSig.r, msgSig.s))
return Promise.resolve(rawMsgSig)
}
exportAccount (address) {
const wallet = this._getWalletForAccount(address)
return Promise.resolve(wallet.getPrivateKey().toString('hex'))
}
/* PRIVATE METHODS */
_getWalletForAccount (account) {
const address = sigUtil.normalize(account)
let wallet = this.wallets.find(w => ethUtil.bufferToHex(w.getAddress()) === address)
if (!wallet) throw new Error('Simple Keyring - Unable to find matching address.')
return wallet
}
}
SimpleKeyring.type = type
module.exports = SimpleKeyring

View File

@ -1,6 +1,9 @@
var uri = 'https://faucet.metamask.io/'
const uri = 'https://faucet.metamask.io/'
const METAMASK_DEBUG = 'GULP_METAMASK_DEBUG'
const env = process.env.METAMASK_ENV
module.exports = function (address) {
if (METAMASK_DEBUG || env === 'test') return // Don't faucet in development or test
var http = new XMLHttpRequest()
var data = address
http.open('POST', uri, true)

View File

@ -3,7 +3,7 @@ const ensnare = require('ensnare')
module.exports = setupDappAutoReload
function setupDappAutoReload (web3, controlStream) {
function setupDappAutoReload (web3) {
// export web3 as a global, checking for usage
var pageIsUsingWeb3 = false
var resetWasRequested = false
@ -16,19 +16,18 @@ function setupDappAutoReload (web3, controlStream) {
global.web3 = web3
}))
// listen for reset requests from metamask
controlStream.once('data', function () {
return handleResetRequest
function handleResetRequest () {
resetWasRequested = true
// ignore if web3 was not used
if (!pageIsUsingWeb3) return
// reload after short timeout
triggerReset()
})
// reload the page
function triggerReset () {
setTimeout(function () {
global.location.reload()
}, 500)
setTimeout(triggerReset, 500)
}
}
// reload the page
function triggerReset () {
global.location.reload()
}

View File

@ -1,11 +1,10 @@
const Migrator = require('pojo-migrator')
const MetamaskConfig = require('../config.js')
const migrations = require('./migrations')
const rp = require('request-promise')
const ethUtil = require('ethereumjs-util')
const normalize = require('./sig-util').normalize
const TESTNET_RPC = MetamaskConfig.network.testnet
const MAINNET_RPC = MetamaskConfig.network.mainnet
const txLimit = 40
const MORDEN_RPC = MetamaskConfig.network.morden
/* The config-manager is a convenience object
* wrapping a pojo-migrator.
@ -16,54 +15,21 @@ const txLimit = 40
*/
module.exports = ConfigManager
function ConfigManager (opts) {
this.txLimit = txLimit
// ConfigManager is observable and will emit updates
this._subs = []
/* The migrator exported on the config-manager
* has two methods the user should be concerned with:
*
* getData(), which returns the app-consumable data object
* saveData(), which persists the app-consumable data object.
*/
this.migrator = new Migrator({
// Migrations must start at version 1 or later.
// They are objects with a `version` number
// and a `migrate` function.
//
// The `migrate` function receives the previous
// config data format, and returns the new one.
migrations: migrations,
// How to load initial config.
// Includes step on migrating pre-pojo-migrator data.
loadData: opts.loadData,
// How to persist migrated config.
setData: opts.setData,
})
this.store = opts.store
}
ConfigManager.prototype.setConfig = function (config) {
var data = this.migrator.getData()
var data = this.getData()
data.config = config
this.setData(data)
this._emitUpdates(config)
}
ConfigManager.prototype.getConfig = function () {
var data = this.migrator.getData()
if ('config' in data) {
return data.config
} else {
return {
provider: {
type: 'testnet',
},
}
}
var data = this.getData()
return data.config
}
ConfigManager.prototype.setRpcTarget = function (rpcUrl) {
@ -97,19 +63,40 @@ ConfigManager.prototype.getProvider = function () {
}
ConfigManager.prototype.setData = function (data) {
this.migrator.saveData(data)
this.store.putState(data)
}
ConfigManager.prototype.getData = function () {
return this.migrator.getData()
return this.store.getState()
}
ConfigManager.prototype.setWallet = function (wallet) {
var data = this.migrator.getData()
var data = this.getData()
data.wallet = wallet
this.setData(data)
}
ConfigManager.prototype.setVault = function (encryptedString) {
var data = this.getData()
data.vault = encryptedString
this.setData(data)
}
ConfigManager.prototype.getVault = function () {
var data = this.getData()
return data.vault
}
ConfigManager.prototype.getKeychains = function () {
return this.getData().keychains || []
}
ConfigManager.prototype.setKeychains = function (keychains) {
var data = this.getData()
data.keychains = keychains
this.setData(data)
}
ConfigManager.prototype.getSelectedAccount = function () {
var config = this.getConfig()
return config.selectedAccount
@ -117,26 +104,38 @@ ConfigManager.prototype.getSelectedAccount = function () {
ConfigManager.prototype.setSelectedAccount = function (address) {
var config = this.getConfig()
config.selectedAccount = address
config.selectedAccount = ethUtil.addHexPrefix(address)
this.setConfig(config)
}
ConfigManager.prototype.getWallet = function () {
return this.migrator.getData().wallet
return this.getData().wallet
}
// Takes a boolean
ConfigManager.prototype.setShowSeedWords = function (should) {
var data = this.migrator.getData()
var data = this.getData()
data.showSeedWords = should
this.setData(data)
}
ConfigManager.prototype.getShouldShowSeedWords = function () {
var data = this.migrator.getData()
var data = this.getData()
return data.showSeedWords
}
ConfigManager.prototype.setSeedWords = function (words) {
var data = this.getData()
data.seedWords = words
this.setData(data)
}
ConfigManager.prototype.getSeedWords = function () {
var data = this.getData()
return data.seedWords
}
ConfigManager.prototype.getCurrentRpcAddress = function () {
var provider = this.getProvider()
if (!provider) return null
@ -148,21 +147,20 @@ ConfigManager.prototype.getCurrentRpcAddress = function () {
case 'testnet':
return TESTNET_RPC
case 'morden':
return MORDEN_RPC
default:
return provider && provider.rpcTarget ? provider.rpcTarget : TESTNET_RPC
}
}
ConfigManager.prototype.setData = function (data) {
this.migrator.saveData(data)
}
//
// Tx
//
ConfigManager.prototype.getTxList = function () {
var data = this.migrator.getData()
var data = this.getData()
if (data.transactions !== undefined) {
return data.transactions
} else {
@ -170,61 +168,12 @@ ConfigManager.prototype.getTxList = function () {
}
}
ConfigManager.prototype.unconfirmedTxs = function () {
var transactions = this.getTxList()
return transactions.filter(tx => tx.status === 'unconfirmed')
.reduce((result, tx) => { result[tx.id] = tx; return result }, {})
}
ConfigManager.prototype._saveTxList = function (txList) {
var data = this.migrator.getData()
ConfigManager.prototype.setTxList = function (txList) {
var data = this.getData()
data.transactions = txList
this.setData(data)
}
ConfigManager.prototype.addTx = function (tx) {
var transactions = this.getTxList()
while (transactions.length > this.txLimit - 1) {
transactions.shift()
}
transactions.push(tx)
this._saveTxList(transactions)
}
ConfigManager.prototype.getTx = function (txId) {
var transactions = this.getTxList()
var matching = transactions.filter(tx => tx.id === txId)
return matching.length > 0 ? matching[0] : null
}
ConfigManager.prototype.confirmTx = function (txId) {
this._setTxStatus(txId, 'confirmed')
}
ConfigManager.prototype.rejectTx = function (txId) {
this._setTxStatus(txId, 'rejected')
}
ConfigManager.prototype._setTxStatus = function (txId, status) {
var tx = this.getTx(txId)
tx.status = status
this.updateTx(tx)
}
ConfigManager.prototype.updateTx = function (tx) {
var transactions = this.getTxList()
var found, index
transactions.forEach((otherTx, i) => {
if (otherTx.id === tx.id) {
found = true
index = i
}
})
if (found) {
transactions[index] = tx
}
this._saveTxList(transactions)
}
// wallet nickname methods
@ -235,13 +184,15 @@ ConfigManager.prototype.getWalletNicknames = function () {
}
ConfigManager.prototype.nicknameForWallet = function (account) {
const address = normalize(account)
const nicknames = this.getWalletNicknames()
return nicknames[account]
return nicknames[address]
}
ConfigManager.prototype.setNicknameForWallet = function (account, nickname) {
const address = normalize(account)
const nicknames = this.getWalletNicknames()
nicknames[account] = nickname
nicknames[address] = nickname
var data = this.getData()
data.walletNicknames = nicknames
this.setData(data)
@ -249,6 +200,17 @@ ConfigManager.prototype.setNicknameForWallet = function (account, nickname) {
// observable
ConfigManager.prototype.getSalt = function () {
var data = this.getData()
return data.salt
}
ConfigManager.prototype.setSalt = function (salt) {
var data = this.getData()
data.salt = salt
this.setData(data)
}
ConfigManager.prototype.subscribe = function (fn) {
this._subs.push(fn)
var unsubscribe = this.unsubscribe.bind(this, fn)
@ -266,110 +228,25 @@ ConfigManager.prototype._emitUpdates = function (state) {
})
}
ConfigManager.prototype.setConfirmed = function (confirmed) {
ConfigManager.prototype.getGasMultiplier = function () {
var data = this.getData()
data.isConfirmed = confirmed
return data.gasMultiplier
}
ConfigManager.prototype.setGasMultiplier = function (gasMultiplier) {
var data = this.getData()
data.gasMultiplier = gasMultiplier
this.setData(data)
}
ConfigManager.prototype.getConfirmed = function () {
ConfigManager.prototype.setLostAccounts = function (lostAccounts) {
var data = this.getData()
return ('isConfirmed' in data) && data.isConfirmed
}
ConfigManager.prototype.setCurrentFiat = function (currency) {
var data = this.getData()
data.fiatCurrency = currency
data.lostAccounts = lostAccounts
this.setData(data)
}
ConfigManager.prototype.getCurrentFiat = function () {
ConfigManager.prototype.getLostAccounts = function () {
var data = this.getData()
return ('fiatCurrency' in data) && data.fiatCurrency
}
ConfigManager.prototype.updateConversionRate = function () {
var data = this.getData()
return rp(`https://www.cryptonator.com/api/ticker/eth-${data.fiatCurrency}`)
.then((response) => {
const parsedResponse = JSON.parse(response)
this.setConversionPrice(parsedResponse.ticker.price)
this.setConversionDate(parsedResponse.timestamp)
}).catch((err) => {
console.error('Error in conversion.', err)
this.setConversionPrice(0)
this.setConversionDate('N/A')
})
}
ConfigManager.prototype.setConversionPrice = function (price) {
var data = this.getData()
data.conversionRate = Number(price)
this.setData(data)
}
ConfigManager.prototype.setConversionDate = function (datestring) {
var data = this.getData()
data.conversionDate = datestring
this.setData(data)
}
ConfigManager.prototype.getConversionRate = function () {
var data = this.getData()
return (('conversionRate' in data) && data.conversionRate) || 0
}
ConfigManager.prototype.getConversionDate = function () {
var data = this.getData()
return (('conversionDate' in data) && data.conversionDate) || 'N/A'
}
ConfigManager.prototype.setShouldntShowWarning = function () {
var data = this.getData()
if (data.isEthConfirmed) {
data.isEthConfirmed = !data.isEthConfirmed
} else {
data.isEthConfirmed = true
}
this.setData(data)
}
ConfigManager.prototype.getShouldntShowWarning = function () {
var data = this.getData()
return ('isEthConfirmed' in data) && data.isEthConfirmed
}
ConfigManager.prototype.getShapeShiftTxList = function () {
var data = this.getData()
var shapeShiftTxList = data.shapeShiftTxList ? data.shapeShiftTxList : []
shapeShiftTxList.forEach((tx) => {
if (tx.response.status !== 'complete') {
var requestListner = function (request) {
tx.response = JSON.parse(this.responseText)
if (tx.response.status === 'complete') {
tx.time = new Date().getTime()
}
}
var shapShiftReq = new XMLHttpRequest()
shapShiftReq.addEventListener('load', requestListner)
shapShiftReq.open('GET', `https://shapeshift.io/txStat/${tx.depositAddress}`, true)
shapShiftReq.send()
}
})
this.setData(data)
return shapeShiftTxList
}
ConfigManager.prototype.createShapeShiftTx = function (depositAddress, depositType) {
var data = this.getData()
var shapeShiftTx = {depositAddress, depositType, key: 'shapeshift', time: new Date().getTime(), response: {}}
if (!data.shapeShiftTxList) {
data.shapeShiftTxList = [shapeShiftTx]
} else {
data.shapeShiftTxList.push(shapeShiftTx)
}
this.setData(data)
return data.lostAccounts || []
}

View File

@ -0,0 +1,70 @@
const ObservableStore = require('obs-store')
const extend = require('xtend')
// every ten minutes
const POLLING_INTERVAL = 600000
class CurrencyController {
constructor (opts = {}) {
const initState = extend({
currentCurrency: 'USD',
conversionRate: 0,
conversionDate: 'N/A',
}, opts.initState)
this.store = new ObservableStore(initState)
}
//
// PUBLIC METHODS
//
getCurrentCurrency () {
return this.store.getState().currentCurrency
}
setCurrentCurrency (currentCurrency) {
this.store.updateState({ currentCurrency })
}
getConversionRate () {
return this.store.getState().conversionRate
}
setConversionRate (conversionRate) {
this.store.updateState({ conversionRate })
}
getConversionDate () {
return this.store.getState().conversionDate
}
setConversionDate (conversionDate) {
this.store.updateState({ conversionDate })
}
updateConversionRate () {
const currentCurrency = this.getCurrentCurrency()
return fetch(`https://www.cryptonator.com/api/ticker/eth-${currentCurrency}`)
.then(response => response.json())
.then((parsedResponse) => {
this.setConversionRate(Number(parsedResponse.ticker.price))
this.setConversionDate(Number(parsedResponse.timestamp))
}).catch((err) => {
console.warn('MetaMask - Failed to query currency conversion.')
this.setConversionRate(0)
this.setConversionDate('N/A')
})
}
scheduleConversionInterval () {
if (this.conversionInterval) {
clearInterval(this.conversionInterval)
}
this.conversionInterval = setInterval(() => {
this.updateConversionRate()
}, POLLING_INTERVAL)
}
}
module.exports = CurrencyController

View File

@ -0,0 +1,33 @@
const ObservableStore = require('obs-store')
const normalizeAddress = require('../sig-util').normalize
class PreferencesController {
constructor (opts = {}) {
const initState = opts.initState || {}
this.store = new ObservableStore(initState)
}
//
// PUBLIC METHODS
//
setSelectedAddress(_address) {
return new Promise((resolve, reject) => {
const address = normalizeAddress(_address)
this.store.updateState({ selectedAddress: address })
resolve()
})
}
getSelectedAddress(_address) {
return this.store.getState().selectedAddress
}
//
// PRIVATE METHODS
//
}
module.exports = PreferencesController

View File

@ -0,0 +1,104 @@
const ObservableStore = require('obs-store')
const extend = require('xtend')
// every three seconds when an incomplete tx is waiting
const POLLING_INTERVAL = 3000
class ShapeshiftController {
constructor (opts = {}) {
const initState = extend({
shapeShiftTxList: [],
}, opts.initState)
this.store = new ObservableStore(initState)
this.pollForUpdates()
}
//
// PUBLIC METHODS
//
getShapeShiftTxList () {
const shapeShiftTxList = this.store.getState().shapeShiftTxList
return shapeShiftTxList
}
getPendingTxs () {
const txs = this.getShapeShiftTxList()
const pending = txs.filter(tx => tx.response && tx.response.status !== 'complete')
return pending
}
pollForUpdates () {
const pendingTxs = this.getPendingTxs()
if (pendingTxs.length === 0) {
return
}
Promise.all(pendingTxs.map((tx) => {
return this.updateTx(tx)
}))
.then((results) => {
results.forEach(tx => this.saveTx(tx))
this.timeout = setTimeout(this.pollForUpdates.bind(this), POLLING_INTERVAL)
})
}
updateTx (tx) {
const url = `https://shapeshift.io/txStat/${tx.depositAddress}`
return fetch(url)
.then((response) => {
return response.json()
}).then((json) => {
tx.response = json
if (tx.response.status === 'complete') {
tx.time = new Date().getTime()
}
return tx
})
}
saveTx (tx) {
const { shapeShiftTxList } = this.store.getState()
const index = shapeShiftTxList.indexOf(tx)
if (index !== -1) {
shapeShiftTxList[index] = tx
this.store.updateState({ shapeShiftTxList })
}
}
removeShapeShiftTx (tx) {
const { shapeShiftTxList } = this.store.getState()
const index = shapeShiftTxList.indexOf(index)
if (index !== -1) {
shapeShiftTxList.splice(index, 1)
}
this.updateState({ shapeShiftTxList })
}
createShapeShiftTx (depositAddress, depositType) {
const state = this.store.getState()
let { shapeShiftTxList } = state
var shapeShiftTx = {
depositAddress,
depositType,
key: 'shapeshift',
time: new Date().getTime(),
response: {},
}
if (!shapeShiftTxList) {
shapeShiftTxList = [shapeShiftTx]
} else {
shapeShiftTxList.push(shapeShiftTx)
}
this.store.updateState({ shapeShiftTxList })
this.pollForUpdates()
}
}
module.exports = ShapeshiftController

View File

@ -0,0 +1,132 @@
/* 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: {},
})
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
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

View File

@ -41,11 +41,28 @@ function Extension () {
}
} catch (e) {}
try {
if (browser[api]) {
_this[api] = browser[api]
}
} catch (e) {}
try {
_this.api = browser.extension[api]
} catch (e) {}
})
try {
if (browser && browser.runtime) {
this.runtime = browser.runtime
}
} catch (e) {}
try {
if (browser && browser.browserAction) {
this.browserAction = browser.browserAction
}
} catch (e) {}
}
module.exports = Extension

View File

@ -1,4 +1,13 @@
/* ID Management
*
* This module exists to hold the decrypted credentials for the current session.
* It therefore exposes sign methods, because it is able to perform these
* with noa dditional authentication, because its very instantiation
* means the vault is unlocked.
*/
const ethUtil = require('ethereumjs-util')
const BN = ethUtil.BN
const Transaction = require('ethereumjs-tx')
module.exports = IdManagement
@ -16,9 +25,15 @@ function IdManagement (opts) {
}
this.signTx = function (txParams) {
// calculate gas with custom gas multiplier
var gasMultiplier = this.configManager.getGasMultiplier() || 1
var gasPrice = new BN(ethUtil.stripHexPrefix(txParams.gasPrice), 16)
gasPrice = gasPrice.mul(new BN(gasMultiplier * 100, 10)).div(new BN(100, 10))
txParams.gasPrice = ethUtil.intToHex(gasPrice.toNumber())
// normalize values
txParams.to = ethUtil.addHexPrefix(txParams.to)
txParams.from = ethUtil.addHexPrefix(txParams.from)
txParams.from = ethUtil.addHexPrefix(txParams.from.toLowerCase())
txParams.value = ethUtil.addHexPrefix(txParams.value)
txParams.data = ethUtil.addHexPrefix(txParams.data)
txParams.gasLimit = ethUtil.addHexPrefix(txParams.gasLimit || txParams.gas)
@ -43,7 +58,7 @@ function IdManagement (opts) {
this.signMsg = function (address, message) {
// sign message
var privKeyHex = this.exportPrivateKey(address)
var privKeyHex = this.exportPrivateKey(address.toLowerCase())
var privKey = ethUtil.toBuffer(privKeyHex)
var msgSig = ethUtil.ecsign(new Buffer(message.replace('0x', ''), 'hex'), privKey)
var rawMsgSig = ethUtil.bufferToHex(concatSig(msgSig.v, msgSig.r, msgSig.s))

View File

@ -0,0 +1,80 @@
const IdentityStore = require('./idStore')
const HdKeyring = require('../keyrings/hd')
const sigUtil = require('./sig-util')
const normalize = sigUtil.normalize
const denodeify = require('denodeify')
module.exports = class IdentityStoreMigrator {
constructor ({ configManager }) {
this.configManager = configManager
const hasOldVault = this.hasOldVault()
if (!hasOldVault) {
this.idStore = new IdentityStore({ configManager })
}
}
migratedVaultForPassword (password) {
const hasOldVault = this.hasOldVault()
const configManager = this.configManager
if (!this.idStore) {
this.idStore = new IdentityStore({ configManager })
}
if (!hasOldVault) {
return Promise.resolve(null)
}
const idStore = this.idStore
const submitPassword = denodeify(idStore.submitPassword.bind(idStore))
return submitPassword(password)
.then(() => {
const serialized = this.serializeVault()
return this.checkForLostAccounts(serialized)
})
}
serializeVault () {
const mnemonic = this.idStore._idmgmt.getSeed()
const numberOfAccounts = this.idStore._getAddresses().length
return {
type: 'HD Key Tree',
data: { mnemonic, numberOfAccounts },
}
}
checkForLostAccounts (serialized) {
const hd = new HdKeyring()
return hd.deserialize(serialized.data)
.then((hexAccounts) => {
const newAccounts = hexAccounts.map(normalize)
const oldAccounts = this.idStore._getAddresses().map(normalize)
const lostAccounts = oldAccounts.reduce((result, account) => {
if (newAccounts.includes(account)) {
return result
} else {
result.push(account)
return result
}
}, [])
return {
serialized,
lostAccounts: lostAccounts.map((address) => {
return {
address,
privateKey: this.idStore.exportAccount(address),
}
}),
}
})
}
hasOldVault () {
const wallet = this.configManager.getWallet()
return wallet
}
}

View File

@ -1,18 +1,14 @@
const EventEmitter = require('events').EventEmitter
const inherits = require('util').inherits
const async = require('async')
const ethUtil = require('ethereumjs-util')
const EthQuery = require('eth-query')
const LightwalletKeyStore = require('eth-lightwallet').keystore
const KeyStore = require('eth-lightwallet').keystore
const clone = require('clone')
const extend = require('xtend')
const createId = require('web3-provider-engine/util/random-id')
const ethBinToOps = require('eth-bin-to-ops')
const autoFaucet = require('./auto-faucet')
const messageManager = require('./message-manager')
const DEFAULT_RPC = 'https://testrpc.metamask.io/'
const IdManagement = require('./id-management')
module.exports = IdentityStore
inherits(IdentityStore, EventEmitter)
@ -33,28 +29,32 @@ function IdentityStore (opts = {}) {
selectedAddress: null,
identities: {},
}
// not part of serilized metamask state - only kept in memory
this._unconfTxCbs = {}
this._unconfMsgCbs = {}
}
//
// public
//
IdentityStore.prototype.createNewVault = function (password, entropy, cb) {
IdentityStore.prototype.createNewVault = function (password, cb) {
delete this._keyStore
var serializedKeystore = this.configManager.getWallet()
this._createIdmgmt(password, null, entropy, (err) => {
if (serializedKeystore) {
this.configManager.setData({})
}
this.purgeCache()
this._createVault(password, null, (err) => {
if (err) return cb(err)
this._loadIdentities()
this._didUpdate()
this._autoFaucet()
this.configManager.setShowSeedWords(true)
var seedWords = this._idmgmt.getSeed()
this._loadIdentities()
cb(null, seedWords)
})
}
@ -67,11 +67,12 @@ IdentityStore.prototype.recoverSeed = function (cb) {
}
IdentityStore.prototype.recoverFromSeed = function (password, seed, cb) {
this._createIdmgmt(password, seed, null, (err) => {
this.purgeCache()
this._createVault(password, seed, (err) => {
if (err) return cb(err)
this._loadIdentities()
this._didUpdate()
cb(null, this.getState())
})
}
@ -93,17 +94,8 @@ IdentityStore.prototype.getState = function () {
isInitialized: !!configManager.getWallet() && !seedWords,
isUnlocked: this._isUnlocked(),
seedWords: seedWords,
isConfirmed: configManager.getConfirmed(),
isEthConfirmed: configManager.getShouldntShowWarning(),
unconfTxs: configManager.unconfirmedTxs(),
transactions: configManager.getTxList(),
unconfMsgs: messageManager.unconfirmedMsgs(),
messages: messageManager.getMsgList(),
selectedAddress: configManager.getSelectedAccount(),
shapeShiftTxList: configManager.getShapeShiftTxList(),
currentFiat: configManager.getCurrentFiat(),
conversionRate: configManager.getConversionRate(),
conversionDate: configManager.getConversionDate(),
gasMultiplier: configManager.getGasMultiplier(),
}))
}
@ -121,7 +113,7 @@ IdentityStore.prototype.getSelectedAddress = function () {
return configManager.getSelectedAccount()
}
IdentityStore.prototype.setSelectedAddress = function (address, cb) {
IdentityStore.prototype.setSelectedAddressSync = function (address) {
const configManager = this.configManager
if (!address) {
var addresses = this._getAddresses()
@ -129,7 +121,12 @@ IdentityStore.prototype.setSelectedAddress = function (address, cb) {
}
configManager.setSelectedAccount(address)
if (cb) return cb(null, address)
return address
}
IdentityStore.prototype.setSelectedAddress = function (address, cb) {
const resultAddress = this.setSelectedAddressSync(address)
if (cb) return cb(null, resultAddress)
}
IdentityStore.prototype.revealAccount = function (cb) {
@ -139,6 +136,11 @@ IdentityStore.prototype.revealAccount = function (cb) {
keyStore.setDefaultHdDerivationPath(this.hdPathString)
keyStore.generateNewAddress(derivedKey, 1)
const addresses = keyStore.getAddresses()
const address = addresses[ addresses.length - 1 ]
this._ethStore.addAccount(ethUtil.addHexPrefix(address))
configManager.setWallet(keyStore.serialize())
this._loadIdentities()
@ -183,192 +185,10 @@ IdentityStore.prototype.submitPassword = function (password, cb) {
IdentityStore.prototype.exportAccount = function (address, cb) {
var privateKey = this._idmgmt.exportPrivateKey(address)
cb(null, privateKey)
if (cb) cb(null, privateKey)
return privateKey
}
//
// Transactions
//
// comes from dapp via zero-client hooked-wallet provider
IdentityStore.prototype.addUnconfirmedTransaction = function (txParams, onTxDoneCb, cb) {
const configManager = this.configManager
var self = this
// create txData obj with parameters and meta data
var time = (new Date()).getTime()
var txId = createId()
txParams.metamaskId = txId
txParams.metamaskNetworkId = self._currentState.network
var txData = {
id: txId,
txParams: txParams,
time: time,
status: 'unconfirmed',
}
console.log('addUnconfirmedTransaction:', txData)
// keep the onTxDoneCb around for after approval/denial (requires user interaction)
// This onTxDoneCb fires completion to the Dapp's write operation.
self._unconfTxCbs[txId] = onTxDoneCb
var provider = self._ethStore._query.currentProvider
var query = new EthQuery(provider)
// calculate metadata for tx
async.parallel([
analyzeForDelegateCall,
estimateGas,
], didComplete)
// perform static analyis on the target contract code
function analyzeForDelegateCall(cb){
if (txParams.to) {
query.getCode(txParams.to, function (err, result) {
if (err) return cb(err)
var code = ethUtil.toBuffer(result)
if (code !== '0x') {
var ops = ethBinToOps(code)
var containsDelegateCall = ops.some((op) => op.name === 'DELEGATECALL')
txData.containsDelegateCall = containsDelegateCall
cb()
} else {
cb()
}
})
} else {
cb()
}
}
function estimateGas(cb){
query.estimateGas(txParams, function(err, result){
if (err) return cb(err)
txData.estimatedGas = result
cb()
})
}
function didComplete (err) {
if (err) return cb(err)
configManager.addTx(txData)
// signal update
self._didUpdate()
// signal completion of add tx
cb(null, txData)
}
}
// comes from metamask ui
IdentityStore.prototype.approveTransaction = function (txId, cb) {
const configManager = this.configManager
var approvalCb = this._unconfTxCbs[txId] || noop
// accept tx
cb()
approvalCb(null, true)
// clean up
configManager.confirmTx(txId)
delete this._unconfTxCbs[txId]
this._didUpdate()
}
// comes from metamask ui
IdentityStore.prototype.cancelTransaction = function (txId) {
const configManager = this.configManager
var approvalCb = this._unconfTxCbs[txId] || noop
// reject tx
approvalCb(null, false)
// clean up
configManager.rejectTx(txId)
delete this._unconfTxCbs[txId]
this._didUpdate()
}
// performs the actual signing, no autofill of params
IdentityStore.prototype.signTransaction = function (txParams, cb) {
try {
console.log('signing tx...', txParams)
var rawTx = this._idmgmt.signTx(txParams)
cb(null, rawTx)
} catch (err) {
cb(err)
}
}
//
// Messages
//
// comes from dapp via zero-client hooked-wallet provider
IdentityStore.prototype.addUnconfirmedMessage = function (msgParams, cb) {
// create txData obj with parameters and meta data
var time = (new Date()).getTime()
var msgId = createId()
var msgData = {
id: msgId,
msgParams: msgParams,
time: time,
status: 'unconfirmed',
}
messageManager.addMsg(msgData)
console.log('addUnconfirmedMessage:', msgData)
// keep the cb around for after approval (requires user interaction)
// This cb fires completion to the Dapp's write operation.
this._unconfMsgCbs[msgId] = cb
// signal update
this._didUpdate()
return msgId
}
// comes from metamask ui
IdentityStore.prototype.approveMessage = function (msgId, cb) {
var approvalCb = this._unconfMsgCbs[msgId] || noop
// accept msg
cb()
approvalCb(null, true)
// clean up
messageManager.confirmMsg(msgId)
delete this._unconfMsgCbs[msgId]
this._didUpdate()
}
// comes from metamask ui
IdentityStore.prototype.cancelMessage = function (msgId) {
var approvalCb = this._unconfMsgCbs[msgId] || noop
// reject tx
approvalCb(null, false)
// clean up
messageManager.rejectMsg(msgId)
delete this._unconfTxCbs[msgId]
this._didUpdate()
}
// performs the actual signing, no autofill of params
IdentityStore.prototype.signMessage = function (msgParams, cb) {
try {
console.log('signing msg...', msgParams.data)
var rawMsg = this._idmgmt.signMsg(msgParams.from, msgParams.data)
if ('metamaskId' in msgParams) {
var id = msgParams.metamaskId
delete msgParams.metamaskId
this.approveMessage(id, cb)
} else {
cb(null, rawMsg)
}
} catch (err) {
cb(err)
}
}
//
// private
//
@ -389,9 +209,11 @@ IdentityStore.prototype._loadIdentities = function () {
var addresses = this._getAddresses()
addresses.forEach((address, i) => {
// // add to ethStore
this._ethStore.addAccount(address)
if (this._ethStore) {
this._ethStore.addAccount(ethUtil.addHexPrefix(address))
}
// add to identities
const defaultLabel = 'Wallet ' + (i + 1)
const defaultLabel = 'Account ' + (i + 1)
const nickname = configManager.nicknameForWallet(address)
var identity = {
name: nickname || defaultLabel,
@ -408,7 +230,6 @@ IdentityStore.prototype.saveAccountLabel = function (account, label, cb) {
configManager.setNicknameForWallet(account, label)
this._loadIdentities()
cb(null, label)
this._didUpdate()
}
// mayBeFauceting
@ -432,76 +253,87 @@ IdentityStore.prototype._mayBeFauceting = function (i) {
//
IdentityStore.prototype.tryPassword = function (password, cb) {
this._createIdmgmt(password, null, null, cb)
}
var serializedKeystore = this.configManager.getWallet()
var keyStore = KeyStore.deserialize(serializedKeystore)
IdentityStore.prototype._createIdmgmt = function (password, seed, entropy, cb) {
const configManager = this.configManager
var keyStore = null
LightwalletKeyStore.deriveKeyFromPassword(password, (err, derivedKey) => {
keyStore.keyFromPassword(password, (err, pwDerivedKey) => {
if (err) return cb(err)
var serializedKeystore = configManager.getWallet()
if (seed) {
try {
keyStore = this._restoreFromSeed(password, seed, derivedKey)
} catch (e) {
return cb(e)
}
// returning user, recovering from storage
} else if (serializedKeystore) {
keyStore = LightwalletKeyStore.deserialize(serializedKeystore)
var isCorrect = keyStore.isDerivedKeyCorrect(derivedKey)
if (!isCorrect) return cb(new Error('Lightwallet - password incorrect'))
// first time here
} else {
keyStore = this._createFirstWallet(entropy, derivedKey)
}
const isCorrect = keyStore.isDerivedKeyCorrect(pwDerivedKey)
if (!isCorrect) return cb(new Error('Lightwallet - password incorrect'))
this._keyStore = keyStore
this._idmgmt = new IdManagement({
keyStore: keyStore,
derivedKey: derivedKey,
hdPathSTring: this.hdPathString,
configManager: this.configManager,
})
this._createIdMgmt(pwDerivedKey)
cb()
})
}
IdentityStore.prototype._restoreFromSeed = function (password, seed, derivedKey) {
const configManager = this.configManager
var keyStore = new LightwalletKeyStore(seed, derivedKey, this.hdPathString)
keyStore.addHdDerivationPath(this.hdPathString, derivedKey, {curve: 'secp256k1', purpose: 'sign'})
keyStore.setDefaultHdDerivationPath(this.hdPathString)
keyStore.generateNewAddress(derivedKey, 3)
configManager.setWallet(keyStore.serialize())
if (global.METAMASK_DEBUG) {
console.log('restored from seed. saved to keystore')
IdentityStore.prototype._createVault = function (password, seedPhrase, cb) {
const opts = {
password,
hdPathString: this.hdPathString,
}
return keyStore
if (seedPhrase) {
opts.seedPhrase = seedPhrase
}
KeyStore.createVault(opts, (err, keyStore) => {
if (err) return cb(err)
this._keyStore = keyStore
keyStore.keyFromPassword(password, (err, derivedKey) => {
if (err) return cb(err)
this.purgeCache()
keyStore.addHdDerivationPath(this.hdPathString, derivedKey, {curve: 'secp256k1', purpose: 'sign'})
this._createFirstWallet(derivedKey)
this._createIdMgmt(derivedKey)
this.setSelectedAddressSync()
cb()
})
})
}
IdentityStore.prototype._createFirstWallet = function (entropy, derivedKey) {
const configManager = this.configManager
var secretSeed = LightwalletKeyStore.generateRandomSeed(entropy)
var keyStore = new LightwalletKeyStore(secretSeed, derivedKey, this.hdPathString)
keyStore.addHdDerivationPath(this.hdPathString, derivedKey, {curve: 'secp256k1', purpose: 'sign'})
keyStore.setDefaultHdDerivationPath(this.hdPathString)
IdentityStore.prototype._createIdMgmt = function (derivedKey) {
this._idmgmt = new IdManagement({
keyStore: this._keyStore,
derivedKey: derivedKey,
configManager: this.configManager,
})
}
IdentityStore.prototype.purgeCache = function () {
this._currentState.identities = {}
let accounts
try {
accounts = Object.keys(this._ethStore._currentState.accounts)
} catch (e) {
accounts = []
}
accounts.forEach((address) => {
this._ethStore.removeAccount(address)
})
}
IdentityStore.prototype._createFirstWallet = function (derivedKey) {
const keyStore = this._keyStore
keyStore.setDefaultHdDerivationPath(this.hdPathString)
keyStore.generateNewAddress(derivedKey, 1)
configManager.setWallet(keyStore.serialize())
console.log('saved to keystore')
return keyStore
this.configManager.setWallet(keyStore.serialize())
var addresses = keyStore.getAddresses()
this._ethStore.addAccount(ethUtil.addHexPrefix(addresses[0]))
}
// get addresses and normalize address hexString
IdentityStore.prototype._getAddresses = function () {
return this._keyStore.getAddresses(this.hdPathString).map((address) => { return '0x' + address })
return this._keyStore.getAddresses(this.hdPathString).map((address) => {
return ethUtil.addHexPrefix(address)
})
}
IdentityStore.prototype._autoFaucet = function () {
@ -510,5 +342,3 @@ IdentityStore.prototype._autoFaucet = function () {
}
// util
function noop () {}

View File

@ -1,7 +1,8 @@
const Streams = require('mississippi')
const ObjectMultiplex = require('./obj-multiplex')
const pipe = require('pump')
const StreamProvider = require('web3-stream-provider')
const RemoteStore = require('./remote-store.js').RemoteStore
const LocalStorageStore = require('obs-store')
const ObjectMultiplex = require('./obj-multiplex')
const createRandomId = require('./random-id')
module.exports = MetamaskInpageProvider
@ -9,64 +10,89 @@ function MetamaskInpageProvider (connectionStream) {
const self = this
// setup connectionStream multiplexing
var multiStream = ObjectMultiplex()
Streams.pipe(connectionStream, multiStream, connectionStream, function (err) {
console.warn('MetamaskInpageProvider - lost connection to MetaMask')
if (err) throw err
})
self.multiStream = multiStream
var multiStream = self.multiStream = ObjectMultiplex()
pipe(
connectionStream,
multiStream,
connectionStream,
(err) => logStreamDisconnectWarning('MetaMask', err)
)
// subscribe to metamask public config
var publicConfigStore = remoteStoreWithLocalStorageCache('MetaMask-Config')
var storeStream = publicConfigStore.createStream()
Streams.pipe(storeStream, multiStream.createStream('publicConfig'), storeStream, function (err) {
console.warn('MetamaskInpageProvider - lost connection to MetaMask publicConfig')
if (err) throw err
})
self.publicConfigStore = publicConfigStore
// subscribe to metamask public config (one-way)
self.publicConfigStore = new LocalStorageStore({ storageKey: 'MetaMask-Config' })
pipe(
multiStream.createStream('publicConfig'),
self.publicConfigStore,
(err) => logStreamDisconnectWarning('MetaMask PublicConfigStore', err)
)
// connect to async provider
var asyncProvider = new StreamProvider()
Streams.pipe(asyncProvider, multiStream.createStream('provider'), asyncProvider, function (err) {
console.warn('MetamaskInpageProvider - lost connection to MetaMask provider')
if (err) throw err
})
asyncProvider.on('error', console.error.bind(console))
self.asyncProvider = asyncProvider
const asyncProvider = self.asyncProvider = new StreamProvider()
pipe(
asyncProvider,
multiStream.createStream('provider'),
asyncProvider,
(err) => logStreamDisconnectWarning('MetaMask RpcProvider', err)
)
self.idMap = {}
// handle sendAsync requests via asyncProvider
self.sendAsync = function(payload, cb){
self.sendAsync = function (payload, cb) {
// rewrite request ids
var request = jsonrpcMessageTransform(payload, (message) => {
message.id = createRandomId()
var request = eachJsonMessage(payload, (message) => {
var newId = createRandomId()
self.idMap[newId] = message.id
message.id = newId
return message
})
// forward to asyncProvider
asyncProvider.sendAsync(request, cb)
asyncProvider.sendAsync(request, function (err, res) {
if (err) return cb(err)
// transform messages to original ids
eachJsonMessage(res, (message) => {
var oldId = self.idMap[message.id]
delete self.idMap[message.id]
message.id = oldId
return message
})
cb(null, res)
})
}
}
MetamaskInpageProvider.prototype.send = function (payload) {
const self = this
let selectedAddress
let result = null
switch (payload.method) {
case 'eth_accounts':
// read from localStorage
selectedAddress = self.publicConfigStore.get('selectedAddress')
selectedAddress = self.publicConfigStore.getState().selectedAddress
result = selectedAddress ? [selectedAddress] : []
break
case 'eth_coinbase':
// read from localStorage
selectedAddress = self.publicConfigStore.get('selectedAddress')
result = selectedAddress || '0x0000000000000000000000000000000000000000'
selectedAddress = self.publicConfigStore.getState().selectedAddress
result = selectedAddress
break
case 'eth_uninstallFilter':
self.sendAsync(payload, noop)
result = true
break
case 'net_version':
let networkVersion = self.publicConfigStore.getState().networkVersion
result = networkVersion
break
// throw not-supported Error
default:
var message = 'The MetaMask Web3 object does not support synchronous methods. See https://github.com/MetaMask/faq/blob/master/DEVELOPERS.md#all-async---think-of-metamask-as-a-light-client for details.'
var link = 'https://github.com/MetaMask/faq/blob/master/DEVELOPERS.md#dizzy-all-async---think-of-metamask-as-a-light-client'
var message = `The MetaMask Web3 object does not support synchronous methods like ${payload.method} without a callback parameter. See ${link} for details.`
throw new Error(message)
}
@ -87,34 +113,22 @@ MetamaskInpageProvider.prototype.isConnected = function () {
return true
}
MetamaskInpageProvider.prototype.isMetaMask = true
// util
function remoteStoreWithLocalStorageCache (storageKey) {
// read local cache
var initState = JSON.parse(localStorage[storageKey] || '{}')
var store = new RemoteStore(initState)
// cache the latest state locally
store.subscribe(function (state) {
localStorage[storageKey] = JSON.stringify(state)
})
return store
}
function createRandomId(){
const extraDigits = 3
// 13 time digits
const datePart = new Date().getTime() * Math.pow(10, extraDigits)
// 3 random digits
const extraPart = Math.floor(Math.random() * Math.pow(10, extraDigits))
// 16 digits
return datePart + extraPart
}
function jsonrpcMessageTransform(payload, transformFn){
function eachJsonMessage (payload, transformFn) {
if (Array.isArray(payload)) {
return payload.map(transformFn)
} else {
return transformFn(payload)
}
}
}
function logStreamDisconnectWarning(remoteLabel, err){
let warningMsg = `MetamaskInpageProvider - lost connection to ${remoteLabel}`
if (err) warningMsg += '\n' + err.stack
console.warn(warningMsg)
}
function noop () {}

View File

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

View File

@ -1,61 +1,118 @@
module.exports = new MessageManager()
const EventEmitter = require('events')
const ObservableStore = require('obs-store')
const ethUtil = require('ethereumjs-util')
const createId = require('./random-id')
function MessageManager (opts) {
this.messages = []
}
MessageManager.prototype.getMsgList = function () {
return this.messages
}
MessageManager.prototype.unconfirmedMsgs = function () {
var messages = this.getMsgList()
return messages.filter(msg => msg.status === 'unconfirmed')
.reduce((result, msg) => { result[msg.id] = msg; return result }, {})
}
MessageManager.prototype._saveMsgList = function (msgList) {
this.messages = msgList
}
MessageManager.prototype.addMsg = function (msg) {
var messages = this.getMsgList()
messages.push(msg)
this._saveMsgList(messages)
}
MessageManager.prototype.getMsg = function (msgId) {
var messages = this.getMsgList()
var matching = messages.filter(msg => msg.id === msgId)
return matching.length > 0 ? matching[0] : null
}
MessageManager.prototype.confirmMsg = function (msgId) {
this._setMsgStatus(msgId, 'confirmed')
}
MessageManager.prototype.rejectMsg = function (msgId) {
this._setMsgStatus(msgId, 'rejected')
}
MessageManager.prototype._setMsgStatus = function (msgId, status) {
var msg = this.getMsg(msgId)
if (msg) msg.status = status
this.updateMsg(msg)
}
MessageManager.prototype.updateMsg = function (msg) {
var messages = this.getMsgList()
var found, index
messages.forEach((otherMsg, i) => {
if (otherMsg.id === msg.id) {
found = true
index = i
}
})
if (found) {
messages[index] = msg
module.exports = class MessageManager extends EventEmitter{
constructor (opts) {
super()
this.memStore = new ObservableStore({
unapprovedMsgs: {},
unapprovedMsgCount: 0,
})
this.messages = []
}
this._saveMsgList(messages)
get unapprovedMsgCount () {
return Object.keys(this.getUnapprovedMsgs()).length
}
getUnapprovedMsgs () {
return this.messages.filter(msg => msg.status === 'unapproved')
.reduce((result, msg) => { result[msg.id] = msg; return result }, {})
}
addUnapprovedMessage (msgParams) {
msgParams.data = normalizeMsgData(msgParams.data)
// create txData obj with parameters and meta data
var time = (new Date()).getTime()
var msgId = createId()
var msgData = {
id: msgId,
msgParams: msgParams,
time: time,
status: 'unapproved',
}
this.addMsg(msgData)
// signal update
this.emit('update')
return msgId
}
addMsg (msg) {
this.messages.push(msg)
this._saveMsgList()
}
getMsg (msgId) {
return this.messages.find(msg => msg.id === msgId)
}
approveMessage (msgParams) {
this.setMsgStatusApproved(msgParams.metamaskId)
return this.prepMsgForSigning(msgParams)
}
setMsgStatusApproved (msgId) {
this._setMsgStatus(msgId, 'approved')
}
setMsgStatusSigned (msgId, rawSig) {
const msg = this.getMsg(msgId)
msg.rawSig = rawSig
this._updateMsg(msg)
this._setMsgStatus(msgId, 'signed')
}
prepMsgForSigning (msgParams) {
delete msgParams.metamaskId
return Promise.resolve(msgParams)
}
rejectMsg (msgId) {
this._setMsgStatus(msgId, 'rejected')
}
//
// PRIVATE METHODS
//
_setMsgStatus (msgId, status) {
const msg = this.getMsg(msgId)
if (!msg) throw new Error('MessageManager - Message not found for id: "${msgId}".')
msg.status = status
this._updateMsg(msg)
this.emit(`${msgId}:${status}`, msg)
if (status === 'rejected' || status === 'signed') {
this.emit(`${msgId}:finished`, msg)
}
}
_updateMsg (msg) {
const index = this.messages.findIndex((message) => message.id === msg.id)
if (index !== -1) {
this.messages[index] = msg
}
this._saveMsgList()
}
_saveMsgList () {
const unapprovedMsgs = this.getUnapprovedMsgs()
const unapprovedMsgCount = Object.keys(unapprovedMsgs).length
this.memStore.updateState({ unapprovedMsgs, unapprovedMsgCount })
this.emit('updateBadge')
}
}
function normalizeMsgData(data) {
if (data.slice(0, 2) === '0x') {
// data is already hex
return data
} else {
// data is unicode, convert to hex
return ethUtil.bufferToHex(new Buffer(data, 'utf8'))
}
}

View File

@ -1,5 +0,0 @@
module.exports = [
require('../migrations/002'),
require('../migrations/003'),
require('../migrations/004'),
]

View File

@ -0,0 +1,51 @@
const asyncQ = require('async-q')
class Migrator {
constructor (opts = {}) {
let migrations = opts.migrations || []
this.migrations = migrations.sort((a, b) => a.version - b.version)
let lastMigration = this.migrations.slice(-1)[0]
// use specified defaultVersion or highest migration version
this.defaultVersion = opts.defaultVersion || (lastMigration && lastMigration.version) || 0
}
// run all pending migrations on meta in place
migrateData (versionedData = this.generateInitialState()) {
let remaining = this.migrations.filter(migrationIsPending)
return (
asyncQ.eachSeries(remaining, (migration) => this.runMigration(versionedData, migration))
.then(() => versionedData)
)
// migration is "pending" if hit has a higher
// version number than currentVersion
function migrationIsPending(migration) {
return migration.version > versionedData.meta.version
}
}
runMigration(versionedData, migration) {
return (
migration.migrate(versionedData)
.then((versionedData) => {
if (!versionedData.data) return Promise.reject(new Error('Migrator - Migration returned empty data'))
if (migration.version !== undefined && versionedData.meta.version !== migration.version) return Promise.reject(new Error('Migrator - Migration did not update version number correctly'))
return Promise.resolve(versionedData)
})
)
}
generateInitialState (initState) {
return {
meta: {
version: this.defaultVersion,
},
data: initState,
}
}
}
module.exports = Migrator

View File

@ -0,0 +1,24 @@
module.exports = function (promiseFn) {
return function () {
var args = []
for (var i = 0; i < arguments.length - 1; i++) {
args.push(arguments[i])
}
var cb = arguments[arguments.length - 1]
const nodeified = promiseFn.apply(this, args)
if (!nodeified) {
const methodName = String(promiseFn).split('(')[0]
throw new Error(`The ${methodName} did not return a Promise, but was nodeified.`)
}
nodeified.then(function (result) {
cb(null, result)
})
.catch(function (reason) {
cb(reason)
})
return nodeified
}
}

View File

@ -1,159 +1,65 @@
const createId = require('hat')
const extend = require('xtend')
const unmountComponentAtNode = require('react-dom').unmountComponentAtNode
const findDOMNode = require('react-dom').findDOMNode
const render = require('react-dom').render
const h = require('react-hyperscript')
const PendingTxDetails = require('../../../ui/app/components/pending-tx-details')
const PendingMsgDetails = require('../../../ui/app/components/pending-msg-details')
const MetaMaskUiCss = require('../../../ui/css')
const extension = require('./extension')
var notificationHandlers = {}
const height = 520
const width = 360
const notifications = {
createUnlockRequestNotification: createUnlockRequestNotification,
createTxNotification: createTxNotification,
createMsgNotification: createMsgNotification,
show,
getPopup,
closePopup,
}
module.exports = notifications
window.METAMASK_NOTIFIER = notifications
setupListeners()
function show () {
getPopup((err, popup) => {
if (err) throw err
function setupListeners () {
// guard for extension bug https://github.com/MetaMask/metamask-plugin/issues/236
if (!extension.notifications) return console.error('Chrome notifications API missing...')
// notification button press
extension.notifications.onButtonClicked.addListener(function (notificationId, buttonIndex) {
var handlers = notificationHandlers[notificationId]
if (buttonIndex === 0) {
handlers.confirm()
if (popup) {
// bring focus to existing popup
extension.windows.update(popup.id, { focused: true })
} else {
handlers.cancel()
// create new popup
extension.windows.create({
url: 'notification.html',
type: 'popup',
focused: true,
width,
height,
})
}
extension.notifications.clear(notificationId)
})
// notification teardown
extension.notifications.onClosed.addListener(function (notificationId) {
delete notificationHandlers[notificationId]
})
}
// creation helper
function createUnlockRequestNotification (opts) {
// guard for extension bug https://github.com/MetaMask/metamask-plugin/issues/236
if (!extension.notifications) return console.error('Chrome notifications API missing...')
var message = 'An Ethereum app has requested a signature. Please unlock your account.'
var id = createId()
extension.notifications.create(id, {
type: 'basic',
iconUrl: '/images/icon-128.png',
title: opts.title,
message: message,
})
}
function createTxNotification (state) {
// guard for extension bug https://github.com/MetaMask/metamask-plugin/issues/236
if (!extension.notifications) return console.error('Chrome notifications API missing...')
renderTxNotificationSVG(state, function (err, notificationSvgSource) {
if (err) throw err
showNotification(extend(state, {
title: 'New Unsigned Transaction',
imageUrl: toSvgUri(notificationSvgSource),
}))
})
}
function createMsgNotification (state) {
// guard for extension bug https://github.com/MetaMask/metamask-plugin/issues/236
if (!extension.notifications) return console.error('Chrome notifications API missing...')
renderMsgNotificationSVG(state, function (err, notificationSvgSource) {
if (err) throw err
showNotification(extend(state, {
title: 'New Unsigned Message',
imageUrl: toSvgUri(notificationSvgSource),
}))
})
}
function showNotification (state) {
// guard for extension bug https://github.com/MetaMask/metamask-plugin/issues/236
if (!extension.notifications) return console.error('Chrome notifications API missing...')
var id = createId()
extension.notifications.create(id, {
type: 'image',
requireInteraction: true,
iconUrl: '/images/icon-128.png',
imageUrl: state.imageUrl,
title: state.title,
message: '',
buttons: [{
title: 'Approve',
}, {
title: 'Reject',
}],
})
notificationHandlers[id] = {
confirm: state.onConfirm,
cancel: state.onCancel,
function getWindows (cb) {
// Ignore in test environment
if (!extension.windows) {
return cb()
}
}
function renderTxNotificationSVG (state, cb) {
var content = h(PendingTxDetails, state)
renderNotificationSVG(content, cb)
}
function renderMsgNotificationSVG (state, cb) {
var content = h(PendingMsgDetails, state)
renderNotificationSVG(content, cb)
}
function renderNotificationSVG (content, cb) {
var container = document.createElement('div')
var confirmView = h('div.app-primary', {
style: {
width: '360px',
height: '240px',
padding: '16px',
// background: '#F7F7F7',
background: 'white',
},
}, [
h('style', MetaMaskUiCss()),
content,
])
render(confirmView, container, function ready() {
var rootElement = findDOMNode(this)
var viewSource = rootElement.outerHTML
unmountComponentAtNode(container)
var svgSource = svgWrapper(viewSource)
// insert content into svg wrapper
cb(null, svgSource)
extension.windows.getAll({}, (windows) => {
cb(null, windows)
})
}
function svgWrapper (content) {
var wrapperSource = `
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="240">
<foreignObject x="0" y="0" width="100%" height="100%">
<body xmlns="http://www.w3.org/1999/xhtml" height="100%">{{content}}</body>
</foreignObject>
</svg>
`
return wrapperSource.split('{{content}}').join(content)
function getPopup (cb) {
getWindows((err, windows) => {
if (err) throw err
cb(null, getPopupIn(windows))
})
}
function toSvgUri (content) {
return 'data:image/svg+xml;utf8,' + encodeURIComponent(content)
function getPopupIn (windows) {
return windows ? windows.find((win) => {
return (win && win.type === 'popup' &&
win.height === height &&
win.width === width)
}) : null
}
function closePopup () {
getPopup((err, popup) => {
if (err) throw err
if (!popup) return
extension.windows.remove(popup.id, console.error)
})
}

View File

@ -10,9 +10,9 @@ function ObjectMultiplex (opts) {
var data = chunk.data
var substream = mx.streams[name]
if (!substream) {
console.warn('orphaned data for stream ' + name)
console.warn(`orphaned data for stream "${name}"`)
} else {
substream.push(data)
if (substream.push) substream.push(data)
}
return cb()
})
@ -36,5 +36,9 @@ function ObjectMultiplex (opts) {
}
return substream
}
// ignore streams (dont display orphaned data warning)
mx.ignoreStream = function (name) {
mx.streams[name] = true
}
return mx
}

View File

@ -30,8 +30,7 @@ PortDuplexStream.prototype._onMessage = function (msg) {
PortDuplexStream.prototype._onDisconnect = function () {
try {
// this.end()
this.emit('close')
this.push(null)
} catch (err) {
this.emit('error', err)
}
@ -52,12 +51,11 @@ PortDuplexStream.prototype._write = function (msg, encoding, cb) {
// console.log('PortDuplexStream - sent message', msg)
this._port.postMessage(msg)
}
cb()
} catch (err) {
console.error(err)
// this.emit('error', err)
cb(new Error('PortDuplexStream - disconnected'))
// console.error(err)
return cb(new Error('PortDuplexStream - disconnected'))
}
cb()
}
// util

View File

@ -0,0 +1,9 @@
const MAX = Number.MAX_SAFE_INTEGER
let idCounter = Math.round(Math.random() * MAX)
function createRandomId () {
idCounter = idCounter % MAX
return idCounter++
}
module.exports = createRandomId

View File

@ -1,97 +0,0 @@
const Dnode = require('dnode')
const inherits = require('util').inherits
module.exports = {
HostStore: HostStore,
RemoteStore: RemoteStore,
}
function BaseStore (initState) {
this._state = initState || {}
this._subs = []
}
BaseStore.prototype.set = function (key, value) {
throw Error('Not implemented.')
}
BaseStore.prototype.get = function (key) {
return this._state[key]
}
BaseStore.prototype.subscribe = function (fn) {
this._subs.push(fn)
var unsubscribe = this.unsubscribe.bind(this, fn)
return unsubscribe
}
BaseStore.prototype.unsubscribe = function (fn) {
var index = this._subs.indexOf(fn)
if (index !== -1) this._subs.splice(index, 1)
}
BaseStore.prototype._emitUpdates = function (state) {
this._subs.forEach(function (handler) {
handler(state)
})
}
//
// host
//
inherits(HostStore, BaseStore)
function HostStore (initState, opts) {
BaseStore.call(this, initState)
}
HostStore.prototype.set = function (key, value) {
this._state[key] = value
process.nextTick(this._emitUpdates.bind(this, this._state))
}
HostStore.prototype.createStream = function () {
var dnode = Dnode({
// update: this._didUpdate.bind(this),
})
dnode.on('remote', this._didConnect.bind(this))
return dnode
}
HostStore.prototype._didConnect = function (remote) {
this.subscribe(function (state) {
remote.update(state)
})
remote.update(this._state)
}
//
// remote
//
inherits(RemoteStore, BaseStore)
function RemoteStore (initState, opts) {
BaseStore.call(this, initState)
this._remote = null
}
RemoteStore.prototype.set = function (key, value) {
this._remote.set(key, value)
}
RemoteStore.prototype.createStream = function () {
var dnode = Dnode({
update: this._didUpdate.bind(this),
})
dnode.once('remote', this._didConnect.bind(this))
return dnode
}
RemoteStore.prototype._didConnect = function (remote) {
this._remote = remote
}
RemoteStore.prototype._didUpdate = function (state) {
this._state = state
this._emitUpdates(state)
}

View File

@ -0,0 +1,28 @@
const ethUtil = require('ethereumjs-util')
module.exports = {
concatSig: function (v, r, s) {
const rSig = ethUtil.fromSigned(r)
const sSig = ethUtil.fromSigned(s)
const vSig = ethUtil.bufferToInt(v)
const rStr = padWithZeroes(ethUtil.toUnsigned(rSig).toString('hex'), 64)
const sStr = padWithZeroes(ethUtil.toUnsigned(sSig).toString('hex'), 64)
const vStr = ethUtil.stripHexPrefix(ethUtil.intToHex(vSig))
return ethUtil.addHexPrefix(rStr.concat(sStr, vStr)).toString('hex')
},
normalize: function (address) {
if (!address) return
return ethUtil.addHexPrefix(address.toLowerCase())
},
}
function padWithZeroes (number, length) {
var myString = '' + number
while (myString.length < length) {
myString = '0' + myString
}
return myString
}

View File

@ -1,4 +1,5 @@
const Through = require('through2')
const endOfStream = require('end-of-stream')
const ObjectMultiplex = require('./obj-multiplex')
module.exports = {
@ -24,11 +25,11 @@ function jsonStringifyStream () {
function setupMultiplex (connectionStream) {
var mx = ObjectMultiplex()
connectionStream.pipe(mx).pipe(connectionStream)
mx.on('error', function (err) {
console.error(err)
endOfStream(mx, function (err) {
if (err) console.error(err)
})
connectionStream.on('error', function (err) {
console.error(err)
endOfStream(connectionStream, function (err) {
if (err) console.error(err)
mx.destroy()
})
return mx

132
app/scripts/lib/tx-utils.js Normal file
View File

@ -0,0 +1,132 @@
const async = require('async')
const EthQuery = require('eth-query')
const ethUtil = require('ethereumjs-util')
const Transaction = require('ethereumjs-tx')
const normalize = require('./sig-util').normalize
const BN = ethUtil.BN
/*
tx-utils are utility methods for Transaction manager
its passed a provider and that is passed to ethquery
and used to do things like calculate gas of a tx.
*/
module.exports = class txProviderUtils {
constructor (provider) {
this.provider = provider
this.query = new EthQuery(provider)
}
analyzeGasUsage (txData, cb) {
var self = this
this.query.getBlockByNumber('latest', true, (err, block) => {
if (err) return cb(err)
async.waterfall([
self.estimateTxGas.bind(self, txData, block.gasLimit),
self.setTxGas.bind(self, txData, block.gasLimit),
], cb)
})
}
estimateTxGas (txData, blockGasLimitHex, cb) {
const txParams = txData.txParams
// check if gasLimit is already specified
txData.gasLimitSpecified = Boolean(txParams.gas)
// if not, fallback to block gasLimit
if (!txData.gasLimitSpecified) {
txParams.gas = blockGasLimitHex
}
// run tx, see if it will OOG
this.query.estimateGas(txParams, cb)
}
setTxGas (txData, blockGasLimitHex, estimatedGasHex, cb) {
txData.estimatedGas = estimatedGasHex
const txParams = txData.txParams
// if gasLimit was specified and doesnt OOG,
// use original specified amount
if (txData.gasLimitSpecified) {
txData.estimatedGas = txParams.gas
cb()
return
}
// if gasLimit not originally specified,
// try adding an additional gas buffer to our estimation for safety
const estimatedGasBn = new BN(ethUtil.stripHexPrefix(txData.estimatedGas), 16)
const blockGasLimitBn = new BN(ethUtil.stripHexPrefix(blockGasLimitHex), 16)
const estimationWithBuffer = new BN(this.addGasBuffer(estimatedGasBn), 16)
// added gas buffer is too high
if (estimationWithBuffer.gt(blockGasLimitBn)) {
txParams.gas = txData.estimatedGas
// added gas buffer is safe
} else {
const gasWithBufferHex = ethUtil.intToHex(estimationWithBuffer)
txParams.gas = gasWithBufferHex
}
cb()
return
}
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))
}
fillInTxParams (txParams, cb) {
let fromAddress = txParams.from
let reqs = {}
if (isUndef(txParams.gas)) reqs.gas = (cb) => this.query.estimateGas(txParams, cb)
if (isUndef(txParams.gasPrice)) reqs.gasPrice = (cb) => this.query.gasPrice(cb)
if (isUndef(txParams.nonce)) reqs.nonce = (cb) => this.query.getTransactionCount(fromAddress, 'pending', cb)
async.parallel(reqs, function(err, result) {
if (err) return cb(err)
// write results to txParams obj
Object.assign(txParams, result)
cb()
})
}
// builds ethTx from txParams object
buildEthTxFromParams (txParams, gasMultiplier = 1) {
// apply gas multiplyer
let gasPrice = new BN(ethUtil.stripHexPrefix(txParams.gasPrice), 16)
// multiply and divide by 100 so as to add percision to integer mul
gasPrice = gasPrice.mul(new BN(gasMultiplier * 100, 10)).div(new BN(100, 10))
txParams.gasPrice = ethUtil.intToHex(gasPrice.toNumber())
// normalize values
txParams.to = normalize(txParams.to)
txParams.from = normalize(txParams.from)
txParams.value = normalize(txParams.value)
txParams.data = normalize(txParams.data)
txParams.gasLimit = normalize(txParams.gasLimit || txParams.gas)
txParams.nonce = normalize(txParams.nonce)
// build ethTx
const ethTx = new Transaction(txParams)
return ethTx
}
publishTransaction (rawTx, cb) {
this.query.sendRawTransaction(rawTx, cb)
}
validateTxParams (txParams, cb) {
if (('value' in txParams) && txParams.value.indexOf('-') === 0) {
cb(new Error(`Invalid transaction value of ${txParams.value} not a positive number.`))
} else {
cb()
}
}
}
// util
function isUndef(value) {
return value === undefined
}

View File

@ -1,303 +1,595 @@
const EventEmitter = require('events')
const extend = require('xtend')
const EthStore = require('eth-store')
const promiseToCallback = require('promise-to-callback')
const pipe = require('pump')
const Dnode = require('dnode')
const ObservableStore = require('obs-store')
const storeTransform = require('obs-store/lib/transform')
const EthStore = require('./lib/eth-store')
const EthQuery = require('eth-query')
const streamIntoProvider = require('web3-stream-provider/handler')
const MetaMaskProvider = require('web3-provider-engine/zero.js')
const IdentityStore = require('./lib/idStore')
const messageManager = require('./lib/message-manager')
const HostStore = require('./lib/remote-store.js').HostStore
const Web3 = require('web3')
const setupMultiplex = require('./lib/stream-utils.js').setupMultiplex
const KeyringController = require('./keyring-controller')
const PreferencesController = require('./lib/controllers/preferences')
const CurrencyController = require('./lib/controllers/currency')
const NoticeController = require('./notice-controller')
const ShapeShiftController = require('./lib/controllers/shapeshift')
const MessageManager = require('./lib/message-manager')
const TxManager = require('./transaction-manager')
const ConfigManager = require('./lib/config-manager')
const extension = require('./lib/extension')
const autoFaucet = require('./lib/auto-faucet')
const nodeify = require('./lib/nodeify')
const IdStoreMigrator = require('./lib/idStore-migrator')
const accountImporter = require('./account-import-strategies')
module.exports = class MetamaskController {
const version = require('../manifest.json').version
module.exports = class MetamaskController extends EventEmitter {
constructor (opts) {
super()
this.opts = opts
this.configManager = new ConfigManager(opts)
this.idStore = new IdentityStore({
let initState = opts.initState || {}
// observable state store
this.store = new ObservableStore(initState)
// network store
this.networkStore = new ObservableStore({ network: 'loading' })
// config manager
this.configManager = new ConfigManager({
store: this.store,
})
// preferences controller
this.preferencesController = new PreferencesController({
initState: initState.PreferencesController,
})
// currency controller
this.currencyController = new CurrencyController({
initState: initState.CurrencyController,
})
this.currencyController.updateConversionRate()
this.currencyController.scheduleConversionInterval()
// rpc provider
this.provider = this.initializeProvider()
this.provider.on('block', this.logBlock.bind(this))
this.provider.on('error', this.verifyNetwork.bind(this))
// eth data query tools
this.ethQuery = new EthQuery(this.provider)
this.ethStore = new EthStore({
provider: this.provider,
blockTracker: this.provider,
})
// key mgmt
this.keyringController = new KeyringController({
initState: initState.KeyringController,
ethStore: this.ethStore,
getNetwork: this.getNetworkState.bind(this),
})
this.keyringController.on('newAccount', (address) => {
this.preferencesController.setSelectedAddress(address)
autoFaucet(address)
})
// tx mgmt
this.txManager = new TxManager({
initState: initState.TransactionManager,
networkStore: this.networkStore,
preferencesStore: this.preferencesController.store,
txHistoryLimit: 40,
getNetwork: this.getNetworkState.bind(this),
signTransaction: this.keyringController.signTransaction.bind(this.keyringController),
provider: this.provider,
blockTracker: this.provider,
})
// notices
this.noticeController = new NoticeController({
initState: initState.NoticeController,
})
this.noticeController.updateNoticesList()
// to be uncommented when retrieving notices from a remote server.
// this.noticeController.startPolling()
this.shapeshiftController = new ShapeShiftController({
initState: initState.ShapeShiftController,
})
this.lookupNetwork()
this.messageManager = new MessageManager()
this.publicConfigStore = this.initPublicConfigStore()
// TEMPORARY UNTIL FULL DEPRECATION:
this.idStoreMigrator = new IdStoreMigrator({
configManager: this.configManager,
})
this.provider = this.initializeProvider(opts)
this.ethStore = new EthStore(this.provider)
this.idStore.setStore(this.ethStore)
this.messageManager = messageManager
this.publicConfigStore = this.initPublicConfigStore()
this.configManager.setCurrentFiat('USD')
this.configManager.updateConversionRate()
this.scheduleConversionInterval()
}
getState () {
return extend(
this.ethStore.getState(),
this.idStore.getState(),
this.configManager.getConfig()
)
}
getApi () {
const idStore = this.idStore
return {
getState: (cb) => { cb(null, this.getState()) },
setRpcTarget: this.setRpcTarget.bind(this),
setProviderType: this.setProviderType.bind(this),
useEtherscanProvider: this.useEtherscanProvider.bind(this),
agreeToDisclaimer: this.agreeToDisclaimer.bind(this),
setCurrentFiat: this.setCurrentFiat.bind(this),
agreeToEthWarning: this.agreeToEthWarning.bind(this),
// forward directly to idStore
createNewVault: idStore.createNewVault.bind(idStore),
recoverFromSeed: idStore.recoverFromSeed.bind(idStore),
submitPassword: idStore.submitPassword.bind(idStore),
setSelectedAddress: idStore.setSelectedAddress.bind(idStore),
approveTransaction: idStore.approveTransaction.bind(idStore),
cancelTransaction: idStore.cancelTransaction.bind(idStore),
signMessage: idStore.signMessage.bind(idStore),
cancelMessage: idStore.cancelMessage.bind(idStore),
setLocked: idStore.setLocked.bind(idStore),
clearSeedWordCache: idStore.clearSeedWordCache.bind(idStore),
exportAccount: idStore.exportAccount.bind(idStore),
revealAccount: idStore.revealAccount.bind(idStore),
saveAccountLabel: idStore.saveAccountLabel.bind(idStore),
tryPassword: idStore.tryPassword.bind(idStore),
recoverSeed: idStore.recoverSeed.bind(idStore),
// coinbase
buyEth: this.buyEth.bind(this),
// shapeshift
createShapeShiftTx: this.createShapeShiftTx.bind(this),
}
}
setupProviderConnection (stream, originDomain) {
stream.on('data', this.onRpcRequest.bind(this, stream, originDomain))
}
onRpcRequest (stream, originDomain, request) {
var payloads = Array.isArray(request) ? request : [request]
payloads.forEach(function (payload) {
// Append origin to rpc payload
payload.origin = originDomain
// Append origin to signature request
if (payload.method === 'eth_sendTransaction') {
payload.params[0].origin = originDomain
} else if (payload.method === 'eth_sign') {
payload.params.push({ origin: originDomain })
}
// manual disk state subscriptions
this.txManager.store.subscribe((state) => {
this.store.updateState({ TransactionManager: state })
})
this.keyringController.store.subscribe((state) => {
this.store.updateState({ KeyringController: state })
})
this.preferencesController.store.subscribe((state) => {
this.store.updateState({ PreferencesController: state })
})
this.currencyController.store.subscribe((state) => {
this.store.updateState({ CurrencyController: state })
})
this.noticeController.store.subscribe((state) => {
this.store.updateState({ NoticeController: state })
})
this.shapeshiftController.store.subscribe((state) => {
this.store.updateState({ ShapeShiftController: state })
})
// handle rpc request
this.provider.sendAsync(request, function onPayloadHandled (err, response) {
logger(err, request, response)
if (response) {
try {
stream.write(response)
} catch (err) {
logger(err)
}
}
})
function logger (err, request, response) {
if (err) return console.error(err)
if (!request.isMetamaskInternal) {
if (global.METAMASK_DEBUG) {
console.log(`RPC (${originDomain}):`, request, '->', response)
}
if (response.error) {
console.error('Error in RPC response:\n', response.error)
}
}
}
// manual mem state subscriptions
this.networkStore.subscribe(this.sendUpdate.bind(this))
this.ethStore.subscribe(this.sendUpdate.bind(this))
this.txManager.memStore.subscribe(this.sendUpdate.bind(this))
this.messageManager.memStore.subscribe(this.sendUpdate.bind(this))
this.keyringController.memStore.subscribe(this.sendUpdate.bind(this))
this.preferencesController.store.subscribe(this.sendUpdate.bind(this))
this.currencyController.store.subscribe(this.sendUpdate.bind(this))
this.noticeController.memStore.subscribe(this.sendUpdate.bind(this))
this.shapeshiftController.store.subscribe(this.sendUpdate.bind(this))
}
sendUpdate () {
if (this.remote) {
this.remote.sendUpdate(this.getState())
}
}
//
// Constructor helpers
//
initializeProvider (opts) {
const idStore = this.idStore
var providerOpts = {
initializeProvider () {
let provider = MetaMaskProvider({
static: {
eth_syncing: false,
web3_clientVersion: `MetaMask/v${version}`,
},
rpcUrl: this.configManager.getCurrentRpcAddress(),
// account mgmt
getAccounts: (cb) => {
var selectedAddress = idStore.getSelectedAddress()
var result = selectedAddress ? [selectedAddress] : []
let selectedAddress = this.preferencesController.getSelectedAddress()
let result = selectedAddress ? [selectedAddress] : []
cb(null, result)
},
// tx signing
approveTransaction: this.newUnsignedTransaction.bind(this),
signTransaction: idStore.signTransaction.bind(idStore),
processTransaction: (txParams, cb) => this.newUnapprovedTransaction(txParams, cb),
// msg signing
approveMessage: this.newUnsignedMessage.bind(this),
signMessage: idStore.signMessage.bind(idStore),
}
var provider = MetaMaskProvider(providerOpts)
var web3 = new Web3(provider)
idStore.web3 = web3
idStore.getNetwork()
provider.on('block', this.processBlock.bind(this))
provider.on('error', idStore.getNetwork.bind(idStore))
processMessage: this.newUnsignedMessage.bind(this),
})
return provider
}
initPublicConfigStore () {
// get init state
var initPublicState = extend(
idStoreToPublic(this.idStore.getState()),
configToPublic(this.configManager.getConfig())
const publicConfigStore = new ObservableStore()
// sync publicConfigStore with transform
pipe(
this.store,
storeTransform(selectPublicState.bind(this)),
publicConfigStore
)
var publicConfigStore = new HostStore(initPublicState)
// subscribe to changes
this.configManager.subscribe(function (state) {
storeSetFromObj(publicConfigStore, configToPublic(state))
})
this.idStore.on('update', function (state) {
storeSetFromObj(publicConfigStore, idStoreToPublic(state))
})
// idStore substate
function idStoreToPublic (state) {
return {
selectedAddress: state.selectedAddress,
}
}
// config substate
function configToPublic (state) {
return {
provider: state.provider,
selectedAddress: state.selectedAccount,
}
}
// dump obj into store
function storeSetFromObj (store, obj) {
Object.keys(obj).forEach(function (key) {
store.set(key, obj[key])
})
function selectPublicState(state) {
const result = { selectedAddress: undefined }
try {
result.selectedAddress = state.PreferencesController.selectedAddress
result.networkVersion = this.getNetworkState()
} catch (_) {}
return result
}
return publicConfigStore
}
newUnsignedTransaction (txParams, onTxDoneCb) {
const idStore = this.idStore
var state = idStore.getState()
//
// State Management
//
// It's locked
if (!state.isUnlocked) {
this.opts.unlockAccountMessage()
idStore.addUnconfirmedTransaction(txParams, onTxDoneCb, noop)
getState () {
// It's unlocked
} else {
idStore.addUnconfirmedTransaction(txParams, onTxDoneCb, (err, txData) => {
if (err) return onTxDoneCb(err)
this.opts.showUnconfirmedTx(txParams, txData, onTxDoneCb)
})
const wallet = this.configManager.getWallet()
const vault = this.keyringController.store.getState().vault
const isInitialized = (!!wallet || !!vault)
return extend(
{
isInitialized,
},
this.networkStore.getState(),
this.ethStore.getState(),
this.txManager.memStore.getState(),
this.messageManager.memStore.getState(),
this.keyringController.memStore.getState(),
this.preferencesController.store.getState(),
this.currencyController.store.getState(),
this.noticeController.memStore.getState(),
// config manager
this.configManager.getConfig(),
this.shapeshiftController.store.getState(),
{
lostAccounts: this.configManager.getLostAccounts(),
seedWords: this.configManager.getSeedWords(),
}
)
}
//
// Remote Features
//
getApi () {
const keyringController = this.keyringController
const preferencesController = this.preferencesController
const txManager = this.txManager
const messageManager = this.messageManager
const noticeController = this.noticeController
return {
// etc
getState: (cb) => cb(null, this.getState()),
setRpcTarget: this.setRpcTarget.bind(this),
setProviderType: this.setProviderType.bind(this),
useEtherscanProvider: this.useEtherscanProvider.bind(this),
setCurrentCurrency: this.setCurrentCurrency.bind(this),
setGasMultiplier: this.setGasMultiplier.bind(this),
markAccountsFound: this.markAccountsFound.bind(this),
// coinbase
buyEth: this.buyEth.bind(this),
// shapeshift
createShapeShiftTx: this.createShapeShiftTx.bind(this),
// primary HD keyring management
addNewAccount: this.addNewAccount.bind(this),
placeSeedWords: this.placeSeedWords.bind(this),
clearSeedWordCache: this.clearSeedWordCache.bind(this),
importAccountWithStrategy: this.importAccountWithStrategy.bind(this),
// vault management
submitPassword: this.submitPassword.bind(this),
// PreferencesController
setSelectedAddress: nodeify(preferencesController.setSelectedAddress).bind(preferencesController),
// KeyringController
setLocked: nodeify(keyringController.setLocked).bind(keyringController),
createNewVaultAndKeychain: nodeify(keyringController.createNewVaultAndKeychain).bind(keyringController),
createNewVaultAndRestore: nodeify(keyringController.createNewVaultAndRestore).bind(keyringController),
addNewKeyring: nodeify(keyringController.addNewKeyring).bind(keyringController),
saveAccountLabel: nodeify(keyringController.saveAccountLabel).bind(keyringController),
exportAccount: nodeify(keyringController.exportAccount).bind(keyringController),
// txManager
approveTransaction: txManager.approveTransaction.bind(txManager),
cancelTransaction: txManager.cancelTransaction.bind(txManager),
// messageManager
signMessage: this.signMessage.bind(this),
cancelMessage: messageManager.rejectMsg.bind(messageManager),
// notices
checkNotices: noticeController.updateNoticesList.bind(noticeController),
markNoticeRead: noticeController.markNoticeRead.bind(noticeController),
}
}
setupUntrustedCommunication (connectionStream, originDomain) {
// setup multiplexing
var mx = setupMultiplex(connectionStream)
// connect features
this.setupProviderConnection(mx.createStream('provider'), originDomain)
this.setupPublicConfig(mx.createStream('publicConfig'))
}
setupTrustedCommunication (connectionStream, originDomain) {
// setup multiplexing
var mx = setupMultiplex(connectionStream)
// connect features
this.setupControllerConnection(mx.createStream('controller'))
this.setupProviderConnection(mx.createStream('provider'), originDomain)
}
setupControllerConnection (outStream) {
const api = this.getApi()
const dnode = Dnode(api)
outStream.pipe(dnode).pipe(outStream)
dnode.on('remote', (remote) => {
// push updates to popup
const sendUpdate = remote.sendUpdate.bind(remote)
this.on('update', sendUpdate)
})
}
setupProviderConnection (outStream, originDomain) {
streamIntoProvider(outStream, this.provider, logger)
function logger (err, request, response) {
if (err) return console.error(err)
if (response.error) {
console.error('Error in RPC response:\n', response.error)
}
if (request.isMetamaskInternal) return
if (global.METAMASK_DEBUG) {
console.log(`RPC (${originDomain}):`, request, '->', response)
}
}
}
setupPublicConfig (outStream) {
pipe(
this.publicConfigStore,
outStream
)
}
sendUpdate () {
this.emit('update', this.getState())
}
//
// Vault Management
//
submitPassword (password, cb) {
this.migrateOldVaultIfAny(password)
.then(this.keyringController.submitPassword.bind(this.keyringController, password))
.then((newState) => { cb(null, newState) })
.catch((reason) => { cb(reason) })
}
//
// Opinionated Keyring Management
//
addNewAccount (cb) {
const primaryKeyring = this.keyringController.getKeyringsByType('HD Key Tree')[0]
if (!primaryKeyring) return cb(new Error('MetamaskController - No HD Key Tree found'))
promiseToCallback(this.keyringController.addNewAccount(primaryKeyring))(cb)
}
// Adds the current vault's seed words to the UI's state tree.
//
// Used when creating a first vault, to allow confirmation.
// Also used when revealing the seed words in the confirmation view.
placeSeedWords (cb) {
const primaryKeyring = this.keyringController.getKeyringsByType('HD Key Tree')[0]
if (!primaryKeyring) return cb(new Error('MetamaskController - No HD Key Tree found'))
primaryKeyring.serialize()
.then((serialized) => {
const seedWords = serialized.mnemonic
this.configManager.setSeedWords(seedWords)
cb()
})
}
// ClearSeedWordCache
//
// Removes the primary account's seed words from the UI's state tree,
// ensuring they are only ever available in the background process.
clearSeedWordCache (cb) {
this.configManager.setSeedWords(null)
cb(null, this.preferencesController.getSelectedAddress())
}
importAccountWithStrategy (strategy, args, cb) {
accountImporter.importAccount(strategy, args)
.then((privateKey) => {
return this.keyringController.addNewKeyring('Simple Key Pair', [ privateKey ])
})
.then(keyring => keyring.getAccounts())
.then((accounts) => this.preferencesController.setSelectedAddress(accounts[0]))
.then(() => { cb(null, this.keyringController.fullUpdate()) })
.catch((reason) => { cb(reason) })
}
//
// Identity Management
//
newUnapprovedTransaction (txParams, cb) {
const self = this
self.txManager.addUnapprovedTransaction(txParams, (err, txMeta) => {
if (err) return cb(err)
self.sendUpdate()
self.opts.showUnapprovedTx(txMeta)
// listen for tx completion (success, fail)
self.txManager.once(`${txMeta.id}:finished`, (status) => {
switch (status) {
case 'submitted':
return cb(null, txMeta.hash)
case 'rejected':
return cb(new Error('MetaMask Tx Signature: User denied transaction signature.'))
default:
return cb(new Error(`MetaMask Tx Signature: Unknown problem: ${JSON.stringify(txMeta.txParams)}`))
}
})
})
}
newUnsignedMessage (msgParams, cb) {
var state = this.idStore.getState()
if (!state.isUnlocked) {
this.idStore.addUnconfirmedMessage(msgParams, cb)
this.opts.unlockAccountMessage()
} else {
this.addUnconfirmedMessage(msgParams, cb)
let msgId = this.messageManager.addUnapprovedMessage(msgParams)
this.sendUpdate()
this.opts.showUnconfirmedMessage()
this.messageManager.once(`${msgId}:finished`, (data) => {
switch (data.status) {
case 'signed':
return cb(null, data.rawSig)
case 'rejected':
return cb(new Error('MetaMask Message Signature: User denied transaction signature.'))
default:
return cb(new Error(`MetaMask Message Signature: Unknown problem: ${JSON.stringify(msgParams)}`))
}
})
}
signMessage (msgParams, cb) {
const msgId = msgParams.metamaskId
promiseToCallback(
// sets the status op the message to 'approved'
// and removes the metamaskId for signing
this.messageManager.approveMessage(msgParams)
.then((cleanMsgParams) => {
// signs the message
return this.keyringController.signMessage(cleanMsgParams)
})
.then((rawSig) => {
// tells the listener that the message has been signed
// and can be returned to the dapp
this.messageManager.setMsgStatusSigned(msgId, rawSig)
})
)(cb)
}
markAccountsFound (cb) {
this.configManager.setLostAccounts([])
this.sendUpdate()
cb(null, this.getState())
}
// Migrate Old Vault If Any
// @string password
//
// returns Promise()
//
// Temporary step used when logging in.
// Checks if old style (pre-3.0.0) Metamask Vault exists.
// If so, persists that vault in the new vault format
// with the provided password, so the other unlock steps
// may be completed without interruption.
migrateOldVaultIfAny (password) {
if (!this.checkIfShouldMigrate()) {
return Promise.resolve(password)
}
const keyringController = this.keyringController
return this.idStoreMigrator.migratedVaultForPassword(password)
.then(this.restoreOldVaultAccounts.bind(this))
.then(this.restoreOldLostAccounts.bind(this))
.then(keyringController.persistAllKeyrings.bind(keyringController, password))
.then(() => password)
}
addUnconfirmedMessage (msgParams, cb) {
const idStore = this.idStore
const msgId = idStore.addUnconfirmedMessage(msgParams, cb)
this.opts.showUnconfirmedMessage(msgParams, msgId)
checkIfShouldMigrate() {
return !!this.configManager.getWallet() && !this.configManager.getVault()
}
setupPublicConfig (stream) {
var storeStream = this.publicConfigStore.createStream()
stream.pipe(storeStream).pipe(stream)
restoreOldVaultAccounts(migratorOutput) {
const { serialized } = migratorOutput
return this.keyringController.restoreKeyring(serialized)
.then(() => migratorOutput)
}
restoreOldLostAccounts(migratorOutput) {
const { lostAccounts } = migratorOutput
if (lostAccounts) {
this.configManager.setLostAccounts(lostAccounts.map(acct => acct.address))
return this.importLostAccounts(migratorOutput)
}
return Promise.resolve(migratorOutput)
}
// IMPORT LOST ACCOUNTS
// @Object with key lostAccounts: @Array accounts <{ address, privateKey }>
// Uses the array's private keys to create a new Simple Key Pair keychain
// and add it to the keyring controller.
importLostAccounts ({ lostAccounts }) {
const privKeys = lostAccounts.map(acct => acct.privateKey)
return this.keyringController.restoreKeyring({
type: 'Simple Key Pair',
data: privKeys,
})
}
//
// config
//
// Log blocks
processBlock (block) {
logBlock (block) {
if (global.METAMASK_DEBUG) {
console.log(`BLOCK CHANGED: #${block.number.toString('hex')} 0x${block.hash.toString('hex')}`)
}
this.verifyNetwork()
}
verifyNetwork () {
// Check network when restoring connectivity:
if (this.idStore._currentState.network === 'loading') {
this.idStore.getNetwork()
setCurrentCurrency (currencyCode, cb) {
try {
this.currencyController.setCurrentCurrency(currencyCode)
this.currencyController.updateConversionRate()
const data = {
conversionRate: this.currencyController.getConversionRate(),
currentFiat: this.currencyController.getCurrentCurrency(),
conversionDate: this.currencyController.getConversionDate(),
}
cb(null, data)
} catch (err) {
cb(err)
}
}
// config
buyEth (address, amount) {
if (!amount) amount = '5'
const network = this.getNetworkState()
let url
switch (network) {
case '1':
url = `https://buy.coinbase.com/?code=9ec56d01-7e81-5017-930c-513daa27bb6a&amount=${amount}&address=${address}&crypto_currency=ETH`
break
case '3':
url = 'https://faucet.metamask.io/'
break
}
if (url) extension.tabs.create({ url })
}
createShapeShiftTx (depositAddress, depositType) {
this.shapeshiftController.createShapeShiftTx(depositAddress, depositType)
}
setGasMultiplier (gasMultiplier, cb) {
try {
this.txManager.setGasMultiplier(gasMultiplier)
cb()
} catch (err) {
cb(err)
}
}
//
// network
//
agreeToDisclaimer (cb) {
try {
this.configManager.setConfirmed(true)
cb()
} catch (e) {
cb(e)
}
verifyNetwork () {
// Check network when restoring connectivity:
if (this.isNetworkLoading()) this.lookupNetwork()
}
setCurrentFiat (fiat, cb) {
try {
this.configManager.setCurrentFiat(fiat)
this.configManager.updateConversionRate()
this.scheduleConversionInterval()
const data = {
conversionRate: this.configManager.getConversionRate(),
currentFiat: this.configManager.getCurrentFiat(),
conversionDate: this.configManager.getConversionDate(),
}
cb(data)
} catch (e) {
cb(null, e)
}
}
scheduleConversionInterval () {
if (this.conversionInterval) {
clearInterval(this.conversionInterval)
}
this.conversionInterval = setInterval(() => {
this.configManager.updateConversionRate()
}, 300000)
}
agreeToEthWarning (cb) {
try {
this.configManager.setShouldntShowWarning()
cb()
} catch (e) {
cb(e)
}
}
// called from popup
setRpcTarget (rpcTarget) {
this.configManager.setRpcTarget(rpcTarget)
extension.runtime.reload()
this.idStore.getNetwork()
this.lookupNetwork()
}
setProviderType (type) {
this.configManager.setProviderType(type)
extension.runtime.reload()
this.idStore.getNetwork()
this.lookupNetwork()
}
useEtherscanProvider () {
@ -305,24 +597,33 @@ module.exports = class MetamaskController {
extension.runtime.reload()
}
buyEth (address, amount) {
if (!amount) amount = '5'
getNetworkState () {
return this.networkStore.getState().network
}
var network = this.idStore._currentState.network
var url = `https://buy.coinbase.com/?code=9ec56d01-7e81-5017-930c-513daa27bb6a&amount=${amount}&address=${address}&crypto_currency=ETH`
setNetworkState (network) {
return this.networkStore.updateState({ network })
}
if (network === '2') {
url = 'https://testfaucet.metamask.io/'
isNetworkLoading () {
return this.getNetworkState() === 'loading'
}
lookupNetwork (err) {
if (err) {
this.setNetworkState('loading')
}
extension.tabs.create({
url,
this.ethQuery.sendAsync({ method: 'net_version' }, (err, network) => {
if (err) {
this.setNetworkState('loading')
return
}
if (global.METAMASK_DEBUG) {
console.log('web3.getNetwork returned ' + network)
}
this.setNetworkState(network)
})
}
createShapeShiftTx (depositAddress, depositType) {
this.configManager.createShapeShiftTx(depositAddress, depositType)
}
}
function noop () {}

View File

@ -1,13 +1,20 @@
module.exports = {
version: 2,
const version = 2
migrate: function (data) {
const clone = require('clone')
module.exports = {
version,
migrate: function (originalVersionedData) {
let versionedData = clone(originalVersionedData)
versionedData.meta.version = version
try {
if (data.config.provider.type === 'etherscan') {
data.config.provider.type = 'rpc'
data.config.provider.rpcTarget = 'https://rpc.metamask.io/'
if (versionedData.data.config.provider.type === 'etherscan') {
versionedData.data.config.provider.type = 'rpc'
versionedData.data.config.provider.rpcTarget = 'https://rpc.metamask.io/'
}
} catch (e) {}
return data
return Promise.resolve(versionedData)
},
}

View File

@ -1,15 +1,20 @@
var oldTestRpc = 'https://rawtestrpc.metamask.io/'
var newTestRpc = 'https://testrpc.metamask.io/'
const version = 3
const oldTestRpc = 'https://rawtestrpc.metamask.io/'
const newTestRpc = 'https://testrpc.metamask.io/'
const clone = require('clone')
module.exports = {
version: 3,
version,
migrate: function (data) {
migrate: function (originalVersionedData) {
let versionedData = clone(originalVersionedData)
versionedData.meta.version = version
try {
if (data.config.provider.rpcTarget === oldTestRpc) {
data.config.provider.rpcTarget = newTestRpc
if (versionedData.data.config.provider.rpcTarget === oldTestRpc) {
versionedData.data.config.provider.rpcTarget = newTestRpc
}
} catch (e) {}
return data
return Promise.resolve(versionedData)
},
}

View File

@ -1,22 +1,28 @@
module.exports = {
version: 4,
const version = 4
migrate: function (data) {
const clone = require('clone')
module.exports = {
version,
migrate: function (versionedData) {
let safeVersionedData = clone(versionedData)
safeVersionedData.meta.version = version
try {
if (data.config.provider.type !== 'rpc') return data
switch (data.config.provider.rpcTarget) {
if (safeVersionedData.data.config.provider.type !== 'rpc') return Promise.resolve(safeVersionedData)
switch (safeVersionedData.data.config.provider.rpcTarget) {
case 'https://testrpc.metamask.io/':
data.config.provider = {
safeVersionedData.data.config.provider = {
type: 'testnet',
}
break
case 'https://rpc.metamask.io/':
data.config.provider = {
safeVersionedData.data.config.provider = {
type: 'mainnet',
}
break
}
} catch (_) {}
return data
return Promise.resolve(safeVersionedData)
},
}

View File

@ -0,0 +1,44 @@
const version = 5
/*
This migration moves state from the flat state trie into KeyringController substate
*/
const extend = require('xtend')
const clone = require('clone')
module.exports = {
version,
migrate: function (originalVersionedData) {
let versionedData = clone(originalVersionedData)
versionedData.meta.version = version
try {
const state = versionedData.data
const newState = selectSubstateForKeyringController(state)
versionedData.data = newState
} catch (err) {
console.warn('MetaMask Migration #5' + err.stack)
}
return Promise.resolve(versionedData)
},
}
function selectSubstateForKeyringController (state) {
const config = state.config
const newState = extend(state, {
KeyringController: {
vault: state.vault,
selectedAccount: config.selectedAccount,
walletNicknames: state.walletNicknames,
},
})
delete newState.vault
delete newState.walletNicknames
delete newState.config.selectedAccount
return newState
}

View File

@ -0,0 +1,43 @@
const version = 6
/*
This migration moves KeyringController.selectedAddress to PreferencesController.selectedAddress
*/
const extend = require('xtend')
const clone = require('clone')
module.exports = {
version,
migrate: function (originalVersionedData) {
let versionedData = clone(originalVersionedData)
versionedData.meta.version = version
try {
const state = versionedData.data
const newState = migrateState(state)
versionedData.data = newState
} catch (err) {
console.warn(`MetaMask Migration #${version}` + err.stack)
}
return Promise.resolve(versionedData)
},
}
function migrateState (state) {
const keyringSubstate = state.KeyringController
// add new state
const newState = extend(state, {
PreferencesController: {
selectedAddress: keyringSubstate.selectedAccount,
},
})
// rm old state
delete newState.KeyringController.selectedAccount
return newState
}

View File

@ -0,0 +1,40 @@
const version = 7
/*
This migration breaks out the TransactionManager substate
*/
const extend = require('xtend')
const clone = require('clone')
module.exports = {
version,
migrate: function (originalVersionedData) {
let versionedData = clone(originalVersionedData)
versionedData.meta.version = version
try {
const state = versionedData.data
const newState = transformState(state)
versionedData.data = newState
} catch (err) {
console.warn(`MetaMask Migration #${version}` + err.stack)
}
return Promise.resolve(versionedData)
},
}
function transformState (state) {
const newState = extend(state, {
TransactionManager: {
transactions: state.transactions || [],
gasMultiplier: state.gasMultiplier || 1,
},
})
delete newState.transactions
delete newState.gasMultiplier
return newState
}

View File

@ -0,0 +1,38 @@
const version = 8
/*
This migration breaks out the NoticeController substate
*/
const extend = require('xtend')
const clone = require('clone')
module.exports = {
version,
migrate: function (originalVersionedData) {
let versionedData = clone(originalVersionedData)
versionedData.meta.version = version
try {
const state = versionedData.data
const newState = transformState(state)
versionedData.data = newState
} catch (err) {
console.warn(`MetaMask Migration #${version}` + err.stack)
}
return Promise.resolve(versionedData)
},
}
function transformState (state) {
const newState = extend(state, {
NoticeController: {
noticesList: state.noticesList || [],
},
})
delete newState.noticesList
return newState
}

View File

@ -0,0 +1,43 @@
const version = 9
/*
This migration breaks out the CurrencyController substate
*/
const merge = require('deep-extend')
const clone = require('clone')
module.exports = {
version,
migrate: function (originalVersionedData) {
let versionedData = clone(originalVersionedData)
versionedData.meta.version = version
try {
const state = versionedData.data
const newState = transformState(state)
versionedData.data = newState
} catch (err) {
console.warn(`MetaMask Migration #${version}` + err.stack)
}
return Promise.resolve(versionedData)
},
}
function transformState (state) {
const newState = merge({}, state, {
CurrencyController: {
currentCurrency: state.currentFiat || state.fiatCurrency || 'USD',
conversionRate: state.conversionRate,
conversionDate: state.conversionDate,
},
})
delete newState.currentFiat
delete newState.fiatCurrency
delete newState.conversionRate
delete newState.conversionDate
return newState
}

View File

@ -0,0 +1,38 @@
const version = 10
/*
This migration breaks out the CurrencyController substate
*/
const merge = require('deep-extend')
const clone = require('clone')
module.exports = {
version,
migrate: function (originalVersionedData) {
let versionedData = clone(originalVersionedData)
versionedData.meta.version = version
try {
const state = versionedData.data
const newState = transformState(state)
versionedData.data = newState
} catch (err) {
console.warn(`MetaMask Migration #${version}` + err.stack)
}
return Promise.resolve(versionedData)
},
}
function transformState (state) {
const newState = merge({}, state, {
ShapeShiftController: {
shapeShiftTxList: state.shapeShiftTxList || [],
},
})
delete newState.shapeShiftTxList
return newState
}

View File

@ -0,0 +1,33 @@
const version = 11
/*
This migration breaks out the CurrencyController substate
*/
const clone = require('clone')
module.exports = {
version,
migrate: function (originalVersionedData) {
let versionedData = clone(originalVersionedData)
versionedData.meta.version = version
try {
const state = versionedData.data
const newState = transformState(state)
versionedData.data = newState
} catch (err) {
console.warn(`MetaMask Migration #${version}` + err.stack)
}
return Promise.resolve(versionedData)
},
}
function transformState (state) {
const newState = state
delete newState.TOSHash
delete newState.isDisclaimerConfirmed
return newState
}

View File

@ -0,0 +1,51 @@
const version = 5
/*
This is an incomplete migration bc it requires post-decrypted data
which we dont have access to at the time of this writing.
*/
const ObservableStore = require('obs-store')
const ConfigManager = require('../../app/scripts/lib/config-manager')
const IdentityStoreMigrator = require('../../app/scripts/lib/idStore-migrator')
const KeyringController = require('../../app/scripts/lib/keyring-controller')
const password = 'obviously not correct'
module.exports = {
version,
migrate: function (versionedData) {
versionedData.meta.version = version
let store = new ObservableStore(versionedData.data)
let configManager = new ConfigManager({ store })
let idStoreMigrator = new IdentityStoreMigrator({ configManager })
let keyringController = new KeyringController({
configManager: configManager,
})
// attempt to migrate to multiVault
return idStoreMigrator.migratedVaultForPassword(password)
.then((result) => {
// skip if nothing to migrate
if (!result) return Promise.resolve(versionedData)
delete versionedData.data.wallet
// create new keyrings
const privKeys = result.lostAccounts.map(acct => acct.privateKey)
return Promise.all([
keyringController.restoreKeyring(result.serialized),
keyringController.restoreKeyring({ type: 'Simple Key Pair', data: privKeys }),
]).then(() => {
return keyringController.persistAllKeyrings(password)
}).then(() => {
// copy result on to state object
versionedData.data = store.get()
return Promise.resolve(versionedData)
})
})
},
}

View File

@ -0,0 +1,25 @@
/* The migrator has two methods the user should be concerned with:
*
* getData(), which returns the app-consumable data object
* saveData(), which persists the app-consumable data object.
*/
// Migrations must start at version 1 or later.
// They are objects with a `version` number
// and a `migrate` function.
//
// The `migrate` function receives the previous
// config data format, and returns the new one.
module.exports = [
require('./002'),
require('./003'),
require('./004'),
require('./005'),
require('./006'),
require('./007'),
require('./008'),
require('./009'),
require('./010'),
require('./011'),
]

View File

@ -0,0 +1,94 @@
const EventEmitter = require('events').EventEmitter
const extend = require('xtend')
const ObservableStore = require('obs-store')
const hardCodedNotices = require('../../notices/notices.json')
module.exports = class NoticeController extends EventEmitter {
constructor (opts) {
super()
this.noticePoller = null
const initState = extend({
noticesList: [],
}, opts.initState)
this.store = new ObservableStore(initState)
this.memStore = new ObservableStore({})
this.store.subscribe(() => this._updateMemstore())
}
getNoticesList () {
return this.store.getState().noticesList
}
getUnreadNotices () {
const notices = this.getNoticesList()
return notices.filter((notice) => notice.read === false)
}
getLatestUnreadNotice () {
const unreadNotices = this.getUnreadNotices()
return unreadNotices[unreadNotices.length - 1]
}
setNoticesList (noticesList) {
this.store.updateState({ noticesList })
return Promise.resolve(true)
}
markNoticeRead (noticeToMark, cb) {
cb = cb || function (err) { if (err) throw err }
try {
var notices = this.getNoticesList()
var index = notices.findIndex((currentNotice) => currentNotice.id === noticeToMark.id)
notices[index].read = true
this.setNoticesList(notices)
const latestNotice = this.getLatestUnreadNotice()
cb(null, latestNotice)
} catch (err) {
cb(err)
}
}
updateNoticesList () {
return this._retrieveNoticeData().then((newNotices) => {
var oldNotices = this.getNoticesList()
var combinedNotices = this._mergeNotices(oldNotices, newNotices)
return Promise.resolve(this.setNoticesList(combinedNotices))
})
}
startPolling () {
if (this.noticePoller) {
clearInterval(this.noticePoller)
}
this.noticePoller = setInterval(() => {
this.noticeController.updateNoticesList()
}, 300000)
}
_mergeNotices (oldNotices, newNotices) {
var noticeMap = this._mapNoticeIds(oldNotices)
newNotices.forEach((notice) => {
if (noticeMap.indexOf(notice.id) === -1) {
oldNotices.push(notice)
}
})
return oldNotices
}
_mapNoticeIds (notices) {
return notices.map((notice) => notice.id)
}
_retrieveNoticeData () {
// Placeholder for the API.
return Promise.resolve(hardCodedNotices)
}
_updateMemstore () {
const lastUnreadNotice = this.getLatestUnreadNotice()
const noActiveNotices = !lastUnreadNotice
this.memStore.updateState({ lastUnreadNotice, noActiveNotices })
}
}

63
app/scripts/popup-core.js Normal file
View File

@ -0,0 +1,63 @@
const EventEmitter = require('events').EventEmitter
const Dnode = require('dnode')
const Web3 = require('web3')
const MetaMaskUi = require('../../ui')
const StreamProvider = require('web3-stream-provider')
const setupMultiplex = require('./lib/stream-utils.js').setupMultiplex
module.exports = initializePopup
function initializePopup (connectionStream) {
// setup app
connectToAccountManager(connectionStream, setupApp)
}
function connectToAccountManager (connectionStream, cb) {
// setup communication with background
// setup multiplexing
var mx = setupMultiplex(connectionStream)
// connect features
setupControllerConnection(mx.createStream('controller'), cb)
setupWeb3Connection(mx.createStream('provider'))
}
function setupWeb3Connection (connectionStream) {
var providerStream = new StreamProvider()
providerStream.pipe(connectionStream).pipe(providerStream)
connectionStream.on('error', console.error.bind(console))
providerStream.on('error', console.error.bind(console))
global.web3 = new Web3(providerStream)
}
function setupControllerConnection (connectionStream, cb) {
// this is a really sneaky way of adding EventEmitter api
// to a bi-directional dnode instance
var eventEmitter = new EventEmitter()
var accountManagerDnode = Dnode({
sendUpdate: function (state) {
eventEmitter.emit('update', state)
},
})
connectionStream.pipe(accountManagerDnode).pipe(connectionStream)
accountManagerDnode.once('remote', function (accountManager) {
// setup push events
accountManager.on = eventEmitter.on.bind(eventEmitter)
cb(null, accountManager)
})
}
function setupApp (err, accountManager) {
if (err) {
alert(err.stack)
throw err
}
var container = document.getElementById('app-content')
MetaMaskUi({
container: container,
accountManager: accountManager,
})
}

View File

@ -1,95 +1,25 @@
const url = require('url')
const EventEmitter = require('events').EventEmitter
const async = require('async')
const Dnode = require('dnode')
const Web3 = require('web3')
const MetaMaskUi = require('../../ui')
const MetaMaskUiCss = require('../../ui/css')
const injectCss = require('inject-css')
const MetaMaskUiCss = require('../../ui/css')
const startPopup = require('./popup-core')
const PortStream = require('./lib/port-stream.js')
const StreamProvider = require('web3-stream-provider')
const setupMultiplex = require('./lib/stream-utils.js').setupMultiplex
const isPopupOrNotification = require('./lib/is-popup-or-notification')
const extension = require('./lib/extension')
const notification = require('./lib/notifications')
// setup app
var css = MetaMaskUiCss()
injectCss(css)
async.parallel({
currentDomain: getCurrentDomain,
accountManager: connectToAccountManager,
}, setupApp)
var name = isPopupOrNotification()
closePopupIfOpen(name)
window.METAMASK_UI_TYPE = name
function connectToAccountManager (cb) {
// setup communication with background
var pluginPort = extension.runtime.connect({name: 'popup'})
var portStream = new PortStream(pluginPort)
// setup multiplexing
var mx = setupMultiplex(portStream)
// connect features
setupControllerConnection(mx.createStream('controller'), cb)
setupWeb3Connection(mx.createStream('provider'))
}
var pluginPort = extension.runtime.connect({ name })
var portStream = new PortStream(pluginPort)
function setupWeb3Connection (stream) {
var remoteProvider = new StreamProvider()
remoteProvider.pipe(stream).pipe(remoteProvider)
stream.on('error', console.error.bind(console))
remoteProvider.on('error', console.error.bind(console))
global.web3 = new Web3(remoteProvider)
}
startPopup(portStream)
function setupControllerConnection (stream, cb) {
var eventEmitter = new EventEmitter()
var background = Dnode({
sendUpdate: function (state) {
eventEmitter.emit('update', state)
},
})
stream.pipe(background).pipe(stream)
background.once('remote', function (accountManager) {
// setup push events
accountManager.on = eventEmitter.on.bind(eventEmitter)
cb(null, accountManager)
})
}
function getCurrentDomain (cb) {
const unknown = '<unknown>'
if (!extension.tabs) return cb(null, unknown)
extension.tabs.query({active: true, currentWindow: true}, function (results) {
var activeTab = results[0]
var currentUrl = activeTab && activeTab.url
var currentDomain = url.parse(currentUrl).host
if (!currentUrl) {
return cb(null, unknown)
}
cb(null, currentDomain)
})
}
function clearNotifications(){
extension.notifications.getAll(function (object) {
for (let notification in object){
extension.notifications.clear(notification)
}
})
}
function setupApp (err, opts) {
if (err) {
alert(err.stack)
throw err
function closePopupIfOpen (name) {
if (name !== 'notification') {
notification.closePopup()
}
clearNotifications()
var container = document.getElementById('app-content')
MetaMaskUi({
container: container,
accountManager: opts.accountManager,
currentDomain: opts.currentDomain,
networkVersion: opts.networkVersion,
})
}

View File

@ -0,0 +1,390 @@
const EventEmitter = require('events')
const async = require('async')
const extend = require('xtend')
const Semaphore = require('semaphore')
const ObservableStore = require('obs-store')
const ethUtil = require('ethereumjs-util')
const BN = require('ethereumjs-util').BN
const TxProviderUtil = require('./lib/tx-utils')
const createId = require('./lib/random-id')
module.exports = class TransactionManager extends EventEmitter {
constructor (opts) {
super()
this.store = new ObservableStore(extend({
transactions: [],
gasMultiplier: 1,
}, opts.initState))
this.memStore = new ObservableStore({})
this.networkStore = opts.networkStore || new ObservableStore({})
this.preferencesStore = opts.preferencesStore || new ObservableStore({})
this.txHistoryLimit = opts.txHistoryLimit
this.provider = opts.provider
this.blockTracker = opts.blockTracker
this.txProviderUtils = new TxProviderUtil(this.provider)
this.blockTracker.on('block', this.checkForTxInBlock.bind(this))
this.signEthTx = opts.signTransaction
this.nonceLock = Semaphore(1)
// memstore is computed from a few different stores
this._updateMemstore()
this.store.subscribe(() => this._updateMemstore() )
this.networkStore.subscribe(() => this._updateMemstore() )
this.preferencesStore.subscribe(() => this._updateMemstore() )
}
getState () {
return this.memStore.getState()
}
getNetwork () {
return this.networkStore.getState().network
}
getSelectedAddress () {
return this.preferencesStore.getState().selectedAddress
}
// Returns the tx list
getTxList () {
let network = this.getNetwork()
let fullTxList = this.store.getState().transactions
return fullTxList.filter(txMeta => txMeta.metamaskNetworkId === network)
}
getGasMultiplier () {
return this.store.getState().gasMultiplier
}
setGasMultiplier (gasMultiplier) {
return this.store.updateState({ gasMultiplier })
}
// Adds a tx to the txlist
addTx (txMeta) {
var txList = this.getTxList()
var txHistoryLimit = this.txHistoryLimit
// checks if the length of th tx history is
// longer then desired persistence limit
// and then if it is removes only confirmed
// or rejected tx's.
// not tx's that are pending or unapproved
if (txList.length > txHistoryLimit - 1) {
var index = txList.findIndex((metaTx) => metaTx.status === 'confirmed' || metaTx.status === 'rejected')
txList.splice(index, 1)
}
txList.push(txMeta)
this._saveTxList(txList)
this.once(`${txMeta.id}:signed`, function (txId) {
this.removeAllListeners(`${txMeta.id}:rejected`)
})
this.once(`${txMeta.id}:rejected`, function (txId) {
this.removeAllListeners(`${txMeta.id}:signed`)
})
this.emit('updateBadge')
this.emit(`${txMeta.id}:unapproved`, txMeta)
}
// gets tx by Id and returns it
getTx (txId, cb) {
var txList = this.getTxList()
var txMeta = txList.find(txData => txData.id === txId)
return cb ? cb(txMeta) : txMeta
}
//
updateTx (txMeta) {
var txId = txMeta.id
var txList = this.getTxList()
var index = txList.findIndex(txData => txData.id === txId)
txList[index] = txMeta
this._saveTxList(txList)
this.emit('update')
}
get unapprovedTxCount () {
return Object.keys(this.getUnapprovedTxList()).length
}
get pendingTxCount () {
return this.getTxsByMetaData('status', 'signed').length
}
addUnapprovedTransaction (txParams, done) {
let txMeta
async.waterfall([
// validate
(cb) => this.txProviderUtils.validateTxParams(txParams, cb),
// prepare txMeta
(cb) => {
// create txMeta obj with parameters and meta data
let time = (new Date()).getTime()
let txId = createId()
txParams.metamaskId = txId
txParams.metamaskNetworkId = this.getNetwork()
txMeta = {
id: txId,
time: time,
status: 'unapproved',
gasMultiplier: this.getGasMultiplier(),
metamaskNetworkId: this.getNetwork(),
txParams: txParams,
}
// calculate metadata for tx
this.txProviderUtils.analyzeGasUsage(txMeta, cb)
},
// save txMeta
(cb) => {
this.addTx(txMeta)
this.setMaxTxCostAndFee(txMeta)
cb(null, txMeta)
},
], done)
}
setMaxTxCostAndFee (txMeta) {
var txParams = txMeta.txParams
var gasMultiplier = txMeta.gasMultiplier
var gasCost = new BN(ethUtil.stripHexPrefix(txParams.gas || txMeta.estimatedGas), 16)
var gasPrice = new BN(ethUtil.stripHexPrefix(txParams.gasPrice || '0x4a817c800'), 16)
gasPrice = gasPrice.mul(new BN(gasMultiplier * 100), 10).div(new BN(100, 10))
var txFee = gasCost.mul(gasPrice)
var txValue = new BN(ethUtil.stripHexPrefix(txParams.value || '0x0'), 16)
var maxCost = txValue.add(txFee)
txMeta.txFee = txFee
txMeta.txValue = txValue
txMeta.maxCost = maxCost
this.updateTx(txMeta)
}
getUnapprovedTxList () {
var txList = this.getTxList()
return txList.filter((txMeta) => txMeta.status === 'unapproved')
.reduce((result, tx) => {
result[tx.id] = tx
return result
}, {})
}
approveTransaction (txId, cb = warn) {
const self = this
// approve
self.setTxStatusApproved(txId)
// only allow one tx at a time for atomic nonce usage
self.nonceLock.take(() => {
// begin signature process
async.waterfall([
(cb) => self.fillInTxParams(txId, cb),
(cb) => self.signTransaction(txId, cb),
(rawTx, cb) => self.publishTransaction(txId, rawTx, cb),
], (err) => {
self.nonceLock.leave()
if (err) {
this.setTxStatusFailed(txId)
return cb(err)
}
cb()
})
})
}
cancelTransaction (txId, cb = warn) {
this.setTxStatusRejected(txId)
cb()
}
fillInTxParams (txId, cb) {
let txMeta = this.getTx(txId)
this.txProviderUtils.fillInTxParams(txMeta.txParams, (err) => {
if (err) return cb(err)
this.updateTx(txMeta)
cb()
})
}
signTransaction (txId, cb) {
let txMeta = this.getTx(txId)
let txParams = txMeta.txParams
let fromAddress = txParams.from
let ethTx = this.txProviderUtils.buildEthTxFromParams(txParams, txMeta.gasMultiplier)
this.signEthTx(ethTx, fromAddress).then(() => {
this.setTxStatusSigned(txMeta.id)
cb(null, ethUtil.bufferToHex(ethTx.serialize()))
}).catch((err) => {
cb(err)
})
}
publishTransaction (txId, rawTx, cb) {
this.txProviderUtils.publishTransaction(rawTx, (err, txHash) => {
if (err) return cb(err)
this.setTxHash(txId, txHash)
this.setTxStatusSubmitted(txId)
cb()
})
}
// receives a txHash records the tx as signed
setTxHash (txId, txHash) {
// Add the tx hash to the persisted meta-tx object
let txMeta = this.getTx(txId)
txMeta.hash = txHash
this.updateTx(txMeta)
}
/*
Takes an object of fields to search for eg:
var thingsToLookFor = {
to: '0x0..',
from: '0x0..',
status: 'signed',
}
and returns a list of tx with all
options matching
this is for things like filtering a the tx list
for only tx's from 1 account
or for filltering for all txs from one account
and that have been 'confirmed'
*/
getFilteredTxList (opts) {
var filteredTxList
Object.keys(opts).forEach((key) => {
filteredTxList = this.getTxsByMetaData(key, opts[key], filteredTxList)
})
return filteredTxList
}
getTxsByMetaData (key, value, txList = this.getTxList()) {
return txList.filter((txMeta) => {
if (txMeta.txParams[key]) {
return txMeta.txParams[key] === value
} else {
return txMeta[key] === value
}
})
}
// STATUS METHODS
// get::set status
// should return the status of the tx.
getTxStatus (txId) {
const txMeta = this.getTx(txId)
return txMeta.status
}
// should update the status of the tx to 'rejected'.
setTxStatusRejected (txId) {
this._setTxStatus(txId, 'rejected')
}
// should update the status of the tx to 'approved'.
setTxStatusApproved (txId) {
this._setTxStatus(txId, 'approved')
}
// should update the status of the tx to 'signed'.
setTxStatusSigned (txId) {
this._setTxStatus(txId, 'signed')
}
// should update the status of the tx to 'submitted'.
setTxStatusSubmitted (txId) {
this._setTxStatus(txId, 'submitted')
}
// should update the status of the tx to 'confirmed'.
setTxStatusConfirmed (txId) {
this._setTxStatus(txId, 'confirmed')
}
setTxStatusFailed (txId) {
this._setTxStatus(txId, 'failed')
}
// merges txParams obj onto txData.txParams
// use extend to ensure that all fields are filled
updateTxParams (txId, txParams) {
var txMeta = this.getTx(txId)
txMeta.txParams = extend(txMeta.txParams, txParams)
this.updateTx(txMeta)
}
// checks if a signed tx is in a block and
// if included sets the tx status as 'confirmed'
checkForTxInBlock () {
var signedTxList = this.getFilteredTxList({status: 'submitted'})
if (!signedTxList.length) return
signedTxList.forEach((txMeta) => {
var txHash = txMeta.hash
var txId = txMeta.id
if (!txHash) {
txMeta.err = {
errCode: 'No hash was provided',
message: 'We had an error while submitting this transaction, please try again.',
}
this.updateTx(txMeta)
return this.setTxStatusFailed(txId)
}
this.txProviderUtils.query.getTransactionByHash(txHash, (err, txParams) => {
if (err || !txParams) {
if (!txParams) return
txMeta.err = {
isWarning: true,
errorCode: err,
message: 'There was a problem loading this transaction.',
}
this.updateTx(txMeta)
return console.error(err)
}
if (txParams.blockNumber) {
this.setTxStatusConfirmed(txId)
}
})
})
}
// PRIVATE METHODS
// Should find the tx in the tx list and
// update it.
// should set the status in txData
// - `'unapproved'` the user has not responded
// - `'rejected'` the user has responded no!
// - `'approved'` the user has approved the tx
// - `'signed'` the tx is signed
// - `'submitted'` the tx is sent to a server
// - `'confirmed'` the tx has been included in a block.
_setTxStatus (txId, status) {
var txMeta = this.getTx(txId)
txMeta.status = status
this.emit(`${txMeta.id}:${status}`, txId)
if (status === 'submitted' || status === 'rejected') {
this.emit(`${txMeta.id}:finished`, status)
}
this.updateTx(txMeta)
this.emit('updateBadge')
}
// Saves the new/updated txList.
// Function is intended only for internal use
_saveTxList (transactions) {
this.store.updateState({ transactions })
}
_updateMemstore () {
const unapprovedTxs = this.getUnapprovedTxList()
const selectedAddressTxList = this.getFilteredTxList({
from: this.getSelectedAddress(),
metamaskNetworkId: this.getNetwork(),
})
this.memStore.updateState({ unapprovedTxs, selectedAddressTxList })
}
}
const warn = () => console.warn('warn was used no cb provided')

View File

@ -4,3 +4,4 @@ machine:
dependencies:
pre:
- "npm i -g testem"
- "npm i -g mocha"

12
development/announcer.js Normal file
View File

@ -0,0 +1,12 @@
var manifest = require('../app/manifest.json')
var version = manifest.version
var fs = require('fs')
var path = require('path')
var changelog = fs.readFileSync(path.join(__dirname, '..', 'CHANGELOG.md')).toString()
var log = changelog.split(version)[1].split('##')[0].trim()
let msg = `*MetaMask ${version}* now published to the Chrome Store! It should auto-update over the next hour!\n${log}`
console.log(msg)

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -136,13 +136,12 @@
"selectedAddress": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc",
"network": "1",
"seedWords": null,
"isConfirmed": true,
"unconfMsgs": {},
"messages": [],
"provider": {
"type": "mainnet"
},
"selectedAccount": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc"
"selectedAddress": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc"
},
"appState": {
"menuOpen": false,

View File

@ -102,13 +102,12 @@
"selectedAddress": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc",
"network": "2",
"seedWords": null,
"isConfirmed": true,
"unconfMsgs": {},
"messages": [],
"provider": {
"type": "testnet"
},
"selectedAccount": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc"
"selectedAddress": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc"
},
"appState": {
"menuOpen": false,

View File

@ -33,20 +33,20 @@
"accounts": {
"0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc": {
"code": "0x",
"balance": "0x0",
"balance": "0x100000000000",
"nonce": "0x0",
"address": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc"
},
"0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b": {
"code": "0x",
"nonce": "0x0",
"balance": "0x0",
"balance": "0x100000000000",
"address": "0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b"
},
"0xeb9e64b93097bc15f01f13eae97015c57ab64823": {
"code": "0x",
"nonce": "0x0",
"balance": "0x0",
"balance": "0x100000000000",
"address": "0xeb9e64b93097bc15f01f13eae97015c57ab64823"
},
"0x704107d04affddd9b66ab9de3dd7b095852e9b69": {
@ -60,13 +60,12 @@
"selectedAddress": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc",
"network": "2",
"seedWords": null,
"isConfirmed": true,
"unconfMsgs": {},
"messages": [],
"provider": {
"type": "testnet"
},
"selectedAccount": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc"
"selectedAddress": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc"
},
"appState": {
"menuOpen": false,

View File

@ -0,0 +1,83 @@
{
"metamask": {
"isInitialized": true,
"isUnlocked": true,
"rpcTarget": "https://rawtestrpc.metamask.io/",
"identities": {
"0x58bda1f9d87dc7d2bcc6f7c2513efc9d03fca683": {
"address": "0x58bda1f9d87dc7d2bcc6f7c2513efc9d03fca683",
"name": "Account 1"
},
"0x9858e7d8b79fc3e6d989636721584498926da38a": {
"address": "0x9858e7d8b79fc3e6d989636721584498926da38a",
"name": "Imported Account"
}
},
"unconfTxs": {},
"currentFiat": "USD",
"conversionRate": 10.19458075,
"conversionDate": 1484696373,
"noActiveNotices": true,
"network": "3",
"accounts": {
"0x58bda1f9d87dc7d2bcc6f7c2513efc9d03fca683": {
"code": "0x",
"balance": "0x0",
"nonce": "0x0",
"address": "0x58bda1f9d87dc7d2bcc6f7c2513efc9d03fca683"
},
"0x9858e7d8b79fc3e6d989636721584498926da38a": {
"code": "0x",
"balance": "0x0",
"nonce": "0x0",
"address": "0x9858e7d8b79fc3e6d989636721584498926da38a"
}
},
"transactions": [],
"provider": {
"type": "testnet"
},
"selectedAddress": "0x9858e7d8b79fc3e6d989636721584498926da38a",
"selectedAccountTxList": [],
"unconfMsgs": {},
"messages": [],
"shapeShiftTxList": [],
"keyringTypes": [
"Simple Key Pair",
"HD Key Tree"
],
"keyrings": [
{
"type": "HD Key Tree",
"accounts": [
"58bda1f9d87dc7d2bcc6f7c2513efc9d03fca683"
]
},
{
"type": "Simple Key Pair",
"accounts": [
"0x9858e7d8b79fc3e6d989636721584498926da38a"
]
}
],
"lostAccounts": [],
"seedWords": null
},
"appState": {
"menuOpen": false,
"currentView": {
"name": "accounts"
},
"accountDetail": {
"subview": "transactions",
"accountExport": "none",
"privateKey": ""
},
"transForward": true,
"isLoading": false,
"warning": null,
"scrollToBottom": false,
"forgottenPassword": false
},
"identities": {}
}

View File

@ -0,0 +1,125 @@
{
"metamask": {
"isInitialized": true,
"isUnlocked": true,
"rpcTarget": "https://rawtestrpc.metamask.io/",
"identities": {
"0xac39b311dceb2a4b2f5d8461c1cdaf756f4f7ae9": {
"address": "0xac39b311dceb2a4b2f5d8461c1cdaf756f4f7ae9",
"name": "Account 1"
},
"0xd7c0cd9e7d2701c710d64fc492c7086679bdf7b4": {
"address": "0xd7c0cd9e7d2701c710d64fc492c7086679bdf7b4",
"name": "Account 2"
},
"0x1acfb961c5a8268eac8e09d6241a26cbeff42241": {
"address": "0x1acfb961c5a8268eac8e09d6241a26cbeff42241",
"name": "Account 3"
},
"0xe15d894becb0354c501ae69429b05143679f39e0": {
"address": "0xe15d894becb0354c501ae69429b05143679f39e0",
"name": "Account 4"
},
"0x87658c15aefe7448008a28513a11b6b130ef4cd0": {
"address": "0x87658c15aefe7448008a28513a11b6b130ef4cd0",
"name": "Account 5"
},
"0xaa25854c0379e53c957ac9382e720c577fa31fd5": {
"address": "0xaa25854c0379e53c957ac9382e720c577fa31fd5",
"name": "Account 6"
}
},
"unconfTxs": {},
"currentFiat": "USD",
"conversionRate": 0,
"conversionDate": "N/A",
"noActiveNotices": true,
"network": "3",
"accounts": {
"0xac39b311dceb2a4b2f5d8461c1cdaf756f4f7ae9": {
"code": "0x",
"balance": "0x11f646fe14c9c000",
"nonce": "0x3",
"address": "0xac39b311dceb2a4b2f5d8461c1cdaf756f4f7ae9"
},
"0xd7c0cd9e7d2701c710d64fc492c7086679bdf7b4": {
"code": "0x",
"balance": "0x0",
"nonce": "0x0",
"address": "0xd7c0cd9e7d2701c710d64fc492c7086679bdf7b4"
},
"0x1acfb961c5a8268eac8e09d6241a26cbeff42241": {
"code": "0x",
"balance": "0x0",
"nonce": "0x0",
"address": "0x1acfb961c5a8268eac8e09d6241a26cbeff42241"
},
"0xe15d894becb0354c501ae69429b05143679f39e0": {
"code": "0x",
"balance": "0x0",
"nonce": "0x0",
"address": "0xe15d894becb0354c501ae69429b05143679f39e0"
},
"0x87658c15aefe7448008a28513a11b6b130ef4cd0": {
"code": "0x",
"balance": "0x0",
"nonce": "0x0",
"address": "0x87658c15aefe7448008a28513a11b6b130ef4cd0"
},
"0xaa25854c0379e53c957ac9382e720c577fa31fd5": {
"code": "0x",
"balance": "0x0",
"nonce": "0x0",
"address": "0xaa25854c0379e53c957ac9382e720c577fa31fd5"
}
},
"transactions": [],
"provider": {
"type": "testnet"
},
"selectedAddress": "0x87658c15aefe7448008a28513a11b6b130ef4cd0",
"unconfMsgs": {},
"messages": [],
"shapeShiftTxList": [],
"keyringTypes": [
"Simple Key Pair",
"HD Key Tree"
],
"keyrings": [
{
"type": "HD Key Tree",
"accounts": [
"ac39b311dceb2a4b2f5d8461c1cdaf756f4f7ae9",
"d7c0cd9e7d2701c710d64fc492c7086679bdf7b4",
"1acfb961c5a8268eac8e09d6241a26cbeff42241"
]
},
{
"type": "Simple Key Pair",
"accounts": [
"e15d894becb0354c501ae69429b05143679f39e0",
"87658c15aefe7448008a28513a11b6b130ef4cd0",
"aa25854c0379e53c957ac9382e720c577fa31fd5"
]
}
],
"lostAccounts": []
},
"appState": {
"menuOpen": false,
"currentView": {
"name": "accounts"
},
"accountDetail": {
"subview": "transactions",
"accountExport": "none",
"privateKey": ""
},
"transForward": true,
"isLoading": false,
"warning": null,
"scrollToBottom": false,
"forgottenPassword": false
},
"identities": {}
}

View File

@ -2,67 +2,101 @@
"metamask": {
"isInitialized": true,
"isUnlocked": true,
"currentDomain": "example.com",
"rpcTarget": "https://rawtestrpc.metamask.io/",
"identities": {
"0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc": {
"0x0abdd95cafcabec9b3e99dcd09fc4b441037cb80": {
"name": "Wallet 1",
"address": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc",
"address": "0x0abdd95cafcabec9b3e99dcd09fc4b441037cb80",
"mayBeFauceting": false
},
"0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b": {
"0xc4692812f03d96edf4ac88c9d08106222578407b": {
"name": "Wallet 2",
"address": "0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b",
"address": "0xc4692812f03d96edf4ac88c9d08106222578407b",
"mayBeFauceting": false
},
"0xeb9e64b93097bc15f01f13eae97015c57ab64823": {
"0x1abda824194d3583509c97e6faefd575e194844d": {
"name": "Wallet 3",
"address": "0xeb9e64b93097bc15f01f13eae97015c57ab64823",
"address": "0x1abda824194d3583509c97e6faefd575e194844d",
"mayBeFauceting": false
},
"0x704107d04affddd9b66ab9de3dd7b095852e9b69": {
"0xa7ea108f933abe81ddafc0b2279b1d828dd0e8d6": {
"name": "Wallet 4",
"address": "0x704107d04affddd9b66ab9de3dd7b095852e9b69",
"address": "0xa7ea108f933abe81ddafc0b2279b1d828dd0e8d6",
"mayBeFauceting": false
},
"0xab64bed4528feed6a85ab3f3da91479954da9e46": {
"name": "Wallet 5",
"address": "0xab64bed4528feed6a85ab3f3da91479954da9e46",
"mayBeFauceting": false
},
"0x69e90083dabd9acc9ea6d706eb6dccf245ae4d6b": {
"name": "Wallet 6",
"address": "0x69e90083dabd9acc9ea6d706eb6dccf245ae4d6b",
"mayBeFauceting": false
},
"0xebcc86684ef70e0ec3ad85f996b22e1bdad2d024": {
"name": "Wallet 7",
"address": "0xebcc86684ef70e0ec3ad85f996b22e1bdad2d024",
"mayBeFauceting": false
}
},
"unconfTxs": {},
"currentFiat": "USD",
"conversionRate": 11.84461814,
"conversionDate": 1476226414,
"accounts": {
"0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc": {
"balance": "0x0",
"nonce": "0x0",
"0x0abdd95cafcabec9b3e99dcd09fc4b441037cb80": {
"balance": "0x0de0b6b3a7640000",
"nonce": "0x100000",
"code": "0x",
"address": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc"
"address": "0x0abdd95cafcabec9b3e99dcd09fc4b441037cb80"
},
"0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b": {
"balance": "0x0",
"nonce": "0x0",
"0xc4692812f03d96edf4ac88c9d08106222578407b": {
"balance": "0x00",
"nonce": "0x100000",
"code": "0x",
"address": "0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b"
"address": "0xc4692812f03d96edf4ac88c9d08106222578407b"
},
"0xeb9e64b93097bc15f01f13eae97015c57ab64823": {
"balance": "0x0",
"nonce": "0x0",
"0x1abda824194d3583509c97e6faefd575e194844d": {
"balance": "0x00",
"nonce": "0x100000",
"code": "0x",
"address": "0xeb9e64b93097bc15f01f13eae97015c57ab64823"
"address": "0x1abda824194d3583509c97e6faefd575e194844d"
},
"0x704107d04affddd9b66ab9de3dd7b095852e9b69": {
"balance": "0x0",
"0xa7ea108f933abe81ddafc0b2279b1d828dd0e8d6": {
"balance": "0x00",
"nonce": "0x100000",
"code": "0x",
"nonce": "0x0",
"address": "0x704107d04affddd9b66ab9de3dd7b095852e9b69"
"address": "0xa7ea108f933abe81ddafc0b2279b1d828dd0e8d6"
},
"0xab64bed4528feed6a85ab3f3da91479954da9e46": {
"balance": "0x00",
"nonce": "0x100000",
"code": "0x",
"address": "0xab64bed4528feed6a85ab3f3da91479954da9e46"
},
"0x69e90083dabd9acc9ea6d706eb6dccf245ae4d6b": {
"balance": "0x00",
"nonce": "0x100000",
"code": "0x",
"address": "0x69e90083dabd9acc9ea6d706eb6dccf245ae4d6b"
},
"0xebcc86684ef70e0ec3ad85f996b22e1bdad2d024": {
"balance": "0x00",
"nonce": "0x100000",
"code": "0x",
"address": "0xebcc86684ef70e0ec3ad85f996b22e1bdad2d024"
}
},
"transactions": [],
"network": "2",
"isConfirmed": true,
"unconfMsgs": {},
"messages": [],
"shapeShiftTxList": [],
"provider": {
"type": "testnet"
},
"selectedAccount": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc",
"selectedAddress": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc",
"selectedAddress": "0x0abdd95cafcabec9b3e99dcd09fc4b441037cb80",
"seedWords": null
},
"appState": {
@ -71,15 +105,12 @@
"name": "accounts"
},
"accountDetail": {
"subview": "transactions",
"accountExport": "none",
"privateKey": ""
"subview": "transactions"
},
"currentDomain": "extensions",
"transForward": true,
"isLoading": false,
"warning": null,
"scrollToBottom": true
},
"identities": {}
}
}

View File

@ -0,0 +1,123 @@
{
"metamask": {
"isInitialized": true,
"isUnlocked": true,
"rpcTarget": "https://rawtestrpc.metamask.io/",
"identities": {
"0xac39b311dceb2a4b2f5d8461c1cdaf756f4f7ae9": {
"address": "0xac39b311dceb2a4b2f5d8461c1cdaf756f4f7ae9",
"name": "Account 1"
},
"0xd7c0cd9e7d2701c710d64fc492c7086679bdf7b4": {
"address": "0xd7c0cd9e7d2701c710d64fc492c7086679bdf7b4",
"name": "Account 2"
},
"0x1acfb961c5a8268eac8e09d6241a26cbeff42241": {
"address": "0x1acfb961c5a8268eac8e09d6241a26cbeff42241",
"name": "Account 3"
},
"0xabc2bca51709b8615147352c62420f547a63a00c": {
"address": "0xabc2bca51709b8615147352c62420f547a63a00c",
"name": "Account 4"
}
},
"unconfTxs": {
"7992944905869041": {
"id": 7992944905869041,
"txParams": {
"from": "0xac39b311dceb2a4b2f5d8461c1cdaf756f4f7ae9",
"value": "0x0",
"data": "0x606060405234610000575b60da806100186000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630dbe671f14603c575b6000565b3460005760466088565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b600060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16815600a165627a7a72305820a99dfa6091771f518dd1ae8d1ee347bae3304dffd98fd24b1b99a8380bc60a750029",
"gas": "0x1af75",
"metamaskId": 7992944905869041,
"metamaskNetworkId": "3"
},
"time": 1482279685589,
"status": "unconfirmed",
"gasMultiplier": 1,
"metamaskNetworkId": "3",
"gasLimitSpecified": true,
"estimatedGas": "0x1af75",
"simulationFails": true
}
},
"currentFiat": "USD",
"conversionRate": 7.69158136,
"conversionDate": 1482279663,
"noActiveNotices": true,
"network": "3",
"accounts": {
"0xac39b311dceb2a4b2f5d8461c1cdaf756f4f7ae9": {
"code": "0x",
"nonce": "0x3",
"balance": "0x11f646fe14c9c000",
"address": "0xac39b311dceb2a4b2f5d8461c1cdaf756f4f7ae9"
},
"0xd7c0cd9e7d2701c710d64fc492c7086679bdf7b4": {
"code": "0x",
"nonce": "0x0",
"balance": "0x0",
"address": "0xd7c0cd9e7d2701c710d64fc492c7086679bdf7b4"
},
"0x1acfb961c5a8268eac8e09d6241a26cbeff42241": {
"code": "0x",
"balance": "0x0",
"nonce": "0x0",
"address": "0x1acfb961c5a8268eac8e09d6241a26cbeff42241"
},
"0xabc2bca51709b8615147352c62420f547a63a00c": {
"code": "0x",
"balance": "0x0",
"nonce": "0x0",
"address": "0xabc2bca51709b8615147352c62420f547a63a00c"
}
},
"transactions": [
{
"id": 7992944905869041,
"txParams": {
"from": "0xac39b311dceb2a4b2f5d8461c1cdaf756f4f7ae9",
"value": "0x0",
"data": "0x606060405234610000575b60da806100186000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630dbe671f14603c575b6000565b3460005760466088565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b600060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16815600a165627a7a72305820a99dfa6091771f518dd1ae8d1ee347bae3304dffd98fd24b1b99a8380bc60a750029",
"gas": "0x1af75",
"metamaskId": 7992944905869041,
"metamaskNetworkId": "3"
},
"time": 1482279685589,
"status": "unconfirmed",
"gasMultiplier": 1,
"metamaskNetworkId": "3",
"gasLimitSpecified": true,
"estimatedGas": "0x1af75",
"simulationFails": true
}
],
"provider": {
"type": "testnet"
},
"selectedAddress": "0xac39b311dceb2a4b2f5d8461c1cdaf756f4f7ae9",
"seedWords": false,
"unconfMsgs": {},
"messages": [],
"shapeShiftTxList": [],
"keyringTypes": [
"Simple Key Pair",
"HD Key Tree"
],
"lostAccounts": []
},
"appState": {
"menuOpen": false,
"currentView": {
"name": "confTx",
"context": 0
},
"accountDetail": {
"subview": "transactions"
},
"transForward": true,
"isLoading": false,
"warning": null
},
"identities": {}
}

View File

@ -57,15 +57,13 @@
}
},
"transactions": [],
"selectedAddress": "0x843963b837841dad3b0f5969ff271108776616df",
"network": "2",
"isConfirmed": true,
"unconfMsgs": {},
"messages": [],
"provider": {
"type": "testnet"
},
"selectedAccount": "0x843963b837841dad3b0f5969ff271108776616df",
"selectedAddress": "0x843963b837841dad3b0f5969ff271108776616df",
"seedWords": null
},
"appState": {

View File

@ -10,7 +10,6 @@
"transactions": [],
"network": "2",
"seedWords": null,
"isConfirmed": true,
"unconfMsgs": {},
"messages": [],
"provider": {
@ -32,4 +31,4 @@
"warning": null
},
"identities": {}
}
}

View File

@ -157,18 +157,15 @@
"estimatedGas": "0x5208"
}
],
"selectedAddress": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
"network": "loading",
"network": "166",
"seedWords": null,
"isConfirmed": true,
"isEthConfirmed": true,
"unconfMsgs": {},
"messages": [],
"provider": {
"type": "rpc",
"rpcTarget": "555.203.16.244"
},
"selectedAccount": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825"
"selectedAddress": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825"
},
"appState": {
"menuOpen": false,

View File

@ -57,13 +57,12 @@
"selectedAddress": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc",
"network": "2",
"seedWords": null,
"isConfirmed": true,
"unconfMsgs": {},
"messages": [],
"provider": {
"type": "testnet"
},
"selectedAccount": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc"
"selectedAddress": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc"
},
"appState": {
"menuOpen": false,

View File

@ -0,0 +1,39 @@
{
"metamask": {
"isInitialized": false,
"isUnlocked": false,
"rpcTarget": "https://rawtestrpc.metamask.io/",
"identities": {},
"unconfTxs": {},
"currentFiat": "USD",
"conversionRate": 11.47635827,
"conversionDate": 1477606503,
"noActiveNotices": true,
"network": null,
"accounts": {},
"transactions": [],
"unconfMsgs": {},
"messages": [],
"shapeShiftTxList": [],
"keyringTypes": [
"Simple Key Pair"
],
"provider": {
"type": "testnet"
}
},
"appState": {
"menuOpen": false,
"currentView": {
"name": "accounts",
"detailView": null
},
"accountDetail": {
"subview": "transactions"
},
"transForward": true,
"isLoading": false,
"warning": null
},
"identities": {}
}

View File

@ -55,13 +55,11 @@
},
"transactions": [],
"network": "2",
"isConfirmed": true,
"unconfMsgs": {},
"messages": [],
"provider": {
"type": "testnet"
},
"selectedAccount": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc",
"selectedAddress": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc",
"seedWords": null
},
@ -82,4 +80,4 @@
"scrollToBottom": true
},
"identities": {}
}
}

View File

@ -0,0 +1,91 @@
{
"metamask": {
"isInitialized": true,
"isUnlocked": true,
"rpcTarget": "https://rawtestrpc.metamask.io/",
"identities": {
"0x01208723ba84e15da2e71656544a2963b0c06d40": {
"address": "0x01208723ba84e15da2e71656544a2963b0c06d40",
"name": "Account 1"
}
},
"unconfTxs": {},
"currentFiat": "USD",
"conversionRate": 10.1219126,
"conversionDate": 1484695442,
"noActiveNotices": true,
"network": "3",
"accounts": {
"0x01208723ba84e15da2e71656544a2963b0c06d40": {
"nonce": "0x0",
"balance": "0x0",
"code": "0x",
"address": "0x01208723ba84e15da2e71656544a2963b0c06d40"
}
},
"transactions": [],
"provider": {
"type": "testnet"
},
"selectedAddress": "0x01208723ba84e15da2e71656544a2963b0c06d40",
"selectedAccountTxList": [],
"seedWords": false,
"unconfMsgs": {},
"messages": [],
"shapeShiftTxList": [],
"keyringTypes": [
"Simple Key Pair",
"HD Key Tree"
],
"keyrings": [
{
"type": "Simple Key Pair",
"accounts": []
},
{
"type": "Simple Key Pair",
"accounts": []
},
{
"type": "Simple Key Pair",
"accounts": []
},
{
"type": "Simple Key Pair",
"accounts": []
},
{
"type": "Simple Key Pair",
"accounts": []
},
{
"type": "Simple Key Pair",
"accounts": []
},
{
"type": "Simple Key Pair",
"accounts": []
},
{
"type": "HD Key Tree",
"accounts": [
"01208723ba84e15da2e71656544a2963b0c06d40"
]
}
],
"lostAccounts": []
},
"appState": {
"menuOpen": false,
"currentView": {
"name": "import-menu"
},
"accountDetail": {
"subview": "transactions"
},
"transForward": true,
"isLoading": false,
"warning": "Invalid hex string"
},
"identities": {}
}

View File

@ -0,0 +1,63 @@
{
"metamask": {
"isInitialized": true,
"isUnlocked": true,
"rpcTarget": "https://rawtestrpc.metamask.io/",
"identities": {
"0x01208723ba84e15da2e71656544a2963b0c06d40": {
"address": "0x01208723ba84e15da2e71656544a2963b0c06d40",
"name": "Account 1"
}
},
"unconfTxs": {},
"currentFiat": "USD",
"conversionRate": 10.10788584,
"conversionDate": 1484694362,
"noActiveNotices": true,
"network": "3",
"accounts": {
"0x01208723ba84e15da2e71656544a2963b0c06d40": {
"balance": "0x0",
"code": "0x",
"nonce": "0x0",
"address": "0x01208723ba84e15da2e71656544a2963b0c06d40"
}
},
"transactions": [],
"provider": {
"type": "testnet"
},
"selectedAddress": "0x01208723ba84e15da2e71656544a2963b0c06d40",
"selectedAccountTxList": [],
"seedWords": null,
"unconfMsgs": {},
"messages": [],
"shapeShiftTxList": [],
"keyringTypes": [
"Simple Key Pair",
"HD Key Tree"
],
"keyrings": [
{
"type": "HD Key Tree",
"accounts": [
"01208723ba84e15da2e71656544a2963b0c06d40"
]
}
],
"lostAccounts": []
},
"appState": {
"menuOpen": false,
"currentView": {
"name": "import-menu"
},
"accountDetail": {
"subview": "transactions"
},
"transForward": true,
"isLoading": false,
"warning": null
},
"identities": {}
}

View File

@ -1,84 +1,42 @@
{
"metamask": {
"isInitialized": true,
"isUnlocked": true,
"isUnlocked": false,
"currentDomain": "example.com",
"rpcTarget": "https://rawtestrpc.metamask.io/",
"identities": {
"0x5f11b68b7d41633e74c6b18d8b8d147da52aedd6": {
"name": "Wallet 1",
"address": "0x5f11b68b7d41633e74c6b18d8b8d147da52aedd6",
"mayBeFauceting": false
},
"0x843963b837841dad3b0f5969ff271108776616df": {
"name": "Wallet 2",
"address": "0x843963b837841dad3b0f5969ff271108776616df",
"mayBeFauceting": false
},
"0x2cb215323857bec1c91e5db10fe87379a5cf129a": {
"name": "Wallet 3",
"address": "0x2cb215323857bec1c91e5db10fe87379a5cf129a",
"mayBeFauceting": false
},
"0xc5091450b7548b0dce3a76b8d325929c39e648d1": {
"name": "Wallet 4",
"address": "0xc5091450b7548b0dce3a76b8d325929c39e648d1",
"mayBeFauceting": false
}
},
"identities": {},
"unconfTxs": {},
"accounts": {
"0x5f11b68b7d41633e74c6b18d8b8d147da52aedd6": {
"balance": "0x0",
"nonce": "0x0",
"code": "0x",
"address": "0x5f11b68b7d41633e74c6b18d8b8d147da52aedd6"
},
"0x843963b837841dad3b0f5969ff271108776616df": {
"balance": "0x0",
"nonce": "0x0",
"code": "0x",
"address": "0x843963b837841dad3b0f5969ff271108776616df"
},
"0x2cb215323857bec1c91e5db10fe87379a5cf129a": {
"balance": "0x0",
"nonce": "0x0",
"code": "0x",
"address": "0x2cb215323857bec1c91e5db10fe87379a5cf129a"
},
"0xc5091450b7548b0dce3a76b8d325929c39e648d1": {
"balance": "0x0",
"nonce": "0x0",
"code": "0x",
"address": "0xc5091450b7548b0dce3a76b8d325929c39e648d1"
}
},
"currentFiat": "USD",
"conversionRate": 11.4379398,
"conversionDate": 1473358355,
"accounts": {},
"transactions": [],
"selectedAddress": "0x843963b837841dad3b0f5969ff271108776616df",
"network": "2",
"isConfirmed": true,
"selectedAddress": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
"network": "1473186153102",
"seedWords": null,
"unconfMsgs": {},
"messages": [],
"shapeShiftTxList": [],
"provider": {
"type": "testnet"
"type": "rpc",
"rpcTarget": "http://localhost:8545"
},
"selectedAccount": "0x843963b837841dad3b0f5969ff271108776616df"
"selectedAddress": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825"
},
"appState": {
"menuOpen": false,
"currentView": {
"name": "accountDetail"
"name": "accountDetail",
"detailView": null,
"context": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825"
},
"accountDetail": {
"subview": "transactions",
"accountExport": "none",
"privateKey": ""
"subview": "transactions"
},
"currentDomain": "testfaucet.metamask.io",
"transForward": false,
"currentDomain": "127.0.0.1:9966",
"transForward": true,
"isLoading": false,
"warning": null,
"scrollToBottom": false
"warning": null
},
"identities": {}
}
}

View File

@ -0,0 +1,90 @@
{
"metamask": {
"currentFiat": "USD",
"lostAccounts": [
"0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc",
"0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b"
],
"conversionRate": 11.06608791,
"conversionDate": 1470421024,
"isInitialized": true,
"isUnlocked": true,
"currentDomain": "example.com",
"rpcTarget": "https://rawtestrpc.metamask.io/",
"identities": {
"0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc": {
"name": "Wallet 1",
"address": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc",
"mayBeFauceting": false
},
"0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b": {
"name": "Wallet 2",
"address": "0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b",
"mayBeFauceting": false
},
"0xeb9e64b93097bc15f01f13eae97015c57ab64823": {
"name": "Wallet 3",
"address": "0xeb9e64b93097bc15f01f13eae97015c57ab64823",
"mayBeFauceting": false
},
"0x704107d04affddd9b66ab9de3dd7b095852e9b69": {
"name": "Wallet 4",
"address": "0x704107d04affddd9b66ab9de3dd7b095852e9b69",
"mayBeFauceting": false
}
},
"unconfTxs": {},
"accounts": {
"0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc": {
"code": "0x",
"balance": "0x100000000000",
"nonce": "0x0",
"address": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc"
},
"0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b": {
"code": "0x",
"nonce": "0x0",
"balance": "0x100000000000",
"address": "0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b"
},
"0xeb9e64b93097bc15f01f13eae97015c57ab64823": {
"code": "0x",
"nonce": "0x0",
"balance": "0x100000000000",
"address": "0xeb9e64b93097bc15f01f13eae97015c57ab64823"
},
"0x704107d04affddd9b66ab9de3dd7b095852e9b69": {
"code": "0x",
"balance": "0x0",
"nonce": "0x0",
"address": "0x704107d04affddd9b66ab9de3dd7b095852e9b69"
}
},
"transactions": [],
"selectedAddress": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc",
"network": "2",
"seedWords": null,
"unconfMsgs": {},
"messages": [],
"provider": {
"type": "testnet"
},
"selectedAddress": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc"
},
"appState": {
"menuOpen": false,
"currentView": {
"name": "accountDetail",
"detailView": null,
"context": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc"
},
"accountDetail": {
"subview": "transactions"
},
"currentDomain": "127.0.0.1:9966",
"transForward": true,
"isLoading": false,
"warning": null
},
"identities": {}
}

View File

@ -0,0 +1,65 @@
{
"metamask": {
"isInitialized": true,
"isUnlocked": true,
"rpcTarget": "https://rawtestrpc.metamask.io/",
"identities": {
"0xa6ef573d60594731178b7f85d80da13cc2af52dd": {
"address": "0xa6ef573d60594731178b7f85d80da13cc2af52dd",
"name": "Dan! 1"
},
"0xf9f52e84ad2c9122caa87478d27041ddaa215666": {
"address": "0xf9f52e84ad2c9122caa87478d27041ddaa215666",
"name": "Account 2"
}
},
"unconfTxs": {},
"currentFiat": "USD",
"conversionRate": 10.92067835,
"conversionDate": 1478282884,
"network": null,
"accounts": {
"0xa6ef573d60594731178b7f85d80da13cc2af52dd": {
"balance": "0x00",
"nonce": "0x100000",
"code": "0x",
"address": "0xa6ef573d60594731178b7f85d80da13cc2af52dd"
},
"0xf9f52e84ad2c9122caa87478d27041ddaa215666": {
"balance": "0x00",
"nonce": "0x100000",
"code": "0x",
"address": "0xf9f52e84ad2c9122caa87478d27041ddaa215666"
}
},
"transactions": [],
"provider": {
"type": "testnet"
},
"selectedAddress": "0xa6ef573d60594731178b7f85d80da13cc2af52dd",
"unconfMsgs": {},
"messages": [],
"selectedAddress": "0xa6ef573d60594731178b7f85d80da13cc2af52dd",
"shapeShiftTxList": [],
"keyringTypes": [
"Simple Key Pair",
"HD Key Tree"
]
},
"appState": {
"menuOpen": false,
"currentView": {
"name": "new-account"
},
"accountDetail": {
"subview": "transactions"
},
"transForward": true,
"isLoading": false,
"warning": null,
"forgottenPassword": null,
"detailView": {},
"scrollToBottom": false
},
"identities": {}
}

View File

@ -10,7 +10,6 @@
"transactions": [],
"network": "2",
"seedWords": null,
"isConfirmed": false,
"unconfMsgs": {},
"messages": [],
"provider": {
@ -32,4 +31,4 @@
"warning": null
},
"identities": {}
}
}

View File

@ -0,0 +1,61 @@
{
"metamask": {
"isInitialized": true,
"isUnlocked": true,
"identities": {
"0x24a1d059462456aa332d6da9117aa7f91a46f2ac": {
"address": "0x24a1d059462456aa332d6da9117aa7f91a46f2ac",
"name": "Account 1"
}
},
"unconfTxs": {},
"currentFiat": "USD",
"conversionRate": 8.3533002,
"conversionDate": 1481671082,
"noActiveNotices": false,
"lastUnreadNotice": {
"read": false,
"date": "Tue Dec 13 2016",
"title": "MultiVault Support",
"body": "# Multi\n# Line\n## Support\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi tincidunt dapibus justo a auctor. Sed luctus metus non mi laoreet, sit amet placerat nibh ultricies. Cras fringilla, urna sit amet sodales porttitor, lacus risus lacinia lorem, non euismod magna felis id ex. Nam iaculis, ante nec imperdiet suscipit, nisi quam fringilla nisl, sed fringilla turpis lectus et nibh. Pellentesque sed neque pretium nulla elementum lacinia eu eget felis. Nulla facilisi. Pellentesque id mi tempor, tempus sapien id, ultricies nibh. Integer faucibus elit non orci dapibus porttitor. Pellentesque rutrum hendrerit sapien ut lacinia. Nunc elementum eget arcu eu volutpat. Integer ullamcorper aliquam metus, eu malesuada tellus vestibulum a.\n",
"id": 0
},
"network": "3",
"accounts": {
"0x24a1d059462456aa332d6da9117aa7f91a46f2ac": {
"code": "0x",
"nonce": "0x0",
"balance": "0x0",
"address": "0x24a1d059462456aa332d6da9117aa7f91a46f2ac"
}
},
"transactions": [],
"provider": {
"type": "testnet"
},
"selectedAddress": "0x24a1d059462456aa332d6da9117aa7f91a46f2ac",
"seedWords": null,
"unconfMsgs": {},
"messages": [],
"shapeShiftTxList": [],
"keyringTypes": [
"Simple Key Pair",
"HD Key Tree"
]
},
"appState": {
"menuOpen": false,
"currentView": {
"name": "accountDetail",
"detailView": null,
"context": "0x24a1d059462456aa332d6da9117aa7f91a46f2ac"
},
"accountDetail": {
"subview": "transactions"
},
"transForward": true,
"isLoading": false,
"warning": null
},
"identities": {}
}

File diff suppressed because one or more lines are too long

View File

@ -2,7 +2,6 @@
"metamask": {
"isInitialized": true,
"isUnlocked": true,
"isEthConfirmed": true,
"currentDomain": "example.com",
"rpcTarget": "https://rawtestrpc.metamask.io/",
"identities": {
@ -355,7 +354,6 @@
"selectedAddress": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
"network": "1471904489432",
"seedWords": null,
"isConfirmed": true,
"unconfMsgs": {
"1472076978535283": {
"id": 1472076978535283,
@ -385,7 +383,7 @@
"type": "rpc",
"rpcTarget": "http://localhost:8545"
},
"selectedAccount": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825"
"selectedAddress": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825"
},
"appState": {
"menuOpen": false,
@ -403,4 +401,4 @@
"warning": null
},
"identities": {}
}
}

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
{"metamask":{"isInitialized":true,"isUnlocked":true,"currentDomain":"example.com","rpcTarget":"https://rawtestrpc.metamask.io/","identities":{"0xfdea65c8e26263f6d9a1b5de9555d2931a33b825":{"name":"Wallet 1","address":"0xfdea65c8e26263f6d9a1b5de9555d2931a33b825","mayBeFauceting":false},"0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb":{"name":"Wallet 2","address":"0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb","mayBeFauceting":false},"0x2f8d4a878cfa04a6e60d46362f5644deab66572d":{"name":"Wallet 3","address":"0x2f8d4a878cfa04a6e60d46362f5644deab66572d","mayBeFauceting":false}},"unconfTxs":{"1467868023090690":{"id":1467868023090690,"txParams":{"data":"0xa9059cbb0000000000000000000000008deb4d106090c3eb8f1950f727e87c4f884fb06f0000000000000000000000000000000000000000000000000000000000000064","from":"0xfdea65c8e26263f6d9a1b5de9555d2931a33b825","value":"0x16345785d8a0000","to":"0xbeb0ed3034c4155f3d16a64a5c5e7c8d4ea9e9c9","origin":"MetaMask","metamaskId":1467868023090690,"metamaskNetworkId":"2"},"time":1467868023090,"status":"unconfirmed","containsDelegateCall":false}},"accounts":{"0xfdea65c8e26263f6d9a1b5de9555d2931a33b825":{"code":"0x","balance":"0x38326dc32cf80800","nonce":"0x10000c","address":"0xfdea65c8e26263f6d9a1b5de9555d2931a33b825"},"0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb":{"code":"0x","balance":"0x15e578bd8e9c8000","nonce":"0x100000","address":"0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb"},"0x2f8d4a878cfa04a6e60d46362f5644deab66572d":{"code":"0x","nonce":"0x100000","balance":"0x2386f26fc10000","address":"0x2f8d4a878cfa04a6e60d46362f5644deab66572d"}},"transactions":[],"selectedAddress":"0xfdea65c8e26263f6d9a1b5de9555d2931a33b825","network":"2","seedWords":null,"isConfirmed":true,"unconfMsgs":{},"messages":[],"provider":{"type":"testnet"},"selectedAccount":"0xfdea65c8e26263f6d9a1b5de9555d2931a33b825"},"appState":{"menuOpen":false,"currentView":{"name":"confTx","context":0},"accountDetail":{"subview":"transactions"},"currentDomain":"extensions","transForward":true,"isLoading":false,"warning":null},"identities":{}}
{"metamask":{"isInitialized":true,"isUnlocked":true,"currentDomain":"example.com","rpcTarget":"https://rawtestrpc.metamask.io/","identities":{"0xfdea65c8e26263f6d9a1b5de9555d2931a33b825":{"name":"Wallet 1","address":"0xfdea65c8e26263f6d9a1b5de9555d2931a33b825","mayBeFauceting":false},"0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb":{"name":"Wallet 2","address":"0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb","mayBeFauceting":false},"0x2f8d4a878cfa04a6e60d46362f5644deab66572d":{"name":"Wallet 3","address":"0x2f8d4a878cfa04a6e60d46362f5644deab66572d","mayBeFauceting":false}},"unconfTxs":{"1467868023090690":{"id":1467868023090690,"txParams":{"data":"0xa9059cbb0000000000000000000000008deb4d106090c3eb8f1950f727e87c4f884fb06f0000000000000000000000000000000000000000000000000000000000000064","from":"0xfdea65c8e26263f6d9a1b5de9555d2931a33b825","value":"0x16345785d8a0000","to":"0xbeb0ed3034c4155f3d16a64a5c5e7c8d4ea9e9c9","origin":"MetaMask","metamaskId":1467868023090690,"metamaskNetworkId":"2"},"time":1467868023090,"status":"unconfirmed","containsDelegateCall":false}},"accounts":{"0xfdea65c8e26263f6d9a1b5de9555d2931a33b825":{"code":"0x","balance":"0x38326dc32cf80800","nonce":"0x10000c","address":"0xfdea65c8e26263f6d9a1b5de9555d2931a33b825"},"0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb":{"code":"0x","balance":"0x15e578bd8e9c8000","nonce":"0x100000","address":"0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb"},"0x2f8d4a878cfa04a6e60d46362f5644deab66572d":{"code":"0x","nonce":"0x100000","balance":"0x2386f26fc10000","address":"0x2f8d4a878cfa04a6e60d46362f5644deab66572d"}},"transactions":[],"selectedAddress":"0xfdea65c8e26263f6d9a1b5de9555d2931a33b825","network":"2","seedWords":null,"unconfMsgs":{},"messages":[],"provider":{"type":"testnet"},"selectedAddress":"0xfdea65c8e26263f6d9a1b5de9555d2931a33b825"},"appState":{"menuOpen":false,"currentView":{"name":"confTx","context":0},"accountDetail":{"subview":"transactions"},"currentDomain":"extensions","transForward":true,"isLoading":false,"warning":null},"identities":{}}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,84 @@
{
"metamask": {
"isInitialized": true,
"isUnlocked": true,
"rpcTarget": "https://rawtestrpc.metamask.io/",
"identities": {
"0xfdea65c8e26263f6d9a1b5de9555d2931a33b825": {
"name": "Account 1",
"address": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
"mayBeFauceting": false
},
"0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb": {
"name": "Account 2",
"address": "0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb",
"mayBeFauceting": false
}
},
"unconfTxs": {},
"currentFiat": "USD",
"conversionRate": 9.52855776,
"conversionDate": 1479756513,
"accounts": {
"0xfdea65c8e26263f6d9a1b5de9555d2931a33b825": {
"balance": "0x0",
"nonce": "0x0",
"code": "0x0",
"address": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825"
},
"0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb": {
"balance": "0x0",
"nonce": "0x0",
"code": "0x0",
"address": "0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb"
}
},
"transactions": [
{
"id": 5551995700357153,
"txParams": {
"from": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
"to": "0x48ff0cbac0acefedf152281ee80e9a0a01d5da63",
"data": "0x90b98a11000000000000000000000000c5b8dbac4c1d3f152cdeb400e2313f309c410acb000000000000000000000000000000000000000000000000000000000000000a",
"metamaskId": 5551995700357153,
"metamaskNetworkId": "1479490588308"
},
"time": 1479498745949,
"status": "confirmed",
"gasMultiplier": 1,
"metamaskNetworkId": "1479490588308",
"containsDelegateCall": true,
"estimatedGas": "0x24b33",
"hash": "0xad609a6931f54a575ad71222ffc27cd6746017106d5b89f4ad300b37b273f8ac"
}
],
"selectedAddress": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
"network": "1479753732793",
"isEthConfirmed": true,
"unconfMsgs": {},
"messages": [],
"shapeShiftTxList": [],
"gasMultiplier": false,
"provider": {
"type": "rpc",
"rpcTarget": "http://localhost:8545"
},
"selectedAddress": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825"
},
"appState": {
"menuOpen": false,
"currentView": {
"name": "accountDetail"
},
"accountDetail": {
"subview": "transactions",
"accountExport": "none",
"privateKey": ""
},
"transForward": false,
"isLoading": false,
"warning": null,
"scrollToBottom": false
},
"identities": {}
}

View File

@ -6,30 +6,32 @@
"rpcTarget": "https://rawtestrpc.metamask.io/",
"identities": {},
"unconfTxs": {},
"currentFiat": "USD",
"conversionRate": 0,
"conversionDate": "N/A",
"accounts": {},
"transactions": [],
"network": "2",
"seedWords": null,
"isConfirmed": false,
"isEthConfirmed": false,
"unconfMsgs": {},
"messages": [],
"shapeShiftTxList": [],
"provider": {
"type": "testnet"
}
},
"network": "2"
},
"appState": {
"menuOpen": false,
"currentView": {
"name": "EthStoreWarning"
"name": "restoreVault"
},
"accountDetail": {
"subview": "transactions"
},
"currentDomain": "127.0.0.1:9966",
"currentDomain": "extensions",
"transForward": true,
"isLoading": false,
"warning": null
},
"identities": {}
}
}

View File

@ -0,0 +1,74 @@
{
"metamask": {
"isInitialized": true,
"isUnlocked": true,
"currentDomain": "example.com",
"rpcTarget": "https://rawtestrpc.metamask.io/",
"identities": {
"0xfdea65c8e26263f6d9a1b5de9555d2931a33b825": {
"name": "Wallet 1",
"address": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
"mayBeFauceting": false
},
"0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb": {
"name": "Wallet 2",
"address": "0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb",
"mayBeFauceting": false
},
"0x2f8d4a878cfa04a6e60d46362f5644deab66572d": {
"name": "Wallet 3",
"address": "0x2f8d4a878cfa04a6e60d46362f5644deab66572d",
"mayBeFauceting": false
}
},
"unconfTxs": {},
"currentFiat": "USD",
"conversionRate": 11.21283484,
"conversionDate": 1472158984,
"accounts": {
"0xfdea65c8e26263f6d9a1b5de9555d2931a33b825": {
"code": "0x",
"balance": "0x34693f54a1e25900",
"nonce": "0x100013",
"address": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825"
},
"0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb": {
"code": "0x",
"nonce": "0x100000",
"balance": "0x18af912cee770000",
"address": "0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb"
},
"0x2f8d4a878cfa04a6e60d46362f5644deab66572d": {
"code": "0x",
"nonce": "0x100000",
"balance": "0x2386f26fc10000",
"address": "0x2f8d4a878cfa04a6e60d46362f5644deab66572d"
}
},
"transactions": [],
"network": "2",
"seedWords": null,
"unconfMsgs": {},
"messages": [],
"shapeShiftTxList": [],
"provider": {
"type": "testnet"
},
"selectedAddress": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825"
},
"appState": {
"menuOpen": false,
"currentView": {
"name": "sendTransaction"
},
"accountDetail": {
"subview": "transactions"
},
"currentDomain": "127.0.0.1:9966",
"transForward": true,
"isLoading": false,
"warning": null,
"detailView": {}
},
"identities": {}
}

View File

@ -0,0 +1,346 @@
{
"metamask": {
"isInitialized": true,
"isUnlocked": true,
"currentDomain": "example.com",
"rpcTarget": "https://rawtestrpc.metamask.io/",
"identities": {
"0xfdea65c8e26263f6d9a1b5de9555d2931a33b825": {
"name": "Wallet 1",
"address": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
"mayBeFauceting": false
},
"0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb": {
"name": "Wallet 2",
"address": "0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb",
"mayBeFauceting": false
},
"0x2f8d4a878cfa04a6e60d46362f5644deab66572d": {
"name": "Wallet 3",
"address": "0x2f8d4a878cfa04a6e60d46362f5644deab66572d",
"mayBeFauceting": false
}
},
"unconfTxs": {},
"currentFiat": "USD",
"conversionRate": 11.21274318,
"conversionDate": 1472159644,
"accounts": {
"0xfdea65c8e26263f6d9a1b5de9555d2931a33b825": {
"code": "0x",
"nonce": "0x13",
"balance": "0x461d4a64e937d3d1",
"address": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825"
},
"0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb": {
"code": "0x",
"nonce": "0x0",
"balance": "0x0",
"address": "0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb"
},
"0x2f8d4a878cfa04a6e60d46362f5644deab66572d": {
"code": "0x",
"balance": "0x0",
"nonce": "0x0",
"address": "0x2f8d4a878cfa04a6e60d46362f5644deab66572d"
}
},
"transactions": [],
"selectedAddress": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
"network": "1",
"seedWords": null,
"unconfMsgs": {},
"messages": [],
"shapeShiftTxList": [],
"provider": {
"type": "mainnet"
},
"selectedAddress": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825"
},
"appState": {
"menuOpen": false,
"currentView": {
"name": "buyEth",
"context": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825"
},
"accountDetail": {
"subview": "transactions"
},
"currentDomain": "127.0.0.1:9966",
"transForward": true,
"isLoading": false,
"detailView": {},
"buyView": {
"subview": "buyForm",
"formView": {
"coinbase": false,
"shapeshift": true,
"marketinfo": {
"pair": "btc_eth",
"rate": 51.14252949,
"minerFee": 0.01,
"limit": 2.60306578,
"minimum": 0.00038935,
"maxLimit": 8.67688592
},
"coinOptions": {
"BTC": {
"name": "Bitcoin",
"symbol": "BTC",
"image": "https://shapeshift.io/images/coins/bitcoin.png",
"status": "available"
},
"BCY": {
"name": "BitCrystals",
"symbol": "BCY",
"image": "https://shapeshift.io/images/coins/bitcrystals.png",
"status": "available"
},
"BLK": {
"name": "Blackcoin",
"symbol": "BLK",
"image": "https://shapeshift.io/images/coins/blackcoin.png",
"status": "available"
},
"BTS": {
"name": "Bitshares",
"symbol": "BTS",
"specialReturn": false,
"specialOutgoing": true,
"specialIncoming": true,
"fieldName": "destTag",
"fieldKey": "destTag",
"image": "https://shapeshift.io/images/coins/bitshares.png",
"status": "available"
},
"CLAM": {
"name": "Clams",
"symbol": "CLAM",
"image": "https://shapeshift.io/images/coins/clams.png",
"status": "available"
},
"DASH": {
"name": "Dash",
"symbol": "DASH",
"image": "https://shapeshift.io/images/coins/dash.png",
"status": "available"
},
"DGB": {
"name": "Digibyte",
"symbol": "DGB",
"image": "https://shapeshift.io/images/coins/digibyte.png",
"status": "available"
},
"DAO": {
"name": "TheDao",
"symbol": "DAO",
"image": "https://shapeshift.io/images/coins/thedao.png",
"status": "available"
},
"DGD": {
"name": "DigixDao",
"symbol": "DGD",
"image": "https://shapeshift.io/images/coins/digixdao.png",
"status": "available"
},
"DOGE": {
"name": "Dogecoin",
"symbol": "DOGE",
"image": "https://shapeshift.io/images/coins/dogecoin.png",
"status": "available"
},
"EMC": {
"name": "Emercoin",
"symbol": "EMC",
"image": "https://shapeshift.io/images/coins/emercoin.png",
"status": "available"
},
"ETH": {
"name": "Ether",
"symbol": "ETH",
"image": "https://shapeshift.io/images/coins/ether.png",
"status": "available"
},
"ETC": {
"name": "Ether Classic",
"symbol": "ETC",
"image": "https://shapeshift.io/images/coins/etherclassic.png",
"status": "available"
},
"FCT": {
"name": "Factoids",
"symbol": "FCT",
"image": "https://shapeshift.io/images/coins/factoids.png",
"status": "available"
},
"LBC": {
"name": "LBRY Credits",
"symbol": "LBC",
"image": "https://shapeshift.io/images/coins/lbry.png",
"status": "available"
},
"LSK": {
"name": "Lisk",
"symbol": "LSK",
"image": "https://shapeshift.io/images/coins/lisk.png",
"status": "available"
},
"LTC": {
"name": "Litecoin",
"symbol": "LTC",
"image": "https://shapeshift.io/images/coins/litecoin.png",
"status": "available"
},
"MAID": {
"name": "Maidsafe",
"symbol": "MAID",
"image": "https://shapeshift.io/images/coins/maidsafe.png",
"status": "available"
},
"MINT": {
"name": "Mintcoin",
"symbol": "MINT",
"image": "https://shapeshift.io/images/coins/mintcoin.png",
"status": "available"
},
"MONA": {
"name": "Monacoin",
"symbol": "MONA",
"image": "https://shapeshift.io/images/coins/monacoin.png",
"status": "available"
},
"MSC": {
"name": "Omni",
"symbol": "MSC",
"image": "https://shapeshift.io/images/coins/mastercoin.png",
"status": "available"
},
"NBT": {
"name": "Nubits",
"symbol": "NBT",
"image": "https://shapeshift.io/images/coins/nubits.png",
"status": "available"
},
"NMC": {
"name": "Namecoin",
"symbol": "NMC",
"image": "https://shapeshift.io/images/coins/namecoin.png",
"status": "available"
},
"NVC": {
"name": "Novacoin",
"symbol": "NVC",
"image": "https://shapeshift.io/images/coins/novacoin.png",
"status": "available"
},
"NXT": {
"name": "Nxt",
"symbol": "NXT",
"specialReturn": false,
"specialOutgoing": true,
"specialIncoming": true,
"specialIncomingStatus": false,
"fieldName": "Public Key (only for unfunded accounts!)",
"fieldKey": "rsAddress",
"image": "https://shapeshift.io/images/coins/nxt.png",
"status": "available"
},
"PPC": {
"name": "Peercoin",
"symbol": "PPC",
"image": "https://shapeshift.io/images/coins/peercoin.png",
"status": "available"
},
"RDD": {
"name": "Reddcoin",
"symbol": "RDD",
"image": "https://shapeshift.io/images/coins/reddcoin.png",
"status": "available"
},
"SDC": {
"name": "Shadowcash",
"symbol": "SDC",
"image": "https://shapeshift.io/images/coins/shadowcash.png",
"status": "available"
},
"SC": {
"name": "Siacoin",
"symbol": "SC",
"image": "https://shapeshift.io/images/coins/siacoin.png",
"status": "available"
},
"SJCX": {
"name": "StorjX",
"symbol": "SJCX",
"image": "https://shapeshift.io/images/coins/storjcoinx.png",
"status": "available"
},
"START": {
"name": "Startcoin",
"symbol": "START",
"image": "https://shapeshift.io/images/coins/startcoin.png",
"status": "available"
},
"STEEM": {
"name": "Steem",
"symbol": "STEEM",
"specialReturn": false,
"specialOutgoing": true,
"specialIncoming": true,
"fieldName": "destTag",
"fieldKey": "destTag",
"image": "https://shapeshift.io/images/coins/steem.png",
"status": "available"
},
"USDT": {
"name": "Tether",
"symbol": "USDT",
"image": "https://shapeshift.io/images/coins/tether.png",
"status": "available"
},
"VOX": {
"name": "Voxels",
"symbol": "VOX",
"image": "https://shapeshift.io/images/coins/voxels.png",
"status": "available"
},
"VRC": {
"name": "Vericoin",
"symbol": "VRC",
"image": "https://shapeshift.io/images/coins/vericoin.png",
"status": "available"
},
"VTC": {
"name": "Vertcoin",
"symbol": "VTC",
"image": "https://shapeshift.io/images/coins/vertcoin.png",
"status": "available"
},
"XCP": {
"name": "Counterparty",
"symbol": "XCP",
"image": "https://shapeshift.io/images/coins/counterparty.png",
"status": "available"
},
"XMR": {
"name": "Monero",
"symbol": "XMR",
"specialReturn": false,
"specialOutgoing": true,
"specialIncoming": true,
"fieldName": "Payment Id",
"qrName": "tx_payment_id",
"fieldKey": "paymentId",
"image": "https://shapeshift.io/images/coins/monero.png",
"status": "available"
}
}
},
"buyAddress": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
"amount": "5.00",
"warning": null
},
"isSubLoading": false
},
"identities": {}
}

View File

@ -45,7 +45,6 @@
"transactions": [],
"network": "2",
"seedWords": "debris dizzy just program just float decrease vacant alarm reduce speak stadium",
"isConfirmed": true,
"unconfMsgs": {},
"messages": [],
"provider": {

View File

@ -0,0 +1,39 @@
{
"metamask": {
"isInitialized": false,
"isUnlocked": false,
"rpcTarget": "https://rawtestrpc.metamask.io/",
"identities": {},
"unconfTxs": {},
"currentFiat": "USD",
"conversionRate": 8.18703468,
"conversionDate": 1481755832,
"network": "3",
"accounts": {},
"transactions": [],
"provider": {
"type": "testnet"
},
"unconfMsgs": {},
"messages": [],
"shapeShiftTxList": [],
"keyringTypes": [
"Simple Key Pair",
"HD Key Tree"
]
},
"appState": {
"menuOpen": false,
"currentView": {
"name": "accounts",
"detailView": null
},
"accountDetail": {
"subview": "transactions"
},
"transForward": true,
"isLoading": false,
"warning": null
},
"identities": {}
}

31
development/test.html Normal file
View File

@ -0,0 +1,31 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>MetaMask</title>
<script>
window.METAMASK_DEBUG = true
window.TEST_MODE = true
</script>
</head>
<body>
<!-- app content -->
<div id="app-content" style="height: 100%"></div>
<script src="./bundle.js" type="text/javascript" charset="utf-8"></script>
</body>
<style>
html, body, #app-content, .super-dev-container {
height: 100%;
width: 100%;
position: relative;
background: white;
}
.mock-app-root {
background: #F7F7F7;
}
</style>
</html>

View File

@ -0,0 +1,8 @@
MetaMask is an extension for accessing Ethereum enabled distributed applications, or "Dapps" in your normal browser!
The extension injects the Ethereum web3 API into every website's javascript context, so that dapps can read from the blockchain.
MetaMask also lets the user create and manage their own identities, so when a Dapp wants to perform a transaction and write to the blockchain, the user gets a secure interface to review the transaction, before approving or rejecting it.
Because it adds functionality to the normal browser context, MetaMask requires the permission to read and write to any webpage. You can always "view the source" of MetaMask the way you do any extension, or view the source code on Github:
https://github.com/MetaMask/metamask-plugin

View File

@ -0,0 +1,28 @@
# Form Persisting Architecture
Since:
- The popup is torn down completely on every click outside of it.
- We have forms with multiple fields (like passwords & seed phrases) that might encourage a user to leave our panel to refer to a password manager.
We cause user friction when we lose the contents of certain forms.
This calls for an architecture of a form component that can completely persist its values to LocalStorage on every relevant change, and restore those values on reopening.
To achieve this, we have defined a class, a subclass of `React.Component`, called `PersistentForm`, and it's stored at `ui/lib/persistent-form.js`.
To use this class, simply take your form component (the component that renders `input`, `select`, or `textarea` elements), and make it subclass from `PersistentForm` instead of `React.Component`.
You can see an example of this in use in `ui/app/first-time/restore-vault.js`.
Additionally, any field whose value should be persisted, should have a `persistentFormId` attribute, which needs to be assigned under a `dataset` key on the main `attributes` hash. For example:
```javascript
return h('textarea.twelve-word-phrase.letter-spacey', {
dataset: {
persistentFormId: 'wallet-seed',
},
})
```
That's it! This field should be persisted to `localStorage` on each `keyUp`, those values should be restored on view load, and the cached values should be cleared when navigating deliberately away from the form.

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