mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-11-22 18:00:18 +01:00
Merge branch 'master' into i#1203MainNetSwitch
This commit is contained in:
commit
27220b7bcd
5
.babelrc
5
.babelrc
@ -1 +1,4 @@
|
|||||||
{ "presets": ["es2015"] }
|
{
|
||||||
|
"presets": ["es2015"],
|
||||||
|
"plugins": ["transform-runtime"]
|
||||||
|
}
|
||||||
|
3
.dockerignore
Normal file
3
.dockerignore
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
node_modules
|
||||||
|
builds
|
||||||
|
development
|
@ -1 +1,5 @@
|
|||||||
app/scripts/lib/extension-instance.js
|
app/scripts/lib/extension-instance.js
|
||||||
|
test/integration/bundle.js
|
||||||
|
test/integration/jquery-3.1.0.min.js
|
||||||
|
test/integration/helpers.js
|
||||||
|
test/integration/lib/first-time.js
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"parserOptions": {
|
"parserOptions": {
|
||||||
"sourceType": "module",
|
"sourceType": "module",
|
||||||
"ecmaVersion": 6,
|
"ecmaVersion": 2017,
|
||||||
"ecmaFeatures": {
|
"ecmaFeatures": {
|
||||||
"experimentalObjectRestSpread": true,
|
"experimentalObjectRestSpread": true,
|
||||||
"impliedStrict": true,
|
"impliedStrict": true,
|
||||||
@ -17,10 +17,13 @@
|
|||||||
"env": {
|
"env": {
|
||||||
"es6": true,
|
"es6": true,
|
||||||
"node": true,
|
"node": true,
|
||||||
"browser": true
|
"browser": true,
|
||||||
|
"mocha" : true
|
||||||
},
|
},
|
||||||
|
|
||||||
"plugins": [
|
"plugins": [
|
||||||
|
"mocha",
|
||||||
|
"chai"
|
||||||
],
|
],
|
||||||
|
|
||||||
"globals": {
|
"globals": {
|
||||||
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -16,3 +16,6 @@ development/bundle.js
|
|||||||
builds.zip
|
builds.zip
|
||||||
test/integration/bundle.js
|
test/integration/bundle.js
|
||||||
development/states.js
|
development/states.js
|
||||||
|
test/background.js
|
||||||
|
test/bundle.js
|
||||||
|
test/test-bundle.js
|
||||||
|
115
CHANGELOG.md
115
CHANGELOG.md
@ -3,9 +3,124 @@
|
|||||||
## Current Master
|
## Current Master
|
||||||
|
|
||||||
- The default network on installation is now MainNet
|
- The default network on installation is now MainNet
|
||||||
|
- Fix currency API URL from cryptonator.
|
||||||
|
- Update gasLimit params with every new block seen.
|
||||||
|
|
||||||
|
## 3.7.7 2017-6-8
|
||||||
|
|
||||||
|
- Fix bug where metamask would show old data after computer being asleep or disconnected from the internet.
|
||||||
|
|
||||||
|
## 3.7.6 2017-6-5
|
||||||
|
|
||||||
|
- Fix bug that prevented publishing contracts.
|
||||||
|
|
||||||
|
## 3.7.5 2017-6-5
|
||||||
|
|
||||||
|
- Prevent users from sending to the `0x0` address.
|
||||||
|
- Provide useful errors when entering bad characters in ENS name.
|
||||||
|
- Add ability to copy addresses from transaction confirmation view.
|
||||||
|
|
||||||
|
## 3.7.4 2017-6-2
|
||||||
|
|
||||||
|
- Fix bug with inflight cache that caused some block lookups to return bad values (affected OasisDex).
|
||||||
|
- Fixed bug with gas limit calculation that would sometimes create unsubmittable gas limits.
|
||||||
|
|
||||||
|
## 3.7.3 2017-6-1
|
||||||
|
|
||||||
|
- Rebuilt to fix cache clearing bug.
|
||||||
|
|
||||||
|
## 3.7.2 2017-5-31
|
||||||
|
|
||||||
|
- Now when switching networks sites that use web3 will reload
|
||||||
|
- Now when switching networks the extension does not restart
|
||||||
|
- Cleanup decimal bugs in our gas inputs.
|
||||||
|
- Fix bug where submit button was enabled for invalid gas inputs.
|
||||||
|
- Now enforce 95% of block's gasLimit to protect users.
|
||||||
|
- Removing provider-engine from the inpage provider. This fixes some error handling inconsistencies introduced in 3.7.0.
|
||||||
|
- Added "inflight cache", which prevents identical requests from clogging up the network, dramatically improving ENS performance.
|
||||||
|
- Fixed bug where filter subscriptions would sometimes fail to unsubscribe.
|
||||||
|
- Some contracts will now display logos instead of jazzicons.
|
||||||
|
- Some contracts will now have names displayed in the confirmation view.
|
||||||
|
|
||||||
|
## 3.7.0 2017-5-23
|
||||||
|
|
||||||
|
- Add Transaction Number (nonce) to transaction list.
|
||||||
|
- Label the pending tx icon with a tooltip.
|
||||||
|
- Fix bug where website filters would pile up and not deallocate when leaving a site.
|
||||||
|
- Continually resubmit pending txs for a period of time to ensure successful broadcast.
|
||||||
|
- ENS names will no longer resolve to their owner if no resolver is set. Resolvers must be explicitly set and configured.
|
||||||
|
|
||||||
|
## 3.6.5 2017-5-17
|
||||||
|
|
||||||
|
- Fix bug where edited gas parameters would not take effect.
|
||||||
|
- Trim currency list.
|
||||||
|
- Enable decimals in our gas prices.
|
||||||
|
- Fix reset button.
|
||||||
|
- Fix event filter bug introduced by newer versions of Geth.
|
||||||
|
- Fix bug where decimals in gas inputs could result in strange values.
|
||||||
|
|
||||||
|
## 3.6.4 2017-5-8
|
||||||
|
|
||||||
|
- Fix main-net ENS resolution.
|
||||||
|
|
||||||
|
## 3.6.3 2017-5-8
|
||||||
|
|
||||||
|
- Fix bug that could stop newer versions of Geth from working with MetaMask.
|
||||||
|
|
||||||
|
## 3.6.2 2017-5-8
|
||||||
|
|
||||||
|
- Input gas price in Gwei.
|
||||||
|
- Enforce Safe Gas Minimum recommended by EthGasStation.
|
||||||
|
- Fix bug where block-tracker could stop polling for new blocks.
|
||||||
|
- Reduce UI size by removing internal web3.
|
||||||
|
- Fix bug where gas parameters would not properly update on adjustment.
|
||||||
|
|
||||||
|
## 3.6.1 2017-4-30
|
||||||
|
|
||||||
|
- Made fox less nosy.
|
||||||
|
- Fix bug where error was reported in debugger console when Chrome opened a new window.
|
||||||
|
|
||||||
|
## 3.6.0 2017-4-26
|
||||||
|
|
||||||
|
- Add Rinkeby Test Network to our network list.
|
||||||
|
|
||||||
|
## 3.5.4 2017-4-25
|
||||||
|
|
||||||
|
- Fix occasional nonce tracking issue.
|
||||||
|
- Fix bug where some events would not be emitted by web3.
|
||||||
|
- Fix bug where an error would be thrown when composing signatures for networks with large ID values.
|
||||||
|
|
||||||
|
## 3.5.3 2017-4-24
|
||||||
|
|
||||||
|
- Popup new transactions in Firefox.
|
||||||
|
- Fix transition issue from account detail screen.
|
||||||
|
- Revise buy screen for more modularity.
|
||||||
|
- Fixed some other small bugs.
|
||||||
|
|
||||||
|
## 3.5.2 2017-3-28
|
||||||
|
|
||||||
|
- Fix bug where gas estimate totals were sometimes wrong.
|
||||||
|
- Add link to Kovan Test Faucet instructions on buy view.
|
||||||
|
- Inject web3 into loaded iFrames.
|
||||||
|
|
||||||
|
## 3.5.1 2017-3-27
|
||||||
|
|
||||||
|
- Fix edge case where users were unable to enable the notice button if notices were short enough to not require a scrollbar.
|
||||||
|
|
||||||
|
## 3.5.0 2017-3-27
|
||||||
|
|
||||||
|
- Add better error messages for when a transaction fails on approval
|
||||||
- Allow sending to ENS names in send form on Ropsten.
|
- Allow sending to ENS names in send form on Ropsten.
|
||||||
- Added an address book functionality that remembers the last 15 unique addresses sent to.
|
- Added an address book functionality that remembers the last 15 unique addresses sent to.
|
||||||
- Can now change network to custom RPC URL from lock screen.
|
- Can now change network to custom RPC URL from lock screen.
|
||||||
|
- Removed support for old, lightwallet based vault. Users who have not opened app in over a month will need to recover with their seed phrase. This will allow Firefox support sooner.
|
||||||
|
- Fixed bug where spinner wouldn't disappear on incorrect password submission on seed word reveal.
|
||||||
|
- Polish the private key UI.
|
||||||
|
- Enforce minimum values for gas price and gas limit.
|
||||||
|
- Fix bug where total gas was sometimes not live-updated.
|
||||||
|
- Fix bug where editing gas value could have some abrupt behaviors (#1233)
|
||||||
|
- Add Kovan as an option on our network list.
|
||||||
|
- Fixed bug where transactions on other networks would disappear when submitting a transaction on another network.
|
||||||
|
|
||||||
## 3.4.0 2017-3-8
|
## 3.4.0 2017-3-8
|
||||||
|
|
||||||
|
22
Dockerfile
Normal file
22
Dockerfile
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
FROM node:7
|
||||||
|
MAINTAINER kumavis
|
||||||
|
|
||||||
|
# setup app dir
|
||||||
|
RUN mkdir -p /www/
|
||||||
|
WORKDIR /www/
|
||||||
|
|
||||||
|
# install dependencies
|
||||||
|
COPY ./package.json /www/package.json
|
||||||
|
RUN npm install
|
||||||
|
|
||||||
|
# copy over app dir
|
||||||
|
COPY ./ /www/
|
||||||
|
|
||||||
|
# run tests
|
||||||
|
# RUN npm test
|
||||||
|
|
||||||
|
# build app
|
||||||
|
RUN npm run dist
|
||||||
|
|
||||||
|
# start server
|
||||||
|
CMD node mascara/example/server.js
|
134
README.md
134
README.md
@ -18,11 +18,15 @@ If you're a web dapp developer, we've got two types of guides for you:
|
|||||||
|
|
||||||
Uncompressed builds can be found in `/dist`, compressed builds can be found in `/builds` once they're built.
|
Uncompressed builds can be found in `/dist`, compressed builds can be found in `/builds` once they're built.
|
||||||
|
|
||||||
## Installing Local Builds on Chrome
|
### Running Tests
|
||||||
|
|
||||||
To install your locally built extension on Chrome, [follow this guide](http://stackoverflow.com/a/24577660/272576).
|
Requires `mocha` installed. Run `npm install -g mocha`.
|
||||||
|
|
||||||
The built extension is stored in `./dist/chrome/`.
|
Then just run `npm test`.
|
||||||
|
|
||||||
|
You can also test with a continuously watching process, via `npm run watch`.
|
||||||
|
|
||||||
|
You can run the linter by itself with `gulp lint`.
|
||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
@ -41,126 +45,22 @@ npm start
|
|||||||
npm run dist
|
npm run dist
|
||||||
```
|
```
|
||||||
|
|
||||||
#### In Chrome
|
|
||||||
|
|
||||||
Open `Settings` > `Extensions`.
|
|
||||||
|
|
||||||
Check "Developer mode".
|
|
||||||
|
|
||||||
At the top, click `Load Unpacked Extension`.
|
|
||||||
|
|
||||||
Navigate to your `metamask-plugin/dist/chrome` folder.
|
|
||||||
|
|
||||||
Click `Select`.
|
|
||||||
|
|
||||||
You now have the plugin, and can click 'inspect views: background plugin' to view its dev console.
|
|
||||||
|
|
||||||
#### In Firefox
|
|
||||||
|
|
||||||
Go to the url `about:debugging`.
|
|
||||||
|
|
||||||
Click the button `Load Temporary Add-On`.
|
|
||||||
|
|
||||||
Select the file `dist/firefox/manifest.json`.
|
|
||||||
|
|
||||||
You can optionally enable debugging, and click `Debug`, for a console window that logs all of Metamask's processes to a single console.
|
|
||||||
|
|
||||||
If you have problems debugging, try connecting to the IRC channel `#webextensions` on `irc.mozilla.org`.
|
|
||||||
|
|
||||||
For longer questions, use the StackOverfow tag `firefox-addons`.
|
|
||||||
|
|
||||||
### Developing on UI Only
|
|
||||||
|
|
||||||
You can run `npm run ui`, and your browser should open a live-reloading demo version of the plugin UI.
|
|
||||||
|
|
||||||
Some actions will crash the app, so this is only for tuning aesthetics, but it allows live-reloading styles, which is a much faster feedback loop than reloading the full extension.
|
|
||||||
|
|
||||||
### Developing on UI with Mocked Background Process
|
|
||||||
|
|
||||||
You can run `npm run mock` and your browser should open a live-reloading demo version of the plugin UI, just like the `npm run ui`, except that it tries to actually perform all normal operations.
|
|
||||||
|
|
||||||
It does not yet connect to a real blockchain (this could be a good test feature later, connecting to a test blockchain), so only local operations work.
|
|
||||||
|
|
||||||
You can reset the mock ui at any time with the `Reset` button at the top of the screen.
|
|
||||||
|
|
||||||
### Developing on Dependencies
|
|
||||||
|
|
||||||
To enjoy the live-reloading that `gulp dev` offers while working on the `web3-provider-engine` or other dependencies:
|
|
||||||
|
|
||||||
1. Clone the dependency locally.
|
|
||||||
2. `npm install` in its folder.
|
|
||||||
3. Run `npm link` in its folder.
|
|
||||||
4. Run `npm link $DEP_NAME` in this project folder.
|
|
||||||
5. Next time you `npm start` it will watch the dependency for changes as well!
|
|
||||||
|
|
||||||
### Running Tests
|
|
||||||
|
|
||||||
Requires `mocha` installed. Run `npm install -g mocha`.
|
|
||||||
|
|
||||||
Then just run `npm test`.
|
|
||||||
|
|
||||||
You can also test with a continuously watching process, via `npm run watch`.
|
|
||||||
|
|
||||||
You can run the linter by itself with `gulp lint`.
|
|
||||||
|
|
||||||
#### Writing Browser Tests
|
#### Writing Browser Tests
|
||||||
|
|
||||||
To write tests that will be run in the browser using QUnit, add your test files to `test/integration/lib`.
|
To write tests that will be run in the browser using QUnit, add your test files to `test/integration/lib`.
|
||||||
|
|
||||||
### Deploying the UI
|
## Other Docs
|
||||||
|
|
||||||
You must be authorized already on the MetaMask plugin.
|
- [How to add custom build to Chrome](./docs/add-to-chrome.md)
|
||||||
|
- [How to add custom build to Firefox](./docs/add-to-firefox.md)
|
||||||
0. Update the version in `app/manifest.json` and the Changelog in `CHANGELOG.md`.
|
- [How to develop a live-reloading UI](./docs/ui-dev-mode.md)
|
||||||
1. Visit [the chrome developer dashboard](https://chrome.google.com/webstore/developer/dashboard?authuser=2).
|
- [Publishing Guide](./docs/publishing.md)
|
||||||
2. Run `gulp dist` (or `gulp zip` if you've already built)
|
- [How to develop an in-browser mocked UI](./docs/ui-mock-mode.md)
|
||||||
3. Upload the latest zip file from `builds/metamask-$PLATFORM-$VERSION.zip` as the updated package.
|
- [How to live reload on local dependency changes](./docs/developing-on-deps.md)
|
||||||
|
- [How to add new networks to the Provider Menu](./docs/adding-new-networks.md)
|
||||||
|
- [How to manage notices that appear when the app starts up](./docs/notices.md)
|
||||||
|
- [How to generate a visualization of this repository's development](./docs/development-visualization.md)
|
||||||
|
|
||||||
[1]: http://www.nomnoml.com/#view/%5B%3Cactor%3Euser%5D%0A%0A%5Bmetamask-ui%7C%0A%20%20%20%5Btools%7C%0A%20%20%20%20%20react%0A%20%20%20%20%20redux%0A%20%20%20%20%20thunk%0A%20%20%20%20%20ethUtils%0A%20%20%20%20%20jazzicon%0A%20%20%20%5D%0A%20%20%20%5Bcomponents%7C%0A%20%20%20%20%20app%0A%20%20%20%20%20account-detail%0A%20%20%20%20%20accounts%0A%20%20%20%20%20locked-screen%0A%20%20%20%20%20restore-vault%0A%20%20%20%20%20identicon%0A%20%20%20%20%20config%0A%20%20%20%20%20info%0A%20%20%20%5D%0A%20%20%20%5Breducers%7C%0A%20%20%20%20%20app%0A%20%20%20%20%20metamask%0A%20%20%20%20%20identities%0A%20%20%20%5D%0A%20%20%20%5Bactions%7C%0A%20%20%20%20%20%5BaccountManager%5D%0A%20%20%20%5D%0A%20%20%20%5Bcomponents%5D%3A-%3E%5Bactions%5D%0A%20%20%20%5Bactions%5D%3A-%3E%5Breducers%5D%0A%20%20%20%5Breducers%5D%3A-%3E%5Bcomponents%5D%0A%5D%0A%0A%5Bweb%20dapp%7C%0A%20%20%5Bui%20code%5D%0A%20%20%5Bweb3%5D%0A%20%20%5Bmetamask-inpage%5D%0A%20%20%0A%20%20%5B%3Cactor%3Eui%20developer%5D%0A%20%20%5Bui%20developer%5D-%3E%5Bui%20code%5D%0A%20%20%5Bui%20code%5D%3C-%3E%5Bweb3%5D%0A%20%20%5Bweb3%5D%3C-%3E%5Bmetamask-inpage%5D%0A%5D%0A%0A%5Bmetamask-background%7C%0A%20%20%5Bprovider-engine%5D%0A%20%20%5Bhooked%20wallet%20subprovider%5D%0A%20%20%5Bid%20store%5D%0A%20%20%0A%20%20%5Bprovider-engine%5D%3C-%3E%5Bhooked%20wallet%20subprovider%5D%0A%20%20%5Bhooked%20wallet%20subprovider%5D%3C-%3E%5Bid%20store%5D%0A%20%20%5Bconfig%20manager%7C%0A%20%20%20%20%5Brpc%20configuration%5D%0A%20%20%20%20%5Bencrypted%20keys%5D%0A%20%20%20%20%5Bwallet%20nicknames%5D%0A%20%20%5D%0A%20%20%0A%20%20%5Bprovider-engine%5D%3C-%5Bconfig%20manager%5D%0A%20%20%5Bid%20store%5D%3C-%3E%5Bconfig%20manager%5D%0A%5D%0A%0A%5Buser%5D%3C-%3E%5Bmetamask-ui%5D%0A%0A%5Buser%5D%3C%3A--%3A%3E%5Bweb%20dapp%5D%0A%0A%5Bmetamask-contentscript%7C%0A%20%20%5Bplugin%20restart%20detector%5D%0A%20%20%5Brpc%20passthrough%5D%0A%5D%0A%0A%5Brpc%20%7C%0A%20%20%5Bethereum%20blockchain%20%7C%0A%20%20%20%20%5Bcontracts%5D%0A%20%20%20%20%5Baccounts%5D%0A%20%20%5D%0A%5D%0A%0A%5Bweb%20dapp%5D%3C%3A--%3A%3E%5Bmetamask-contentscript%5D%0A%5Bmetamask-contentscript%5D%3C-%3E%5Bmetamask-background%5D%0A%5Bmetamask-background%5D%3C-%3E%5Bmetamask-ui%5D%0A%5Bmetamask-background%5D%3C-%3E%5Brpc%5D%0A
|
[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
|
|
||||||
```
|
|
||||||
|
File diff suppressed because one or more lines are too long
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "MetaMask",
|
"name": "MetaMask",
|
||||||
"short_name": "Metamask",
|
"short_name": "Metamask",
|
||||||
"version": "3.4.0",
|
"version": "3.7.7",
|
||||||
"manifest_version": 2,
|
"manifest_version": 2,
|
||||||
"author": "https://metamask.io",
|
"author": "https://metamask.io",
|
||||||
"description": "Ethereum Browser Extension",
|
"description": "Ethereum Browser Extension",
|
||||||
@ -51,13 +51,14 @@
|
|||||||
"scripts/contentscript.js"
|
"scripts/contentscript.js"
|
||||||
],
|
],
|
||||||
"run_at": "document_start",
|
"run_at": "document_start",
|
||||||
"all_frames": false
|
"all_frames": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"permissions": [
|
"permissions": [
|
||||||
"storage",
|
"storage",
|
||||||
"clipboardWrite",
|
"clipboardWrite",
|
||||||
"http://localhost:8545/"
|
"http://localhost:8545/",
|
||||||
|
"https://api.cryptonator.com/"
|
||||||
],
|
],
|
||||||
"web_accessible_resources": [
|
"web_accessible_resources": [
|
||||||
"scripts/inpage.js"
|
"scripts/inpage.js"
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
const urlUtil = require('url')
|
const urlUtil = require('url')
|
||||||
const endOfStream = require('end-of-stream')
|
const endOfStream = require('end-of-stream')
|
||||||
const asyncQ = require('async-q')
|
|
||||||
const pipe = require('pump')
|
const pipe = require('pump')
|
||||||
const LocalStorageStore = require('obs-store/lib/localStorage')
|
const LocalStorageStore = require('obs-store/lib/localStorage')
|
||||||
const storeTransform = require('obs-store/lib/transform')
|
const storeTransform = require('obs-store/lib/transform')
|
||||||
|
const ExtensionPlatform = require('./platforms/extension')
|
||||||
const Migrator = require('./lib/migrator/')
|
const Migrator = require('./lib/migrator/')
|
||||||
const migrations = require('./migrations/')
|
const migrations = require('./migrations/')
|
||||||
const PortStream = require('./lib/port-stream.js')
|
const PortStream = require('./lib/port-stream.js')
|
||||||
const notification = require('./lib/notifications.js')
|
const NotificationManager = require('./lib/notification-manager.js')
|
||||||
const MetamaskController = require('./metamask-controller')
|
const MetamaskController = require('./metamask-controller')
|
||||||
const extension = require('./lib/extension')
|
const extension = require('extensionizer')
|
||||||
const firstTimeState = require('./first-time-state')
|
const firstTimeState = require('./first-time-state')
|
||||||
|
|
||||||
const STORAGE_KEY = 'metamask-config'
|
const STORAGE_KEY = 'metamask-config'
|
||||||
@ -19,44 +19,42 @@ const log = require('loglevel')
|
|||||||
window.log = log
|
window.log = log
|
||||||
log.setDefaultLevel(METAMASK_DEBUG ? 'debug' : 'warn')
|
log.setDefaultLevel(METAMASK_DEBUG ? 'debug' : 'warn')
|
||||||
|
|
||||||
|
const platform = new ExtensionPlatform()
|
||||||
|
const notificationManager = new NotificationManager()
|
||||||
|
global.METAMASK_NOTIFIER = notificationManager
|
||||||
|
|
||||||
let popupIsOpen = false
|
let popupIsOpen = false
|
||||||
|
|
||||||
// state persistence
|
// state persistence
|
||||||
const diskStore = new LocalStorageStore({ storageKey: STORAGE_KEY })
|
const diskStore = new LocalStorageStore({ storageKey: STORAGE_KEY })
|
||||||
|
|
||||||
// initialization flow
|
// initialization flow
|
||||||
asyncQ.waterfall([
|
initialize().catch(console.error)
|
||||||
() => loadStateFromPersistence(),
|
|
||||||
(initState) => setupController(initState),
|
async function initialize() {
|
||||||
])
|
const initState = await loadStateFromPersistence()
|
||||||
.then(() => console.log('MetaMask initialization complete.'))
|
await setupController(initState)
|
||||||
.catch((err) => { console.error(err) })
|
console.log('MetaMask initialization complete.')
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// State and Persistence
|
// State and Persistence
|
||||||
//
|
//
|
||||||
|
|
||||||
function loadStateFromPersistence() {
|
async function loadStateFromPersistence () {
|
||||||
// migrations
|
// migrations
|
||||||
let migrator = new Migrator({ migrations })
|
const migrator = new Migrator({ migrations })
|
||||||
let initialState = migrator.generateInitialState(firstTimeState)
|
|
||||||
return asyncQ.waterfall([
|
|
||||||
// read from disk
|
// read from disk
|
||||||
() => Promise.resolve(diskStore.getState() || initialState),
|
let versionedData = diskStore.getState() || migrator.generateInitialState(firstTimeState)
|
||||||
// migrate data
|
// migrate data
|
||||||
(versionedData) => migrator.migrateData(versionedData),
|
versionedData = await migrator.migrateData(versionedData)
|
||||||
// write to disk
|
// write to disk
|
||||||
(versionedData) => {
|
|
||||||
diskStore.putState(versionedData)
|
diskStore.putState(versionedData)
|
||||||
return Promise.resolve(versionedData)
|
// return just the data
|
||||||
},
|
return versionedData.data
|
||||||
// resolve to just data
|
|
||||||
(versionedData) => Promise.resolve(versionedData.data),
|
|
||||||
])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function setupController (initState) {
|
function setupController (initState) {
|
||||||
|
|
||||||
//
|
//
|
||||||
// MetaMask Controller
|
// MetaMask Controller
|
||||||
//
|
//
|
||||||
@ -68,6 +66,8 @@ function setupController (initState) {
|
|||||||
showUnapprovedTx: triggerUi,
|
showUnapprovedTx: triggerUi,
|
||||||
// initial state
|
// initial state
|
||||||
initState,
|
initState,
|
||||||
|
// platform specific api
|
||||||
|
platform,
|
||||||
})
|
})
|
||||||
global.metamaskController = controller
|
global.metamaskController = controller
|
||||||
|
|
||||||
@ -79,7 +79,7 @@ function setupController (initState) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
function versionifyData (state) {
|
function versionifyData (state) {
|
||||||
let versionedData = diskStore.getState()
|
const versionedData = diskStore.getState()
|
||||||
versionedData.data = state
|
versionedData.data = state
|
||||||
return versionedData
|
return versionedData
|
||||||
}
|
}
|
||||||
@ -114,13 +114,13 @@ function setupController (initState) {
|
|||||||
//
|
//
|
||||||
|
|
||||||
updateBadge()
|
updateBadge()
|
||||||
controller.txManager.on('updateBadge', updateBadge)
|
controller.txController.on('updateBadge', updateBadge)
|
||||||
controller.messageManager.on('updateBadge', updateBadge)
|
controller.messageManager.on('updateBadge', updateBadge)
|
||||||
|
|
||||||
// plugin badge text
|
// plugin badge text
|
||||||
function updateBadge () {
|
function updateBadge () {
|
||||||
var label = ''
|
var label = ''
|
||||||
var unapprovedTxCount = controller.txManager.unapprovedTxCount
|
var unapprovedTxCount = controller.txController.unapprovedTxCount
|
||||||
var unapprovedMsgCount = controller.messageManager.unapprovedMsgCount
|
var unapprovedMsgCount = controller.messageManager.unapprovedMsgCount
|
||||||
var count = unapprovedTxCount + unapprovedMsgCount
|
var count = unapprovedTxCount + unapprovedMsgCount
|
||||||
if (count) {
|
if (count) {
|
||||||
@ -131,7 +131,6 @@ function setupController (initState) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
@ -140,7 +139,7 @@ function setupController (initState) {
|
|||||||
|
|
||||||
// popup trigger
|
// popup trigger
|
||||||
function triggerUi () {
|
function triggerUi () {
|
||||||
if (!popupIsOpen) notification.show()
|
if (!popupIsOpen) notificationManager.showPopup()
|
||||||
}
|
}
|
||||||
|
|
||||||
// On first install, open a window to MetaMask website to how-it-works.
|
// On first install, open a window to MetaMask website to how-it-works.
|
||||||
|
@ -1,14 +1,15 @@
|
|||||||
const MAINET_RPC_URL = 'https://mainnet.infura.io/metamask'
|
const MAINET_RPC_URL = 'https://mainnet.infura.io/metamask'
|
||||||
const TESTNET_RPC_URL = 'https://ropsten.infura.io/metamask'
|
const ROPSTEN_RPC_URL = 'https://ropsten.infura.io/metamask'
|
||||||
const DEFAULT_RPC_URL = TESTNET_RPC_URL
|
const KOVAN_RPC_URL = 'https://kovan.infura.io/metamask'
|
||||||
|
const RINKEBY_RPC_URL = 'https://rinkeby.infura.io/metamask'
|
||||||
|
|
||||||
global.METAMASK_DEBUG = 'GULP_METAMASK_DEBUG'
|
global.METAMASK_DEBUG = 'GULP_METAMASK_DEBUG'
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
network: {
|
network: {
|
||||||
default: DEFAULT_RPC_URL,
|
|
||||||
mainnet: MAINET_RPC_URL,
|
mainnet: MAINET_RPC_URL,
|
||||||
testnet: TESTNET_RPC_URL,
|
ropsten: ROPSTEN_RPC_URL,
|
||||||
morden: TESTNET_RPC_URL,
|
kovan: KOVAN_RPC_URL,
|
||||||
|
rinkeby: RINKEBY_RPC_URL,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ const LocalMessageDuplexStream = require('post-message-stream')
|
|||||||
const PongStream = require('ping-pong-stream/pong')
|
const PongStream = require('ping-pong-stream/pong')
|
||||||
const PortStream = require('./lib/port-stream.js')
|
const PortStream = require('./lib/port-stream.js')
|
||||||
const ObjectMultiplex = require('./lib/obj-multiplex')
|
const ObjectMultiplex = require('./lib/obj-multiplex')
|
||||||
const extension = require('./lib/extension')
|
const extension = require('extensionizer')
|
||||||
|
|
||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
@ -61,14 +61,22 @@ function setupStreams () {
|
|||||||
// ignore unused channels (handled by background)
|
// ignore unused channels (handled by background)
|
||||||
mx.ignoreStream('provider')
|
mx.ignoreStream('provider')
|
||||||
mx.ignoreStream('publicConfig')
|
mx.ignoreStream('publicConfig')
|
||||||
mx.ignoreStream('reload')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function shouldInjectWeb3 () {
|
function shouldInjectWeb3 () {
|
||||||
return isAllowedSuffix(window.location.href)
|
return doctypeCheck() || suffixCheck()
|
||||||
}
|
}
|
||||||
|
|
||||||
function isAllowedSuffix (testCase) {
|
function doctypeCheck () {
|
||||||
|
const doctype = window.document.doctype
|
||||||
|
if (doctype) {
|
||||||
|
return doctype.name === 'html'
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function suffixCheck () {
|
||||||
var prohibitedTypes = ['xml', 'pdf']
|
var prohibitedTypes = ['xml', 'pdf']
|
||||||
var currentUrl = window.location.href
|
var currentUrl = window.location.href
|
||||||
var currentRegex
|
var currentRegex
|
||||||
|
@ -39,11 +39,11 @@ class AddressBookController {
|
|||||||
// pushed object is an object of two fields. Current behavior does not set an
|
// pushed object is an object of two fields. Current behavior does not set an
|
||||||
// upper limit to the number of addresses.
|
// upper limit to the number of addresses.
|
||||||
_addToAddressBook (address, name) {
|
_addToAddressBook (address, name) {
|
||||||
let addressBook = this._getAddressBook()
|
const addressBook = this._getAddressBook()
|
||||||
let identities = this._getIdentities()
|
const identities = this._getIdentities()
|
||||||
|
|
||||||
let addressBookIndex = addressBook.findIndex((element) => { return element.address.toLowerCase() === address.toLowerCase() || element.name === name })
|
const addressBookIndex = addressBook.findIndex((element) => { return element.address.toLowerCase() === address.toLowerCase() || element.name === name })
|
||||||
let identitiesIndex = Object.keys(identities).findIndex((element) => { return element.toLowerCase() === address.toLowerCase() })
|
const identitiesIndex = Object.keys(identities).findIndex((element) => { return element.toLowerCase() === address.toLowerCase() })
|
||||||
// trigger this condition if we own this address--no need to overwrite.
|
// trigger this condition if we own this address--no need to overwrite.
|
||||||
if (identitiesIndex !== -1) {
|
if (identitiesIndex !== -1) {
|
||||||
return Promise.resolve(addressBook)
|
return Promise.resolve(addressBook)
|
||||||
|
@ -45,15 +45,17 @@ class CurrencyController {
|
|||||||
|
|
||||||
updateConversionRate () {
|
updateConversionRate () {
|
||||||
const currentCurrency = this.getCurrentCurrency()
|
const currentCurrency = this.getCurrentCurrency()
|
||||||
return fetch(`https://www.cryptonator.com/api/ticker/eth-${currentCurrency}`)
|
return fetch(`https://api.cryptonator.com/api/ticker/eth-${currentCurrency}`)
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then((parsedResponse) => {
|
.then((parsedResponse) => {
|
||||||
this.setConversionRate(Number(parsedResponse.ticker.price))
|
this.setConversionRate(Number(parsedResponse.ticker.price))
|
||||||
this.setConversionDate(Number(parsedResponse.timestamp))
|
this.setConversionDate(Number(parsedResponse.timestamp))
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
|
if (err) {
|
||||||
console.warn('MetaMask - Failed to query currency conversion.')
|
console.warn('MetaMask - Failed to query currency conversion.')
|
||||||
this.setConversionRate(0)
|
this.setConversionRate(0)
|
||||||
this.setConversionDate('N/A')
|
this.setConversionDate('N/A')
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
129
app/scripts/controllers/network.js
Normal file
129
app/scripts/controllers/network.js
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
const EventEmitter = require('events')
|
||||||
|
const MetaMaskProvider = require('web3-provider-engine/zero.js')
|
||||||
|
const ObservableStore = require('obs-store')
|
||||||
|
const ComposedStore = require('obs-store/lib/composed')
|
||||||
|
const extend = require('xtend')
|
||||||
|
const EthQuery = require('eth-query')
|
||||||
|
const RPC_ADDRESS_LIST = require('../config.js').network
|
||||||
|
const DEFAULT_RPC = RPC_ADDRESS_LIST['rinkeby']
|
||||||
|
|
||||||
|
module.exports = class NetworkController extends EventEmitter {
|
||||||
|
constructor (config) {
|
||||||
|
super()
|
||||||
|
this.networkStore = new ObservableStore('loading')
|
||||||
|
config.provider.rpcTarget = this.getRpcAddressForType(config.provider.type, config.provider)
|
||||||
|
this.providerStore = new ObservableStore(config.provider)
|
||||||
|
this.store = new ComposedStore({ provider: this.providerStore, network: this.networkStore })
|
||||||
|
this._providerListeners = {}
|
||||||
|
|
||||||
|
this.on('networkDidChange', this.lookupNetwork)
|
||||||
|
this.providerStore.subscribe((state) => this.switchNetwork({rpcUrl: state.rpcTarget}))
|
||||||
|
}
|
||||||
|
|
||||||
|
get provider () {
|
||||||
|
return this._proxy
|
||||||
|
}
|
||||||
|
|
||||||
|
set provider (provider) {
|
||||||
|
this._provider = provider
|
||||||
|
}
|
||||||
|
|
||||||
|
initializeProvider (opts) {
|
||||||
|
this.providerInit = opts
|
||||||
|
this._provider = MetaMaskProvider(opts)
|
||||||
|
this._proxy = new Proxy(this._provider, {
|
||||||
|
get: (obj, name) => {
|
||||||
|
if (name === 'on') return this._on.bind(this)
|
||||||
|
return this._provider[name]
|
||||||
|
},
|
||||||
|
set: (obj, name, value) => {
|
||||||
|
this._provider[name] = value
|
||||||
|
},
|
||||||
|
})
|
||||||
|
this.provider.on('block', this._logBlock.bind(this))
|
||||||
|
this.provider.on('error', this.verifyNetwork.bind(this))
|
||||||
|
this.ethQuery = new EthQuery(this.provider)
|
||||||
|
this.lookupNetwork()
|
||||||
|
return this.provider
|
||||||
|
}
|
||||||
|
|
||||||
|
switchNetwork (providerInit) {
|
||||||
|
this.setNetworkState('loading')
|
||||||
|
const newInit = extend(this.providerInit, providerInit)
|
||||||
|
this.providerInit = newInit
|
||||||
|
|
||||||
|
this._provider.removeAllListeners()
|
||||||
|
this._provider.stop()
|
||||||
|
this.provider = MetaMaskProvider(newInit)
|
||||||
|
// apply the listners created by other controllers
|
||||||
|
Object.keys(this._providerListeners).forEach((key) => {
|
||||||
|
this._providerListeners[key].forEach((handler) => this._provider.addListener(key, handler))
|
||||||
|
})
|
||||||
|
this.emit('networkDidChange')
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
verifyNetwork () {
|
||||||
|
// Check network when restoring connectivity:
|
||||||
|
if (this.isNetworkLoading()) this.lookupNetwork()
|
||||||
|
}
|
||||||
|
|
||||||
|
getNetworkState () {
|
||||||
|
return this.networkStore.getState()
|
||||||
|
}
|
||||||
|
|
||||||
|
setNetworkState (network) {
|
||||||
|
return this.networkStore.putState(network)
|
||||||
|
}
|
||||||
|
|
||||||
|
isNetworkLoading () {
|
||||||
|
return this.getNetworkState() === 'loading'
|
||||||
|
}
|
||||||
|
|
||||||
|
lookupNetwork () {
|
||||||
|
this.ethQuery.sendAsync({ method: 'net_version' }, (err, network) => {
|
||||||
|
if (err) return this.setNetworkState('loading')
|
||||||
|
log.info('web3.getNetwork returned ' + network)
|
||||||
|
this.setNetworkState(network)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
setRpcTarget (rpcUrl) {
|
||||||
|
this.providerStore.updateState({
|
||||||
|
type: 'rpc',
|
||||||
|
rpcTarget: rpcUrl,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
getCurrentRpcAddress () {
|
||||||
|
const provider = this.getProviderConfig()
|
||||||
|
if (!provider) return null
|
||||||
|
return this.getRpcAddressForType(provider.type)
|
||||||
|
}
|
||||||
|
|
||||||
|
setProviderType (type) {
|
||||||
|
if (type === this.getProviderConfig().type) return
|
||||||
|
const rpcTarget = this.getRpcAddressForType(type)
|
||||||
|
this.providerStore.updateState({type, rpcTarget})
|
||||||
|
}
|
||||||
|
|
||||||
|
getProviderConfig () {
|
||||||
|
return this.providerStore.getState()
|
||||||
|
}
|
||||||
|
|
||||||
|
getRpcAddressForType (type, provider = this.getProviderConfig()) {
|
||||||
|
if (RPC_ADDRESS_LIST[type]) return RPC_ADDRESS_LIST[type]
|
||||||
|
return provider && provider.rpcTarget ? provider.rpcTarget : DEFAULT_RPC
|
||||||
|
}
|
||||||
|
|
||||||
|
_logBlock (block) {
|
||||||
|
log.info(`BLOCK CHANGED: #${block.number.toString('hex')} 0x${block.hash.toString('hex')}`)
|
||||||
|
this.verifyNetwork()
|
||||||
|
}
|
||||||
|
|
||||||
|
_on (event, handler) {
|
||||||
|
if (!this._providerListeners[event]) this._providerListeners[event] = []
|
||||||
|
this._providerListeners[event].push(handler)
|
||||||
|
this._provider.on(event, handler)
|
||||||
|
}
|
||||||
|
}
|
@ -36,8 +36,8 @@ class PreferencesController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
addToFrequentRpcList (_url) {
|
addToFrequentRpcList (_url) {
|
||||||
let rpcList = this.getFrequentRpcList()
|
const rpcList = this.getFrequentRpcList()
|
||||||
let index = rpcList.findIndex((element) => { return element === _url })
|
const index = rpcList.findIndex((element) => { return element === _url })
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
rpcList.splice(index, 1)
|
rpcList.splice(index, 1)
|
||||||
}
|
}
|
||||||
@ -53,13 +53,9 @@ class PreferencesController {
|
|||||||
getFrequentRpcList () {
|
getFrequentRpcList () {
|
||||||
return this.store.getState().frequentRpcList
|
return this.store.getState().frequentRpcList
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// PRIVATE METHODS
|
// PRIVATE METHODS
|
||||||
//
|
//
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = PreferencesController
|
module.exports = PreferencesController
|
||||||
|
@ -4,11 +4,14 @@ const extend = require('xtend')
|
|||||||
const Semaphore = require('semaphore')
|
const Semaphore = require('semaphore')
|
||||||
const ObservableStore = require('obs-store')
|
const ObservableStore = require('obs-store')
|
||||||
const ethUtil = require('ethereumjs-util')
|
const ethUtil = require('ethereumjs-util')
|
||||||
const BN = require('ethereumjs-util').BN
|
const TxProviderUtil = require('../lib/tx-utils')
|
||||||
const TxProviderUtil = require('./lib/tx-utils')
|
const createId = require('../lib/random-id')
|
||||||
const createId = require('./lib/random-id')
|
const denodeify = require('denodeify')
|
||||||
|
|
||||||
module.exports = class TransactionManager extends EventEmitter {
|
const RETRY_LIMIT = 200
|
||||||
|
const RESUBMIT_INTERVAL = 10000 // Ten seconds
|
||||||
|
|
||||||
|
module.exports = class TransactionController extends EventEmitter {
|
||||||
constructor (opts) {
|
constructor (opts) {
|
||||||
super()
|
super()
|
||||||
this.store = new ObservableStore(extend({
|
this.store = new ObservableStore(extend({
|
||||||
@ -20,7 +23,8 @@ module.exports = class TransactionManager extends EventEmitter {
|
|||||||
this.txHistoryLimit = opts.txHistoryLimit
|
this.txHistoryLimit = opts.txHistoryLimit
|
||||||
this.provider = opts.provider
|
this.provider = opts.provider
|
||||||
this.blockTracker = opts.blockTracker
|
this.blockTracker = opts.blockTracker
|
||||||
this.txProviderUtils = new TxProviderUtil(this.provider)
|
this.query = opts.ethQuery
|
||||||
|
this.txProviderUtils = new TxProviderUtil(this.query)
|
||||||
this.blockTracker.on('block', this.checkForTxInBlock.bind(this))
|
this.blockTracker.on('block', this.checkForTxInBlock.bind(this))
|
||||||
this.signEthTx = opts.signTransaction
|
this.signEthTx = opts.signTransaction
|
||||||
this.nonceLock = Semaphore(1)
|
this.nonceLock = Semaphore(1)
|
||||||
@ -30,6 +34,8 @@ module.exports = class TransactionManager extends EventEmitter {
|
|||||||
this.store.subscribe(() => this._updateMemstore())
|
this.store.subscribe(() => this._updateMemstore())
|
||||||
this.networkStore.subscribe(() => this._updateMemstore())
|
this.networkStore.subscribe(() => this._updateMemstore())
|
||||||
this.preferencesStore.subscribe(() => this._updateMemstore())
|
this.preferencesStore.subscribe(() => this._updateMemstore())
|
||||||
|
|
||||||
|
this.continuallyResubmitPendingTxs()
|
||||||
}
|
}
|
||||||
|
|
||||||
getState () {
|
getState () {
|
||||||
@ -37,7 +43,7 @@ module.exports = class TransactionManager extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getNetwork () {
|
getNetwork () {
|
||||||
return this.networkStore.getState().network
|
return this.networkStore.getState()
|
||||||
}
|
}
|
||||||
|
|
||||||
getSelectedAddress () {
|
getSelectedAddress () {
|
||||||
@ -46,28 +52,41 @@ module.exports = class TransactionManager extends EventEmitter {
|
|||||||
|
|
||||||
// Returns the tx list
|
// Returns the tx list
|
||||||
getTxList () {
|
getTxList () {
|
||||||
let network = this.getNetwork()
|
const network = this.getNetwork()
|
||||||
let fullTxList = this.store.getState().transactions
|
const fullTxList = this.getFullTxList()
|
||||||
return fullTxList.filter(txMeta => txMeta.metamaskNetworkId === network)
|
return fullTxList.filter(txMeta => txMeta.metamaskNetworkId === network)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns the number of txs for the current network.
|
||||||
|
getTxCount () {
|
||||||
|
return this.getTxList().length
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the full tx list across all networks
|
||||||
|
getFullTxList () {
|
||||||
|
return this.store.getState().transactions
|
||||||
|
}
|
||||||
|
|
||||||
// Adds a tx to the txlist
|
// Adds a tx to the txlist
|
||||||
addTx (txMeta) {
|
addTx (txMeta) {
|
||||||
var txList = this.getTxList()
|
const txCount = this.getTxCount()
|
||||||
var txHistoryLimit = this.txHistoryLimit
|
const network = this.getNetwork()
|
||||||
|
const fullTxList = this.getFullTxList()
|
||||||
|
const txHistoryLimit = this.txHistoryLimit
|
||||||
|
|
||||||
// checks if the length of th tx history is
|
// checks if the length of the tx history is
|
||||||
// longer then desired persistence limit
|
// longer then desired persistence limit
|
||||||
// and then if it is removes only confirmed
|
// and then if it is removes only confirmed
|
||||||
// or rejected tx's.
|
// or rejected tx's.
|
||||||
// not tx's that are pending or unapproved
|
// not tx's that are pending or unapproved
|
||||||
if (txList.length > txHistoryLimit - 1) {
|
if (txCount > txHistoryLimit - 1) {
|
||||||
var index = txList.findIndex((metaTx) => metaTx.status === 'confirmed' || metaTx.status === 'rejected')
|
var index = fullTxList.findIndex((metaTx) => ((metaTx.status === 'confirmed' || metaTx.status === 'rejected') && network === txMeta.metamaskNetworkId))
|
||||||
txList.splice(index, 1)
|
fullTxList.splice(index, 1)
|
||||||
}
|
}
|
||||||
txList.push(txMeta)
|
fullTxList.push(txMeta)
|
||||||
|
this._saveTxList(fullTxList)
|
||||||
|
this.emit('update')
|
||||||
|
|
||||||
this._saveTxList(txList)
|
|
||||||
this.once(`${txMeta.id}:signed`, function (txId) {
|
this.once(`${txMeta.id}:signed`, function (txId) {
|
||||||
this.removeAllListeners(`${txMeta.id}:rejected`)
|
this.removeAllListeners(`${txMeta.id}:rejected`)
|
||||||
})
|
})
|
||||||
@ -89,7 +108,7 @@ module.exports = class TransactionManager extends EventEmitter {
|
|||||||
//
|
//
|
||||||
updateTx (txMeta) {
|
updateTx (txMeta) {
|
||||||
var txId = txMeta.id
|
var txId = txMeta.id
|
||||||
var txList = this.getTxList()
|
var txList = this.getFullTxList()
|
||||||
var index = txList.findIndex(txData => txData.id === txId)
|
var index = txList.findIndex(txData => txData.id === txId)
|
||||||
txList[index] = txMeta
|
txList[index] = txMeta
|
||||||
this._saveTxList(txList)
|
this._saveTxList(txList)
|
||||||
@ -109,44 +128,38 @@ module.exports = class TransactionManager extends EventEmitter {
|
|||||||
async.waterfall([
|
async.waterfall([
|
||||||
// validate
|
// validate
|
||||||
(cb) => this.txProviderUtils.validateTxParams(txParams, cb),
|
(cb) => this.txProviderUtils.validateTxParams(txParams, cb),
|
||||||
// prepare txMeta
|
// construct txMeta
|
||||||
(cb) => {
|
(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 = {
|
txMeta = {
|
||||||
id: txId,
|
id: createId(),
|
||||||
time: time,
|
time: (new Date()).getTime(),
|
||||||
status: 'unapproved',
|
status: 'unapproved',
|
||||||
metamaskNetworkId: this.getNetwork(),
|
metamaskNetworkId: this.getNetwork(),
|
||||||
txParams: txParams,
|
txParams: txParams,
|
||||||
}
|
}
|
||||||
// calculate metadata for tx
|
cb()
|
||||||
this.txProviderUtils.analyzeGasUsage(txMeta, cb)
|
|
||||||
},
|
},
|
||||||
|
// add default tx params
|
||||||
|
(cb) => this.addTxDefaults(txMeta, cb),
|
||||||
// save txMeta
|
// save txMeta
|
||||||
(cb) => {
|
(cb) => {
|
||||||
this.addTx(txMeta)
|
this.addTx(txMeta)
|
||||||
this.setMaxTxCostAndFee(txMeta)
|
|
||||||
cb(null, txMeta)
|
cb(null, txMeta)
|
||||||
},
|
},
|
||||||
], done)
|
], done)
|
||||||
}
|
}
|
||||||
|
|
||||||
setMaxTxCostAndFee (txMeta) {
|
addTxDefaults (txMeta, cb) {
|
||||||
var txParams = txMeta.txParams
|
const txParams = txMeta.txParams
|
||||||
var gasCost = new BN(ethUtil.stripHexPrefix(txParams.gas || txMeta.estimatedGas), 16)
|
// ensure value
|
||||||
var gasPrice = new BN(ethUtil.stripHexPrefix(txParams.gasPrice || '0x4a817c800'), 16)
|
txParams.value = txParams.value || '0x0'
|
||||||
var txFee = gasCost.mul(gasPrice)
|
this.query.gasPrice((err, gasPrice) => {
|
||||||
var txValue = new BN(ethUtil.stripHexPrefix(txParams.value || '0x0'), 16)
|
if (err) return cb(err)
|
||||||
var maxCost = txValue.add(txFee)
|
// set gasPrice
|
||||||
txMeta.txFee = txFee
|
txParams.gasPrice = gasPrice
|
||||||
txMeta.txValue = txValue
|
// set gasLimit
|
||||||
txMeta.maxCost = maxCost
|
this.txProviderUtils.analyzeGasUsage(txMeta, cb)
|
||||||
txMeta.gasPrice = gasPrice
|
})
|
||||||
this.updateTx(txMeta)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getUnapprovedTxList () {
|
getUnapprovedTxList () {
|
||||||
@ -172,7 +185,10 @@ module.exports = class TransactionManager extends EventEmitter {
|
|||||||
], (err) => {
|
], (err) => {
|
||||||
self.nonceLock.leave()
|
self.nonceLock.leave()
|
||||||
if (err) {
|
if (err) {
|
||||||
this.setTxStatusFailed(txId)
|
this.setTxStatusFailed(txId, {
|
||||||
|
errCode: err.errCode || err,
|
||||||
|
message: err.message || 'Transaction failed during approval',
|
||||||
|
})
|
||||||
return cb(err)
|
return cb(err)
|
||||||
}
|
}
|
||||||
cb()
|
cb()
|
||||||
@ -186,7 +202,7 @@ module.exports = class TransactionManager extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fillInTxParams (txId, cb) {
|
fillInTxParams (txId, cb) {
|
||||||
let txMeta = this.getTx(txId)
|
const txMeta = this.getTx(txId)
|
||||||
this.txProviderUtils.fillInTxParams(txMeta.txParams, (err) => {
|
this.txProviderUtils.fillInTxParams(txMeta.txParams, (err) => {
|
||||||
if (err) return cb(err)
|
if (err) return cb(err)
|
||||||
this.updateTx(txMeta)
|
this.updateTx(txMeta)
|
||||||
@ -194,11 +210,23 @@ module.exports = class TransactionManager extends EventEmitter {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getChainId () {
|
||||||
|
const networkState = this.networkStore.getState()
|
||||||
|
const getChainId = parseInt(networkState.network)
|
||||||
|
if (Number.isNaN(getChainId)) {
|
||||||
|
return 0
|
||||||
|
} else {
|
||||||
|
return getChainId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
signTransaction (txId, cb) {
|
signTransaction (txId, cb) {
|
||||||
let txMeta = this.getTx(txId)
|
const txMeta = this.getTx(txId)
|
||||||
let txParams = txMeta.txParams
|
const txParams = txMeta.txParams
|
||||||
let fromAddress = txParams.from
|
const fromAddress = txParams.from
|
||||||
let ethTx = this.txProviderUtils.buildEthTxFromParams(txParams)
|
// add network/chain id
|
||||||
|
txParams.chainId = this.getChainId()
|
||||||
|
const ethTx = this.txProviderUtils.buildEthTxFromParams(txParams)
|
||||||
this.signEthTx(ethTx, fromAddress).then(() => {
|
this.signEthTx(ethTx, fromAddress).then(() => {
|
||||||
this.setTxStatusSigned(txMeta.id)
|
this.setTxStatusSigned(txMeta.id)
|
||||||
cb(null, ethUtil.bufferToHex(ethTx.serialize()))
|
cb(null, ethUtil.bufferToHex(ethTx.serialize()))
|
||||||
@ -207,7 +235,11 @@ module.exports = class TransactionManager extends EventEmitter {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
publishTransaction (txId, rawTx, cb) {
|
publishTransaction (txId, rawTx, cb = warn) {
|
||||||
|
const txMeta = this.getTx(txId)
|
||||||
|
txMeta.rawTx = rawTx
|
||||||
|
this.updateTx(txMeta)
|
||||||
|
|
||||||
this.txProviderUtils.publishTransaction(rawTx, (err, txHash) => {
|
this.txProviderUtils.publishTransaction(rawTx, (err, txHash) => {
|
||||||
if (err) return cb(err)
|
if (err) return cb(err)
|
||||||
this.setTxHash(txId, txHash)
|
this.setTxHash(txId, txHash)
|
||||||
@ -219,7 +251,7 @@ module.exports = class TransactionManager extends EventEmitter {
|
|||||||
// receives a txHash records the tx as signed
|
// receives a txHash records the tx as signed
|
||||||
setTxHash (txId, txHash) {
|
setTxHash (txId, txHash) {
|
||||||
// Add the tx hash to the persisted meta-tx object
|
// Add the tx hash to the persisted meta-tx object
|
||||||
let txMeta = this.getTx(txId)
|
const txMeta = this.getTx(txId)
|
||||||
txMeta.hash = txHash
|
txMeta.hash = txHash
|
||||||
this.updateTx(txMeta)
|
this.updateTx(txMeta)
|
||||||
}
|
}
|
||||||
@ -291,7 +323,10 @@ module.exports = class TransactionManager extends EventEmitter {
|
|||||||
this._setTxStatus(txId, 'confirmed')
|
this._setTxStatus(txId, 'confirmed')
|
||||||
}
|
}
|
||||||
|
|
||||||
setTxStatusFailed (txId) {
|
setTxStatusFailed (txId, reason) {
|
||||||
|
const txMeta = this.getTx(txId)
|
||||||
|
txMeta.err = reason
|
||||||
|
this.updateTx(txMeta)
|
||||||
this._setTxStatus(txId, 'failed')
|
this._setTxStatus(txId, 'failed')
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -312,14 +347,13 @@ module.exports = class TransactionManager extends EventEmitter {
|
|||||||
var txHash = txMeta.hash
|
var txHash = txMeta.hash
|
||||||
var txId = txMeta.id
|
var txId = txMeta.id
|
||||||
if (!txHash) {
|
if (!txHash) {
|
||||||
txMeta.err = {
|
const errReason = {
|
||||||
errCode: 'No hash was provided',
|
errCode: 'No hash was provided',
|
||||||
message: 'We had an error while submitting this transaction, please try again.',
|
message: 'We had an error while submitting this transaction, please try again.',
|
||||||
}
|
}
|
||||||
this.updateTx(txMeta)
|
return this.setTxStatusFailed(txId, errReason)
|
||||||
return this.setTxStatusFailed(txId)
|
|
||||||
}
|
}
|
||||||
this.txProviderUtils.query.getTransactionByHash(txHash, (err, txParams) => {
|
this.query.getTransactionByHash(txHash, (err, txParams) => {
|
||||||
if (err || !txParams) {
|
if (err || !txParams) {
|
||||||
if (!txParams) return
|
if (!txParams) return
|
||||||
txMeta.err = {
|
txMeta.err = {
|
||||||
@ -328,7 +362,7 @@ module.exports = class TransactionManager extends EventEmitter {
|
|||||||
message: 'There was a problem loading this transaction.',
|
message: 'There was a problem loading this transaction.',
|
||||||
}
|
}
|
||||||
this.updateTx(txMeta)
|
this.updateTx(txMeta)
|
||||||
return console.error(err)
|
return log.error(err)
|
||||||
}
|
}
|
||||||
if (txParams.blockNumber) {
|
if (txParams.blockNumber) {
|
||||||
this.setTxStatusConfirmed(txId)
|
this.setTxStatusConfirmed(txId)
|
||||||
@ -354,6 +388,7 @@ module.exports = class TransactionManager extends EventEmitter {
|
|||||||
this.emit(`${txMeta.id}:${status}`, txId)
|
this.emit(`${txMeta.id}:${status}`, txId)
|
||||||
if (status === 'submitted' || status === 'rejected') {
|
if (status === 'submitted' || status === 'rejected') {
|
||||||
this.emit(`${txMeta.id}:finished`, txMeta)
|
this.emit(`${txMeta.id}:finished`, txMeta)
|
||||||
|
|
||||||
}
|
}
|
||||||
this.updateTx(txMeta)
|
this.updateTx(txMeta)
|
||||||
this.emit('updateBadge')
|
this.emit('updateBadge')
|
||||||
@ -373,7 +408,47 @@ module.exports = class TransactionManager extends EventEmitter {
|
|||||||
})
|
})
|
||||||
this.memStore.updateState({ unapprovedTxs, selectedAddressTxList })
|
this.memStore.updateState({ unapprovedTxs, selectedAddressTxList })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
continuallyResubmitPendingTxs () {
|
||||||
|
const pending = this.getTxsByMetaData('status', 'submitted')
|
||||||
|
const resubmit = denodeify(this.resubmitTx.bind(this))
|
||||||
|
Promise.all(pending.map(txMeta => resubmit(txMeta)))
|
||||||
|
.catch((reason) => {
|
||||||
|
log.info('Problem resubmitting tx', reason)
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
global.setTimeout(() => {
|
||||||
|
this.continuallyResubmitPendingTxs()
|
||||||
|
}, RESUBMIT_INTERVAL)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
resubmitTx (txMeta, cb) {
|
||||||
|
// Increment a try counter.
|
||||||
|
if (!('retryCount' in txMeta)) {
|
||||||
|
txMeta.retryCount = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only auto-submit already-signed txs:
|
||||||
|
if (!('rawTx' in txMeta)) {
|
||||||
|
return cb()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (txMeta.retryCount > RETRY_LIMIT) {
|
||||||
|
txMeta.err = {
|
||||||
|
isWarning: true,
|
||||||
|
message: 'Gave up submitting tx.',
|
||||||
|
}
|
||||||
|
this.updateTx(txMeta)
|
||||||
|
return log.error(txMeta.err.message)
|
||||||
|
}
|
||||||
|
|
||||||
|
txMeta.retryCount++
|
||||||
|
const rawTx = txMeta.rawTx
|
||||||
|
this.txProviderUtils.publishTransaction(rawTx, cb)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const warn = () => console.warn('warn was used no cb provided')
|
const warn = () => log.warn('warn was used no cb provided')
|
@ -6,9 +6,11 @@ const METAMASK_DEBUG = 'GULP_METAMASK_DEBUG'
|
|||||||
// The default state of MetaMask
|
// The default state of MetaMask
|
||||||
//
|
//
|
||||||
module.exports = {
|
module.exports = {
|
||||||
config: {
|
config: {},
|
||||||
|
NetworkController: {
|
||||||
provider: {
|
provider: {
|
||||||
type: (METAMASK_DEBUG || env === 'test') ? 'testnet' : 'mainnet',
|
type: (METAMASK_DEBUG || env === 'test') ? 'rinkeby' : 'mainnet',
|
||||||
|
type: 'rinkeby',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -31,26 +31,11 @@ web3.setProvider = function () {
|
|||||||
console.log('MetaMask - overrode web3.setProvider')
|
console.log('MetaMask - overrode web3.setProvider')
|
||||||
}
|
}
|
||||||
console.log('MetaMask - injected web3')
|
console.log('MetaMask - injected web3')
|
||||||
// export global web3, with usage-detection reload fn
|
// export global web3, with usage-detection
|
||||||
var triggerReload = setupDappAutoReload(web3)
|
setupDappAutoReload(web3, inpageProvider.publicConfigStore)
|
||||||
|
|
||||||
// listen for reset requests from metamask
|
|
||||||
var reloadStream = inpageProvider.multiStream.createStream('reload')
|
|
||||||
reloadStream.once('data', triggerReload)
|
|
||||||
|
|
||||||
// setup ping timeout autoreload
|
|
||||||
// LocalMessageDuplexStream does not self-close, so reload if pingStream fails
|
|
||||||
// var pingChannel = inpageProvider.multiStream.createStream('pingpong')
|
|
||||||
// var pingStream = new PingStream({ objectMode: true })
|
|
||||||
// wait for first successful reponse
|
|
||||||
|
|
||||||
// disable pingStream until https://github.com/MetaMask/metamask-plugin/issues/746 is resolved more gracefully
|
|
||||||
// metamaskStream.once('data', function(){
|
|
||||||
// pingStream.pipe(pingChannel).pipe(pingStream)
|
|
||||||
// })
|
|
||||||
// endOfStream(pingStream, triggerReload)
|
|
||||||
|
|
||||||
// set web3 defaultAccount
|
// set web3 defaultAccount
|
||||||
|
|
||||||
inpageProvider.publicConfigStore.subscribe(function (state) {
|
inpageProvider.publicConfigStore.subscribe(function (state) {
|
||||||
web3.eth.defaultAccount = state.selectedAddress
|
web3.eth.defaultAccount = state.selectedAddress
|
||||||
})
|
})
|
||||||
|
@ -187,7 +187,7 @@ class KeyringController extends EventEmitter {
|
|||||||
.then((accounts) => {
|
.then((accounts) => {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'Simple Key Pair':
|
case 'Simple Key Pair':
|
||||||
let isNotIncluded = !accounts.find((key) => key === newAccount[0] || key === ethUtil.stripHexPrefix(newAccount[0]))
|
const isNotIncluded = !accounts.find((key) => key === newAccount[0] || key === ethUtil.stripHexPrefix(newAccount[0]))
|
||||||
return (isNotIncluded) ? Promise.resolve(newAccount) : Promise.reject(new Error('The account you\'re are trying to import is a duplicate'))
|
return (isNotIncluded) ? Promise.resolve(newAccount) : Promise.reject(new Error('The account you\'re are trying to import is a duplicate'))
|
||||||
default:
|
default:
|
||||||
return Promise.resolve(newAccount)
|
return Promise.resolve(newAccount)
|
||||||
@ -324,6 +324,7 @@ class KeyringController extends EventEmitter {
|
|||||||
if (!firstAccount) throw new Error('KeyringController - No account found on keychain.')
|
if (!firstAccount) throw new Error('KeyringController - No account found on keychain.')
|
||||||
const hexAccount = normalizeAddress(firstAccount)
|
const hexAccount = normalizeAddress(firstAccount)
|
||||||
this.emit('newAccount', hexAccount)
|
this.emit('newAccount', hexAccount)
|
||||||
|
this.emit('newVault', hexAccount)
|
||||||
return this.setupAccounts(accounts)
|
return this.setupAccounts(accounts)
|
||||||
})
|
})
|
||||||
.then(this.persistAllKeyrings.bind(this))
|
.then(this.persistAllKeyrings.bind(this))
|
||||||
|
@ -3,10 +3,18 @@ const METAMASK_DEBUG = 'GULP_METAMASK_DEBUG'
|
|||||||
const env = process.env.METAMASK_ENV
|
const env = process.env.METAMASK_ENV
|
||||||
|
|
||||||
module.exports = function (address) {
|
module.exports = function (address) {
|
||||||
if (METAMASK_DEBUG || env === 'test') return // Don't faucet in development or test
|
// Don't faucet in development or test
|
||||||
var http = new XMLHttpRequest()
|
if (METAMASK_DEBUG === true || env === 'test') return
|
||||||
var data = address
|
global.log.info('auto-fauceting:', address)
|
||||||
http.open('POST', uri, true)
|
const data = address
|
||||||
http.setRequestHeader('Content-type', 'application/rawdata')
|
const headers = new Headers()
|
||||||
http.send(data)
|
headers.append('Content-type', 'application/rawdata')
|
||||||
|
fetch(uri, {
|
||||||
|
method: 'POST',
|
||||||
|
headers,
|
||||||
|
body: data,
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error(err)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
@ -1,30 +1,33 @@
|
|||||||
const once = require('once')
|
|
||||||
const ensnare = require('ensnare')
|
|
||||||
|
|
||||||
module.exports = setupDappAutoReload
|
module.exports = setupDappAutoReload
|
||||||
|
|
||||||
function setupDappAutoReload (web3) {
|
function setupDappAutoReload (web3, observable) {
|
||||||
// export web3 as a global, checking for usage
|
// export web3 as a global, checking for usage
|
||||||
var pageIsUsingWeb3 = false
|
global.web3 = new Proxy(web3, {
|
||||||
var resetWasRequested = false
|
get: (_web3, name) => {
|
||||||
global.web3 = ensnare(web3, once(function () {
|
// get the time of use
|
||||||
// if web3 usage happened after a reset request, trigger reset late
|
if (name !== '_used') _web3._used = Date.now()
|
||||||
if (resetWasRequested) return triggerReset()
|
return _web3[name]
|
||||||
// mark web3 as used
|
},
|
||||||
pageIsUsingWeb3 = true
|
set: (_web3, name, value) => {
|
||||||
// reset web3 reference
|
_web3[name] = value
|
||||||
global.web3 = web3
|
},
|
||||||
}))
|
})
|
||||||
|
var networkVersion
|
||||||
|
|
||||||
return handleResetRequest
|
observable.subscribe(function (state) {
|
||||||
|
// get the initial network
|
||||||
|
const curentNetVersion = state.networkVersion
|
||||||
|
if (!networkVersion) networkVersion = curentNetVersion
|
||||||
|
|
||||||
function handleResetRequest () {
|
if (curentNetVersion !== networkVersion && web3._used) {
|
||||||
resetWasRequested = true
|
const timeSinceUse = Date.now() - web3._used
|
||||||
// ignore if web3 was not used
|
// if web3 was recently used then delay the reloading of the page
|
||||||
if (!pageIsUsingWeb3) return
|
timeSinceUse > 500 ? triggerReset() : setTimeout(triggerReset, 500)
|
||||||
// reload after short timeout
|
// prevent reentry into if statement if state updates again before
|
||||||
setTimeout(triggerReset, 500)
|
// reload
|
||||||
|
networkVersion = curentNetVersion
|
||||||
}
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// reload the page
|
// reload the page
|
||||||
|
23
app/scripts/lib/buy-eth-url.js
Normal file
23
app/scripts/lib/buy-eth-url.js
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
module.exports = getBuyEthUrl
|
||||||
|
|
||||||
|
function getBuyEthUrl ({ network, amount, address }) {
|
||||||
|
let url
|
||||||
|
switch (network) {
|
||||||
|
case '1':
|
||||||
|
url = `https://buy.coinbase.com/?code=9ec56d01-7e81-5017-930c-513daa27bb6a&amount=${amount}&address=${address}&crypto_currency=ETH`
|
||||||
|
break
|
||||||
|
|
||||||
|
case '3':
|
||||||
|
url = 'https://faucet.metamask.io/'
|
||||||
|
break
|
||||||
|
|
||||||
|
case '4':
|
||||||
|
url = 'https://www.rinkeby.io/'
|
||||||
|
break
|
||||||
|
|
||||||
|
case '42':
|
||||||
|
url = 'https://github.com/kovan-testnet/faucet'
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return url
|
||||||
|
}
|
@ -1,10 +1,12 @@
|
|||||||
const MetamaskConfig = require('../config.js')
|
|
||||||
const ethUtil = require('ethereumjs-util')
|
const ethUtil = require('ethereumjs-util')
|
||||||
const normalize = require('eth-sig-util').normalize
|
const normalize = require('eth-sig-util').normalize
|
||||||
|
const MetamaskConfig = require('../config.js')
|
||||||
|
|
||||||
|
|
||||||
const TESTNET_RPC = MetamaskConfig.network.testnet
|
|
||||||
const MAINNET_RPC = MetamaskConfig.network.mainnet
|
const MAINNET_RPC = MetamaskConfig.network.mainnet
|
||||||
const MORDEN_RPC = MetamaskConfig.network.morden
|
const ROPSTEN_RPC = MetamaskConfig.network.ropsten
|
||||||
|
const KOVAN_RPC = MetamaskConfig.network.kovan
|
||||||
|
const RINKEBY_RPC = MetamaskConfig.network.rinkeby
|
||||||
|
|
||||||
/* The config-manager is a convenience object
|
/* The config-manager is a convenience object
|
||||||
* wrapping a pojo-migrator.
|
* wrapping a pojo-migrator.
|
||||||
@ -32,36 +34,6 @@ ConfigManager.prototype.getConfig = function () {
|
|||||||
return data.config
|
return data.config
|
||||||
}
|
}
|
||||||
|
|
||||||
ConfigManager.prototype.setRpcTarget = function (rpcUrl) {
|
|
||||||
var config = this.getConfig()
|
|
||||||
config.provider = {
|
|
||||||
type: 'rpc',
|
|
||||||
rpcTarget: rpcUrl,
|
|
||||||
}
|
|
||||||
this.setConfig(config)
|
|
||||||
}
|
|
||||||
|
|
||||||
ConfigManager.prototype.setProviderType = function (type) {
|
|
||||||
var config = this.getConfig()
|
|
||||||
config.provider = {
|
|
||||||
type: type,
|
|
||||||
}
|
|
||||||
this.setConfig(config)
|
|
||||||
}
|
|
||||||
|
|
||||||
ConfigManager.prototype.useEtherscanProvider = function () {
|
|
||||||
var config = this.getConfig()
|
|
||||||
config.provider = {
|
|
||||||
type: 'etherscan',
|
|
||||||
}
|
|
||||||
this.setConfig(config)
|
|
||||||
}
|
|
||||||
|
|
||||||
ConfigManager.prototype.getProvider = function () {
|
|
||||||
var config = this.getConfig()
|
|
||||||
return config.provider
|
|
||||||
}
|
|
||||||
|
|
||||||
ConfigManager.prototype.setData = function (data) {
|
ConfigManager.prototype.setData = function (data) {
|
||||||
this.store.putState(data)
|
this.store.putState(data)
|
||||||
}
|
}
|
||||||
@ -135,6 +107,35 @@ ConfigManager.prototype.getSeedWords = function () {
|
|||||||
var data = this.getData()
|
var data = this.getData()
|
||||||
return data.seedWords
|
return data.seedWords
|
||||||
}
|
}
|
||||||
|
ConfigManager.prototype.setRpcTarget = function (rpcUrl) {
|
||||||
|
var config = this.getConfig()
|
||||||
|
config.provider = {
|
||||||
|
type: 'rpc',
|
||||||
|
rpcTarget: rpcUrl,
|
||||||
|
}
|
||||||
|
this.setConfig(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfigManager.prototype.setProviderType = function (type) {
|
||||||
|
var config = this.getConfig()
|
||||||
|
config.provider = {
|
||||||
|
type: type,
|
||||||
|
}
|
||||||
|
this.setConfig(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfigManager.prototype.useEtherscanProvider = function () {
|
||||||
|
var config = this.getConfig()
|
||||||
|
config.provider = {
|
||||||
|
type: 'etherscan',
|
||||||
|
}
|
||||||
|
this.setConfig(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfigManager.prototype.getProvider = function () {
|
||||||
|
var config = this.getConfig()
|
||||||
|
return config.provider
|
||||||
|
}
|
||||||
|
|
||||||
ConfigManager.prototype.getCurrentRpcAddress = function () {
|
ConfigManager.prototype.getCurrentRpcAddress = function () {
|
||||||
var provider = this.getProvider()
|
var provider = this.getProvider()
|
||||||
@ -144,14 +145,17 @@ ConfigManager.prototype.getCurrentRpcAddress = function () {
|
|||||||
case 'mainnet':
|
case 'mainnet':
|
||||||
return MAINNET_RPC
|
return MAINNET_RPC
|
||||||
|
|
||||||
case 'testnet':
|
case 'ropsten':
|
||||||
return TESTNET_RPC
|
return ROPSTEN_RPC
|
||||||
|
|
||||||
case 'morden':
|
case 'kovan':
|
||||||
return MORDEN_RPC
|
return KOVAN_RPC
|
||||||
|
|
||||||
|
case 'rinkeby':
|
||||||
|
return RINKEBY_RPC
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return provider && provider.rpcTarget ? provider.rpcTarget : TESTNET_RPC
|
return provider && provider.rpcTarget ? provider.rpcTarget : RINKEBY_RPC
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,6 +19,9 @@ class EthereumStore extends ObservableStore {
|
|||||||
super({
|
super({
|
||||||
accounts: {},
|
accounts: {},
|
||||||
transactions: {},
|
transactions: {},
|
||||||
|
currentBlockNumber: '0',
|
||||||
|
currentBlockHash: '',
|
||||||
|
currentBlockGasLimit: '',
|
||||||
})
|
})
|
||||||
this._provider = opts.provider
|
this._provider = opts.provider
|
||||||
this._query = new EthQuery(this._provider)
|
this._query = new EthQuery(this._provider)
|
||||||
@ -69,6 +72,9 @@ class EthereumStore extends ObservableStore {
|
|||||||
_updateForBlock (block) {
|
_updateForBlock (block) {
|
||||||
const blockNumber = '0x' + block.number.toString('hex')
|
const blockNumber = '0x' + block.number.toString('hex')
|
||||||
this._currentBlockNumber = blockNumber
|
this._currentBlockNumber = blockNumber
|
||||||
|
this.updateState({ currentBlockNumber: parseInt(blockNumber) })
|
||||||
|
this.updateState({ currentBlockHash: `0x${block.hash.toString('hex')}`})
|
||||||
|
this.updateState({ currentBlockGasLimit: `0x${block.gasLimit.toString('hex')}` })
|
||||||
async.parallel([
|
async.parallel([
|
||||||
this._updateAccounts.bind(this),
|
this._updateAccounts.bind(this),
|
||||||
this._updateTransactions.bind(this, blockNumber),
|
this._updateTransactions.bind(this, blockNumber),
|
||||||
|
@ -1,68 +0,0 @@
|
|||||||
const apis = [
|
|
||||||
'alarms',
|
|
||||||
'bookmarks',
|
|
||||||
'browserAction',
|
|
||||||
'commands',
|
|
||||||
'contextMenus',
|
|
||||||
'cookies',
|
|
||||||
'downloads',
|
|
||||||
'events',
|
|
||||||
'extension',
|
|
||||||
'extensionTypes',
|
|
||||||
'history',
|
|
||||||
'i18n',
|
|
||||||
'idle',
|
|
||||||
'notifications',
|
|
||||||
'pageAction',
|
|
||||||
'runtime',
|
|
||||||
'storage',
|
|
||||||
'tabs',
|
|
||||||
'webNavigation',
|
|
||||||
'webRequest',
|
|
||||||
'windows',
|
|
||||||
]
|
|
||||||
|
|
||||||
function Extension () {
|
|
||||||
const _this = this
|
|
||||||
|
|
||||||
apis.forEach(function (api) {
|
|
||||||
|
|
||||||
_this[api] = null
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (chrome[api]) {
|
|
||||||
_this[api] = chrome[api]
|
|
||||||
}
|
|
||||||
} catch (e) {}
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (window[api]) {
|
|
||||||
_this[api] = window[api]
|
|
||||||
}
|
|
||||||
} catch (e) {}
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (browser[api]) {
|
|
||||||
_this[api] = browser[api]
|
|
||||||
}
|
|
||||||
} catch (e) {}
|
|
||||||
try {
|
|
||||||
_this.api = browser.extension[api]
|
|
||||||
} catch (e) {}
|
|
||||||
})
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (browser && browser.runtime) {
|
|
||||||
this.runtime = browser.runtime
|
|
||||||
}
|
|
||||||
} catch (e) {}
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (browser && browser.browserAction) {
|
|
||||||
this.browserAction = browser.browserAction
|
|
||||||
}
|
|
||||||
} catch (e) {}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = Extension
|
|
@ -1,14 +0,0 @@
|
|||||||
/* Extension.js
|
|
||||||
*
|
|
||||||
* A module for unifying browser differences in the WebExtension API.
|
|
||||||
*
|
|
||||||
* Initially implemented because Chrome hides all of their WebExtension API
|
|
||||||
* behind a global `chrome` variable, but we'd like to start grooming
|
|
||||||
* the code-base for cross-browser extension support.
|
|
||||||
*
|
|
||||||
* You can read more about the WebExtension API here:
|
|
||||||
* https://developer.mozilla.org/en-US/Add-ons/WebExtensions
|
|
||||||
*/
|
|
||||||
|
|
||||||
const Extension = require('./extension-instance')
|
|
||||||
module.exports = new Extension()
|
|
7
app/scripts/lib/hex-to-bn.js
Normal file
7
app/scripts/lib/hex-to-bn.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
const ethUtil = require('ethereumjs-util')
|
||||||
|
const BN = ethUtil.BN
|
||||||
|
|
||||||
|
module.exports = function hexToBn (hex) {
|
||||||
|
return new BN(ethUtil.stripHexPrefix(hex), 16)
|
||||||
|
}
|
||||||
|
|
@ -1,90 +0,0 @@
|
|||||||
/* ID Management
|
|
||||||
*
|
|
||||||
* This module exists to hold the decrypted credentials for the current session.
|
|
||||||
* It therefore exposes sign methods, because it is able to perform these
|
|
||||||
* with noa dditional authentication, because its very instantiation
|
|
||||||
* means the vault is unlocked.
|
|
||||||
*/
|
|
||||||
|
|
||||||
const ethUtil = require('ethereumjs-util')
|
|
||||||
const Transaction = require('ethereumjs-tx')
|
|
||||||
|
|
||||||
module.exports = IdManagement
|
|
||||||
|
|
||||||
function IdManagement (opts) {
|
|
||||||
if (!opts) opts = {}
|
|
||||||
|
|
||||||
this.keyStore = opts.keyStore
|
|
||||||
this.derivedKey = opts.derivedKey
|
|
||||||
this.configManager = opts.configManager
|
|
||||||
this.hdPathString = "m/44'/60'/0'/0"
|
|
||||||
|
|
||||||
this.getAddresses = function () {
|
|
||||||
return this.keyStore.getAddresses(this.hdPathString).map(function (address) { return '0x' + address })
|
|
||||||
}
|
|
||||||
|
|
||||||
this.signTx = function (txParams) {
|
|
||||||
|
|
||||||
// normalize values
|
|
||||||
txParams.gasPrice = ethUtil.intToHex(txParams.gasPrice)
|
|
||||||
txParams.to = ethUtil.addHexPrefix(txParams.to)
|
|
||||||
txParams.from = ethUtil.addHexPrefix(txParams.from.toLowerCase())
|
|
||||||
txParams.value = ethUtil.addHexPrefix(txParams.value)
|
|
||||||
txParams.data = ethUtil.addHexPrefix(txParams.data)
|
|
||||||
txParams.gasLimit = ethUtil.addHexPrefix(txParams.gasLimit || txParams.gas)
|
|
||||||
txParams.nonce = ethUtil.addHexPrefix(txParams.nonce)
|
|
||||||
var tx = new Transaction(txParams)
|
|
||||||
|
|
||||||
// sign tx
|
|
||||||
var privKeyHex = this.exportPrivateKey(txParams.from)
|
|
||||||
var privKey = ethUtil.toBuffer(privKeyHex)
|
|
||||||
tx.sign(privKey)
|
|
||||||
|
|
||||||
// Add the tx hash to the persisted meta-tx object
|
|
||||||
var txHash = ethUtil.bufferToHex(tx.hash())
|
|
||||||
var metaTx = this.configManager.getTx(txParams.metamaskId)
|
|
||||||
metaTx.hash = txHash
|
|
||||||
this.configManager.updateTx(metaTx)
|
|
||||||
|
|
||||||
// return raw serialized tx
|
|
||||||
var rawTx = ethUtil.bufferToHex(tx.serialize())
|
|
||||||
return rawTx
|
|
||||||
}
|
|
||||||
|
|
||||||
this.signMsg = function (address, message) {
|
|
||||||
// sign message
|
|
||||||
var privKeyHex = this.exportPrivateKey(address.toLowerCase())
|
|
||||||
var privKey = ethUtil.toBuffer(privKeyHex)
|
|
||||||
var msgSig = ethUtil.ecsign(new Buffer(message.replace('0x', ''), 'hex'), privKey)
|
|
||||||
var rawMsgSig = ethUtil.bufferToHex(concatSig(msgSig.v, msgSig.r, msgSig.s))
|
|
||||||
return rawMsgSig
|
|
||||||
}
|
|
||||||
|
|
||||||
this.getSeed = function () {
|
|
||||||
return this.keyStore.getSeed(this.derivedKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
this.exportPrivateKey = function (address) {
|
|
||||||
var privKeyHex = ethUtil.addHexPrefix(this.keyStore.exportPrivateKey(address, this.derivedKey, this.hdPathString))
|
|
||||||
return privKeyHex
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function padWithZeroes (number, length) {
|
|
||||||
var myString = '' + number
|
|
||||||
while (myString.length < length) {
|
|
||||||
myString = '0' + myString
|
|
||||||
}
|
|
||||||
return myString
|
|
||||||
}
|
|
||||||
|
|
||||||
function concatSig (v, r, s) {
|
|
||||||
const rSig = ethUtil.fromSigned(r)
|
|
||||||
const sSig = ethUtil.fromSigned(s)
|
|
||||||
const vSig = ethUtil.bufferToInt(v)
|
|
||||||
const rStr = padWithZeroes(ethUtil.toUnsigned(rSig).toString('hex'), 64)
|
|
||||||
const sStr = padWithZeroes(ethUtil.toUnsigned(sSig).toString('hex'), 64)
|
|
||||||
const vStr = ethUtil.stripHexPrefix(ethUtil.intToHex(vSig))
|
|
||||||
return ethUtil.addHexPrefix(rStr.concat(sStr, vStr)).toString('hex')
|
|
||||||
}
|
|
||||||
|
|
@ -1,80 +0,0 @@
|
|||||||
const IdentityStore = require('./idStore')
|
|
||||||
const HdKeyring = require('eth-hd-keyring')
|
|
||||||
const sigUtil = require('eth-sig-util')
|
|
||||||
const normalize = sigUtil.normalize
|
|
||||||
const denodeify = require('denodeify')
|
|
||||||
|
|
||||||
module.exports = class IdentityStoreMigrator {
|
|
||||||
|
|
||||||
constructor ({ configManager }) {
|
|
||||||
this.configManager = configManager
|
|
||||||
const hasOldVault = this.hasOldVault()
|
|
||||||
if (!hasOldVault) {
|
|
||||||
this.idStore = new IdentityStore({ configManager })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
migratedVaultForPassword (password) {
|
|
||||||
const hasOldVault = this.hasOldVault()
|
|
||||||
const configManager = this.configManager
|
|
||||||
|
|
||||||
if (!this.idStore) {
|
|
||||||
this.idStore = new IdentityStore({ configManager })
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!hasOldVault) {
|
|
||||||
return Promise.resolve(null)
|
|
||||||
}
|
|
||||||
|
|
||||||
const idStore = this.idStore
|
|
||||||
const submitPassword = denodeify(idStore.submitPassword.bind(idStore))
|
|
||||||
|
|
||||||
return submitPassword(password)
|
|
||||||
.then(() => {
|
|
||||||
const serialized = this.serializeVault()
|
|
||||||
return this.checkForLostAccounts(serialized)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
serializeVault () {
|
|
||||||
const mnemonic = this.idStore._idmgmt.getSeed()
|
|
||||||
const numberOfAccounts = this.idStore._getAddresses().length
|
|
||||||
|
|
||||||
return {
|
|
||||||
type: 'HD Key Tree',
|
|
||||||
data: { mnemonic, numberOfAccounts },
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
checkForLostAccounts (serialized) {
|
|
||||||
const hd = new HdKeyring()
|
|
||||||
return hd.deserialize(serialized.data)
|
|
||||||
.then((hexAccounts) => {
|
|
||||||
const newAccounts = hexAccounts.map(normalize)
|
|
||||||
const oldAccounts = this.idStore._getAddresses().map(normalize)
|
|
||||||
const lostAccounts = oldAccounts.reduce((result, account) => {
|
|
||||||
if (newAccounts.includes(account)) {
|
|
||||||
return result
|
|
||||||
} else {
|
|
||||||
result.push(account)
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
return {
|
|
||||||
serialized,
|
|
||||||
lostAccounts: lostAccounts.map((address) => {
|
|
||||||
return {
|
|
||||||
address,
|
|
||||||
privateKey: this.idStore.exportAccount(address),
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
hasOldVault () {
|
|
||||||
const wallet = this.configManager.getWallet()
|
|
||||||
return wallet
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,343 +0,0 @@
|
|||||||
const EventEmitter = require('events').EventEmitter
|
|
||||||
const inherits = require('util').inherits
|
|
||||||
const ethUtil = require('ethereumjs-util')
|
|
||||||
const KeyStore = require('eth-lightwallet').keystore
|
|
||||||
const clone = require('clone')
|
|
||||||
const extend = require('xtend')
|
|
||||||
const autoFaucet = require('./auto-faucet')
|
|
||||||
const DEFAULT_RPC = 'https://testrpc.metamask.io/'
|
|
||||||
const IdManagement = require('./id-management')
|
|
||||||
|
|
||||||
|
|
||||||
module.exports = IdentityStore
|
|
||||||
|
|
||||||
inherits(IdentityStore, EventEmitter)
|
|
||||||
function IdentityStore (opts = {}) {
|
|
||||||
EventEmitter.call(this)
|
|
||||||
|
|
||||||
// we just use the ethStore to auto-add accounts
|
|
||||||
this._ethStore = opts.ethStore
|
|
||||||
this.configManager = opts.configManager
|
|
||||||
// lightwallet key store
|
|
||||||
this._keyStore = null
|
|
||||||
// lightwallet wrapper
|
|
||||||
this._idmgmt = null
|
|
||||||
|
|
||||||
this.hdPathString = "m/44'/60'/0'/0"
|
|
||||||
|
|
||||||
this._currentState = {
|
|
||||||
selectedAddress: null,
|
|
||||||
identities: {},
|
|
||||||
}
|
|
||||||
// not part of serilized metamask state - only kept in memory
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// public
|
|
||||||
//
|
|
||||||
|
|
||||||
IdentityStore.prototype.createNewVault = function (password, cb) {
|
|
||||||
delete this._keyStore
|
|
||||||
var serializedKeystore = this.configManager.getWallet()
|
|
||||||
|
|
||||||
if (serializedKeystore) {
|
|
||||||
this.configManager.setData({})
|
|
||||||
}
|
|
||||||
|
|
||||||
this.purgeCache()
|
|
||||||
this._createVault(password, null, (err) => {
|
|
||||||
if (err) return cb(err)
|
|
||||||
|
|
||||||
this._autoFaucet()
|
|
||||||
|
|
||||||
this.configManager.setShowSeedWords(true)
|
|
||||||
var seedWords = this._idmgmt.getSeed()
|
|
||||||
|
|
||||||
this._loadIdentities()
|
|
||||||
|
|
||||||
cb(null, seedWords)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
IdentityStore.prototype.recoverSeed = function (cb) {
|
|
||||||
this.configManager.setShowSeedWords(true)
|
|
||||||
if (!this._idmgmt) return cb(new Error('Unauthenticated. Please sign in.'))
|
|
||||||
var seedWords = this._idmgmt.getSeed()
|
|
||||||
cb(null, seedWords)
|
|
||||||
}
|
|
||||||
|
|
||||||
IdentityStore.prototype.recoverFromSeed = function (password, seed, cb) {
|
|
||||||
this.purgeCache()
|
|
||||||
|
|
||||||
this._createVault(password, seed, (err) => {
|
|
||||||
if (err) return cb(err)
|
|
||||||
|
|
||||||
this._loadIdentities()
|
|
||||||
cb(null, this.getState())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
IdentityStore.prototype.setStore = function (store) {
|
|
||||||
this._ethStore = store
|
|
||||||
}
|
|
||||||
|
|
||||||
IdentityStore.prototype.clearSeedWordCache = function (cb) {
|
|
||||||
const configManager = this.configManager
|
|
||||||
configManager.setShowSeedWords(false)
|
|
||||||
cb(null, configManager.getSelectedAccount())
|
|
||||||
}
|
|
||||||
|
|
||||||
IdentityStore.prototype.getState = function () {
|
|
||||||
const configManager = this.configManager
|
|
||||||
var seedWords = this.getSeedIfUnlocked()
|
|
||||||
return clone(extend(this._currentState, {
|
|
||||||
isInitialized: !!configManager.getWallet() && !seedWords,
|
|
||||||
isUnlocked: this._isUnlocked(),
|
|
||||||
seedWords: seedWords,
|
|
||||||
selectedAddress: configManager.getSelectedAccount(),
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
IdentityStore.prototype.getSeedIfUnlocked = function () {
|
|
||||||
const configManager = this.configManager
|
|
||||||
var showSeed = configManager.getShouldShowSeedWords()
|
|
||||||
var idmgmt = this._idmgmt
|
|
||||||
var shouldShow = showSeed && !!idmgmt
|
|
||||||
var seedWords = shouldShow ? idmgmt.getSeed() : null
|
|
||||||
return seedWords
|
|
||||||
}
|
|
||||||
|
|
||||||
IdentityStore.prototype.getSelectedAddress = function () {
|
|
||||||
const configManager = this.configManager
|
|
||||||
return configManager.getSelectedAccount()
|
|
||||||
}
|
|
||||||
|
|
||||||
IdentityStore.prototype.setSelectedAddressSync = function (address) {
|
|
||||||
const configManager = this.configManager
|
|
||||||
if (!address) {
|
|
||||||
var addresses = this._getAddresses()
|
|
||||||
address = addresses[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
configManager.setSelectedAccount(address)
|
|
||||||
return address
|
|
||||||
}
|
|
||||||
|
|
||||||
IdentityStore.prototype.setSelectedAddress = function (address, cb) {
|
|
||||||
const resultAddress = this.setSelectedAddressSync(address)
|
|
||||||
if (cb) return cb(null, resultAddress)
|
|
||||||
}
|
|
||||||
|
|
||||||
IdentityStore.prototype.revealAccount = function (cb) {
|
|
||||||
const derivedKey = this._idmgmt.derivedKey
|
|
||||||
const keyStore = this._keyStore
|
|
||||||
const configManager = this.configManager
|
|
||||||
|
|
||||||
keyStore.setDefaultHdDerivationPath(this.hdPathString)
|
|
||||||
keyStore.generateNewAddress(derivedKey, 1)
|
|
||||||
const addresses = keyStore.getAddresses()
|
|
||||||
const address = addresses[ addresses.length - 1 ]
|
|
||||||
|
|
||||||
this._ethStore.addAccount(ethUtil.addHexPrefix(address))
|
|
||||||
|
|
||||||
configManager.setWallet(keyStore.serialize())
|
|
||||||
|
|
||||||
this._loadIdentities()
|
|
||||||
this._didUpdate()
|
|
||||||
cb(null)
|
|
||||||
}
|
|
||||||
|
|
||||||
IdentityStore.prototype.getNetwork = function (err) {
|
|
||||||
if (err) {
|
|
||||||
this._currentState.network = 'loading'
|
|
||||||
this._didUpdate()
|
|
||||||
}
|
|
||||||
|
|
||||||
this.web3.version.getNetwork((err, network) => {
|
|
||||||
if (err) {
|
|
||||||
this._currentState.network = 'loading'
|
|
||||||
return this._didUpdate()
|
|
||||||
}
|
|
||||||
if (global.METAMASK_DEBUG) {
|
|
||||||
console.log('web3.getNetwork returned ' + network)
|
|
||||||
}
|
|
||||||
this._currentState.network = network
|
|
||||||
this._didUpdate()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
IdentityStore.prototype.setLocked = function (cb) {
|
|
||||||
delete this._keyStore
|
|
||||||
delete this._idmgmt
|
|
||||||
cb()
|
|
||||||
}
|
|
||||||
|
|
||||||
IdentityStore.prototype.submitPassword = function (password, cb) {
|
|
||||||
const configManager = this.configManager
|
|
||||||
this.tryPassword(password, (err) => {
|
|
||||||
if (err) return cb(err)
|
|
||||||
// load identities before returning...
|
|
||||||
this._loadIdentities()
|
|
||||||
cb(null, configManager.getSelectedAccount())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
IdentityStore.prototype.exportAccount = function (address, cb) {
|
|
||||||
var privateKey = this._idmgmt.exportPrivateKey(address)
|
|
||||||
if (cb) cb(null, privateKey)
|
|
||||||
return privateKey
|
|
||||||
}
|
|
||||||
|
|
||||||
// private
|
|
||||||
//
|
|
||||||
|
|
||||||
IdentityStore.prototype._didUpdate = function () {
|
|
||||||
this.emit('update', this.getState())
|
|
||||||
}
|
|
||||||
|
|
||||||
IdentityStore.prototype._isUnlocked = function () {
|
|
||||||
var result = Boolean(this._keyStore) && Boolean(this._idmgmt)
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// load identities from keyStoreet
|
|
||||||
IdentityStore.prototype._loadIdentities = function () {
|
|
||||||
const configManager = this.configManager
|
|
||||||
if (!this._isUnlocked()) throw new Error('not unlocked')
|
|
||||||
|
|
||||||
var addresses = this._getAddresses()
|
|
||||||
addresses.forEach((address, i) => {
|
|
||||||
// // add to ethStore
|
|
||||||
if (this._ethStore) {
|
|
||||||
this._ethStore.addAccount(ethUtil.addHexPrefix(address))
|
|
||||||
}
|
|
||||||
// add to identities
|
|
||||||
const defaultLabel = 'Account ' + (i + 1)
|
|
||||||
const nickname = configManager.nicknameForWallet(address)
|
|
||||||
var identity = {
|
|
||||||
name: nickname || defaultLabel,
|
|
||||||
address: address,
|
|
||||||
mayBeFauceting: this._mayBeFauceting(i),
|
|
||||||
}
|
|
||||||
this._currentState.identities[address] = identity
|
|
||||||
})
|
|
||||||
this._didUpdate()
|
|
||||||
}
|
|
||||||
|
|
||||||
IdentityStore.prototype.saveAccountLabel = function (account, label, cb) {
|
|
||||||
const configManager = this.configManager
|
|
||||||
configManager.setNicknameForWallet(account, label)
|
|
||||||
this._loadIdentities()
|
|
||||||
cb(null, label)
|
|
||||||
}
|
|
||||||
|
|
||||||
// mayBeFauceting
|
|
||||||
// If on testnet, index 0 may be fauceting.
|
|
||||||
// The UI will have to check the balance to know.
|
|
||||||
// If there is no balance and it mayBeFauceting,
|
|
||||||
// then it is in fact fauceting.
|
|
||||||
IdentityStore.prototype._mayBeFauceting = function (i) {
|
|
||||||
const configManager = this.configManager
|
|
||||||
var config = configManager.getProvider()
|
|
||||||
if (i === 0 &&
|
|
||||||
config.type === 'rpc' &&
|
|
||||||
config.rpcTarget === DEFAULT_RPC) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// keyStore managment - unlocking + deserialization
|
|
||||||
//
|
|
||||||
|
|
||||||
IdentityStore.prototype.tryPassword = function (password, cb) {
|
|
||||||
var serializedKeystore = this.configManager.getWallet()
|
|
||||||
var keyStore = KeyStore.deserialize(serializedKeystore)
|
|
||||||
|
|
||||||
keyStore.keyFromPassword(password, (err, pwDerivedKey) => {
|
|
||||||
if (err) return cb(err)
|
|
||||||
|
|
||||||
const isCorrect = keyStore.isDerivedKeyCorrect(pwDerivedKey)
|
|
||||||
if (!isCorrect) return cb(new Error('Lightwallet - password incorrect'))
|
|
||||||
|
|
||||||
this._keyStore = keyStore
|
|
||||||
this._createIdMgmt(pwDerivedKey)
|
|
||||||
cb()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
IdentityStore.prototype._createVault = function (password, seedPhrase, cb) {
|
|
||||||
const opts = {
|
|
||||||
password,
|
|
||||||
hdPathString: this.hdPathString,
|
|
||||||
}
|
|
||||||
|
|
||||||
if (seedPhrase) {
|
|
||||||
opts.seedPhrase = seedPhrase
|
|
||||||
}
|
|
||||||
|
|
||||||
KeyStore.createVault(opts, (err, keyStore) => {
|
|
||||||
if (err) return cb(err)
|
|
||||||
|
|
||||||
this._keyStore = keyStore
|
|
||||||
|
|
||||||
keyStore.keyFromPassword(password, (err, derivedKey) => {
|
|
||||||
if (err) return cb(err)
|
|
||||||
|
|
||||||
this.purgeCache()
|
|
||||||
|
|
||||||
keyStore.addHdDerivationPath(this.hdPathString, derivedKey, {curve: 'secp256k1', purpose: 'sign'})
|
|
||||||
|
|
||||||
this._createFirstWallet(derivedKey)
|
|
||||||
this._createIdMgmt(derivedKey)
|
|
||||||
this.setSelectedAddressSync()
|
|
||||||
|
|
||||||
cb()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
IdentityStore.prototype._createIdMgmt = function (derivedKey) {
|
|
||||||
this._idmgmt = new IdManagement({
|
|
||||||
keyStore: this._keyStore,
|
|
||||||
derivedKey: derivedKey,
|
|
||||||
configManager: this.configManager,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
IdentityStore.prototype.purgeCache = function () {
|
|
||||||
this._currentState.identities = {}
|
|
||||||
let accounts
|
|
||||||
try {
|
|
||||||
accounts = Object.keys(this._ethStore._currentState.accounts)
|
|
||||||
} catch (e) {
|
|
||||||
accounts = []
|
|
||||||
}
|
|
||||||
accounts.forEach((address) => {
|
|
||||||
this._ethStore.removeAccount(address)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
IdentityStore.prototype._createFirstWallet = function (derivedKey) {
|
|
||||||
const keyStore = this._keyStore
|
|
||||||
keyStore.setDefaultHdDerivationPath(this.hdPathString)
|
|
||||||
keyStore.generateNewAddress(derivedKey, 1)
|
|
||||||
this.configManager.setWallet(keyStore.serialize())
|
|
||||||
var addresses = keyStore.getAddresses()
|
|
||||||
this._ethStore.addAccount(ethUtil.addHexPrefix(addresses[0]))
|
|
||||||
}
|
|
||||||
|
|
||||||
// get addresses and normalize address hexString
|
|
||||||
IdentityStore.prototype._getAddresses = function () {
|
|
||||||
return this._keyStore.getAddresses(this.hdPathString).map((address) => {
|
|
||||||
return ethUtil.addHexPrefix(address)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
IdentityStore.prototype._autoFaucet = function () {
|
|
||||||
var addresses = this._getAddresses()
|
|
||||||
autoFaucet(addresses[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
// util
|
|
@ -34,6 +34,7 @@ function MetamaskInpageProvider (connectionStream) {
|
|||||||
asyncProvider,
|
asyncProvider,
|
||||||
(err) => logStreamDisconnectWarning('MetaMask RpcProvider', err)
|
(err) => logStreamDisconnectWarning('MetaMask RpcProvider', err)
|
||||||
)
|
)
|
||||||
|
// start and stop polling to unblock first block lock
|
||||||
|
|
||||||
self.idMap = {}
|
self.idMap = {}
|
||||||
// handle sendAsync requests via asyncProvider
|
// handle sendAsync requests via asyncProvider
|
||||||
@ -85,7 +86,7 @@ MetamaskInpageProvider.prototype.send = function (payload) {
|
|||||||
break
|
break
|
||||||
|
|
||||||
case 'net_version':
|
case 'net_version':
|
||||||
let networkVersion = self.publicConfigStore.getState().networkVersion
|
const networkVersion = self.publicConfigStore.getState().networkVersion
|
||||||
result = networkVersion
|
result = networkVersion
|
||||||
break
|
break
|
||||||
|
|
||||||
|
@ -1,42 +1,35 @@
|
|||||||
const asyncQ = require('async-q')
|
|
||||||
|
|
||||||
class Migrator {
|
class Migrator {
|
||||||
|
|
||||||
constructor (opts = {}) {
|
constructor (opts = {}) {
|
||||||
let migrations = opts.migrations || []
|
const migrations = opts.migrations || []
|
||||||
|
// sort migrations by version
|
||||||
this.migrations = migrations.sort((a, b) => a.version - b.version)
|
this.migrations = migrations.sort((a, b) => a.version - b.version)
|
||||||
let lastMigration = this.migrations.slice(-1)[0]
|
// grab migration with highest version
|
||||||
|
const lastMigration = this.migrations.slice(-1)[0]
|
||||||
// use specified defaultVersion or highest migration version
|
// use specified defaultVersion or highest migration version
|
||||||
this.defaultVersion = opts.defaultVersion || (lastMigration && lastMigration.version) || 0
|
this.defaultVersion = opts.defaultVersion || (lastMigration && lastMigration.version) || 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// run all pending migrations on meta in place
|
// run all pending migrations on meta in place
|
||||||
migrateData (versionedData = this.generateInitialState()) {
|
async migrateData (versionedData = this.generateInitialState()) {
|
||||||
let remaining = this.migrations.filter(migrationIsPending)
|
const pendingMigrations = this.migrations.filter(migrationIsPending)
|
||||||
|
|
||||||
return (
|
for (let index in pendingMigrations) {
|
||||||
asyncQ.eachSeries(remaining, (migration) => this.runMigration(versionedData, migration))
|
let migration = pendingMigrations[index]
|
||||||
.then(() => versionedData)
|
versionedData = await migration.migrate(versionedData)
|
||||||
)
|
if (!versionedData.data) throw new Error('Migrator - migration returned empty data')
|
||||||
|
if (versionedData.version !== undefined && versionedData.meta.version !== migration.version) throw new Error('Migrator - Migration did not update version number correctly')
|
||||||
|
}
|
||||||
|
|
||||||
// migration is "pending" if hit has a higher
|
return versionedData
|
||||||
|
|
||||||
|
// migration is "pending" if it has a higher
|
||||||
// version number than currentVersion
|
// version number than currentVersion
|
||||||
function migrationIsPending (migration) {
|
function migrationIsPending (migration) {
|
||||||
return migration.version > versionedData.meta.version
|
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) {
|
generateInitialState (initState) {
|
||||||
return {
|
return {
|
||||||
meta: {
|
meta: {
|
||||||
|
71
app/scripts/lib/notification-manager.js
Normal file
71
app/scripts/lib/notification-manager.js
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
const extension = require('extensionizer')
|
||||||
|
const height = 520
|
||||||
|
const width = 360
|
||||||
|
|
||||||
|
|
||||||
|
class NotificationManager {
|
||||||
|
|
||||||
|
//
|
||||||
|
// Public
|
||||||
|
//
|
||||||
|
|
||||||
|
showPopup () {
|
||||||
|
this._getPopup((err, popup) => {
|
||||||
|
if (err) throw err
|
||||||
|
|
||||||
|
if (popup) {
|
||||||
|
// bring focus to existing popup
|
||||||
|
extension.windows.update(popup.id, { focused: true })
|
||||||
|
} else {
|
||||||
|
// create new popup
|
||||||
|
extension.windows.create({
|
||||||
|
url: 'notification.html',
|
||||||
|
type: 'popup',
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
closePopup () {
|
||||||
|
this._getPopup((err, popup) => {
|
||||||
|
if (err) throw err
|
||||||
|
if (!popup) return
|
||||||
|
extension.windows.remove(popup.id, console.error)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Private
|
||||||
|
//
|
||||||
|
|
||||||
|
_getPopup (cb) {
|
||||||
|
this._getWindows((err, windows) => {
|
||||||
|
if (err) throw err
|
||||||
|
cb(null, this._getPopupIn(windows))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
_getWindows (cb) {
|
||||||
|
// Ignore in test environment
|
||||||
|
if (!extension.windows) {
|
||||||
|
return cb()
|
||||||
|
}
|
||||||
|
|
||||||
|
extension.windows.getAll({}, (windows) => {
|
||||||
|
cb(null, windows)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
_getPopupIn (windows) {
|
||||||
|
return windows ? windows.find((win) => {
|
||||||
|
return (win && win.type === 'popup' &&
|
||||||
|
win.height === height &&
|
||||||
|
win.width === width)
|
||||||
|
}) : null
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = NotificationManager
|
@ -1,65 +0,0 @@
|
|||||||
const extension = require('./extension')
|
|
||||||
const height = 520
|
|
||||||
const width = 360
|
|
||||||
|
|
||||||
const notifications = {
|
|
||||||
show,
|
|
||||||
getPopup,
|
|
||||||
closePopup,
|
|
||||||
}
|
|
||||||
module.exports = notifications
|
|
||||||
window.METAMASK_NOTIFIER = notifications
|
|
||||||
|
|
||||||
function show () {
|
|
||||||
getPopup((err, popup) => {
|
|
||||||
if (err) throw err
|
|
||||||
|
|
||||||
if (popup) {
|
|
||||||
// bring focus to existing popup
|
|
||||||
extension.windows.update(popup.id, { focused: true })
|
|
||||||
} else {
|
|
||||||
// create new popup
|
|
||||||
extension.windows.create({
|
|
||||||
url: 'notification.html',
|
|
||||||
type: 'popup',
|
|
||||||
focused: true,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function getWindows (cb) {
|
|
||||||
// Ignore in test environment
|
|
||||||
if (!extension.windows) {
|
|
||||||
return cb()
|
|
||||||
}
|
|
||||||
|
|
||||||
extension.windows.getAll({}, (windows) => {
|
|
||||||
cb(null, windows)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function getPopup (cb) {
|
|
||||||
getWindows((err, windows) => {
|
|
||||||
if (err) throw err
|
|
||||||
cb(null, getPopupIn(windows))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function getPopupIn (windows) {
|
|
||||||
return windows ? windows.find((win) => {
|
|
||||||
return (win && win.type === 'popup' &&
|
|
||||||
win.height === height &&
|
|
||||||
win.width === width)
|
|
||||||
}) : null
|
|
||||||
}
|
|
||||||
|
|
||||||
function closePopup () {
|
|
||||||
getPopup((err, popup) => {
|
|
||||||
if (err) throw err
|
|
||||||
if (!popup) return
|
|
||||||
extension.windows.remove(popup.id, console.error)
|
|
||||||
})
|
|
||||||
}
|
|
@ -1,5 +1,4 @@
|
|||||||
const async = require('async')
|
const async = require('async')
|
||||||
const EthQuery = require('eth-query')
|
|
||||||
const ethUtil = require('ethereumjs-util')
|
const ethUtil = require('ethereumjs-util')
|
||||||
const Transaction = require('ethereumjs-tx')
|
const Transaction = require('ethereumjs-tx')
|
||||||
const normalize = require('eth-sig-util').normalize
|
const normalize = require('eth-sig-util').normalize
|
||||||
@ -7,53 +6,55 @@ const BN = ethUtil.BN
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
tx-utils are utility methods for Transaction manager
|
tx-utils are utility methods for Transaction manager
|
||||||
its passed a provider and that is passed to ethquery
|
its passed ethquery
|
||||||
and used to do things like calculate gas of a tx.
|
and used to do things like calculate gas of a tx.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
module.exports = class txProviderUtils {
|
module.exports = class txProviderUtils {
|
||||||
constructor (provider) {
|
|
||||||
this.provider = provider
|
constructor (ethQuery) {
|
||||||
this.query = new EthQuery(provider)
|
this.query = ethQuery
|
||||||
}
|
}
|
||||||
|
|
||||||
analyzeGasUsage (txData, cb) {
|
analyzeGasUsage (txMeta, cb) {
|
||||||
var self = this
|
var self = this
|
||||||
this.query.getBlockByNumber('latest', true, (err, block) => {
|
this.query.getBlockByNumber('latest', true, (err, block) => {
|
||||||
if (err) return cb(err)
|
if (err) return cb(err)
|
||||||
async.waterfall([
|
async.waterfall([
|
||||||
self.estimateTxGas.bind(self, txData, block.gasLimit),
|
self.estimateTxGas.bind(self, txMeta, block.gasLimit),
|
||||||
self.setTxGas.bind(self, txData, block.gasLimit),
|
self.setTxGas.bind(self, txMeta, block.gasLimit),
|
||||||
], cb)
|
], cb)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
estimateTxGas (txData, blockGasLimitHex, cb) {
|
estimateTxGas (txMeta, blockGasLimitHex, cb) {
|
||||||
const txParams = txData.txParams
|
const txParams = txMeta.txParams
|
||||||
// check if gasLimit is already specified
|
// check if gasLimit is already specified
|
||||||
txData.gasLimitSpecified = Boolean(txParams.gas)
|
txMeta.gasLimitSpecified = Boolean(txParams.gas)
|
||||||
// if not, fallback to block gasLimit
|
// if not, fallback to block gasLimit
|
||||||
if (!txData.gasLimitSpecified) {
|
if (!txMeta.gasLimitSpecified) {
|
||||||
txParams.gas = blockGasLimitHex
|
const blockGasLimitBN = hexToBn(blockGasLimitHex)
|
||||||
|
const saferGasLimitBN = BnMultiplyByFraction(blockGasLimitBN, 19, 20)
|
||||||
|
txParams.gas = bnToHex(saferGasLimitBN)
|
||||||
}
|
}
|
||||||
// run tx, see if it will OOG
|
// run tx, see if it will OOG
|
||||||
this.query.estimateGas(txParams, cb)
|
this.query.estimateGas(txParams, cb)
|
||||||
}
|
}
|
||||||
|
|
||||||
setTxGas (txData, blockGasLimitHex, estimatedGasHex, cb) {
|
setTxGas (txMeta, blockGasLimitHex, estimatedGasHex, cb) {
|
||||||
txData.estimatedGas = estimatedGasHex
|
txMeta.estimatedGas = estimatedGasHex
|
||||||
const txParams = txData.txParams
|
const txParams = txMeta.txParams
|
||||||
|
|
||||||
// if gasLimit was specified and doesnt OOG,
|
// if gasLimit was specified and doesnt OOG,
|
||||||
// use original specified amount
|
// use original specified amount
|
||||||
if (txData.gasLimitSpecified) {
|
if (txMeta.gasLimitSpecified) {
|
||||||
txData.estimatedGas = txParams.gas
|
txMeta.estimatedGas = txParams.gas
|
||||||
cb()
|
cb()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// if gasLimit not originally specified,
|
// if gasLimit not originally specified,
|
||||||
// try adding an additional gas buffer to our estimation for safety
|
// try adding an additional gas buffer to our estimation for safety
|
||||||
const recommendedGasHex = this.addGasBuffer(txData.estimatedGas, blockGasLimitHex)
|
const recommendedGasHex = this.addGasBuffer(txMeta.estimatedGas, blockGasLimitHex)
|
||||||
txParams.gas = recommendedGasHex
|
txParams.gas = recommendedGasHex
|
||||||
cb()
|
cb()
|
||||||
return
|
return
|
||||||
@ -62,19 +63,20 @@ module.exports = class txProviderUtils {
|
|||||||
addGasBuffer (initialGasLimitHex, blockGasLimitHex) {
|
addGasBuffer (initialGasLimitHex, blockGasLimitHex) {
|
||||||
const initialGasLimitBn = hexToBn(initialGasLimitHex)
|
const initialGasLimitBn = hexToBn(initialGasLimitHex)
|
||||||
const blockGasLimitBn = hexToBn(blockGasLimitHex)
|
const blockGasLimitBn = hexToBn(blockGasLimitHex)
|
||||||
|
const upperGasLimitBn = blockGasLimitBn.muln(0.9)
|
||||||
const bufferedGasLimitBn = initialGasLimitBn.muln(1.5)
|
const bufferedGasLimitBn = initialGasLimitBn.muln(1.5)
|
||||||
|
|
||||||
// if initialGasLimit is above blockGasLimit, dont modify it
|
// if initialGasLimit is above blockGasLimit, dont modify it
|
||||||
if (initialGasLimitBn.gt(blockGasLimitBn)) return bnToHex(initialGasLimitBn)
|
if (initialGasLimitBn.gt(upperGasLimitBn)) return bnToHex(initialGasLimitBn)
|
||||||
// if bufferedGasLimit is below blockGasLimit, use bufferedGasLimit
|
// if bufferedGasLimit is below blockGasLimit, use bufferedGasLimit
|
||||||
if (bufferedGasLimitBn.lt(blockGasLimitBn)) return bnToHex(bufferedGasLimitBn)
|
if (bufferedGasLimitBn.lt(upperGasLimitBn)) return bnToHex(bufferedGasLimitBn)
|
||||||
// otherwise use blockGasLimit
|
// otherwise use blockGasLimit
|
||||||
return bnToHex(blockGasLimitBn)
|
return bnToHex(upperGasLimitBn)
|
||||||
}
|
}
|
||||||
|
|
||||||
fillInTxParams (txParams, cb) {
|
fillInTxParams (txParams, cb) {
|
||||||
let fromAddress = txParams.from
|
const fromAddress = txParams.from
|
||||||
let reqs = {}
|
const reqs = {}
|
||||||
|
|
||||||
if (isUndef(txParams.gas)) reqs.gas = (cb) => this.query.estimateGas(txParams, cb)
|
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.gasPrice)) reqs.gasPrice = (cb) => this.query.gasPrice(cb)
|
||||||
@ -90,16 +92,13 @@ module.exports = class txProviderUtils {
|
|||||||
|
|
||||||
// builds ethTx from txParams object
|
// builds ethTx from txParams object
|
||||||
buildEthTxFromParams (txParams) {
|
buildEthTxFromParams (txParams) {
|
||||||
// apply gas multiplyer
|
|
||||||
let gasPrice = hexToBn(txParams.gasPrice)
|
|
||||||
// multiply and divide by 100 so as to add percision to integer mul
|
|
||||||
txParams.gasPrice = ethUtil.intToHex(gasPrice.toNumber())
|
|
||||||
// normalize values
|
// normalize values
|
||||||
txParams.to = normalize(txParams.to)
|
txParams.to = normalize(txParams.to)
|
||||||
txParams.from = normalize(txParams.from)
|
txParams.from = normalize(txParams.from)
|
||||||
txParams.value = normalize(txParams.value)
|
txParams.value = normalize(txParams.value)
|
||||||
txParams.data = normalize(txParams.data)
|
txParams.data = normalize(txParams.data)
|
||||||
txParams.gasLimit = normalize(txParams.gasLimit || txParams.gas)
|
txParams.gas = normalize(txParams.gas || txParams.gasLimit)
|
||||||
|
txParams.gasPrice = normalize(txParams.gasPrice)
|
||||||
txParams.nonce = normalize(txParams.nonce)
|
txParams.nonce = normalize(txParams.nonce)
|
||||||
// build ethTx
|
// build ethTx
|
||||||
log.info(`Prepared tx for signing: ${JSON.stringify(txParams)}`)
|
log.info(`Prepared tx for signing: ${JSON.stringify(txParams)}`)
|
||||||
@ -135,3 +134,9 @@ function bnToHex(inputBn) {
|
|||||||
function hexToBn (inputHex) {
|
function hexToBn (inputHex) {
|
||||||
return new BN(ethUtil.stripHexPrefix(inputHex), 16)
|
return new BN(ethUtil.stripHexPrefix(inputHex), 16)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function BnMultiplyByFraction (targetBN, numerator, denominator) {
|
||||||
|
const numBN = new BN(numerator)
|
||||||
|
const denomBN = new BN(denominator)
|
||||||
|
return targetBN.mul(numBN).div(denomBN)
|
||||||
|
}
|
||||||
|
@ -4,13 +4,12 @@ const promiseToCallback = require('promise-to-callback')
|
|||||||
const pipe = require('pump')
|
const pipe = require('pump')
|
||||||
const Dnode = require('dnode')
|
const Dnode = require('dnode')
|
||||||
const ObservableStore = require('obs-store')
|
const ObservableStore = require('obs-store')
|
||||||
const storeTransform = require('obs-store/lib/transform')
|
|
||||||
const EthStore = require('./lib/eth-store')
|
const EthStore = require('./lib/eth-store')
|
||||||
const EthQuery = require('eth-query')
|
const EthQuery = require('eth-query')
|
||||||
const streamIntoProvider = require('web3-stream-provider/handler')
|
const streamIntoProvider = require('web3-stream-provider/handler')
|
||||||
const MetaMaskProvider = require('web3-provider-engine/zero.js')
|
|
||||||
const setupMultiplex = require('./lib/stream-utils.js').setupMultiplex
|
const setupMultiplex = require('./lib/stream-utils.js').setupMultiplex
|
||||||
const KeyringController = require('./keyring-controller')
|
const KeyringController = require('./keyring-controller')
|
||||||
|
const NetworkController = require('./controllers/network')
|
||||||
const PreferencesController = require('./controllers/preferences')
|
const PreferencesController = require('./controllers/preferences')
|
||||||
const CurrencyController = require('./controllers/currency')
|
const CurrencyController = require('./controllers/currency')
|
||||||
const NoticeController = require('./notice-controller')
|
const NoticeController = require('./notice-controller')
|
||||||
@ -18,13 +17,12 @@ const ShapeShiftController = require('./controllers/shapeshift')
|
|||||||
const AddressBookController = require('./controllers/address-book')
|
const AddressBookController = require('./controllers/address-book')
|
||||||
const MessageManager = require('./lib/message-manager')
|
const MessageManager = require('./lib/message-manager')
|
||||||
const PersonalMessageManager = require('./lib/personal-message-manager')
|
const PersonalMessageManager = require('./lib/personal-message-manager')
|
||||||
const TxManager = require('./transaction-manager')
|
const TransactionController = require('./controllers/transactions')
|
||||||
const ConfigManager = require('./lib/config-manager')
|
const ConfigManager = require('./lib/config-manager')
|
||||||
const extension = require('./lib/extension')
|
|
||||||
const autoFaucet = require('./lib/auto-faucet')
|
const autoFaucet = require('./lib/auto-faucet')
|
||||||
const nodeify = require('./lib/nodeify')
|
const nodeify = require('./lib/nodeify')
|
||||||
const IdStoreMigrator = require('./lib/idStore-migrator')
|
|
||||||
const accountImporter = require('./account-import-strategies')
|
const accountImporter = require('./account-import-strategies')
|
||||||
|
const getBuyEthUrl = require('./lib/buy-eth-url')
|
||||||
|
|
||||||
const version = require('../manifest.json').version
|
const version = require('../manifest.json').version
|
||||||
|
|
||||||
@ -33,14 +31,17 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||||||
constructor (opts) {
|
constructor (opts) {
|
||||||
super()
|
super()
|
||||||
this.opts = opts
|
this.opts = opts
|
||||||
let initState = opts.initState || {}
|
const initState = opts.initState || {}
|
||||||
|
|
||||||
|
// platform-specific api
|
||||||
|
this.platform = opts.platform
|
||||||
|
|
||||||
// observable state store
|
// observable state store
|
||||||
this.store = new ObservableStore(initState)
|
this.store = new ObservableStore(initState)
|
||||||
|
|
||||||
// network store
|
// network store
|
||||||
this.networkStore = new ObservableStore({ network: 'loading' })
|
|
||||||
|
|
||||||
|
this.networkController = new NetworkController(initState.NetworkController)
|
||||||
// config manager
|
// config manager
|
||||||
this.configManager = new ConfigManager({
|
this.configManager = new ConfigManager({
|
||||||
store: this.store,
|
store: this.store,
|
||||||
@ -60,8 +61,6 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||||||
|
|
||||||
// rpc provider
|
// rpc provider
|
||||||
this.provider = this.initializeProvider()
|
this.provider = this.initializeProvider()
|
||||||
this.provider.on('block', this.logBlock.bind(this))
|
|
||||||
this.provider.on('error', this.verifyNetwork.bind(this))
|
|
||||||
|
|
||||||
// eth data query tools
|
// eth data query tools
|
||||||
this.ethQuery = new EthQuery(this.provider)
|
this.ethQuery = new EthQuery(this.provider)
|
||||||
@ -74,10 +73,12 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||||||
this.keyringController = new KeyringController({
|
this.keyringController = new KeyringController({
|
||||||
initState: initState.KeyringController,
|
initState: initState.KeyringController,
|
||||||
ethStore: this.ethStore,
|
ethStore: this.ethStore,
|
||||||
getNetwork: this.getNetworkState.bind(this),
|
getNetwork: this.networkController.getNetworkState.bind(this.networkController),
|
||||||
})
|
})
|
||||||
this.keyringController.on('newAccount', (address) => {
|
this.keyringController.on('newAccount', (address) => {
|
||||||
this.preferencesController.setSelectedAddress(address)
|
this.preferencesController.setSelectedAddress(address)
|
||||||
|
})
|
||||||
|
this.keyringController.on('newVault', (address) => {
|
||||||
autoFaucet(address)
|
autoFaucet(address)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -87,15 +88,16 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||||||
}, this.keyringController)
|
}, this.keyringController)
|
||||||
|
|
||||||
// tx mgmt
|
// tx mgmt
|
||||||
this.txManager = new TxManager({
|
this.txController = new TransactionController({
|
||||||
initState: initState.TransactionManager,
|
initState: initState.TransactionController || initState.TransactionManager,
|
||||||
networkStore: this.networkStore,
|
networkStore: this.networkController.networkStore,
|
||||||
preferencesStore: this.preferencesController.store,
|
preferencesStore: this.preferencesController.store,
|
||||||
txHistoryLimit: 40,
|
txHistoryLimit: 40,
|
||||||
getNetwork: this.getNetworkState.bind(this),
|
getNetwork: this.networkController.getNetworkState.bind(this),
|
||||||
signTransaction: this.keyringController.signTransaction.bind(this.keyringController),
|
signTransaction: this.keyringController.signTransaction.bind(this.keyringController),
|
||||||
provider: this.provider,
|
provider: this.provider,
|
||||||
blockTracker: this.provider,
|
blockTracker: this.provider,
|
||||||
|
ethQuery: this.ethQuery,
|
||||||
})
|
})
|
||||||
|
|
||||||
// notices
|
// notices
|
||||||
@ -110,19 +112,14 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||||||
initState: initState.ShapeShiftController,
|
initState: initState.ShapeShiftController,
|
||||||
})
|
})
|
||||||
|
|
||||||
this.lookupNetwork()
|
this.networkController.lookupNetwork()
|
||||||
this.messageManager = new MessageManager()
|
this.messageManager = new MessageManager()
|
||||||
this.personalMessageManager = new PersonalMessageManager()
|
this.personalMessageManager = new PersonalMessageManager()
|
||||||
this.publicConfigStore = this.initPublicConfigStore()
|
this.publicConfigStore = this.initPublicConfigStore()
|
||||||
|
|
||||||
// TEMPORARY UNTIL FULL DEPRECATION:
|
|
||||||
this.idStoreMigrator = new IdStoreMigrator({
|
|
||||||
configManager: this.configManager,
|
|
||||||
})
|
|
||||||
|
|
||||||
// manual disk state subscriptions
|
// manual disk state subscriptions
|
||||||
this.txManager.store.subscribe((state) => {
|
this.txController.store.subscribe((state) => {
|
||||||
this.store.updateState({ TransactionManager: state })
|
this.store.updateState({ TransactionController: state })
|
||||||
})
|
})
|
||||||
this.keyringController.store.subscribe((state) => {
|
this.keyringController.store.subscribe((state) => {
|
||||||
this.store.updateState({ KeyringController: state })
|
this.store.updateState({ KeyringController: state })
|
||||||
@ -142,11 +139,14 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||||||
this.shapeshiftController.store.subscribe((state) => {
|
this.shapeshiftController.store.subscribe((state) => {
|
||||||
this.store.updateState({ ShapeShiftController: state })
|
this.store.updateState({ ShapeShiftController: state })
|
||||||
})
|
})
|
||||||
|
this.networkController.store.subscribe((state) => {
|
||||||
|
this.store.updateState({ NetworkController: state })
|
||||||
|
})
|
||||||
|
|
||||||
// manual mem state subscriptions
|
// manual mem state subscriptions
|
||||||
this.networkStore.subscribe(this.sendUpdate.bind(this))
|
this.networkController.store.subscribe(this.sendUpdate.bind(this))
|
||||||
this.ethStore.subscribe(this.sendUpdate.bind(this))
|
this.ethStore.subscribe(this.sendUpdate.bind(this))
|
||||||
this.txManager.memStore.subscribe(this.sendUpdate.bind(this))
|
this.txController.memStore.subscribe(this.sendUpdate.bind(this))
|
||||||
this.messageManager.memStore.subscribe(this.sendUpdate.bind(this))
|
this.messageManager.memStore.subscribe(this.sendUpdate.bind(this))
|
||||||
this.personalMessageManager.memStore.subscribe(this.sendUpdate.bind(this))
|
this.personalMessageManager.memStore.subscribe(this.sendUpdate.bind(this))
|
||||||
this.keyringController.memStore.subscribe(this.sendUpdate.bind(this))
|
this.keyringController.memStore.subscribe(this.sendUpdate.bind(this))
|
||||||
@ -162,17 +162,21 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||||||
//
|
//
|
||||||
|
|
||||||
initializeProvider () {
|
initializeProvider () {
|
||||||
|
return this.networkController.initializeProvider({
|
||||||
let provider = MetaMaskProvider({
|
|
||||||
static: {
|
static: {
|
||||||
eth_syncing: false,
|
eth_syncing: false,
|
||||||
web3_clientVersion: `MetaMask/v${version}`,
|
web3_clientVersion: `MetaMask/v${version}`,
|
||||||
},
|
},
|
||||||
rpcUrl: this.configManager.getCurrentRpcAddress(),
|
rpcUrl: this.networkController.getCurrentRpcAddress(),
|
||||||
// account mgmt
|
// account mgmt
|
||||||
getAccounts: (cb) => {
|
getAccounts: (cb) => {
|
||||||
let selectedAddress = this.preferencesController.getSelectedAddress()
|
const isUnlocked = this.keyringController.memStore.getState().isUnlocked
|
||||||
let result = selectedAddress ? [selectedAddress] : []
|
const result = []
|
||||||
|
const selectedAddress = this.preferencesController.getSelectedAddress()
|
||||||
|
// only show address if account is unlocked
|
||||||
|
if (isUnlocked && selectedAddress) {
|
||||||
|
result.push(selectedAddress)
|
||||||
|
}
|
||||||
cb(null, result)
|
cb(null, result)
|
||||||
},
|
},
|
||||||
// tx signing
|
// tx signing
|
||||||
@ -183,26 +187,23 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||||||
// new style msg signing
|
// new style msg signing
|
||||||
processPersonalMessage: this.newUnsignedPersonalMessage.bind(this),
|
processPersonalMessage: this.newUnsignedPersonalMessage.bind(this),
|
||||||
})
|
})
|
||||||
return provider
|
|
||||||
}
|
}
|
||||||
|
|
||||||
initPublicConfigStore () {
|
initPublicConfigStore () {
|
||||||
// get init state
|
// get init state
|
||||||
const publicConfigStore = new ObservableStore()
|
const publicConfigStore = new ObservableStore()
|
||||||
|
|
||||||
// sync publicConfigStore with transform
|
// memStore -> transform -> publicConfigStore
|
||||||
pipe(
|
this.on('update', (memState) => {
|
||||||
this.store,
|
const publicState = selectPublicState(memState)
|
||||||
storeTransform(selectPublicState.bind(this)),
|
publicConfigStore.putState(publicState)
|
||||||
publicConfigStore
|
})
|
||||||
)
|
|
||||||
|
|
||||||
function selectPublicState(state) {
|
function selectPublicState (memState) {
|
||||||
const result = { selectedAddress: undefined }
|
const result = {
|
||||||
try {
|
selectedAddress: memState.isUnlocked ? memState.selectedAddress : undefined,
|
||||||
result.selectedAddress = state.PreferencesController.selectedAddress
|
networkVersion: memState.network,
|
||||||
result.networkVersion = this.getNetworkState()
|
}
|
||||||
} catch (_) {}
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -214,7 +215,6 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||||||
//
|
//
|
||||||
|
|
||||||
getState () {
|
getState () {
|
||||||
|
|
||||||
const wallet = this.configManager.getWallet()
|
const wallet = this.configManager.getWallet()
|
||||||
const vault = this.keyringController.store.getState().vault
|
const vault = this.keyringController.store.getState().vault
|
||||||
const isInitialized = (!!wallet || !!vault)
|
const isInitialized = (!!wallet || !!vault)
|
||||||
@ -222,9 +222,9 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||||||
{
|
{
|
||||||
isInitialized,
|
isInitialized,
|
||||||
},
|
},
|
||||||
this.networkStore.getState(),
|
this.networkController.store.getState(),
|
||||||
this.ethStore.getState(),
|
this.ethStore.getState(),
|
||||||
this.txManager.memStore.getState(),
|
this.txController.memStore.getState(),
|
||||||
this.messageManager.memStore.getState(),
|
this.messageManager.memStore.getState(),
|
||||||
this.personalMessageManager.memStore.getState(),
|
this.personalMessageManager.memStore.getState(),
|
||||||
this.keyringController.memStore.getState(),
|
this.keyringController.memStore.getState(),
|
||||||
@ -249,15 +249,14 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||||||
getApi () {
|
getApi () {
|
||||||
const keyringController = this.keyringController
|
const keyringController = this.keyringController
|
||||||
const preferencesController = this.preferencesController
|
const preferencesController = this.preferencesController
|
||||||
const txManager = this.txManager
|
const txController = this.txController
|
||||||
const noticeController = this.noticeController
|
const noticeController = this.noticeController
|
||||||
const addressBookController = this.addressBookController
|
const addressBookController = this.addressBookController
|
||||||
|
|
||||||
return {
|
return {
|
||||||
// etc
|
// etc
|
||||||
getState: (cb) => cb(null, this.getState()),
|
getState: (cb) => cb(null, this.getState()),
|
||||||
setProviderType: this.setProviderType.bind(this),
|
setProviderType: this.networkController.setProviderType.bind(this.networkController),
|
||||||
useEtherscanProvider: this.useEtherscanProvider.bind(this),
|
|
||||||
setCurrentCurrency: this.setCurrentCurrency.bind(this),
|
setCurrentCurrency: this.setCurrentCurrency.bind(this),
|
||||||
markAccountsFound: this.markAccountsFound.bind(this),
|
markAccountsFound: this.markAccountsFound.bind(this),
|
||||||
// coinbase
|
// coinbase
|
||||||
@ -290,9 +289,9 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||||||
saveAccountLabel: nodeify(keyringController.saveAccountLabel).bind(keyringController),
|
saveAccountLabel: nodeify(keyringController.saveAccountLabel).bind(keyringController),
|
||||||
exportAccount: nodeify(keyringController.exportAccount).bind(keyringController),
|
exportAccount: nodeify(keyringController.exportAccount).bind(keyringController),
|
||||||
|
|
||||||
// txManager
|
// txController
|
||||||
approveTransaction: txManager.approveTransaction.bind(txManager),
|
approveTransaction: txController.approveTransaction.bind(txController),
|
||||||
cancelTransaction: txManager.cancelTransaction.bind(txManager),
|
cancelTransaction: txController.cancelTransaction.bind(txController),
|
||||||
updateAndApproveTransaction: this.updateAndApproveTx.bind(this),
|
updateAndApproveTransaction: this.updateAndApproveTx.bind(this),
|
||||||
|
|
||||||
// messageManager
|
// messageManager
|
||||||
@ -344,9 +343,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||||||
console.error('Error in RPC response:\n', response.error)
|
console.error('Error in RPC response:\n', response.error)
|
||||||
}
|
}
|
||||||
if (request.isMetamaskInternal) return
|
if (request.isMetamaskInternal) return
|
||||||
if (global.METAMASK_DEBUG) {
|
log.info(`RPC (${originDomain}):`, request, '->', response)
|
||||||
console.log(`RPC (${originDomain}):`, request, '->', response)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -366,8 +363,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||||||
//
|
//
|
||||||
|
|
||||||
submitPassword (password, cb) {
|
submitPassword (password, cb) {
|
||||||
this.migrateOldVaultIfAny(password)
|
return this.keyringController.submitPassword(password)
|
||||||
.then(this.keyringController.submitPassword.bind(this.keyringController, password))
|
|
||||||
.then((newState) => { cb(null, newState) })
|
.then((newState) => { cb(null, newState) })
|
||||||
.catch((reason) => { cb(reason) })
|
.catch((reason) => { cb(reason) })
|
||||||
}
|
}
|
||||||
@ -393,7 +389,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||||||
.then((serialized) => {
|
.then((serialized) => {
|
||||||
const seedWords = serialized.mnemonic
|
const seedWords = serialized.mnemonic
|
||||||
this.configManager.setSeedWords(seedWords)
|
this.configManager.setSeedWords(seedWords)
|
||||||
cb()
|
cb(null, seedWords)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -425,12 +421,12 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||||||
newUnapprovedTransaction (txParams, cb) {
|
newUnapprovedTransaction (txParams, cb) {
|
||||||
log.debug(`MetaMaskController newUnapprovedTransaction ${JSON.stringify(txParams)}`)
|
log.debug(`MetaMaskController newUnapprovedTransaction ${JSON.stringify(txParams)}`)
|
||||||
const self = this
|
const self = this
|
||||||
self.txManager.addUnapprovedTransaction(txParams, (err, txMeta) => {
|
self.txController.addUnapprovedTransaction(txParams, (err, txMeta) => {
|
||||||
if (err) return cb(err)
|
if (err) return cb(err)
|
||||||
self.sendUpdate()
|
self.sendUpdate()
|
||||||
self.opts.showUnapprovedTx(txMeta)
|
self.opts.showUnapprovedTx(txMeta)
|
||||||
// listen for tx completion (success, fail)
|
// listen for tx completion (success, fail)
|
||||||
self.txManager.once(`${txMeta.id}:finished`, (completedTx) => {
|
self.txController.once(`${txMeta.id}:finished`, (completedTx) => {
|
||||||
switch (completedTx.status) {
|
switch (completedTx.status) {
|
||||||
case 'submitted':
|
case 'submitted':
|
||||||
return cb(null, completedTx.hash)
|
return cb(null, completedTx.hash)
|
||||||
@ -444,7 +440,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
newUnsignedMessage (msgParams, cb) {
|
newUnsignedMessage (msgParams, cb) {
|
||||||
let msgId = this.messageManager.addUnapprovedMessage(msgParams)
|
const msgId = this.messageManager.addUnapprovedMessage(msgParams)
|
||||||
this.sendUpdate()
|
this.sendUpdate()
|
||||||
this.opts.showUnconfirmedMessage()
|
this.opts.showUnconfirmedMessage()
|
||||||
this.messageManager.once(`${msgId}:finished`, (data) => {
|
this.messageManager.once(`${msgId}:finished`, (data) => {
|
||||||
@ -464,7 +460,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||||||
return cb(new Error('MetaMask Message Signature: from field is required.'))
|
return cb(new Error('MetaMask Message Signature: from field is required.'))
|
||||||
}
|
}
|
||||||
|
|
||||||
let msgId = this.personalMessageManager.addUnapprovedMessage(msgParams)
|
const msgId = this.personalMessageManager.addUnapprovedMessage(msgParams)
|
||||||
this.sendUpdate()
|
this.sendUpdate()
|
||||||
this.opts.showUnconfirmedMessage()
|
this.opts.showUnconfirmedMessage()
|
||||||
this.personalMessageManager.once(`${msgId}:finished`, (data) => {
|
this.personalMessageManager.once(`${msgId}:finished`, (data) => {
|
||||||
@ -481,9 +477,9 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||||||
|
|
||||||
updateAndApproveTx (txMeta, cb) {
|
updateAndApproveTx (txMeta, cb) {
|
||||||
log.debug(`MetaMaskController - updateAndApproveTx: ${JSON.stringify(txMeta)}`)
|
log.debug(`MetaMaskController - updateAndApproveTx: ${JSON.stringify(txMeta)}`)
|
||||||
const txManager = this.txManager
|
const txController = this.txController
|
||||||
txManager.updateTx(txMeta)
|
txController.updateTx(txMeta)
|
||||||
txManager.approveTransaction(txMeta.id, cb)
|
txController.approveTransaction(txMeta.id, cb)
|
||||||
}
|
}
|
||||||
|
|
||||||
signMessage (msgParams, cb) {
|
signMessage (msgParams, cb) {
|
||||||
@ -515,7 +511,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||||||
|
|
||||||
// Prefixed Style Message Signing Methods:
|
// Prefixed Style Message Signing Methods:
|
||||||
approvePersonalMessage (msgParams, cb) {
|
approvePersonalMessage (msgParams, cb) {
|
||||||
let msgId = this.personalMessageManager.addUnapprovedMessage(msgParams)
|
const msgId = this.personalMessageManager.addUnapprovedMessage(msgParams)
|
||||||
this.sendUpdate()
|
this.sendUpdate()
|
||||||
this.opts.showUnconfirmedMessage()
|
this.opts.showUnconfirmedMessage()
|
||||||
this.personalMessageManager.once(`${msgId}:finished`, (data) => {
|
this.personalMessageManager.once(`${msgId}:finished`, (data) => {
|
||||||
@ -562,35 +558,6 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||||||
cb(null, this.getState())
|
cb(null, this.getState())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Migrate Old Vault If Any
|
|
||||||
// @string password
|
|
||||||
//
|
|
||||||
// returns Promise()
|
|
||||||
//
|
|
||||||
// Temporary step used when logging in.
|
|
||||||
// Checks if old style (pre-3.0.0) Metamask Vault exists.
|
|
||||||
// If so, persists that vault in the new vault format
|
|
||||||
// with the provided password, so the other unlock steps
|
|
||||||
// may be completed without interruption.
|
|
||||||
migrateOldVaultIfAny (password) {
|
|
||||||
|
|
||||||
if (!this.checkIfShouldMigrate()) {
|
|
||||||
return Promise.resolve(password)
|
|
||||||
}
|
|
||||||
|
|
||||||
const keyringController = this.keyringController
|
|
||||||
|
|
||||||
return this.idStoreMigrator.migratedVaultForPassword(password)
|
|
||||||
.then(this.restoreOldVaultAccounts.bind(this))
|
|
||||||
.then(this.restoreOldLostAccounts.bind(this))
|
|
||||||
.then(keyringController.persistAllKeyrings.bind(keyringController, password))
|
|
||||||
.then(() => password)
|
|
||||||
}
|
|
||||||
|
|
||||||
checkIfShouldMigrate() {
|
|
||||||
return !!this.configManager.getWallet() && !this.configManager.getVault()
|
|
||||||
}
|
|
||||||
|
|
||||||
restoreOldVaultAccounts (migratorOutput) {
|
restoreOldVaultAccounts (migratorOutput) {
|
||||||
const { serialized } = migratorOutput
|
const { serialized } = migratorOutput
|
||||||
return this.keyringController.restoreKeyring(serialized)
|
return this.keyringController.restoreKeyring(serialized)
|
||||||
@ -623,12 +590,6 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||||||
//
|
//
|
||||||
|
|
||||||
// Log blocks
|
// Log blocks
|
||||||
logBlock (block) {
|
|
||||||
if (global.METAMASK_DEBUG) {
|
|
||||||
console.log(`BLOCK CHANGED: #${block.number.toString('hex')} 0x${block.hash.toString('hex')}`)
|
|
||||||
}
|
|
||||||
this.verifyNetwork()
|
|
||||||
}
|
|
||||||
|
|
||||||
setCurrentCurrency (currencyCode, cb) {
|
setCurrentCurrency (currencyCode, cb) {
|
||||||
try {
|
try {
|
||||||
@ -647,91 +608,29 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||||||
|
|
||||||
buyEth (address, amount) {
|
buyEth (address, amount) {
|
||||||
if (!amount) amount = '5'
|
if (!amount) amount = '5'
|
||||||
|
const network = this.networkController.getNetworkState()
|
||||||
const network = this.getNetworkState()
|
const url = getBuyEthUrl({ network, address, amount })
|
||||||
let url
|
if (url) this.platform.openWindow({ 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) {
|
createShapeShiftTx (depositAddress, depositType) {
|
||||||
this.shapeshiftController.createShapeShiftTx(depositAddress, depositType)
|
this.shapeshiftController.createShapeShiftTx(depositAddress, depositType)
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
|
||||||
// network
|
// network
|
||||||
//
|
|
||||||
|
|
||||||
verifyNetwork () {
|
|
||||||
// Check network when restoring connectivity:
|
|
||||||
if (this.isNetworkLoading()) this.lookupNetwork()
|
|
||||||
}
|
|
||||||
|
|
||||||
setDefaultRpc () {
|
setDefaultRpc () {
|
||||||
this.configManager.setRpcTarget('http://localhost:8545')
|
this.networkController.setRpcTarget('http://localhost:8545')
|
||||||
extension.runtime.reload()
|
|
||||||
this.lookupNetwork()
|
|
||||||
return Promise.resolve('http://localhost:8545')
|
return Promise.resolve('http://localhost:8545')
|
||||||
}
|
}
|
||||||
|
|
||||||
setCustomRpc (rpcTarget, rpcList) {
|
setCustomRpc (rpcTarget, rpcList) {
|
||||||
this.configManager.setRpcTarget(rpcTarget)
|
this.networkController.setRpcTarget(rpcTarget)
|
||||||
|
|
||||||
return this.preferencesController.updateFrequentRpcList(rpcTarget)
|
return this.preferencesController.updateFrequentRpcList(rpcTarget)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
extension.runtime.reload()
|
|
||||||
this.lookupNetwork()
|
|
||||||
return Promise.resolve(rpcTarget)
|
return Promise.resolve(rpcTarget)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
setProviderType (type) {
|
|
||||||
this.configManager.setProviderType(type)
|
|
||||||
extension.runtime.reload()
|
|
||||||
this.lookupNetwork()
|
|
||||||
}
|
|
||||||
|
|
||||||
useEtherscanProvider () {
|
|
||||||
this.configManager.useEtherscanProvider()
|
|
||||||
extension.runtime.reload()
|
|
||||||
}
|
|
||||||
|
|
||||||
getNetworkState () {
|
|
||||||
return this.networkStore.getState().network
|
|
||||||
}
|
|
||||||
|
|
||||||
setNetworkState (network) {
|
|
||||||
return this.networkStore.updateState({ network })
|
|
||||||
}
|
|
||||||
|
|
||||||
isNetworkLoading () {
|
|
||||||
return this.getNetworkState() === 'loading'
|
|
||||||
}
|
|
||||||
|
|
||||||
lookupNetwork (err) {
|
|
||||||
if (err) {
|
|
||||||
this.setNetworkState('loading')
|
|
||||||
}
|
|
||||||
|
|
||||||
this.ethQuery.sendAsync({ method: 'net_version' }, (err, network) => {
|
|
||||||
if (err) {
|
|
||||||
this.setNetworkState('loading')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (global.METAMASK_DEBUG) {
|
|
||||||
console.log('web3.getNetwork returned ' + network)
|
|
||||||
}
|
|
||||||
this.setNetworkState(network)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@ module.exports = {
|
|||||||
version,
|
version,
|
||||||
|
|
||||||
migrate: function (originalVersionedData) {
|
migrate: function (originalVersionedData) {
|
||||||
let versionedData = clone(originalVersionedData)
|
const versionedData = clone(originalVersionedData)
|
||||||
versionedData.meta.version = version
|
versionedData.meta.version = version
|
||||||
try {
|
try {
|
||||||
if (versionedData.data.config.provider.type === 'etherscan') {
|
if (versionedData.data.config.provider.type === 'etherscan') {
|
||||||
|
@ -8,7 +8,7 @@ module.exports = {
|
|||||||
version,
|
version,
|
||||||
|
|
||||||
migrate: function (originalVersionedData) {
|
migrate: function (originalVersionedData) {
|
||||||
let versionedData = clone(originalVersionedData)
|
const versionedData = clone(originalVersionedData)
|
||||||
versionedData.meta.version = version
|
versionedData.meta.version = version
|
||||||
try {
|
try {
|
||||||
if (versionedData.data.config.provider.rpcTarget === oldTestRpc) {
|
if (versionedData.data.config.provider.rpcTarget === oldTestRpc) {
|
||||||
|
@ -6,7 +6,7 @@ module.exports = {
|
|||||||
version,
|
version,
|
||||||
|
|
||||||
migrate: function (versionedData) {
|
migrate: function (versionedData) {
|
||||||
let safeVersionedData = clone(versionedData)
|
const safeVersionedData = clone(versionedData)
|
||||||
safeVersionedData.meta.version = version
|
safeVersionedData.meta.version = version
|
||||||
try {
|
try {
|
||||||
if (safeVersionedData.data.config.provider.type !== 'rpc') return Promise.resolve(safeVersionedData)
|
if (safeVersionedData.data.config.provider.type !== 'rpc') return Promise.resolve(safeVersionedData)
|
||||||
|
@ -14,7 +14,7 @@ module.exports = {
|
|||||||
version,
|
version,
|
||||||
|
|
||||||
migrate: function (originalVersionedData) {
|
migrate: function (originalVersionedData) {
|
||||||
let versionedData = clone(originalVersionedData)
|
const versionedData = clone(originalVersionedData)
|
||||||
versionedData.meta.version = version
|
versionedData.meta.version = version
|
||||||
try {
|
try {
|
||||||
const state = versionedData.data
|
const state = versionedData.data
|
||||||
|
@ -13,7 +13,7 @@ module.exports = {
|
|||||||
version,
|
version,
|
||||||
|
|
||||||
migrate: function (originalVersionedData) {
|
migrate: function (originalVersionedData) {
|
||||||
let versionedData = clone(originalVersionedData)
|
const versionedData = clone(originalVersionedData)
|
||||||
versionedData.meta.version = version
|
versionedData.meta.version = version
|
||||||
try {
|
try {
|
||||||
const state = versionedData.data
|
const state = versionedData.data
|
||||||
|
@ -13,7 +13,7 @@ module.exports = {
|
|||||||
version,
|
version,
|
||||||
|
|
||||||
migrate: function (originalVersionedData) {
|
migrate: function (originalVersionedData) {
|
||||||
let versionedData = clone(originalVersionedData)
|
const versionedData = clone(originalVersionedData)
|
||||||
versionedData.meta.version = version
|
versionedData.meta.version = version
|
||||||
try {
|
try {
|
||||||
const state = versionedData.data
|
const state = versionedData.data
|
||||||
|
@ -13,7 +13,7 @@ module.exports = {
|
|||||||
version,
|
version,
|
||||||
|
|
||||||
migrate: function (originalVersionedData) {
|
migrate: function (originalVersionedData) {
|
||||||
let versionedData = clone(originalVersionedData)
|
const versionedData = clone(originalVersionedData)
|
||||||
versionedData.meta.version = version
|
versionedData.meta.version = version
|
||||||
try {
|
try {
|
||||||
const state = versionedData.data
|
const state = versionedData.data
|
||||||
|
@ -13,7 +13,7 @@ module.exports = {
|
|||||||
version,
|
version,
|
||||||
|
|
||||||
migrate: function (originalVersionedData) {
|
migrate: function (originalVersionedData) {
|
||||||
let versionedData = clone(originalVersionedData)
|
const versionedData = clone(originalVersionedData)
|
||||||
versionedData.meta.version = version
|
versionedData.meta.version = version
|
||||||
try {
|
try {
|
||||||
const state = versionedData.data
|
const state = versionedData.data
|
||||||
|
@ -13,7 +13,7 @@ module.exports = {
|
|||||||
version,
|
version,
|
||||||
|
|
||||||
migrate: function (originalVersionedData) {
|
migrate: function (originalVersionedData) {
|
||||||
let versionedData = clone(originalVersionedData)
|
const versionedData = clone(originalVersionedData)
|
||||||
versionedData.meta.version = version
|
versionedData.meta.version = version
|
||||||
try {
|
try {
|
||||||
const state = versionedData.data
|
const state = versionedData.data
|
||||||
|
@ -12,7 +12,7 @@ module.exports = {
|
|||||||
version,
|
version,
|
||||||
|
|
||||||
migrate: function (originalVersionedData) {
|
migrate: function (originalVersionedData) {
|
||||||
let versionedData = clone(originalVersionedData)
|
const versionedData = clone(originalVersionedData)
|
||||||
versionedData.meta.version = version
|
versionedData.meta.version = version
|
||||||
try {
|
try {
|
||||||
const state = versionedData.data
|
const state = versionedData.data
|
||||||
|
36
app/scripts/migrations/012.js
Normal file
36
app/scripts/migrations/012.js
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
const version = 12
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
This migration modifies our notices to delete their body after being read.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
const clone = require('clone')
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
version,
|
||||||
|
|
||||||
|
migrate: function (originalVersionedData) {
|
||||||
|
const versionedData = clone(originalVersionedData)
|
||||||
|
versionedData.meta.version = version
|
||||||
|
try {
|
||||||
|
const state = versionedData.data
|
||||||
|
const newState = transformState(state)
|
||||||
|
versionedData.data = newState
|
||||||
|
} catch (err) {
|
||||||
|
console.warn(`MetaMask Migration #${version}` + err.stack)
|
||||||
|
}
|
||||||
|
return Promise.resolve(versionedData)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
function transformState (state) {
|
||||||
|
const newState = state
|
||||||
|
newState.NoticeController.noticesList.forEach((notice) => {
|
||||||
|
if (notice.read) {
|
||||||
|
notice.body = ''
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return newState
|
||||||
|
}
|
34
app/scripts/migrations/013.js
Normal file
34
app/scripts/migrations/013.js
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
const version = 13
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
This migration modifies the network config from ambiguous 'testnet' to explicit 'ropsten'
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
const clone = require('clone')
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
version,
|
||||||
|
|
||||||
|
migrate: function (originalVersionedData) {
|
||||||
|
const versionedData = clone(originalVersionedData)
|
||||||
|
versionedData.meta.version = version
|
||||||
|
try {
|
||||||
|
const state = versionedData.data
|
||||||
|
const newState = transformState(state)
|
||||||
|
versionedData.data = newState
|
||||||
|
} catch (err) {
|
||||||
|
console.warn(`MetaMask Migration #${version}` + err.stack)
|
||||||
|
}
|
||||||
|
return Promise.resolve(versionedData)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
function transformState (state) {
|
||||||
|
const newState = state
|
||||||
|
if (newState.config.provider.type === 'testnet') {
|
||||||
|
newState.config.provider.type = 'ropsten'
|
||||||
|
}
|
||||||
|
return newState
|
||||||
|
}
|
34
app/scripts/migrations/014.js
Normal file
34
app/scripts/migrations/014.js
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
const version = 14
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
This migration removes provider from config and moves it too NetworkController.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
const clone = require('clone')
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
version,
|
||||||
|
|
||||||
|
migrate: function (originalVersionedData) {
|
||||||
|
const versionedData = clone(originalVersionedData)
|
||||||
|
versionedData.meta.version = version
|
||||||
|
try {
|
||||||
|
const state = versionedData.data
|
||||||
|
const newState = transformState(state)
|
||||||
|
versionedData.data = newState
|
||||||
|
} catch (err) {
|
||||||
|
console.warn(`MetaMask Migration #${version}` + err.stack)
|
||||||
|
}
|
||||||
|
return Promise.resolve(versionedData)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
function transformState (state) {
|
||||||
|
const newState = state
|
||||||
|
newState.NetworkController = {}
|
||||||
|
newState.NetworkController.provider = newState.config.provider
|
||||||
|
delete newState.config.provider
|
||||||
|
return newState
|
||||||
|
}
|
@ -20,10 +20,10 @@ module.exports = {
|
|||||||
migrate: function (versionedData) {
|
migrate: function (versionedData) {
|
||||||
versionedData.meta.version = version
|
versionedData.meta.version = version
|
||||||
|
|
||||||
let store = new ObservableStore(versionedData.data)
|
const store = new ObservableStore(versionedData.data)
|
||||||
let configManager = new ConfigManager({ store })
|
const configManager = new ConfigManager({ store })
|
||||||
let idStoreMigrator = new IdentityStoreMigrator({ configManager })
|
const idStoreMigrator = new IdentityStoreMigrator({ configManager })
|
||||||
let keyringController = new KeyringController({
|
const keyringController = new KeyringController({
|
||||||
configManager: configManager,
|
configManager: configManager,
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -46,6 +46,5 @@ module.exports = {
|
|||||||
return Promise.resolve(versionedData)
|
return Promise.resolve(versionedData)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -22,4 +22,7 @@ module.exports = [
|
|||||||
require('./009'),
|
require('./009'),
|
||||||
require('./010'),
|
require('./010'),
|
||||||
require('./011'),
|
require('./011'),
|
||||||
|
require('./012'),
|
||||||
|
require('./013'),
|
||||||
|
require('./014'),
|
||||||
]
|
]
|
||||||
|
@ -41,6 +41,7 @@ module.exports = class NoticeController extends EventEmitter {
|
|||||||
var notices = this.getNoticesList()
|
var notices = this.getNoticesList()
|
||||||
var index = notices.findIndex((currentNotice) => currentNotice.id === noticeToMark.id)
|
var index = notices.findIndex((currentNotice) => currentNotice.id === noticeToMark.id)
|
||||||
notices[index].read = true
|
notices[index].read = true
|
||||||
|
notices[index].body = ''
|
||||||
this.setNoticesList(notices)
|
this.setNoticesList(notices)
|
||||||
const latestNotice = this.getLatestUnreadNotice()
|
const latestNotice = this.getLatestUnreadNotice()
|
||||||
cb(null, latestNotice)
|
cb(null, latestNotice)
|
||||||
|
23
app/scripts/platforms/extension.js
Normal file
23
app/scripts/platforms/extension.js
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
const extension = require('extensionizer')
|
||||||
|
|
||||||
|
class ExtensionPlatform {
|
||||||
|
|
||||||
|
//
|
||||||
|
// Public
|
||||||
|
//
|
||||||
|
|
||||||
|
reload () {
|
||||||
|
extension.runtime.reload()
|
||||||
|
}
|
||||||
|
|
||||||
|
openWindow ({ url }) {
|
||||||
|
extension.tabs.create({ url })
|
||||||
|
}
|
||||||
|
|
||||||
|
getVersion () {
|
||||||
|
return extension.runtime.getManifest().version
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = ExtensionPlatform
|
24
app/scripts/platforms/sw.js
Normal file
24
app/scripts/platforms/sw.js
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
|
||||||
|
class SwPlatform {
|
||||||
|
|
||||||
|
//
|
||||||
|
// Public
|
||||||
|
//
|
||||||
|
|
||||||
|
reload () {
|
||||||
|
// you cant actually do this
|
||||||
|
global.location.reload()
|
||||||
|
}
|
||||||
|
|
||||||
|
openWindow ({ url }) {
|
||||||
|
// this doesnt actually work
|
||||||
|
global.open(url, '_blank')
|
||||||
|
}
|
||||||
|
|
||||||
|
getVersion () {
|
||||||
|
return '<unable to read version>'
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = SwPlatform
|
22
app/scripts/platforms/window.js
Normal file
22
app/scripts/platforms/window.js
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
|
||||||
|
class WindowPlatform {
|
||||||
|
|
||||||
|
//
|
||||||
|
// Public
|
||||||
|
//
|
||||||
|
|
||||||
|
reload () {
|
||||||
|
global.location.reload()
|
||||||
|
}
|
||||||
|
|
||||||
|
openWindow ({ url }) {
|
||||||
|
global.open(url, '_blank')
|
||||||
|
}
|
||||||
|
|
||||||
|
getVersion () {
|
||||||
|
return '<unable to read version>'
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = WindowPlatform
|
@ -1,7 +1,8 @@
|
|||||||
const EventEmitter = require('events').EventEmitter
|
const EventEmitter = require('events').EventEmitter
|
||||||
|
const async = require('async')
|
||||||
const Dnode = require('dnode')
|
const Dnode = require('dnode')
|
||||||
const Web3 = require('web3')
|
const EthQuery = require('eth-query')
|
||||||
const MetaMaskUi = require('../../ui')
|
const launchMetamaskUi = require('../../ui')
|
||||||
const StreamProvider = require('web3-stream-provider')
|
const StreamProvider = require('web3-stream-provider')
|
||||||
const setupMultiplex = require('./lib/stream-utils.js').setupMultiplex
|
const setupMultiplex = require('./lib/stream-utils.js').setupMultiplex
|
||||||
|
|
||||||
@ -9,9 +10,12 @@ const setupMultiplex = require('./lib/stream-utils.js').setupMultiplex
|
|||||||
module.exports = initializePopup
|
module.exports = initializePopup
|
||||||
|
|
||||||
|
|
||||||
function initializePopup (connectionStream) {
|
function initializePopup ({ container, connectionStream }, cb) {
|
||||||
// setup app
|
// setup app
|
||||||
connectToAccountManager(connectionStream, setupApp)
|
async.waterfall([
|
||||||
|
(cb) => connectToAccountManager(connectionStream, cb),
|
||||||
|
(accountManager, cb) => launchMetamaskUi({ container, accountManager }, cb),
|
||||||
|
], cb)
|
||||||
}
|
}
|
||||||
|
|
||||||
function connectToAccountManager (connectionStream, cb) {
|
function connectToAccountManager (connectionStream, cb) {
|
||||||
@ -28,7 +32,8 @@ function setupWeb3Connection (connectionStream) {
|
|||||||
providerStream.pipe(connectionStream).pipe(providerStream)
|
providerStream.pipe(connectionStream).pipe(providerStream)
|
||||||
connectionStream.on('error', console.error.bind(console))
|
connectionStream.on('error', console.error.bind(console))
|
||||||
providerStream.on('error', console.error.bind(console))
|
providerStream.on('error', console.error.bind(console))
|
||||||
global.web3 = new Web3(providerStream)
|
global.ethereumProvider = providerStream
|
||||||
|
global.ethQuery = new EthQuery(providerStream)
|
||||||
}
|
}
|
||||||
|
|
||||||
function setupControllerConnection (connectionStream, cb) {
|
function setupControllerConnection (connectionStream, cb) {
|
||||||
@ -47,19 +52,3 @@ function setupControllerConnection (connectionStream, cb) {
|
|||||||
cb(null, accountManager)
|
cb(null, accountManager)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function setupApp (err, accountManager) {
|
|
||||||
var container = document.getElementById('app-content')
|
|
||||||
if (err) {
|
|
||||||
container.innerHTML = '<div class="critical-error">The MetaMask app failed to load: please open and close MetaMask again to restart.</div>'
|
|
||||||
container.style.height = '80px'
|
|
||||||
log.error(err.stack)
|
|
||||||
throw err
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
MetaMaskUi({
|
|
||||||
container: container,
|
|
||||||
accountManager: accountManager,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
@ -3,23 +3,47 @@ const MetaMaskUiCss = require('../../ui/css')
|
|||||||
const startPopup = require('./popup-core')
|
const startPopup = require('./popup-core')
|
||||||
const PortStream = require('./lib/port-stream.js')
|
const PortStream = require('./lib/port-stream.js')
|
||||||
const isPopupOrNotification = require('./lib/is-popup-or-notification')
|
const isPopupOrNotification = require('./lib/is-popup-or-notification')
|
||||||
const extension = require('./lib/extension')
|
const extension = require('extensionizer')
|
||||||
const notification = require('./lib/notifications')
|
const ExtensionPlatform = require('./platforms/extension')
|
||||||
|
const NotificationManager = require('./lib/notification-manager')
|
||||||
|
const notificationManager = new NotificationManager()
|
||||||
|
|
||||||
var css = MetaMaskUiCss()
|
// create platform global
|
||||||
|
global.platform = new ExtensionPlatform()
|
||||||
|
|
||||||
|
// inject css
|
||||||
|
const css = MetaMaskUiCss()
|
||||||
injectCss(css)
|
injectCss(css)
|
||||||
|
|
||||||
var name = isPopupOrNotification()
|
// identify window type (popup, notification)
|
||||||
closePopupIfOpen(name)
|
const windowType = isPopupOrNotification()
|
||||||
window.METAMASK_UI_TYPE = name
|
global.METAMASK_UI_TYPE = windowType
|
||||||
|
closePopupIfOpen(windowType)
|
||||||
|
|
||||||
var pluginPort = extension.runtime.connect({ name })
|
// setup stream to background
|
||||||
var portStream = new PortStream(pluginPort)
|
const extensionPort = extension.runtime.connect({ name: windowType })
|
||||||
|
const connectionStream = new PortStream(extensionPort)
|
||||||
|
|
||||||
startPopup(portStream)
|
// start ui
|
||||||
|
const container = document.getElementById('app-content')
|
||||||
|
startPopup({ container, connectionStream }, (err, store) => {
|
||||||
|
if (err) return displayCriticalError(err)
|
||||||
|
store.subscribe(() => {
|
||||||
|
const state = store.getState()
|
||||||
|
if (state.appState.shouldClose) notificationManager.closePopup()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
function closePopupIfOpen (name) {
|
|
||||||
if (name !== 'notification') {
|
function closePopupIfOpen (windowType) {
|
||||||
notification.closePopup()
|
if (windowType !== 'notification') {
|
||||||
|
notificationManager.closePopup()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function displayCriticalError (err) {
|
||||||
|
container.innerHTML = '<div class="critical-error">The MetaMask app failed to load: please open and close MetaMask again to restart.</div>'
|
||||||
|
container.style.height = '80px'
|
||||||
|
log.error(err.stack)
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
machine:
|
machine:
|
||||||
node:
|
node:
|
||||||
version: 6.0.0
|
version: 7.6.0
|
||||||
dependencies:
|
dependencies:
|
||||||
pre:
|
pre:
|
||||||
- "npm i -g testem"
|
- "npm i -g testem"
|
||||||
|
@ -7,6 +7,6 @@ var changelog = fs.readFileSync(path.join(__dirname, '..', 'CHANGELOG.md')).toSt
|
|||||||
|
|
||||||
var log = changelog.split(version)[1].split('##')[0].trim()
|
var log = changelog.split(version)[1].split('##')[0].trim()
|
||||||
|
|
||||||
let msg = `*MetaMask ${version}* now published to the Chrome Store! It should auto-update over the next hour!\n${log}`
|
let msg = `*MetaMask ${version}* now published to the Chrome Store! It should auto-update soon!\n${log}`
|
||||||
|
|
||||||
console.log(msg)
|
console.log(msg)
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
* and stubbing out all the extension methods with appropriate mocks.
|
* and stubbing out all the extension methods with appropriate mocks.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const extension = require('../app/scripts/lib/extension')
|
const extension = require('extensionizer')
|
||||||
const noop = function () {}
|
const noop = function () {}
|
||||||
|
|
||||||
const apis = [
|
const apis = [
|
||||||
|
70
development/states/private-key-export-success.json
Normal file
70
development/states/private-key-export-success.json
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
{
|
||||||
|
"metamask": {
|
||||||
|
"isInitialized": true,
|
||||||
|
"isUnlocked": true,
|
||||||
|
"rpcTarget": "https://rawtestrpc.metamask.io/",
|
||||||
|
"identities": {
|
||||||
|
"0x07284e146926a4facd0ea60598dc4f001ad620f1": {
|
||||||
|
"address": "0x07284e146926a4facd0ea60598dc4f001ad620f1",
|
||||||
|
"name": "Account 1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"unapprovedTxs": {},
|
||||||
|
"noActiveNotices": true,
|
||||||
|
"frequentRpcList": [],
|
||||||
|
"addressBook": [],
|
||||||
|
"network": "3",
|
||||||
|
"accounts": {
|
||||||
|
"0x07284e146926a4facd0ea60598dc4f001ad620f1": {
|
||||||
|
"code": "0x",
|
||||||
|
"nonce": "0x0",
|
||||||
|
"balance": "0x0",
|
||||||
|
"address": "0x07284e146926a4facd0ea60598dc4f001ad620f1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"transactions": {},
|
||||||
|
"selectedAddressTxList": [],
|
||||||
|
"unapprovedMsgs": {},
|
||||||
|
"unapprovedMsgCount": 0,
|
||||||
|
"unapprovedPersonalMsgs": {},
|
||||||
|
"unapprovedPersonalMsgCount": 0,
|
||||||
|
"keyringTypes": [
|
||||||
|
"Simple Key Pair",
|
||||||
|
"HD Key Tree"
|
||||||
|
],
|
||||||
|
"keyrings": [
|
||||||
|
{
|
||||||
|
"type": "HD Key Tree",
|
||||||
|
"accounts": [
|
||||||
|
"07284e146926a4facd0ea60598dc4f001ad620f1"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"selectedAddress": "0x07284e146926a4facd0ea60598dc4f001ad620f1",
|
||||||
|
"currentCurrency": "USD",
|
||||||
|
"conversionRate": 43.35903476,
|
||||||
|
"conversionDate": 1490105102,
|
||||||
|
"provider": {
|
||||||
|
"type": "testnet"
|
||||||
|
},
|
||||||
|
"shapeShiftTxList": [],
|
||||||
|
"lostAccounts": [],
|
||||||
|
"seedWords": null
|
||||||
|
},
|
||||||
|
"appState": {
|
||||||
|
"menuOpen": false,
|
||||||
|
"currentView": {
|
||||||
|
"name": "accountDetail",
|
||||||
|
"context": "0x07284e146926a4facd0ea60598dc4f001ad620f1"
|
||||||
|
},
|
||||||
|
"accountDetail": {
|
||||||
|
"subview": "export",
|
||||||
|
"accountExport": "completed",
|
||||||
|
"privateKey": "549c9638ad06432568969accacad4a02f8548cc358085938071745138ec134b7"
|
||||||
|
},
|
||||||
|
"transForward": true,
|
||||||
|
"isLoading": false,
|
||||||
|
"warning": null
|
||||||
|
},
|
||||||
|
"identities": {}
|
||||||
|
}
|
69
development/states/private-key-export.json
Normal file
69
development/states/private-key-export.json
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
{
|
||||||
|
"metamask": {
|
||||||
|
"isInitialized": true,
|
||||||
|
"isUnlocked": true,
|
||||||
|
"rpcTarget": "https://rawtestrpc.metamask.io/",
|
||||||
|
"identities": {
|
||||||
|
"0x07284e146926a4facd0ea60598dc4f001ad620f1": {
|
||||||
|
"address": "0x07284e146926a4facd0ea60598dc4f001ad620f1",
|
||||||
|
"name": "Account 1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"unapprovedTxs": {},
|
||||||
|
"noActiveNotices": true,
|
||||||
|
"frequentRpcList": [],
|
||||||
|
"addressBook": [],
|
||||||
|
"network": "3",
|
||||||
|
"accounts": {
|
||||||
|
"0x07284e146926a4facd0ea60598dc4f001ad620f1": {
|
||||||
|
"code": "0x",
|
||||||
|
"nonce": "0x0",
|
||||||
|
"balance": "0x0",
|
||||||
|
"address": "0x07284e146926a4facd0ea60598dc4f001ad620f1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"transactions": {},
|
||||||
|
"selectedAddressTxList": [],
|
||||||
|
"unapprovedMsgs": {},
|
||||||
|
"unapprovedMsgCount": 0,
|
||||||
|
"unapprovedPersonalMsgs": {},
|
||||||
|
"unapprovedPersonalMsgCount": 0,
|
||||||
|
"keyringTypes": [
|
||||||
|
"Simple Key Pair",
|
||||||
|
"HD Key Tree"
|
||||||
|
],
|
||||||
|
"keyrings": [
|
||||||
|
{
|
||||||
|
"type": "HD Key Tree",
|
||||||
|
"accounts": [
|
||||||
|
"07284e146926a4facd0ea60598dc4f001ad620f1"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"selectedAddress": "0x07284e146926a4facd0ea60598dc4f001ad620f1",
|
||||||
|
"currentCurrency": "USD",
|
||||||
|
"conversionRate": 43.35903476,
|
||||||
|
"conversionDate": 1490105102,
|
||||||
|
"provider": {
|
||||||
|
"type": "testnet"
|
||||||
|
},
|
||||||
|
"shapeShiftTxList": [],
|
||||||
|
"lostAccounts": [],
|
||||||
|
"seedWords": null
|
||||||
|
},
|
||||||
|
"appState": {
|
||||||
|
"menuOpen": false,
|
||||||
|
"currentView": {
|
||||||
|
"name": "accountDetail",
|
||||||
|
"context": "0x07284e146926a4facd0ea60598dc4f001ad620f1"
|
||||||
|
},
|
||||||
|
"accountDetail": {
|
||||||
|
"subview": "export",
|
||||||
|
"accountExport": "requested"
|
||||||
|
},
|
||||||
|
"transForward": true,
|
||||||
|
"isLoading": false,
|
||||||
|
"warning": null
|
||||||
|
},
|
||||||
|
"identities": {}
|
||||||
|
}
|
11
docker-compose.yml
Normal file
11
docker-compose.yml
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
metamascara:
|
||||||
|
build: ./
|
||||||
|
restart: always
|
||||||
|
ports:
|
||||||
|
- "9001"
|
||||||
|
environment:
|
||||||
|
MASCARA_ORIGIN: "https://zero.metamask.io"
|
||||||
|
VIRTUAL_PORT: "9001"
|
||||||
|
VIRTUAL_HOST: "zero.metamask.io"
|
||||||
|
LETSENCRYPT_HOST: "zero.metamask.io"
|
||||||
|
LETSENCRYPT_EMAIL: "admin@metamask.io"
|
14
docs/add-to-chrome.md
Normal file
14
docs/add-to-chrome.md
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
## Add Custom Build to Chrome
|
||||||
|
|
||||||
|
Open `Settings` > `Extensions`.
|
||||||
|
|
||||||
|
Check "Developer mode".
|
||||||
|
|
||||||
|
At the top, click `Load Unpacked Extension`.
|
||||||
|
|
||||||
|
Navigate to your `metamask-plugin/dist/chrome` folder.
|
||||||
|
|
||||||
|
Click `Select`.
|
||||||
|
|
||||||
|
You now have the plugin, and can click 'inspect views: background plugin' to view its dev console.
|
||||||
|
|
14
docs/add-to-firef.md
Normal file
14
docs/add-to-firef.md
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
# Add Custom Build to Firefox
|
||||||
|
|
||||||
|
Go to the url `about:debugging`.
|
||||||
|
|
||||||
|
Click the button `Load Temporary Add-On`.
|
||||||
|
|
||||||
|
Select the file `dist/firefox/manifest.json`.
|
||||||
|
|
||||||
|
You can optionally enable debugging, and click `Debug`, for a console window that logs all of Metamask's processes to a single console.
|
||||||
|
|
||||||
|
If you have problems debugging, try connecting to the IRC channel `#webextensions` on `irc.mozilla.org`.
|
||||||
|
|
||||||
|
For longer questions, use the StackOverfow tag `firefox-addons`.
|
||||||
|
|
25
docs/adding-new-networks.md
Normal file
25
docs/adding-new-networks.md
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
## Adding Custom Networks
|
||||||
|
|
||||||
|
To add another network to our dropdown menu, make sure the following files are adjusted properly:
|
||||||
|
|
||||||
|
```
|
||||||
|
app/scripts/config.js
|
||||||
|
app/scripts/lib/buy-eth-url.js
|
||||||
|
app/scripts/lib/config-manager.js
|
||||||
|
ui/app/app.js
|
||||||
|
ui/app/components/buy-button-subview.js
|
||||||
|
ui/app/components/drop-menu-item.js
|
||||||
|
ui/app/components/network.js
|
||||||
|
ui/app/components/transaction-list-item.js
|
||||||
|
ui/app/config.js
|
||||||
|
ui/app/css/lib.css
|
||||||
|
ui/lib/account-link.js
|
||||||
|
ui/lib/explorer-link.js
|
||||||
|
```
|
||||||
|
|
||||||
|
You will need:
|
||||||
|
+ The network ID
|
||||||
|
+ An RPC Endpoint url
|
||||||
|
+ An explorer link
|
||||||
|
+ CSS for the display icon
|
||||||
|
|
10
docs/developing-on-deps.md
Normal file
10
docs/developing-on-deps.md
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
### Developing on Dependencies
|
||||||
|
|
||||||
|
To enjoy the live-reloading that `gulp dev` offers while working on the `web3-provider-engine` or other dependencies:
|
||||||
|
|
||||||
|
1. Clone the dependency locally.
|
||||||
|
2. `npm install` in its folder.
|
||||||
|
3. Run `npm link` in its folder.
|
||||||
|
4. Run `npm link $DEP_NAME` in this project folder.
|
||||||
|
5. Next time you `npm start` it will watch the dependency for changes as well!
|
||||||
|
|
35
docs/development-visualization.md
Normal file
35
docs/development-visualization.md
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
### Generate Development Visualization
|
||||||
|
|
||||||
|
This will generate a video of the repo commit history.
|
||||||
|
|
||||||
|
Install preqs:
|
||||||
|
```
|
||||||
|
brew install gource
|
||||||
|
brew install ffmpeg
|
||||||
|
```
|
||||||
|
|
||||||
|
From the repo dir, pipe `gource` into `ffmpeg`:
|
||||||
|
```
|
||||||
|
gource \
|
||||||
|
--seconds-per-day .1 \
|
||||||
|
--user-scale 1.5 \
|
||||||
|
--default-user-image "./images/icon-512.png" \
|
||||||
|
--viewport 1280x720 \
|
||||||
|
--auto-skip-seconds .1 \
|
||||||
|
--multi-sampling \
|
||||||
|
--stop-at-end \
|
||||||
|
--highlight-users \
|
||||||
|
--hide mouse,progress \
|
||||||
|
--file-idle-time 0 \
|
||||||
|
--max-files 0 \
|
||||||
|
--background-colour 000000 \
|
||||||
|
--font-size 18 \
|
||||||
|
--date-format "%b %d, %Y" \
|
||||||
|
--highlight-dirs \
|
||||||
|
--user-friction 0.1 \
|
||||||
|
--title "MetaMask Development History" \
|
||||||
|
--output-ppm-stream - \
|
||||||
|
--output-framerate 30 \
|
||||||
|
| ffmpeg -y -r 30 -f image2pipe -vcodec ppm -i - -b 65536K metamask-dev-history.mp4
|
||||||
|
```
|
||||||
|
|
15
docs/notices.md
Normal file
15
docs/notices.md
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
## Generating Notices
|
||||||
|
|
||||||
|
To add a notice:
|
||||||
|
```
|
||||||
|
npm run generateNotice
|
||||||
|
```
|
||||||
|
Enter the body of your notice into the text editor that pops up, without including the body. Be sure to save the file before closing the window!
|
||||||
|
Afterwards, enter the title of the notice in the command line and press enter. Afterwards, add and commit the new changes made.
|
||||||
|
|
||||||
|
To delete a notice:
|
||||||
|
```
|
||||||
|
npm run deleteNotice
|
||||||
|
```
|
||||||
|
A list of active notices will pop up. Enter the corresponding id in the command line prompt and add and commit the new changes afterwards.
|
||||||
|
|
19
docs/publishing.md
Normal file
19
docs/publishing.md
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# Publishing Guide
|
||||||
|
|
||||||
|
When publishing a new version of MetaMask, we follow this procedure:
|
||||||
|
|
||||||
|
## Incrementing Version & Changelog
|
||||||
|
|
||||||
|
You must be authorized already on the MetaMask plugin.
|
||||||
|
|
||||||
|
1. Update the version in `app/manifest.json` and the Changelog in `CHANGELOG.md`.
|
||||||
|
2. Visit [the chrome developer dashboard](https://chrome.google.com/webstore/developer/dashboard?authuser=2).
|
||||||
|
|
||||||
|
## Publishing
|
||||||
|
|
||||||
|
1. `npm run dist` to generate the latest build.
|
||||||
|
2. Publish to chrome store.
|
||||||
|
3. Publish to firefox addon marketplace.
|
||||||
|
4. Post on Github releases page.
|
||||||
|
5. `npm run announce`, post that announcement in our public places.
|
||||||
|
|
6
docs/ui-dev-mode.md
Normal file
6
docs/ui-dev-mode.md
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
# Running UI Dev Mode
|
||||||
|
|
||||||
|
You can run `npm run ui`, and your browser should open a live-reloading demo version of the plugin UI.
|
||||||
|
|
||||||
|
Some actions will crash the app, so this is only for tuning aesthetics, but it allows live-reloading styles, which is a much faster feedback loop than reloading the full extension.
|
||||||
|
|
8
docs/ui-mock-mode.md
Normal file
8
docs/ui-mock-mode.md
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
### Developing on UI with Mocked Background Process
|
||||||
|
|
||||||
|
You can run `npm run mock` and your browser should open a live-reloading demo version of the plugin UI, just like the `npm run ui`, except that it tries to actually perform all normal operations.
|
||||||
|
|
||||||
|
It does not yet connect to a real blockchain (this could be a good test feature later, connecting to a test blockchain), so only local operations work.
|
||||||
|
|
||||||
|
You can reset the mock ui at any time with the `Reset` button at the top of the screen.
|
||||||
|
|
14
gulpfile.js
14
gulpfile.js
@ -52,6 +52,15 @@ gulp.task('copy:images', copyTask({
|
|||||||
'./dist/opera/images',
|
'./dist/opera/images',
|
||||||
],
|
],
|
||||||
}))
|
}))
|
||||||
|
gulp.task('copy:contractImages', copyTask({
|
||||||
|
source: './node_modules/ethereum-contract-icons/images/',
|
||||||
|
destinations: [
|
||||||
|
'./dist/firefox/images/contract',
|
||||||
|
'./dist/chrome/images/contract',
|
||||||
|
'./dist/edge/images/contract',
|
||||||
|
'./dist/opera/images/contract',
|
||||||
|
],
|
||||||
|
}))
|
||||||
gulp.task('copy:fonts', copyTask({
|
gulp.task('copy:fonts', copyTask({
|
||||||
source: './app/fonts/',
|
source: './app/fonts/',
|
||||||
destinations: [
|
destinations: [
|
||||||
@ -127,6 +136,7 @@ const staticFiles = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
var copyStrings = staticFiles.map(staticFile => `copy:${staticFile}`)
|
var copyStrings = staticFiles.map(staticFile => `copy:${staticFile}`)
|
||||||
|
copyStrings.push('copy:contractImages')
|
||||||
|
|
||||||
if (!disableLiveReload) {
|
if (!disableLiveReload) {
|
||||||
copyStrings.push('copy:reload')
|
copyStrings.push('copy:reload')
|
||||||
@ -182,7 +192,7 @@ gulp.task('build:js', gulp.parallel(...jsBuildStrings))
|
|||||||
// disc bundle analyzer tasks
|
// disc bundle analyzer tasks
|
||||||
|
|
||||||
jsFiles.forEach((jsFile) => {
|
jsFiles.forEach((jsFile) => {
|
||||||
gulp.task(`disc:${jsFile}`, bundleTask({ label: jsFile, filename: `${jsFile}.js` }))
|
gulp.task(`disc:${jsFile}`, discTask({ label: jsFile, filename: `${jsFile}.js` }))
|
||||||
})
|
})
|
||||||
|
|
||||||
gulp.task('disc', gulp.parallel(jsFiles.map(jsFile => `disc:${jsFile}`)))
|
gulp.task('disc', gulp.parallel(jsFiles.map(jsFile => `disc:${jsFile}`)))
|
||||||
@ -296,8 +306,6 @@ function bundleTask(opts) {
|
|||||||
return (
|
return (
|
||||||
|
|
||||||
bundler.bundle()
|
bundler.bundle()
|
||||||
// log errors if they happen
|
|
||||||
.on('error', gutil.log.bind(gutil, 'Browserify Error'))
|
|
||||||
// convert bundle stream to gulp vinyl stream
|
// convert bundle stream to gulp vinyl stream
|
||||||
.pipe(source(opts.filename))
|
.pipe(source(opts.filename))
|
||||||
// inject variables into bundle
|
// inject variables into bundle
|
||||||
|
@ -1,24 +0,0 @@
|
|||||||
start the dual servers (dapp + mascara)
|
|
||||||
```
|
|
||||||
node server.js
|
|
||||||
```
|
|
||||||
|
|
||||||
open the example dapp at `http://localhost:9002/`
|
|
||||||
|
|
||||||
*You will need to build MetaMask in order for this to work*
|
|
||||||
```
|
|
||||||
gulp dev
|
|
||||||
```
|
|
||||||
to build MetaMask and have it live reload if you make changes
|
|
||||||
|
|
||||||
|
|
||||||
## First time use:
|
|
||||||
|
|
||||||
- navigate to: http://127.0.0.1:9001/popup/popup.html
|
|
||||||
- Create an Account
|
|
||||||
- go back to http://localhost:9002/
|
|
||||||
- open devTools
|
|
||||||
- click Sync Tx
|
|
||||||
|
|
||||||
### Todos
|
|
||||||
- Look into using [Service Workers](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API)
|
|
@ -1,159 +0,0 @@
|
|||||||
const urlUtil = require('url')
|
|
||||||
const extend = require('xtend')
|
|
||||||
const Dnode = require('dnode')
|
|
||||||
const eos = require('end-of-stream')
|
|
||||||
const ParentStream = require('iframe-stream').ParentStream
|
|
||||||
const PortStream = require('../app/scripts/lib/port-stream.js')
|
|
||||||
const notification = require('../app/scripts/lib/notifications.js')
|
|
||||||
const messageManager = require('../app/scripts/lib/message-manager')
|
|
||||||
const setupMultiplex = require('../app/scripts/lib/stream-utils.js').setupMultiplex
|
|
||||||
const MetamaskController = require('../app/scripts/metamask-controller')
|
|
||||||
const extension = require('../app/scripts/lib/extension')
|
|
||||||
|
|
||||||
const STORAGE_KEY = 'metamask-config'
|
|
||||||
|
|
||||||
|
|
||||||
initializeZeroClient()
|
|
||||||
|
|
||||||
function initializeZeroClient() {
|
|
||||||
|
|
||||||
const controller = new MetamaskController({
|
|
||||||
// User confirmation callbacks:
|
|
||||||
showUnconfirmedMessage,
|
|
||||||
unlockAccountMessage,
|
|
||||||
showUnapprovedTx,
|
|
||||||
// Persistence Methods:
|
|
||||||
setData,
|
|
||||||
loadData,
|
|
||||||
})
|
|
||||||
const idStore = controller.idStore
|
|
||||||
|
|
||||||
function unlockAccountMessage () {
|
|
||||||
console.log('notif stub - unlockAccountMessage')
|
|
||||||
}
|
|
||||||
|
|
||||||
function showUnconfirmedMessage (msgParams, msgId) {
|
|
||||||
console.log('notif stub - showUnconfirmedMessage')
|
|
||||||
}
|
|
||||||
|
|
||||||
function showUnapprovedTx (txParams, txData, onTxDoneCb) {
|
|
||||||
console.log('notif stub - showUnapprovedTx')
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// connect to other contexts
|
|
||||||
//
|
|
||||||
|
|
||||||
var connectionStream = new ParentStream()
|
|
||||||
|
|
||||||
connectRemote(connectionStream, getParentHref())
|
|
||||||
|
|
||||||
function connectRemote (connectionStream, originDomain) {
|
|
||||||
var isMetaMaskInternalProcess = (originDomain === '127.0.0.1:9001')
|
|
||||||
if (isMetaMaskInternalProcess) {
|
|
||||||
// communication with popup
|
|
||||||
setupTrustedCommunication(connectionStream, 'MetaMask')
|
|
||||||
} else {
|
|
||||||
// communication with page
|
|
||||||
setupUntrustedCommunication(connectionStream, originDomain)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function setupUntrustedCommunication (connectionStream, originDomain) {
|
|
||||||
// setup multiplexing
|
|
||||||
var mx = setupMultiplex(connectionStream)
|
|
||||||
// connect features
|
|
||||||
controller.setupProviderConnection(mx.createStream('provider'), originDomain)
|
|
||||||
controller.setupPublicConfig(mx.createStream('publicConfig'))
|
|
||||||
}
|
|
||||||
|
|
||||||
function setupTrustedCommunication (connectionStream, originDomain) {
|
|
||||||
// setup multiplexing
|
|
||||||
var mx = setupMultiplex(connectionStream)
|
|
||||||
// connect features
|
|
||||||
setupControllerConnection(mx.createStream('controller'))
|
|
||||||
controller.setupProviderConnection(mx.createStream('provider'), originDomain)
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// remote features
|
|
||||||
//
|
|
||||||
|
|
||||||
function setupControllerConnection (stream) {
|
|
||||||
controller.stream = stream
|
|
||||||
var api = controller.getApi()
|
|
||||||
var dnode = Dnode(api)
|
|
||||||
stream.pipe(dnode).pipe(stream)
|
|
||||||
dnode.on('remote', (remote) => {
|
|
||||||
// push updates to popup
|
|
||||||
controller.ethStore.on('update', controller.sendUpdate.bind(controller))
|
|
||||||
controller.listeners.push(remote)
|
|
||||||
idStore.on('update', controller.sendUpdate.bind(controller))
|
|
||||||
|
|
||||||
// teardown on disconnect
|
|
||||||
eos(stream, () => {
|
|
||||||
controller.ethStore.removeListener('update', controller.sendUpdate.bind(controller))
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadData () {
|
|
||||||
var oldData = getOldStyleData()
|
|
||||||
var newData
|
|
||||||
try {
|
|
||||||
newData = JSON.parse(window.localStorage[STORAGE_KEY])
|
|
||||||
} catch (e) {}
|
|
||||||
|
|
||||||
var data = extend({
|
|
||||||
meta: {
|
|
||||||
version: 0,
|
|
||||||
},
|
|
||||||
data: {
|
|
||||||
config: {
|
|
||||||
provider: {
|
|
||||||
type: 'testnet',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}, oldData || null, newData || null)
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
|
|
||||||
function getOldStyleData () {
|
|
||||||
var config, wallet, seedWords
|
|
||||||
|
|
||||||
var result = {
|
|
||||||
meta: { version: 0 },
|
|
||||||
data: {},
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
config = JSON.parse(window.localStorage['config'])
|
|
||||||
result.data.config = config
|
|
||||||
} catch (e) {}
|
|
||||||
try {
|
|
||||||
wallet = JSON.parse(window.localStorage['lightwallet'])
|
|
||||||
result.data.wallet = wallet
|
|
||||||
} catch (e) {}
|
|
||||||
try {
|
|
||||||
seedWords = window.localStorage['seedWords']
|
|
||||||
result.data.seedWords = seedWords
|
|
||||||
} catch (e) {}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
function setData (data) {
|
|
||||||
window.localStorage[STORAGE_KEY] = JSON.stringify(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
function getParentHref(){
|
|
||||||
try {
|
|
||||||
var parentLocation = window.parent.location
|
|
||||||
return parentLocation.hostname + ':' + parentLocation.port
|
|
||||||
} catch (err) {
|
|
||||||
return 'unknown'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,43 +0,0 @@
|
|||||||
const Web3 = require('web3')
|
|
||||||
const setupProvider = require('./lib/setup-provider.js')
|
|
||||||
|
|
||||||
//
|
|
||||||
// setup web3
|
|
||||||
//
|
|
||||||
var provider = setupProvider()
|
|
||||||
hijackProvider(provider)
|
|
||||||
var web3 = new Web3(provider)
|
|
||||||
web3.setProvider = function(){
|
|
||||||
console.log('MetaMask - overrode web3.setProvider')
|
|
||||||
}
|
|
||||||
console.log('metamask lib hijacked provider')
|
|
||||||
|
|
||||||
//
|
|
||||||
// export web3
|
|
||||||
//
|
|
||||||
|
|
||||||
global.web3 = web3
|
|
||||||
|
|
||||||
//
|
|
||||||
// ui stuff
|
|
||||||
//
|
|
||||||
|
|
||||||
var shouldPop = false
|
|
||||||
window.addEventListener('click', function(){
|
|
||||||
if (!shouldPop) return
|
|
||||||
shouldPop = false
|
|
||||||
window.open('http://127.0.0.1:9001/popup/popup.html', '', 'width=360 height=500')
|
|
||||||
console.log('opening window...')
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
function hijackProvider(provider){
|
|
||||||
var _super = provider.sendAsync.bind(provider)
|
|
||||||
provider.sendAsync = function(payload, cb){
|
|
||||||
if (payload.method === 'eth_sendTransaction') {
|
|
||||||
console.log('saw send')
|
|
||||||
shouldPop = true
|
|
||||||
}
|
|
||||||
_super(payload, cb)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,19 +0,0 @@
|
|||||||
const injectCss = require('inject-css')
|
|
||||||
const MetaMaskUiCss = require('../ui/css')
|
|
||||||
const startPopup = require('../app/scripts/popup-core')
|
|
||||||
const setupIframe = require('./lib/setup-iframe.js')
|
|
||||||
|
|
||||||
|
|
||||||
var css = MetaMaskUiCss()
|
|
||||||
injectCss(css)
|
|
||||||
|
|
||||||
var name = 'popup'
|
|
||||||
window.METAMASK_UI_TYPE = name
|
|
||||||
|
|
||||||
var iframeStream = setupIframe({
|
|
||||||
zeroClientProvider: 'http://127.0.0.1:9001',
|
|
||||||
sandboxAttributes: ['allow-scripts', 'allow-popups', 'allow-same-origin'],
|
|
||||||
container: document.body,
|
|
||||||
})
|
|
||||||
|
|
||||||
startPopup(iframeStream)
|
|
@ -1,96 +0,0 @@
|
|||||||
const express = require('express')
|
|
||||||
const browserify = require('browserify')
|
|
||||||
const watchify = require('watchify')
|
|
||||||
const babelify = require('babelify')
|
|
||||||
|
|
||||||
const zeroBundle = createBundle('./index.js')
|
|
||||||
const controllerBundle = createBundle('./controller.js')
|
|
||||||
const popupBundle = createBundle('./popup.js')
|
|
||||||
const appBundle = createBundle('./example/index.js')
|
|
||||||
|
|
||||||
//
|
|
||||||
// Iframe Server
|
|
||||||
//
|
|
||||||
|
|
||||||
const iframeServer = express()
|
|
||||||
|
|
||||||
// serve popup window
|
|
||||||
iframeServer.get('/popup/scripts/popup.js', function(req, res){
|
|
||||||
res.send(popupBundle.latest)
|
|
||||||
})
|
|
||||||
iframeServer.use('/popup', express.static('../dist/chrome'))
|
|
||||||
|
|
||||||
// serve controller bundle
|
|
||||||
iframeServer.get('/controller.js', function(req, res){
|
|
||||||
res.send(controllerBundle.latest)
|
|
||||||
})
|
|
||||||
|
|
||||||
// serve background controller
|
|
||||||
iframeServer.use(express.static('./server'))
|
|
||||||
|
|
||||||
// start the server
|
|
||||||
const mascaraPort = 9001
|
|
||||||
iframeServer.listen(mascaraPort)
|
|
||||||
console.log(`Mascara service listening on port ${mascaraPort}`)
|
|
||||||
|
|
||||||
|
|
||||||
//
|
|
||||||
// Dapp Server
|
|
||||||
//
|
|
||||||
|
|
||||||
const dappServer = express()
|
|
||||||
|
|
||||||
// serve metamask-lib bundle
|
|
||||||
dappServer.get('/zero.js', function(req, res){
|
|
||||||
res.send(zeroBundle.latest)
|
|
||||||
})
|
|
||||||
|
|
||||||
// serve dapp bundle
|
|
||||||
dappServer.get('/app.js', function(req, res){
|
|
||||||
res.send(appBundle.latest)
|
|
||||||
})
|
|
||||||
|
|
||||||
// serve static
|
|
||||||
dappServer.use(express.static('./example'))
|
|
||||||
|
|
||||||
// start the server
|
|
||||||
const dappPort = '9002'
|
|
||||||
dappServer.listen(dappPort)
|
|
||||||
console.log(`Dapp listening on port ${dappPort}`)
|
|
||||||
|
|
||||||
//
|
|
||||||
// util
|
|
||||||
//
|
|
||||||
|
|
||||||
function serveBundle(entryPoint){
|
|
||||||
const bundle = createBundle(entryPoint)
|
|
||||||
return function(req, res){
|
|
||||||
res.send(bundle.latest)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function createBundle(entryPoint){
|
|
||||||
|
|
||||||
var bundleContainer = {}
|
|
||||||
|
|
||||||
var bundler = browserify({
|
|
||||||
entries: [entryPoint],
|
|
||||||
cache: {},
|
|
||||||
packageCache: {},
|
|
||||||
plugin: [watchify],
|
|
||||||
})
|
|
||||||
|
|
||||||
bundler.on('update', bundle)
|
|
||||||
bundle()
|
|
||||||
|
|
||||||
return bundleContainer
|
|
||||||
|
|
||||||
function bundle() {
|
|
||||||
bundler.bundle(function(err, result){
|
|
||||||
if (err) throw err
|
|
||||||
console.log(`Bundle updated! (${entryPoint})`)
|
|
||||||
bundleContainer.latest = result.toString()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
33
mascara/README.md
Normal file
33
mascara/README.md
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
start the dual servers (dapp + mascara)
|
||||||
|
```
|
||||||
|
npm run mascara
|
||||||
|
```
|
||||||
|
|
||||||
|
### First time use:
|
||||||
|
|
||||||
|
- navigate to: http://localhost:9001
|
||||||
|
- Create an Account
|
||||||
|
- go back to http://localhost:9002
|
||||||
|
- open devTools
|
||||||
|
- click Sync Tx
|
||||||
|
|
||||||
|
### Tests:
|
||||||
|
|
||||||
|
```
|
||||||
|
npm run testMascara
|
||||||
|
```
|
||||||
|
|
||||||
|
Test will run in browser, you will have to have these browsers installed:
|
||||||
|
|
||||||
|
- Chrome
|
||||||
|
- Firefox
|
||||||
|
- Opera
|
||||||
|
|
||||||
|
|
||||||
|
### Deploy:
|
||||||
|
|
||||||
|
Will build and deploy mascara via docker
|
||||||
|
|
||||||
|
```
|
||||||
|
docker-compose build && docker-compose stop && docker-compose up -d && docker-compose logs --tail 200 -f
|
||||||
|
```
|
@ -1,5 +1,5 @@
|
|||||||
|
|
||||||
window.addEventListener('load', web3Detect)
|
window.addEventListener('load', web3Detect)
|
||||||
|
window.addEventListener('message', console.warn)
|
||||||
|
|
||||||
function web3Detect() {
|
function web3Detect() {
|
||||||
if (global.web3) {
|
if (global.web3) {
|
||||||
@ -13,10 +13,10 @@ function web3Detect() {
|
|||||||
function startApp(){
|
function startApp(){
|
||||||
console.log('app started')
|
console.log('app started')
|
||||||
|
|
||||||
var primaryAccount = null
|
var primaryAccount
|
||||||
console.log('getting main account...')
|
console.log('getting main account...')
|
||||||
web3.eth.getAccounts(function(err, addresses){
|
web3.eth.getAccounts((err, addresses) => {
|
||||||
if (err) throw err
|
if (err) console.error(err)
|
||||||
console.log('set address', addresses[0])
|
console.log('set address', addresses[0])
|
||||||
primaryAccount = addresses[0]
|
primaryAccount = addresses[0]
|
||||||
})
|
})
|
||||||
@ -24,6 +24,7 @@ function startApp(){
|
|||||||
document.querySelector('.action-button-1').addEventListener('click', function(){
|
document.querySelector('.action-button-1').addEventListener('click', function(){
|
||||||
console.log('saw click')
|
console.log('saw click')
|
||||||
console.log('sending tx')
|
console.log('sending tx')
|
||||||
|
primaryAccount
|
||||||
web3.eth.sendTransaction({
|
web3.eth.sendTransaction({
|
||||||
from: primaryAccount,
|
from: primaryAccount,
|
||||||
to: primaryAccount,
|
to: primaryAccount,
|
@ -3,15 +3,13 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
|
|
||||||
<title>MetaMask ZeroClient Example</title>
|
<title>MetaMask ZeroClient Example</title>
|
||||||
|
<script src="http://localhost:9001/metamascara.js"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<button class="action-button-1">SYNC TX</button>
|
<button class="action-button-1">SYNC TX</button>
|
||||||
<button class="action-button-2">ASYNC TX</button>
|
<button class="action-button-2">ASYNC TX</button>
|
||||||
<script src="./zero.js"></script>
|
|
||||||
<script src="./app.js"></script>
|
<script src="./app.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
31
mascara/example/server.js
Normal file
31
mascara/example/server.js
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
const express = require('express')
|
||||||
|
const createMetamascaraServer = require('../server/')
|
||||||
|
const createBundle = require('../server/util').createBundle
|
||||||
|
const serveBundle = require('../server/util').serveBundle
|
||||||
|
|
||||||
|
//
|
||||||
|
// Iframe Server
|
||||||
|
//
|
||||||
|
|
||||||
|
const mascaraServer = createMetamascaraServer()
|
||||||
|
|
||||||
|
// start the server
|
||||||
|
const mascaraPort = 9001
|
||||||
|
mascaraServer.listen(mascaraPort)
|
||||||
|
console.log(`Mascara service listening on port ${mascaraPort}`)
|
||||||
|
|
||||||
|
|
||||||
|
//
|
||||||
|
// Dapp Server
|
||||||
|
//
|
||||||
|
|
||||||
|
const dappServer = express()
|
||||||
|
|
||||||
|
// serve dapp bundle
|
||||||
|
serveBundle(dappServer, '/app.js', createBundle(require.resolve('./app.js')))
|
||||||
|
dappServer.use(express.static(__dirname + '/app/'))
|
||||||
|
|
||||||
|
// start the server
|
||||||
|
const dappPort = '9002'
|
||||||
|
dappServer.listen(dappPort)
|
||||||
|
console.log(`Dapp listening on port ${dappPort}`)
|
@ -15,6 +15,6 @@
|
|||||||
|
|
||||||
<body>
|
<body>
|
||||||
Hello! I am the MetaMask iframe.
|
Hello! I am the MetaMask iframe.
|
||||||
<script src="/controller.js"></script>
|
<script src="./proxy.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
32
mascara/server/index.js
Normal file
32
mascara/server/index.js
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
const express = require('express')
|
||||||
|
const createBundle = require('./util').createBundle
|
||||||
|
const serveBundle = require('./util').serveBundle
|
||||||
|
|
||||||
|
module.exports = createMetamascaraServer
|
||||||
|
|
||||||
|
|
||||||
|
function createMetamascaraServer(){
|
||||||
|
|
||||||
|
// start bundlers
|
||||||
|
const metamascaraBundle = createBundle(__dirname + '/../src/mascara.js')
|
||||||
|
const proxyBundle = createBundle(__dirname + '/../src/proxy.js')
|
||||||
|
const uiBundle = createBundle(__dirname + '/../src/ui.js')
|
||||||
|
const backgroundBuild = createBundle(__dirname + '/../src/background.js')
|
||||||
|
|
||||||
|
// serve bundles
|
||||||
|
const server = express()
|
||||||
|
// ui window
|
||||||
|
serveBundle(server, '/ui.js', uiBundle)
|
||||||
|
server.use(express.static(__dirname+'/../ui/'))
|
||||||
|
server.use(express.static(__dirname+'/../../dist/chrome'))
|
||||||
|
// metamascara
|
||||||
|
serveBundle(server, '/metamascara.js', metamascaraBundle)
|
||||||
|
// proxy
|
||||||
|
serveBundle(server, '/proxy/proxy.js', proxyBundle)
|
||||||
|
server.use('/proxy/', express.static(__dirname+'/../proxy'))
|
||||||
|
// background
|
||||||
|
serveBundle(server, '/background.js', backgroundBuild)
|
||||||
|
|
||||||
|
return server
|
||||||
|
|
||||||
|
}
|
45
mascara/server/util.js
Normal file
45
mascara/server/util.js
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
const browserify = require('browserify')
|
||||||
|
const watchify = require('watchify')
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
serveBundle,
|
||||||
|
createBundle,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function serveBundle(server, path, bundle){
|
||||||
|
server.get(path, function(req, res){
|
||||||
|
res.setHeader('Content-Type', 'application/javascript; charset=UTF-8')
|
||||||
|
res.send(bundle.latest)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function createBundle(entryPoint){
|
||||||
|
|
||||||
|
var bundleContainer = {}
|
||||||
|
|
||||||
|
var bundler = browserify({
|
||||||
|
entries: [entryPoint],
|
||||||
|
cache: {},
|
||||||
|
packageCache: {},
|
||||||
|
plugin: [watchify],
|
||||||
|
})
|
||||||
|
|
||||||
|
bundler.on('update', bundle)
|
||||||
|
bundle()
|
||||||
|
|
||||||
|
return bundleContainer
|
||||||
|
|
||||||
|
function bundle() {
|
||||||
|
bundler.bundle(function(err, result){
|
||||||
|
if (err) {
|
||||||
|
console.log(`Bundle failed! (${entryPoint})`)
|
||||||
|
console.error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
console.log(`Bundle updated! (${entryPoint})`)
|
||||||
|
bundleContainer.latest = result.toString()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
155
mascara/src/background.js
Normal file
155
mascara/src/background.js
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
global.window = global
|
||||||
|
const self = global
|
||||||
|
const pipe = require('pump')
|
||||||
|
|
||||||
|
const SwGlobalListener = require('sw-stream/lib/sw-global-listener.js')
|
||||||
|
const connectionListener = new SwGlobalListener(self)
|
||||||
|
const setupMultiplex = require('../../app/scripts/lib/stream-utils.js').setupMultiplex
|
||||||
|
const PortStream = require('../../app/scripts/lib/port-stream.js')
|
||||||
|
|
||||||
|
const DbController = require('idb-global')
|
||||||
|
|
||||||
|
const SwPlatform = require('../../app/scripts/platforms/sw')
|
||||||
|
const MetamaskController = require('../../app/scripts/metamask-controller')
|
||||||
|
const extension = {} //require('../../app/scripts/lib/extension')
|
||||||
|
|
||||||
|
const storeTransform = require('obs-store/lib/transform')
|
||||||
|
const Migrator = require('../../app/scripts/lib/migrator/')
|
||||||
|
const migrations = require('../../app/scripts/migrations/')
|
||||||
|
const firstTimeState = require('../../app/scripts/first-time-state')
|
||||||
|
|
||||||
|
const STORAGE_KEY = 'metamask-config'
|
||||||
|
// const METAMASK_DEBUG = 'GULP_METAMASK_DEBUG'
|
||||||
|
const METAMASK_DEBUG = true
|
||||||
|
let popupIsOpen = false
|
||||||
|
let connectedClientCount = 0
|
||||||
|
|
||||||
|
const log = require('loglevel')
|
||||||
|
global.log = log
|
||||||
|
log.setDefaultLevel(METAMASK_DEBUG ? 'debug' : 'warn')
|
||||||
|
|
||||||
|
self.addEventListener('install', function(event) {
|
||||||
|
event.waitUntil(self.skipWaiting())
|
||||||
|
})
|
||||||
|
self.addEventListener('activate', function(event) {
|
||||||
|
event.waitUntil(self.clients.claim())
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log('inside:open')
|
||||||
|
|
||||||
|
|
||||||
|
// // state persistence
|
||||||
|
let diskStore
|
||||||
|
const dbController = new DbController({
|
||||||
|
key: STORAGE_KEY,
|
||||||
|
})
|
||||||
|
loadStateFromPersistence()
|
||||||
|
.then((initState) => setupController(initState))
|
||||||
|
.then(() => console.log('MetaMask initialization complete.'))
|
||||||
|
.catch((err) => console.error('WHILE SETTING UP:', err))
|
||||||
|
|
||||||
|
// initialization flow
|
||||||
|
|
||||||
|
//
|
||||||
|
// State and Persistence
|
||||||
|
//
|
||||||
|
function loadStateFromPersistence() {
|
||||||
|
// migrations
|
||||||
|
let migrator = new Migrator({ migrations })
|
||||||
|
const initialState = migrator.generateInitialState(firstTimeState)
|
||||||
|
dbController.initialState = initialState
|
||||||
|
return dbController.open()
|
||||||
|
.then((versionedData) => migrator.migrateData(versionedData))
|
||||||
|
.then((versionedData) => {
|
||||||
|
dbController.put(versionedData)
|
||||||
|
return Promise.resolve(versionedData)
|
||||||
|
})
|
||||||
|
.then((versionedData) => Promise.resolve(versionedData.data))
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupController (initState, client) {
|
||||||
|
|
||||||
|
//
|
||||||
|
// MetaMask Controller
|
||||||
|
//
|
||||||
|
|
||||||
|
const platform = new SwPlatform()
|
||||||
|
|
||||||
|
const controller = new MetamaskController({
|
||||||
|
// platform specific implementation
|
||||||
|
platform,
|
||||||
|
// User confirmation callbacks:
|
||||||
|
showUnconfirmedMessage: noop,
|
||||||
|
unlockAccountMessage: noop,
|
||||||
|
showUnapprovedTx: noop,
|
||||||
|
// initial state
|
||||||
|
initState,
|
||||||
|
})
|
||||||
|
global.metamaskController = controller
|
||||||
|
|
||||||
|
controller.store.subscribe((state) => {
|
||||||
|
versionifyData(state)
|
||||||
|
.then((versionedData) => dbController.put(versionedData))
|
||||||
|
.catch((err) => {console.error(err)})
|
||||||
|
})
|
||||||
|
function versionifyData(state) {
|
||||||
|
return dbController.get()
|
||||||
|
.then((rawData) => {
|
||||||
|
return Promise.resolve({
|
||||||
|
data: state,
|
||||||
|
meta: rawData.meta,
|
||||||
|
})}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// connect to other contexts
|
||||||
|
//
|
||||||
|
|
||||||
|
connectionListener.on('remote', (portStream, messageEvent) => {
|
||||||
|
console.log('REMOTE CONECTION FOUND***********')
|
||||||
|
connectedClientCount += 1
|
||||||
|
connectRemote(portStream, messageEvent.data.context)
|
||||||
|
})
|
||||||
|
|
||||||
|
function connectRemote (connectionStream, context) {
|
||||||
|
var isMetaMaskInternalProcess = (context === 'popup')
|
||||||
|
if (isMetaMaskInternalProcess) {
|
||||||
|
// communication with popup
|
||||||
|
controller.setupTrustedCommunication(connectionStream, 'MetaMask')
|
||||||
|
popupIsOpen = true
|
||||||
|
} else {
|
||||||
|
// communication with page
|
||||||
|
setupUntrustedCommunication(connectionStream, context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupUntrustedCommunication (connectionStream, originDomain) {
|
||||||
|
// setup multiplexing
|
||||||
|
var mx = setupMultiplex(connectionStream)
|
||||||
|
// connect features
|
||||||
|
controller.setupProviderConnection(mx.createStream('provider'), originDomain)
|
||||||
|
controller.setupPublicConfig(mx.createStream('publicConfig'))
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupTrustedCommunication (connectionStream, originDomain) {
|
||||||
|
// setup multiplexing
|
||||||
|
var mx = setupMultiplex(connectionStream)
|
||||||
|
// connect features
|
||||||
|
controller.setupProviderConnection(mx.createStream('provider'), originDomain)
|
||||||
|
}
|
||||||
|
//
|
||||||
|
// User Interface setup
|
||||||
|
//
|
||||||
|
return Promise.resolve()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendMessageToAllClients (message) {
|
||||||
|
self.clients.matchAll().then(function(clients) {
|
||||||
|
clients.forEach(function(client) {
|
||||||
|
client.postMessage(message)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
function noop () {}
|
@ -1,19 +1,17 @@
|
|||||||
const setupIframe = require('./setup-iframe.js')
|
const setupIframe = require('./setup-iframe.js')
|
||||||
const MetamaskInpageProvider = require('../../app/scripts/lib/inpage-provider.js')
|
const MetamaskInpageProvider = require('../../../app/scripts/lib/inpage-provider.js')
|
||||||
|
|
||||||
module.exports = getProvider
|
module.exports = getProvider
|
||||||
|
|
||||||
|
|
||||||
function getProvider(){
|
function getProvider(opts){
|
||||||
|
|
||||||
if (global.web3) {
|
if (global.web3) {
|
||||||
console.log('MetaMask ZeroClient - using environmental web3 provider')
|
console.log('MetaMask ZeroClient - using environmental web3 provider')
|
||||||
return global.web3.currentProvider
|
return global.web3.currentProvider
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('MetaMask ZeroClient - injecting zero-client iframe!')
|
console.log('MetaMask ZeroClient - injecting zero-client iframe!')
|
||||||
var iframeStream = setupIframe({
|
var iframeStream = setupIframe({
|
||||||
zeroClientProvider: 'http://127.0.0.1:9001',
|
zeroClientProvider: opts.mascaraUrl,
|
||||||
sandboxAttributes: ['allow-scripts', 'allow-popups', 'allow-same-origin'],
|
sandboxAttributes: ['allow-scripts', 'allow-popups', 'allow-same-origin'],
|
||||||
container: document.body,
|
container: document.body,
|
||||||
})
|
})
|
||||||
@ -22,4 +20,3 @@ function getProvider(){
|
|||||||
return inpageProvider
|
return inpageProvider
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
47
mascara/src/mascara.js
Normal file
47
mascara/src/mascara.js
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
const Web3 = require('web3')
|
||||||
|
const setupProvider = require('./lib/setup-provider.js')
|
||||||
|
const setupDappAutoReload = require('../../app/scripts/lib/auto-reload.js')
|
||||||
|
const MASCARA_ORIGIN = process.env.MASCARA_ORIGIN || 'http://localhost:9001'
|
||||||
|
console.log('MASCARA_ORIGIN:', MASCARA_ORIGIN)
|
||||||
|
|
||||||
|
//
|
||||||
|
// setup web3
|
||||||
|
//
|
||||||
|
|
||||||
|
const provider = setupProvider({
|
||||||
|
mascaraUrl: MASCARA_ORIGIN + '/proxy/',
|
||||||
|
})
|
||||||
|
instrumentForUserInteractionTriggers(provider)
|
||||||
|
|
||||||
|
const web3 = new Web3(provider)
|
||||||
|
setupDappAutoReload(web3, provider.publicConfigStore)
|
||||||
|
//
|
||||||
|
// ui stuff
|
||||||
|
//
|
||||||
|
|
||||||
|
let shouldPop = false
|
||||||
|
window.addEventListener('click', maybeTriggerPopup)
|
||||||
|
|
||||||
|
//
|
||||||
|
// util
|
||||||
|
//
|
||||||
|
|
||||||
|
function maybeTriggerPopup(){
|
||||||
|
if (!shouldPop) return
|
||||||
|
shouldPop = false
|
||||||
|
window.open(MASCARA_ORIGIN, '', 'width=360 height=500')
|
||||||
|
console.log('opening window...')
|
||||||
|
}
|
||||||
|
|
||||||
|
function instrumentForUserInteractionTriggers(provider){
|
||||||
|
const _super = provider.sendAsync.bind(provider)
|
||||||
|
provider.sendAsync = function(payload, cb){
|
||||||
|
if (payload.method === 'eth_sendTransaction') {
|
||||||
|
console.log('saw send')
|
||||||
|
shouldPop = true
|
||||||
|
}
|
||||||
|
_super(payload, cb)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
26
mascara/src/proxy.js
Normal file
26
mascara/src/proxy.js
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
const ParentStream = require('iframe-stream').ParentStream
|
||||||
|
const SWcontroller = require('client-sw-ready-event/lib/sw-client.js')
|
||||||
|
const SwStream = require('sw-stream/lib/sw-stream.js')
|
||||||
|
const SetupUntrustedComunication = ('./lib/setup-untrusted-connection.js')
|
||||||
|
|
||||||
|
let intervalDelay = Math.floor(Math.random() * (30000 - 1000)) + 1000
|
||||||
|
const background = new SWcontroller({
|
||||||
|
fileName: '/background.js',
|
||||||
|
letBeIdle: false,
|
||||||
|
wakeUpInterval: 30000,
|
||||||
|
intervalDelay,
|
||||||
|
})
|
||||||
|
|
||||||
|
const pageStream = new ParentStream()
|
||||||
|
background.on('ready', (_) => {
|
||||||
|
let swStream = SwStream({
|
||||||
|
serviceWorker: background.controller,
|
||||||
|
context: 'dapp',
|
||||||
|
})
|
||||||
|
pageStream.pipe(swStream).pipe(pageStream)
|
||||||
|
|
||||||
|
})
|
||||||
|
background.on('updatefound', () => window.location.reload())
|
||||||
|
|
||||||
|
background.on('error', console.error)
|
||||||
|
background.startWorker()
|
56
mascara/src/ui.js
Normal file
56
mascara/src/ui.js
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
const injectCss = require('inject-css')
|
||||||
|
const SWcontroller = require('client-sw-ready-event/lib/sw-client.js')
|
||||||
|
const SwStream = require('sw-stream/lib/sw-stream.js')
|
||||||
|
const MetaMaskUiCss = require('../../ui/css')
|
||||||
|
const setupIframe = require('./lib/setup-iframe.js')
|
||||||
|
const MetamaskInpageProvider = require('../../app/scripts/lib/inpage-provider.js')
|
||||||
|
const MetamascaraPlatform = require('../../app/scripts/platforms/window')
|
||||||
|
const startPopup = require('../../app/scripts/popup-core')
|
||||||
|
|
||||||
|
// create platform global
|
||||||
|
global.platform = new MetamascaraPlatform()
|
||||||
|
|
||||||
|
|
||||||
|
var css = MetaMaskUiCss()
|
||||||
|
injectCss(css)
|
||||||
|
const container = document.getElementById('app-content')
|
||||||
|
|
||||||
|
var name = 'popup'
|
||||||
|
window.METAMASK_UI_TYPE = name
|
||||||
|
|
||||||
|
let intervalDelay = Math.floor(Math.random() * (30000 - 1000)) + 1000
|
||||||
|
|
||||||
|
const background = new SWcontroller({
|
||||||
|
fileName: '/background.js',
|
||||||
|
letBeIdle: false,
|
||||||
|
intervalDelay,
|
||||||
|
wakeUpInterval: 20000
|
||||||
|
})
|
||||||
|
// Setup listener for when the service worker is read
|
||||||
|
const connectApp = function (readSw) {
|
||||||
|
let connectionStream = SwStream({
|
||||||
|
serviceWorker: background.controller,
|
||||||
|
context: name,
|
||||||
|
})
|
||||||
|
startPopup({container, connectionStream}, (err, store) => {
|
||||||
|
if (err) return displayCriticalError(err)
|
||||||
|
store.subscribe(() => {
|
||||||
|
const state = store.getState()
|
||||||
|
if (state.appState.shouldClose) window.close()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
background.on('ready', (sw) => {
|
||||||
|
background.removeListener('updatefound', connectApp)
|
||||||
|
connectApp(sw)
|
||||||
|
})
|
||||||
|
background.on('updatefound', () => window.location.reload())
|
||||||
|
|
||||||
|
background.startWorker()
|
||||||
|
.then(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
const appContent = document.getElementById(`app-content`)
|
||||||
|
if (!appContent.children.length) window.location.reload()
|
||||||
|
}, 2000)
|
||||||
|
})
|
||||||
|
console.log('hello from MetaMascara ui!')
|
7
mascara/test/helpers.js
Normal file
7
mascara/test/helpers.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
function wait(time) {
|
||||||
|
return new Promise(function(resolve, reject) {
|
||||||
|
setTimeout(function() {
|
||||||
|
resolve()
|
||||||
|
}, time * 3 || 1500)
|
||||||
|
})
|
||||||
|
}
|
21
mascara/test/index.html
Normal file
21
mascara/test/index.html
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width">
|
||||||
|
<title>QUnit Example</title>
|
||||||
|
<link rel="stylesheet" href="https://code.jquery.com/qunit/qunit-2.0.0.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="qunit"></div>
|
||||||
|
<div id="qunit-fixture"></div>
|
||||||
|
<script src="https://code.jquery.com/qunit/qunit-2.0.0.js"></script>
|
||||||
|
<script src="./jquery-3.1.0.min.js"></script>
|
||||||
|
<script src="./helpers.js"></script>
|
||||||
|
<script src="./test-bundle.js"></script>
|
||||||
|
<script src="/testem.js"></script>
|
||||||
|
|
||||||
|
<div id="app-content"></div>
|
||||||
|
<script src="./bundle.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
22
mascara/test/index.js
Normal file
22
mascara/test/index.js
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
var fs = require('fs')
|
||||||
|
var path = require('path')
|
||||||
|
var browserify = require('browserify');
|
||||||
|
var tests = fs.readdirSync(path.join(__dirname, 'lib'))
|
||||||
|
var bundlePath = path.join(__dirname, 'test-bundle.js')
|
||||||
|
var b = browserify();
|
||||||
|
|
||||||
|
// Remove old bundle
|
||||||
|
try {
|
||||||
|
fs.unlinkSync(bundlePath)
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
var writeStream = fs.createWriteStream(bundlePath)
|
||||||
|
|
||||||
|
tests.forEach(function(fileName) {
|
||||||
|
b.add(path.join(__dirname, 'lib', fileName))
|
||||||
|
})
|
||||||
|
|
||||||
|
b.bundle().pipe(writeStream);
|
||||||
|
|
4
mascara/test/jquery-3.1.0.min.js
vendored
Normal file
4
mascara/test/jquery-3.1.0.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
119
mascara/test/lib/first-time.js
Normal file
119
mascara/test/lib/first-time.js
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
const PASSWORD = 'password123'
|
||||||
|
|
||||||
|
QUnit.module('first time usage')
|
||||||
|
|
||||||
|
QUnit.test('render init screen', function (assert) {
|
||||||
|
var done = assert.async()
|
||||||
|
let app
|
||||||
|
|
||||||
|
wait(1000).then(function() {
|
||||||
|
app = $('#app-content').contents()
|
||||||
|
const recurseNotices = function () {
|
||||||
|
let button = app.find('button')
|
||||||
|
if (button.html() === 'Accept') {
|
||||||
|
let termsPage = app.find('.markdown')[0]
|
||||||
|
termsPage.scrollTop = termsPage.scrollHeight
|
||||||
|
return wait().then(() => {
|
||||||
|
button.click()
|
||||||
|
return wait()
|
||||||
|
}).then(() => {
|
||||||
|
return recurseNotices()
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
return wait()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return recurseNotices()
|
||||||
|
}).then(function() {
|
||||||
|
// Scroll through terms
|
||||||
|
var title = app.find('h1').text()
|
||||||
|
assert.equal(title, 'MetaMask', 'title screen')
|
||||||
|
|
||||||
|
// enter password
|
||||||
|
var pwBox = app.find('#password-box')[0]
|
||||||
|
var confBox = app.find('#password-box-confirm')[0]
|
||||||
|
pwBox.value = PASSWORD
|
||||||
|
confBox.value = PASSWORD
|
||||||
|
|
||||||
|
return wait()
|
||||||
|
}).then(function() {
|
||||||
|
|
||||||
|
// create vault
|
||||||
|
var createButton = app.find('button.primary')[0]
|
||||||
|
createButton.click()
|
||||||
|
|
||||||
|
return wait(1500)
|
||||||
|
}).then(function() {
|
||||||
|
|
||||||
|
var created = app.find('h3')[0]
|
||||||
|
assert.equal(created.textContent, 'Vault Created', 'Vault created screen')
|
||||||
|
|
||||||
|
// Agree button
|
||||||
|
var button = app.find('button')[0]
|
||||||
|
assert.ok(button, 'button present')
|
||||||
|
button.click()
|
||||||
|
|
||||||
|
return wait(1000)
|
||||||
|
}).then(function() {
|
||||||
|
|
||||||
|
var detail = app.find('.account-detail-section')[0]
|
||||||
|
assert.ok(detail, 'Account detail section loaded.')
|
||||||
|
|
||||||
|
var sandwich = app.find('.sandwich-expando')[0]
|
||||||
|
sandwich.click()
|
||||||
|
|
||||||
|
return wait()
|
||||||
|
}).then(function() {
|
||||||
|
|
||||||
|
var sandwich = app.find('.menu-droppo')[0]
|
||||||
|
var children = sandwich.children
|
||||||
|
var lock = children[children.length - 2]
|
||||||
|
assert.ok(lock, 'Lock menu item found')
|
||||||
|
lock.click()
|
||||||
|
|
||||||
|
return wait(1000)
|
||||||
|
}).then(function() {
|
||||||
|
|
||||||
|
var pwBox = app.find('#password-box')[0]
|
||||||
|
pwBox.value = PASSWORD
|
||||||
|
|
||||||
|
var createButton = app.find('button.primary')[0]
|
||||||
|
createButton.click()
|
||||||
|
|
||||||
|
return wait(1000)
|
||||||
|
}).then(function() {
|
||||||
|
|
||||||
|
var detail = app.find('.account-detail-section')[0]
|
||||||
|
assert.ok(detail, 'Account detail section loaded again.')
|
||||||
|
|
||||||
|
return wait()
|
||||||
|
}).then(function (){
|
||||||
|
|
||||||
|
var qrButton = app.find('.fa.fa-qrcode')[0]
|
||||||
|
qrButton.click()
|
||||||
|
|
||||||
|
return wait(1000)
|
||||||
|
}).then(function (){
|
||||||
|
|
||||||
|
var qrHeader = app.find('.qr-header')[0]
|
||||||
|
var qrContainer = app.find('#qr-container')[0]
|
||||||
|
assert.equal(qrHeader.textContent, 'Account 1', 'Should show account label.')
|
||||||
|
assert.ok(qrContainer, 'QR Container found')
|
||||||
|
|
||||||
|
return wait()
|
||||||
|
}).then(function (){
|
||||||
|
|
||||||
|
var networkMenu = app.find('.network-indicator')[0]
|
||||||
|
networkMenu.click()
|
||||||
|
|
||||||
|
return wait()
|
||||||
|
}).then(function (){
|
||||||
|
|
||||||
|
var networkMenu = app.find('.network-indicator')[0]
|
||||||
|
var children = networkMenu.children
|
||||||
|
children.length[3]
|
||||||
|
assert.ok(children, 'All network options present')
|
||||||
|
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
13
mascara/test/testem.yml
Normal file
13
mascara/test/testem.yml
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
launch_in_dev:
|
||||||
|
- Chrome
|
||||||
|
- Firefox
|
||||||
|
- Opera
|
||||||
|
launch_in_ci:
|
||||||
|
- Chrome
|
||||||
|
- Firefox
|
||||||
|
- Opera
|
||||||
|
framework:
|
||||||
|
- qunit
|
||||||
|
before_tests: "npm run mascaraCi"
|
||||||
|
after_tests: "rm ./background.js ./test-bundle.js ./bundle.js"
|
||||||
|
test_page: "./index.html"
|
40
mascara/test/util/mascara-test-helper.js
Normal file
40
mascara/test/util/mascara-test-helper.js
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
const EventEmitter = require('events')
|
||||||
|
const IDB = require('idb-global')
|
||||||
|
const KEY = 'metamask-test-config'
|
||||||
|
module.exports = class Helper extends EventEmitter {
|
||||||
|
constructor () {
|
||||||
|
super()
|
||||||
|
}
|
||||||
|
|
||||||
|
tryToCleanContext () {
|
||||||
|
this.unregister()
|
||||||
|
.then(() => this.clearDb())
|
||||||
|
.then(() => super.emit('complete'))
|
||||||
|
.catch((err) => super.emit('complete'))
|
||||||
|
}
|
||||||
|
|
||||||
|
unregister () {
|
||||||
|
return global.navigator.serviceWorker.getRegistration()
|
||||||
|
.then((registration) => {
|
||||||
|
if (registration) return registration.unregister()
|
||||||
|
.then((b) => b ? Promise.resolve() : Promise.reject())
|
||||||
|
else return Promise.resolve()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
clearDb () {
|
||||||
|
return new Promise ((resolve, reject) => {
|
||||||
|
const deleteRequest = global.indexDB.deleteDatabase(KEY)
|
||||||
|
deleteRequest.addEventListener('success', resolve)
|
||||||
|
deleteRequest.addEventListener('error', reject)
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
mockState (state) {
|
||||||
|
const db = new IDB({
|
||||||
|
version: 2,
|
||||||
|
key: KEY,
|
||||||
|
initialState: state
|
||||||
|
})
|
||||||
|
return db.open()
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user