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:
commit
6d103dc1e7
@ -1,2 +1 @@
|
||||
app/scripts/lib/extension-instance.js
|
||||
ui/app/conversion-util.js
|
||||
|
13
.eslintrc
13
.eslintrc
@ -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
6
.gitignore
vendored
@ -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
|
||||
|
196
CHANGELOG.md
196
CHANGELOG.md
@ -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.
|
||||
|
73
README.md
73
README.md
@ -1,5 +1,12 @@
|
||||
# MetaMask Plugin [](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
|
||||
|
||||
[][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
|
||||
```
|
||||
|
@ -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 USER’S 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 MetaMask’s 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.
|
||||
|
||||
MetaMask’s 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 arbitrator’s 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 court’s 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
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
BIN
app/images/icon-64.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.5 KiB |
@ -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
16
app/notification.html
Normal 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>
|
45
app/scripts/account-import-strategies/index.js
Normal file
45
app/scripts/account-import-strategies/index.js
Normal 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
|
@ -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 () {}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
},
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
11
app/scripts/first-time-state.js
Normal file
11
app/scripts/first-time-state.js
Normal file
@ -0,0 +1,11 @@
|
||||
//
|
||||
// The default state of MetaMask
|
||||
//
|
||||
|
||||
module.exports = {
|
||||
config: {
|
||||
provider: {
|
||||
type: 'testnet',
|
||||
},
|
||||
},
|
||||
}
|
@ -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
|
||||
})
|
||||
|
554
app/scripts/keyring-controller.js
Normal file
554
app/scripts/keyring-controller.js
Normal 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
125
app/scripts/keyrings/hd.js
Normal 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
|
100
app/scripts/keyrings/simple.js
Normal file
100
app/scripts/keyrings/simple.js
Normal 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
|
@ -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)
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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 || []
|
||||
}
|
||||
|
70
app/scripts/lib/controllers/currency.js
Normal file
70
app/scripts/lib/controllers/currency.js
Normal 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
|
33
app/scripts/lib/controllers/preferences.js
Normal file
33
app/scripts/lib/controllers/preferences.js
Normal 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
|
104
app/scripts/lib/controllers/shapeshift.js
Normal file
104
app/scripts/lib/controllers/shapeshift.js
Normal 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
|
132
app/scripts/lib/eth-store.js
Normal file
132
app/scripts/lib/eth-store.js
Normal 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
|
@ -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
|
||||
|
@ -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))
|
||||
|
80
app/scripts/lib/idStore-migrator.js
Normal file
80
app/scripts/lib/idStore-migrator.js
Normal 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
|
||||
}
|
||||
}
|
@ -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 () {}
|
||||
|
@ -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 () {}
|
||||
|
8
app/scripts/lib/is-popup-or-notification.js
Normal file
8
app/scripts/lib/is-popup-or-notification.js
Normal file
@ -0,0 +1,8 @@
|
||||
module.exports = function isPopupOrNotification () {
|
||||
const url = window.location.href
|
||||
if (url.match(/popup.html$/)) {
|
||||
return 'popup'
|
||||
} else {
|
||||
return 'notification'
|
||||
}
|
||||
}
|
@ -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'))
|
||||
}
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
module.exports = [
|
||||
require('../migrations/002'),
|
||||
require('../migrations/003'),
|
||||
require('../migrations/004'),
|
||||
]
|
51
app/scripts/lib/migrator/index.js
Normal file
51
app/scripts/lib/migrator/index.js
Normal 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
|
24
app/scripts/lib/nodeify.js
Normal file
24
app/scripts/lib/nodeify.js
Normal 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
|
||||
}
|
||||
}
|
@ -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)
|
||||
})
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
9
app/scripts/lib/random-id.js
Normal file
9
app/scripts/lib/random-id.js
Normal 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
|
@ -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)
|
||||
}
|
28
app/scripts/lib/sig-util.js
Normal file
28
app/scripts/lib/sig-util.js
Normal 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
|
||||
}
|
@ -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
132
app/scripts/lib/tx-utils.js
Normal 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
|
||||
}
|
@ -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 () {}
|
||||
|
@ -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)
|
||||
},
|
||||
}
|
||||
|
@ -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)
|
||||
},
|
||||
}
|
||||
|
@ -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)
|
||||
},
|
||||
}
|
||||
|
44
app/scripts/migrations/005.js
Normal file
44
app/scripts/migrations/005.js
Normal 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
|
||||
}
|
43
app/scripts/migrations/006.js
Normal file
43
app/scripts/migrations/006.js
Normal 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
|
||||
}
|
40
app/scripts/migrations/007.js
Normal file
40
app/scripts/migrations/007.js
Normal 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
|
||||
}
|
38
app/scripts/migrations/008.js
Normal file
38
app/scripts/migrations/008.js
Normal 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
|
||||
}
|
43
app/scripts/migrations/009.js
Normal file
43
app/scripts/migrations/009.js
Normal 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
|
||||
}
|
38
app/scripts/migrations/010.js
Normal file
38
app/scripts/migrations/010.js
Normal 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
|
||||
}
|
33
app/scripts/migrations/011.js
Normal file
33
app/scripts/migrations/011.js
Normal 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
|
||||
}
|
51
app/scripts/migrations/_multi-keyring.js
Normal file
51
app/scripts/migrations/_multi-keyring.js
Normal 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)
|
||||
})
|
||||
})
|
||||
|
||||
},
|
||||
}
|
25
app/scripts/migrations/index.js
Normal file
25
app/scripts/migrations/index.js
Normal 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'),
|
||||
]
|
94
app/scripts/notice-controller.js
Normal file
94
app/scripts/notice-controller.js
Normal 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
63
app/scripts/popup-core.js
Normal 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,
|
||||
})
|
||||
}
|
@ -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,
|
||||
})
|
||||
}
|
||||
|
390
app/scripts/transaction-manager.js
Normal file
390
app/scripts/transaction-manager.js
Normal 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')
|
@ -4,3 +4,4 @@ machine:
|
||||
dependencies:
|
||||
pre:
|
||||
- "npm i -g testem"
|
||||
- "npm i -g mocha"
|
||||
|
12
development/announcer.js
Normal file
12
development/announcer.js
Normal 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
@ -136,13 +136,12 @@
|
||||
"selectedAddress": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc",
|
||||
"network": "1",
|
||||
"seedWords": null,
|
||||
"isConfirmed": true,
|
||||
"unconfMsgs": {},
|
||||
"messages": [],
|
||||
"provider": {
|
||||
"type": "mainnet"
|
||||
},
|
||||
"selectedAccount": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc"
|
||||
"selectedAddress": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc"
|
||||
},
|
||||
"appState": {
|
||||
"menuOpen": false,
|
||||
|
@ -102,13 +102,12 @@
|
||||
"selectedAddress": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc",
|
||||
"network": "2",
|
||||
"seedWords": null,
|
||||
"isConfirmed": true,
|
||||
"unconfMsgs": {},
|
||||
"messages": [],
|
||||
"provider": {
|
||||
"type": "testnet"
|
||||
},
|
||||
"selectedAccount": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc"
|
||||
"selectedAddress": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc"
|
||||
},
|
||||
"appState": {
|
||||
"menuOpen": false,
|
||||
|
@ -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,
|
||||
|
83
development/states/account-list-with-imported.json
Normal file
83
development/states/account-list-with-imported.json
Normal 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": {}
|
||||
}
|
125
development/states/accounts-loose.json
Normal file
125
development/states/accounts-loose.json
Normal 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": {}
|
||||
}
|
@ -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": {}
|
||||
}
|
||||
}
|
||||
|
123
development/states/compilation-bug.json
Normal file
123
development/states/compilation-bug.json
Normal 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": {}
|
||||
}
|
@ -57,15 +57,13 @@
|
||||
}
|
||||
},
|
||||
"transactions": [],
|
||||
"selectedAddress": "0x843963b837841dad3b0f5969ff271108776616df",
|
||||
"network": "2",
|
||||
"isConfirmed": true,
|
||||
"unconfMsgs": {},
|
||||
"messages": [],
|
||||
"provider": {
|
||||
"type": "testnet"
|
||||
},
|
||||
"selectedAccount": "0x843963b837841dad3b0f5969ff271108776616df",
|
||||
"selectedAddress": "0x843963b837841dad3b0f5969ff271108776616df",
|
||||
"seedWords": null
|
||||
},
|
||||
"appState": {
|
||||
|
@ -10,7 +10,6 @@
|
||||
"transactions": [],
|
||||
"network": "2",
|
||||
"seedWords": null,
|
||||
"isConfirmed": true,
|
||||
"unconfMsgs": {},
|
||||
"messages": [],
|
||||
"provider": {
|
||||
@ -32,4 +31,4 @@
|
||||
"warning": null
|
||||
},
|
||||
"identities": {}
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -57,13 +57,12 @@
|
||||
"selectedAddress": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc",
|
||||
"network": "2",
|
||||
"seedWords": null,
|
||||
"isConfirmed": true,
|
||||
"unconfMsgs": {},
|
||||
"messages": [],
|
||||
"provider": {
|
||||
"type": "testnet"
|
||||
},
|
||||
"selectedAccount": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc"
|
||||
"selectedAddress": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc"
|
||||
},
|
||||
"appState": {
|
||||
"menuOpen": false,
|
||||
|
39
development/states/first-time.json
Normal file
39
development/states/first-time.json
Normal 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": {}
|
||||
}
|
@ -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": {}
|
||||
}
|
||||
}
|
||||
|
91
development/states/import-private-key-warning.json
Normal file
91
development/states/import-private-key-warning.json
Normal 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": {}
|
||||
}
|
63
development/states/import-private-key.json
Normal file
63
development/states/import-private-key.json
Normal 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": {}
|
||||
}
|
@ -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": {}
|
||||
}
|
||||
}
|
||||
|
90
development/states/lost-accounts.json
Normal file
90
development/states/lost-accounts.json
Normal 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": {}
|
||||
}
|
65
development/states/new-account.json
Normal file
65
development/states/new-account.json
Normal 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": {}
|
||||
}
|
@ -10,7 +10,6 @@
|
||||
"transactions": [],
|
||||
"network": "2",
|
||||
"seedWords": null,
|
||||
"isConfirmed": false,
|
||||
"unconfMsgs": {},
|
||||
"messages": [],
|
||||
"provider": {
|
||||
@ -32,4 +31,4 @@
|
||||
"warning": null
|
||||
},
|
||||
"identities": {}
|
||||
}
|
||||
}
|
||||
|
61
development/states/notice.json
Normal file
61
development/states/notice.json
Normal 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
@ -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
@ -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
84
development/states/private-network.json
Normal file
84
development/states/private-network.json
Normal 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": {}
|
||||
}
|
@ -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": {}
|
||||
}
|
||||
}
|
74
development/states/send.json
Normal file
74
development/states/send.json
Normal 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": {}
|
||||
}
|
346
development/states/shapeshift.json
Normal file
346
development/states/shapeshift.json
Normal 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": {}
|
||||
}
|
@ -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": {
|
||||
|
39
development/states/terms-and-conditions.json
Normal file
39
development/states/terms-and-conditions.json
Normal 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
31
development/test.html
Normal 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>
|
8
docs/extension_description/en.txt
Normal file
8
docs/extension_description/en.txt
Normal 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
|
28
docs/form_persisting_architecture.md
Normal file
28
docs/form_persisting_architecture.md
Normal 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
Loading…
x
Reference in New Issue
Block a user