mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 09:52:26 +01:00
Merge branch 'master' into AddTokenList
This commit is contained in:
commit
a741cc4fc4
@ -1 +1,5 @@
|
||||
app/scripts/lib/extension-instance.js
|
||||
test/integration/bundle.js
|
||||
test/integration/jquery-3.1.0.min.js
|
||||
test/integration/helpers.js
|
||||
test/integration/lib/first-time.js
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"parserOptions": {
|
||||
"sourceType": "module",
|
||||
"ecmaVersion": 6,
|
||||
"ecmaVersion": 2017,
|
||||
"ecmaFeatures": {
|
||||
"experimentalObjectRestSpread": true,
|
||||
"impliedStrict": true,
|
||||
@ -17,10 +17,13 @@
|
||||
"env": {
|
||||
"es6": true,
|
||||
"node": true,
|
||||
"browser": true
|
||||
"browser": true,
|
||||
"mocha" : true
|
||||
},
|
||||
|
||||
"plugins": [
|
||||
"mocha",
|
||||
"chai"
|
||||
],
|
||||
|
||||
"globals": {
|
||||
|
25
.gitignore
vendored
25
.gitignore
vendored
@ -1,18 +1,27 @@
|
||||
dist
|
||||
npm-debug.log
|
||||
node_modules
|
||||
temp
|
||||
.tmp
|
||||
.sass-cache
|
||||
package-lock.json
|
||||
|
||||
app/bower_components
|
||||
test/bower_components
|
||||
package
|
||||
|
||||
temp
|
||||
.tmp
|
||||
.sass-cache
|
||||
.DS_Store
|
||||
app/.DS_Store
|
||||
|
||||
dist
|
||||
builds/
|
||||
disc/
|
||||
notes.txt
|
||||
app/.DS_Store
|
||||
development/bundle.js
|
||||
builds.zip
|
||||
test/integration/bundle.js
|
||||
|
||||
development/bundle.js
|
||||
development/states.js
|
||||
test/integration/bundle.js
|
||||
test/background.js
|
||||
test/bundle.js
|
||||
test/test-bundle.js
|
||||
|
||||
notes.txt
|
95
CHANGELOG.md
95
CHANGELOG.md
@ -2,8 +2,103 @@
|
||||
|
||||
## Current Master
|
||||
|
||||
## 3.7.8 2017-6-12
|
||||
|
||||
- Add a `ethereum:` prefix to the QR code address
|
||||
- The default network on installation is now MainNet
|
||||
- Fix currency API URL from cryptonator.
|
||||
- Update gasLimit params with every new block seen.
|
||||
|
||||
## 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
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
FROM node:6
|
||||
FROM node:7
|
||||
MAINTAINER kumavis
|
||||
|
||||
# setup app dir
|
||||
|
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.
|
||||
|
||||
## Installing Local Builds on Chrome
|
||||
### Running Tests
|
||||
|
||||
To install your locally built extension on Chrome, [follow this guide](http://stackoverflow.com/a/24577660/272576).
|
||||
Requires `mocha` installed. Run `npm install -g mocha`.
|
||||
|
||||
The built extension is stored in `./dist/chrome/`.
|
||||
Then just run `npm test`.
|
||||
|
||||
You can also test with a continuously watching process, via `npm run watch`.
|
||||
|
||||
You can run the linter by itself with `gulp lint`.
|
||||
|
||||
## Architecture
|
||||
|
||||
@ -41,126 +45,22 @@ npm start
|
||||
npm run dist
|
||||
```
|
||||
|
||||
#### In Chrome
|
||||
|
||||
Open `Settings` > `Extensions`.
|
||||
|
||||
Check "Developer mode".
|
||||
|
||||
At the top, click `Load Unpacked Extension`.
|
||||
|
||||
Navigate to your `metamask-plugin/dist/chrome` folder.
|
||||
|
||||
Click `Select`.
|
||||
|
||||
You now have the plugin, and can click 'inspect views: background plugin' to view its dev console.
|
||||
|
||||
#### In Firefox
|
||||
|
||||
Go to the url `about:debugging`.
|
||||
|
||||
Click the button `Load Temporary Add-On`.
|
||||
|
||||
Select the file `dist/firefox/manifest.json`.
|
||||
|
||||
You can optionally enable debugging, and click `Debug`, for a console window that logs all of Metamask's processes to a single console.
|
||||
|
||||
If you have problems debugging, try connecting to the IRC channel `#webextensions` on `irc.mozilla.org`.
|
||||
|
||||
For longer questions, use the StackOverfow tag `firefox-addons`.
|
||||
|
||||
### Developing on UI Only
|
||||
|
||||
You can run `npm run ui`, and your browser should open a live-reloading demo version of the plugin UI.
|
||||
|
||||
Some actions will crash the app, so this is only for tuning aesthetics, but it allows live-reloading styles, which is a much faster feedback loop than reloading the full extension.
|
||||
|
||||
### Developing on UI with Mocked Background Process
|
||||
|
||||
You can run `npm run mock` and your browser should open a live-reloading demo version of the plugin UI, just like the `npm run ui`, except that it tries to actually perform all normal operations.
|
||||
|
||||
It does not yet connect to a real blockchain (this could be a good test feature later, connecting to a test blockchain), so only local operations work.
|
||||
|
||||
You can reset the mock ui at any time with the `Reset` button at the top of the screen.
|
||||
|
||||
### Developing on Dependencies
|
||||
|
||||
To enjoy the live-reloading that `gulp dev` offers while working on the `web3-provider-engine` or other dependencies:
|
||||
|
||||
1. Clone the dependency locally.
|
||||
2. `npm install` in its folder.
|
||||
3. Run `npm link` in its folder.
|
||||
4. Run `npm link $DEP_NAME` in this project folder.
|
||||
5. Next time you `npm start` it will watch the dependency for changes as well!
|
||||
|
||||
### Running Tests
|
||||
|
||||
Requires `mocha` installed. Run `npm install -g mocha`.
|
||||
|
||||
Then just run `npm test`.
|
||||
|
||||
You can also test with a continuously watching process, via `npm run watch`.
|
||||
|
||||
You can run the linter by itself with `gulp lint`.
|
||||
|
||||
#### Writing Browser Tests
|
||||
|
||||
To write tests that will be run in the browser using QUnit, add your test files to `test/integration/lib`.
|
||||
|
||||
### Deploying the UI
|
||||
## Other Docs
|
||||
|
||||
You must be authorized already on the MetaMask plugin.
|
||||
|
||||
0. Update the version in `app/manifest.json` and the Changelog in `CHANGELOG.md`.
|
||||
1. Visit [the chrome developer dashboard](https://chrome.google.com/webstore/developer/dashboard?authuser=2).
|
||||
2. Run `gulp dist` (or `gulp zip` if you've already built)
|
||||
3. Upload the latest zip file from `builds/metamask-$PLATFORM-$VERSION.zip` as the updated package.
|
||||
- [How to add custom build to Chrome](./docs/add-to-chrome.md)
|
||||
- [How to add custom build to Firefox](./docs/add-to-firefox.md)
|
||||
- [How to develop a live-reloading UI](./docs/ui-dev-mode.md)
|
||||
- [Publishing Guide](./docs/publishing.md)
|
||||
- [How to develop an in-browser mocked UI](./docs/ui-mock-mode.md)
|
||||
- [How to live reload on local dependency changes](./docs/developing-on-deps.md)
|
||||
- [How to add new networks to the Provider Menu](./docs/adding-new-networks.md)
|
||||
- [How to manage notices that appear when the app starts up](./docs/notices.md)
|
||||
- [How to generate a visualization of this repository's development](./docs/development-visualization.md)
|
||||
|
||||
[1]: http://www.nomnoml.com/#view/%5B%3Cactor%3Euser%5D%0A%0A%5Bmetamask-ui%7C%0A%20%20%20%5Btools%7C%0A%20%20%20%20%20react%0A%20%20%20%20%20redux%0A%20%20%20%20%20thunk%0A%20%20%20%20%20ethUtils%0A%20%20%20%20%20jazzicon%0A%20%20%20%5D%0A%20%20%20%5Bcomponents%7C%0A%20%20%20%20%20app%0A%20%20%20%20%20account-detail%0A%20%20%20%20%20accounts%0A%20%20%20%20%20locked-screen%0A%20%20%20%20%20restore-vault%0A%20%20%20%20%20identicon%0A%20%20%20%20%20config%0A%20%20%20%20%20info%0A%20%20%20%5D%0A%20%20%20%5Breducers%7C%0A%20%20%20%20%20app%0A%20%20%20%20%20metamask%0A%20%20%20%20%20identities%0A%20%20%20%5D%0A%20%20%20%5Bactions%7C%0A%20%20%20%20%20%5BaccountManager%5D%0A%20%20%20%5D%0A%20%20%20%5Bcomponents%5D%3A-%3E%5Bactions%5D%0A%20%20%20%5Bactions%5D%3A-%3E%5Breducers%5D%0A%20%20%20%5Breducers%5D%3A-%3E%5Bcomponents%5D%0A%5D%0A%0A%5Bweb%20dapp%7C%0A%20%20%5Bui%20code%5D%0A%20%20%5Bweb3%5D%0A%20%20%5Bmetamask-inpage%5D%0A%20%20%0A%20%20%5B%3Cactor%3Eui%20developer%5D%0A%20%20%5Bui%20developer%5D-%3E%5Bui%20code%5D%0A%20%20%5Bui%20code%5D%3C-%3E%5Bweb3%5D%0A%20%20%5Bweb3%5D%3C-%3E%5Bmetamask-inpage%5D%0A%5D%0A%0A%5Bmetamask-background%7C%0A%20%20%5Bprovider-engine%5D%0A%20%20%5Bhooked%20wallet%20subprovider%5D%0A%20%20%5Bid%20store%5D%0A%20%20%0A%20%20%5Bprovider-engine%5D%3C-%3E%5Bhooked%20wallet%20subprovider%5D%0A%20%20%5Bhooked%20wallet%20subprovider%5D%3C-%3E%5Bid%20store%5D%0A%20%20%5Bconfig%20manager%7C%0A%20%20%20%20%5Brpc%20configuration%5D%0A%20%20%20%20%5Bencrypted%20keys%5D%0A%20%20%20%20%5Bwallet%20nicknames%5D%0A%20%20%5D%0A%20%20%0A%20%20%5Bprovider-engine%5D%3C-%5Bconfig%20manager%5D%0A%20%20%5Bid%20store%5D%3C-%3E%5Bconfig%20manager%5D%0A%5D%0A%0A%5Buser%5D%3C-%3E%5Bmetamask-ui%5D%0A%0A%5Buser%5D%3C%3A--%3A%3E%5Bweb%20dapp%5D%0A%0A%5Bmetamask-contentscript%7C%0A%20%20%5Bplugin%20restart%20detector%5D%0A%20%20%5Brpc%20passthrough%5D%0A%5D%0A%0A%5Brpc%20%7C%0A%20%20%5Bethereum%20blockchain%20%7C%0A%20%20%20%20%5Bcontracts%5D%0A%20%20%20%20%5Baccounts%5D%0A%20%20%5D%0A%5D%0A%0A%5Bweb%20dapp%5D%3C%3A--%3A%3E%5Bmetamask-contentscript%5D%0A%5Bmetamask-contentscript%5D%3C-%3E%5Bmetamask-background%5D%0A%5Bmetamask-background%5D%3C-%3E%5Bmetamask-ui%5D%0A%5Bmetamask-background%5D%3C-%3E%5Brpc%5D%0A
|
||||
|
||||
|
||||
### Generate Development Visualization
|
||||
|
||||
This will generate a video of the repo commit history.
|
||||
|
||||
Install preqs:
|
||||
```
|
||||
brew install gource
|
||||
brew install ffmpeg
|
||||
```
|
||||
|
||||
From the repo dir, pipe `gource` into `ffmpeg`:
|
||||
```
|
||||
gource \
|
||||
--seconds-per-day .1 \
|
||||
--user-scale 1.5 \
|
||||
--default-user-image "./images/icon-512.png" \
|
||||
--viewport 1280x720 \
|
||||
--auto-skip-seconds .1 \
|
||||
--multi-sampling \
|
||||
--stop-at-end \
|
||||
--highlight-users \
|
||||
--hide mouse,progress \
|
||||
--file-idle-time 0 \
|
||||
--max-files 0 \
|
||||
--background-colour 000000 \
|
||||
--font-size 18 \
|
||||
--date-format "%b %d, %Y" \
|
||||
--highlight-dirs \
|
||||
--user-friction 0.1 \
|
||||
--title "MetaMask Development History" \
|
||||
--output-ppm-stream - \
|
||||
--output-framerate 30 \
|
||||
| ffmpeg -y -r 30 -f image2pipe -vcodec ppm -i - -b 65536K metamask-dev-history.mp4
|
||||
```
|
||||
|
||||
## Generating Notices
|
||||
|
||||
To add a notice:
|
||||
```
|
||||
npm run generateNotice
|
||||
```
|
||||
To delete a notice:
|
||||
```
|
||||
npm run deleteNotice
|
||||
```
|
||||
|
File diff suppressed because one or more lines are too long
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "MetaMask",
|
||||
"short_name": "Metamask",
|
||||
"version": "3.5.2",
|
||||
"version": "3.7.8",
|
||||
"manifest_version": 2,
|
||||
"author": "https://metamask.io",
|
||||
"description": "Ethereum Browser Extension",
|
||||
@ -58,7 +58,7 @@
|
||||
"storage",
|
||||
"clipboardWrite",
|
||||
"http://localhost:8545/",
|
||||
"https://www.cryptonator.com/"
|
||||
"https://api.cryptonator.com/"
|
||||
],
|
||||
"web_accessible_resources": [
|
||||
"scripts/inpage.js"
|
||||
|
@ -4,7 +4,7 @@ const ethUtil = require('ethereumjs-util')
|
||||
|
||||
const accountImporter = {
|
||||
|
||||
importAccount(strategy, args) {
|
||||
importAccount (strategy, args) {
|
||||
try {
|
||||
const importer = this.strategies[strategy]
|
||||
const privateKeyHex = importer.apply(null, args)
|
||||
|
@ -1,6 +1,5 @@
|
||||
const urlUtil = require('url')
|
||||
const endOfStream = require('end-of-stream')
|
||||
const asyncQ = require('async-q')
|
||||
const pipe = require('pump')
|
||||
const LocalStorageStore = require('obs-store/lib/localStorage')
|
||||
const storeTransform = require('obs-store/lib/transform')
|
||||
@ -30,38 +29,32 @@ let popupIsOpen = false
|
||||
const diskStore = new LocalStorageStore({ storageKey: STORAGE_KEY })
|
||||
|
||||
// initialization flow
|
||||
asyncQ.waterfall([
|
||||
() => loadStateFromPersistence(),
|
||||
(initState) => setupController(initState),
|
||||
])
|
||||
.then(() => console.log('MetaMask initialization complete.'))
|
||||
.catch((err) => { console.error(err) })
|
||||
initialize().catch(console.error)
|
||||
|
||||
async function initialize () {
|
||||
const initState = await loadStateFromPersistence()
|
||||
await setupController(initState)
|
||||
console.log('MetaMask initialization complete.')
|
||||
}
|
||||
|
||||
//
|
||||
// State and Persistence
|
||||
//
|
||||
|
||||
function loadStateFromPersistence() {
|
||||
async function loadStateFromPersistence () {
|
||||
// migrations
|
||||
let migrator = new Migrator({ migrations })
|
||||
let initialState = migrator.generateInitialState(firstTimeState)
|
||||
return asyncQ.waterfall([
|
||||
// read from disk
|
||||
() => Promise.resolve(diskStore.getState() || initialState),
|
||||
// migrate data
|
||||
(versionedData) => migrator.migrateData(versionedData),
|
||||
// write to disk
|
||||
(versionedData) => {
|
||||
diskStore.putState(versionedData)
|
||||
return Promise.resolve(versionedData)
|
||||
},
|
||||
// resolve to just data
|
||||
(versionedData) => Promise.resolve(versionedData.data),
|
||||
])
|
||||
const migrator = new Migrator({ migrations })
|
||||
// read from disk
|
||||
let versionedData = diskStore.getState() || migrator.generateInitialState(firstTimeState)
|
||||
// migrate data
|
||||
versionedData = await migrator.migrateData(versionedData)
|
||||
// write to disk
|
||||
diskStore.putState(versionedData)
|
||||
// return just the data
|
||||
return versionedData.data
|
||||
}
|
||||
|
||||
function setupController (initState) {
|
||||
|
||||
//
|
||||
// MetaMask Controller
|
||||
//
|
||||
@ -85,8 +78,8 @@ function setupController (initState) {
|
||||
diskStore
|
||||
)
|
||||
|
||||
function versionifyData(state) {
|
||||
let versionedData = diskStore.getState()
|
||||
function versionifyData (state) {
|
||||
const versionedData = diskStore.getState()
|
||||
versionedData.data = state
|
||||
return versionedData
|
||||
}
|
||||
@ -121,13 +114,13 @@ function setupController (initState) {
|
||||
//
|
||||
|
||||
updateBadge()
|
||||
controller.txManager.on('updateBadge', updateBadge)
|
||||
controller.txController.on('updateBadge', updateBadge)
|
||||
controller.messageManager.on('updateBadge', updateBadge)
|
||||
|
||||
// plugin badge text
|
||||
function updateBadge () {
|
||||
var label = ''
|
||||
var unapprovedTxCount = controller.txManager.unapprovedTxCount
|
||||
var unapprovedTxCount = controller.txController.unapprovedTxCount
|
||||
var unapprovedMsgCount = controller.messageManager.unapprovedMsgCount
|
||||
var count = unapprovedTxCount + unapprovedMsgCount
|
||||
if (count) {
|
||||
@ -138,7 +131,6 @@ function setupController (initState) {
|
||||
}
|
||||
|
||||
return Promise.resolve()
|
||||
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -1,16 +1,15 @@
|
||||
const MAINET_RPC_URL = 'https://mainnet.infura.io/metamask'
|
||||
const TESTNET_RPC_URL = 'https://ropsten.infura.io/metamask'
|
||||
const ROPSTEN_RPC_URL = 'https://ropsten.infura.io/metamask'
|
||||
const KOVAN_RPC_URL = 'https://kovan.infura.io/metamask'
|
||||
const DEFAULT_RPC_URL = TESTNET_RPC_URL
|
||||
const RINKEBY_RPC_URL = 'https://rinkeby.infura.io/metamask'
|
||||
|
||||
global.METAMASK_DEBUG = 'GULP_METAMASK_DEBUG'
|
||||
|
||||
module.exports = {
|
||||
network: {
|
||||
default: DEFAULT_RPC_URL,
|
||||
mainnet: MAINET_RPC_URL,
|
||||
testnet: TESTNET_RPC_URL,
|
||||
morden: TESTNET_RPC_URL,
|
||||
ropsten: ROPSTEN_RPC_URL,
|
||||
kovan: KOVAN_RPC_URL,
|
||||
rinkeby: RINKEBY_RPC_URL,
|
||||
},
|
||||
}
|
||||
|
@ -61,7 +61,6 @@ function setupStreams () {
|
||||
// ignore unused channels (handled by background)
|
||||
mx.ignoreStream('provider')
|
||||
mx.ignoreStream('publicConfig')
|
||||
mx.ignoreStream('reload')
|
||||
}
|
||||
|
||||
function shouldInjectWeb3 () {
|
||||
@ -77,7 +76,7 @@ function doctypeCheck () {
|
||||
}
|
||||
}
|
||||
|
||||
function suffixCheck() {
|
||||
function suffixCheck () {
|
||||
var prohibitedTypes = ['xml', 'pdf']
|
||||
var currentUrl = window.location.href
|
||||
var currentRegex
|
||||
|
@ -39,11 +39,11 @@ class AddressBookController {
|
||||
// pushed object is an object of two fields. Current behavior does not set an
|
||||
// upper limit to the number of addresses.
|
||||
_addToAddressBook (address, name) {
|
||||
let addressBook = this._getAddressBook()
|
||||
let identities = this._getIdentities()
|
||||
const addressBook = this._getAddressBook()
|
||||
const identities = this._getIdentities()
|
||||
|
||||
let addressBookIndex = addressBook.findIndex((element) => { return element.address.toLowerCase() === address.toLowerCase() || element.name === name })
|
||||
let identitiesIndex = Object.keys(identities).findIndex((element) => { return element.toLowerCase() === address.toLowerCase() })
|
||||
const addressBookIndex = addressBook.findIndex((element) => { return element.address.toLowerCase() === address.toLowerCase() || element.name === name })
|
||||
const identitiesIndex = Object.keys(identities).findIndex((element) => { return element.toLowerCase() === address.toLowerCase() })
|
||||
// trigger this condition if we own this address--no need to overwrite.
|
||||
if (identitiesIndex !== -1) {
|
||||
return Promise.resolve(addressBook)
|
||||
|
@ -45,15 +45,17 @@ class CurrencyController {
|
||||
|
||||
updateConversionRate () {
|
||||
const currentCurrency = this.getCurrentCurrency()
|
||||
return fetch(`https://www.cryptonator.com/api/ticker/eth-${currentCurrency}`)
|
||||
return fetch(`https://api.cryptonator.com/api/ticker/eth-${currentCurrency}`)
|
||||
.then(response => response.json())
|
||||
.then((parsedResponse) => {
|
||||
this.setConversionRate(Number(parsedResponse.ticker.price))
|
||||
this.setConversionDate(Number(parsedResponse.timestamp))
|
||||
}).catch((err) => {
|
||||
console.warn('MetaMask - Failed to query currency conversion.')
|
||||
this.setConversionRate(0)
|
||||
this.setConversionDate('N/A')
|
||||
if (err) {
|
||||
console.warn('MetaMask - Failed to query currency conversion.')
|
||||
this.setConversionRate(0)
|
||||
this.setConversionDate('N/A')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
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) {
|
||||
let rpcList = this.getFrequentRpcList()
|
||||
let index = rpcList.findIndex((element) => { return element === _url })
|
||||
const rpcList = this.getFrequentRpcList()
|
||||
const index = rpcList.findIndex((element) => { return element === _url })
|
||||
if (index !== -1) {
|
||||
rpcList.splice(index, 1)
|
||||
}
|
||||
@ -53,13 +53,9 @@ class PreferencesController {
|
||||
getFrequentRpcList () {
|
||||
return this.store.getState().frequentRpcList
|
||||
}
|
||||
|
||||
//
|
||||
// PRIVATE METHODS
|
||||
//
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
module.exports = PreferencesController
|
||||
|
@ -4,11 +4,14 @@ const extend = require('xtend')
|
||||
const Semaphore = require('semaphore')
|
||||
const ObservableStore = require('obs-store')
|
||||
const ethUtil = require('ethereumjs-util')
|
||||
const EthQuery = require('eth-query')
|
||||
const TxProviderUtil = require('./lib/tx-utils')
|
||||
const createId = require('./lib/random-id')
|
||||
const TxProviderUtil = require('../lib/tx-utils')
|
||||
const createId = require('../lib/random-id')
|
||||
const denodeify = require('denodeify')
|
||||
|
||||
module.exports = class TransactionManager extends EventEmitter {
|
||||
const RETRY_LIMIT = 200
|
||||
const RESUBMIT_INTERVAL = 10000 // Ten seconds
|
||||
|
||||
module.exports = class TransactionController extends EventEmitter {
|
||||
constructor (opts) {
|
||||
super()
|
||||
this.store = new ObservableStore(extend({
|
||||
@ -20,17 +23,19 @@ module.exports = class TransactionManager extends EventEmitter {
|
||||
this.txHistoryLimit = opts.txHistoryLimit
|
||||
this.provider = opts.provider
|
||||
this.blockTracker = opts.blockTracker
|
||||
this.query = new EthQuery(this.provider)
|
||||
this.txProviderUtils = new TxProviderUtil(this.provider)
|
||||
this.query = opts.ethQuery
|
||||
this.txProviderUtils = new TxProviderUtil(this.query)
|
||||
this.blockTracker.on('block', this.checkForTxInBlock.bind(this))
|
||||
this.signEthTx = opts.signTransaction
|
||||
this.nonceLock = Semaphore(1)
|
||||
|
||||
// memstore is computed from a few different stores
|
||||
this._updateMemstore()
|
||||
this.store.subscribe(() => this._updateMemstore() )
|
||||
this.networkStore.subscribe(() => this._updateMemstore() )
|
||||
this.preferencesStore.subscribe(() => this._updateMemstore() )
|
||||
this.store.subscribe(() => this._updateMemstore())
|
||||
this.networkStore.subscribe(() => this._updateMemstore())
|
||||
this.preferencesStore.subscribe(() => this._updateMemstore())
|
||||
|
||||
this.continuallyResubmitPendingTxs()
|
||||
}
|
||||
|
||||
getState () {
|
||||
@ -38,7 +43,7 @@ module.exports = class TransactionManager extends EventEmitter {
|
||||
}
|
||||
|
||||
getNetwork () {
|
||||
return this.networkStore.getState().network
|
||||
return this.networkStore.getState()
|
||||
}
|
||||
|
||||
getSelectedAddress () {
|
||||
@ -47,8 +52,8 @@ module.exports = class TransactionManager extends EventEmitter {
|
||||
|
||||
// Returns the tx list
|
||||
getTxList () {
|
||||
let network = this.getNetwork()
|
||||
let fullTxList = this.getFullTxList()
|
||||
const network = this.getNetwork()
|
||||
const fullTxList = this.getFullTxList()
|
||||
return fullTxList.filter(txMeta => txMeta.metamaskNetworkId === network)
|
||||
}
|
||||
|
||||
@ -64,10 +69,10 @@ module.exports = class TransactionManager extends EventEmitter {
|
||||
|
||||
// Adds a tx to the txlist
|
||||
addTx (txMeta) {
|
||||
let txCount = this.getTxCount()
|
||||
let network = this.getNetwork()
|
||||
let fullTxList = this.getFullTxList()
|
||||
let txHistoryLimit = this.txHistoryLimit
|
||||
const txCount = this.getTxCount()
|
||||
const network = this.getNetwork()
|
||||
const fullTxList = this.getFullTxList()
|
||||
const txHistoryLimit = this.txHistoryLimit
|
||||
|
||||
// checks if the length of the tx history is
|
||||
// longer then desired persistence limit
|
||||
@ -197,7 +202,7 @@ module.exports = class TransactionManager extends EventEmitter {
|
||||
}
|
||||
|
||||
fillInTxParams (txId, cb) {
|
||||
let txMeta = this.getTx(txId)
|
||||
const txMeta = this.getTx(txId)
|
||||
this.txProviderUtils.fillInTxParams(txMeta.txParams, (err) => {
|
||||
if (err) return cb(err)
|
||||
this.updateTx(txMeta)
|
||||
@ -205,7 +210,7 @@ module.exports = class TransactionManager extends EventEmitter {
|
||||
})
|
||||
}
|
||||
|
||||
getChainId() {
|
||||
getChainId () {
|
||||
const networkState = this.networkStore.getState()
|
||||
const getChainId = parseInt(networkState.network)
|
||||
if (Number.isNaN(getChainId)) {
|
||||
@ -230,7 +235,11 @@ module.exports = class TransactionManager extends EventEmitter {
|
||||
})
|
||||
}
|
||||
|
||||
publishTransaction (txId, rawTx, cb) {
|
||||
publishTransaction (txId, rawTx, cb = warn) {
|
||||
const txMeta = this.getTx(txId)
|
||||
txMeta.rawTx = rawTx
|
||||
this.updateTx(txMeta)
|
||||
|
||||
this.txProviderUtils.publishTransaction(rawTx, (err, txHash) => {
|
||||
if (err) return cb(err)
|
||||
this.setTxHash(txId, txHash)
|
||||
@ -242,7 +251,7 @@ module.exports = class TransactionManager extends EventEmitter {
|
||||
// receives a txHash records the tx as signed
|
||||
setTxHash (txId, txHash) {
|
||||
// Add the tx hash to the persisted meta-tx object
|
||||
let txMeta = this.getTx(txId)
|
||||
const txMeta = this.getTx(txId)
|
||||
txMeta.hash = txHash
|
||||
this.updateTx(txMeta)
|
||||
}
|
||||
@ -315,7 +324,7 @@ module.exports = class TransactionManager extends EventEmitter {
|
||||
}
|
||||
|
||||
setTxStatusFailed (txId, reason) {
|
||||
let txMeta = this.getTx(txId)
|
||||
const txMeta = this.getTx(txId)
|
||||
txMeta.err = reason
|
||||
this.updateTx(txMeta)
|
||||
this._setTxStatus(txId, 'failed')
|
||||
@ -338,7 +347,7 @@ module.exports = class TransactionManager extends EventEmitter {
|
||||
var txHash = txMeta.hash
|
||||
var txId = txMeta.id
|
||||
if (!txHash) {
|
||||
let errReason = {
|
||||
const errReason = {
|
||||
errCode: 'No hash was provided',
|
||||
message: 'We had an error while submitting this transaction, please try again.',
|
||||
}
|
||||
@ -353,7 +362,7 @@ module.exports = class TransactionManager extends EventEmitter {
|
||||
message: 'There was a problem loading this transaction.',
|
||||
}
|
||||
this.updateTx(txMeta)
|
||||
return console.error(err)
|
||||
return log.error(err)
|
||||
}
|
||||
if (txParams.blockNumber) {
|
||||
this.setTxStatusConfirmed(txId)
|
||||
@ -380,6 +389,7 @@ module.exports = class TransactionManager extends EventEmitter {
|
||||
this.emit(`${txMeta.id}:${status}`, txId)
|
||||
if (status === 'submitted' || status === 'rejected') {
|
||||
this.emit(`${txMeta.id}:finished`, txMeta)
|
||||
|
||||
}
|
||||
this.updateTx(txMeta)
|
||||
this.emit('updateBadge')
|
||||
@ -399,7 +409,47 @@ module.exports = class TransactionManager extends EventEmitter {
|
||||
})
|
||||
this.memStore.updateState({ unapprovedTxs, selectedAddressTxList })
|
||||
}
|
||||
|
||||
continuallyResubmitPendingTxs () {
|
||||
const pending = this.getTxsByMetaData('status', 'submitted')
|
||||
const resubmit = denodeify(this.resubmitTx.bind(this))
|
||||
Promise.all(pending.map(txMeta => resubmit(txMeta)))
|
||||
.catch((reason) => {
|
||||
log.info('Problem resubmitting tx', reason)
|
||||
})
|
||||
.then(() => {
|
||||
global.setTimeout(() => {
|
||||
this.continuallyResubmitPendingTxs()
|
||||
}, RESUBMIT_INTERVAL)
|
||||
})
|
||||
}
|
||||
|
||||
resubmitTx (txMeta, cb) {
|
||||
// Increment a try counter.
|
||||
if (!('retryCount' in txMeta)) {
|
||||
txMeta.retryCount = 0
|
||||
}
|
||||
|
||||
// Only auto-submit already-signed txs:
|
||||
if (!('rawTx' in txMeta)) {
|
||||
return cb()
|
||||
}
|
||||
|
||||
if (txMeta.retryCount > RETRY_LIMIT) {
|
||||
txMeta.err = {
|
||||
isWarning: true,
|
||||
message: 'Gave up submitting tx.',
|
||||
}
|
||||
this.updateTx(txMeta)
|
||||
return log.error(txMeta.err.message)
|
||||
}
|
||||
|
||||
txMeta.retryCount++
|
||||
const rawTx = txMeta.rawTx
|
||||
this.txProviderUtils.publishTransaction(rawTx, cb)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
const warn = () => console.warn('warn was used no cb provided')
|
||||
const warn = () => log.warn('warn was used no cb provided')
|
@ -1,11 +1,15 @@
|
||||
// test and development environment variables
|
||||
const env = process.env.METAMASK_ENV
|
||||
const METAMASK_DEBUG = 'GULP_METAMASK_DEBUG'
|
||||
|
||||
//
|
||||
// The default state of MetaMask
|
||||
//
|
||||
|
||||
module.exports = {
|
||||
config: {
|
||||
config: {},
|
||||
NetworkController: {
|
||||
provider: {
|
||||
type: 'testnet',
|
||||
type: (METAMASK_DEBUG || env === 'test') ? 'rinkeby' : 'mainnet',
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -31,26 +31,11 @@ web3.setProvider = function () {
|
||||
console.log('MetaMask - overrode web3.setProvider')
|
||||
}
|
||||
console.log('MetaMask - injected web3')
|
||||
// export global web3, with usage-detection reload fn
|
||||
var triggerReload = setupDappAutoReload(web3)
|
||||
|
||||
// listen for reset requests from metamask
|
||||
var reloadStream = inpageProvider.multiStream.createStream('reload')
|
||||
reloadStream.once('data', triggerReload)
|
||||
|
||||
// setup ping timeout autoreload
|
||||
// LocalMessageDuplexStream does not self-close, so reload if pingStream fails
|
||||
// var pingChannel = inpageProvider.multiStream.createStream('pingpong')
|
||||
// var pingStream = new PingStream({ objectMode: true })
|
||||
// wait for first successful reponse
|
||||
|
||||
// disable pingStream until https://github.com/MetaMask/metamask-plugin/issues/746 is resolved more gracefully
|
||||
// metamaskStream.once('data', function(){
|
||||
// pingStream.pipe(pingChannel).pipe(pingStream)
|
||||
// })
|
||||
// endOfStream(pingStream, triggerReload)
|
||||
// export global web3, with usage-detection
|
||||
setupDappAutoReload(web3, inpageProvider.publicConfigStore)
|
||||
|
||||
// set web3 defaultAccount
|
||||
|
||||
inpageProvider.publicConfigStore.subscribe(function (state) {
|
||||
web3.eth.defaultAccount = state.selectedAddress
|
||||
})
|
||||
|
@ -187,7 +187,7 @@ class KeyringController extends EventEmitter {
|
||||
.then((accounts) => {
|
||||
switch (type) {
|
||||
case 'Simple Key Pair':
|
||||
let isNotIncluded = !accounts.find((key) => key === newAccount[0] || key === ethUtil.stripHexPrefix(newAccount[0]))
|
||||
const isNotIncluded = !accounts.find((key) => key === newAccount[0] || key === ethUtil.stripHexPrefix(newAccount[0]))
|
||||
return (isNotIncluded) ? Promise.resolve(newAccount) : Promise.reject(new Error('The account you\'re are trying to import is a duplicate'))
|
||||
default:
|
||||
return Promise.resolve(newAccount)
|
||||
@ -582,7 +582,7 @@ class KeyringController extends EventEmitter {
|
||||
})
|
||||
}
|
||||
|
||||
_updateMemStoreKeyrings() {
|
||||
_updateMemStoreKeyrings () {
|
||||
Promise.all(this.keyrings.map(this.displayForKeyring))
|
||||
.then((keyrings) => {
|
||||
this.memStore.updateState({ keyrings })
|
||||
|
@ -1,30 +1,33 @@
|
||||
const once = require('once')
|
||||
const ensnare = require('ensnare')
|
||||
|
||||
module.exports = setupDappAutoReload
|
||||
|
||||
function setupDappAutoReload (web3) {
|
||||
function setupDappAutoReload (web3, observable) {
|
||||
// export web3 as a global, checking for usage
|
||||
var pageIsUsingWeb3 = false
|
||||
var resetWasRequested = false
|
||||
global.web3 = ensnare(web3, once(function () {
|
||||
// if web3 usage happened after a reset request, trigger reset late
|
||||
if (resetWasRequested) return triggerReset()
|
||||
// mark web3 as used
|
||||
pageIsUsingWeb3 = true
|
||||
// reset web3 reference
|
||||
global.web3 = web3
|
||||
}))
|
||||
global.web3 = new Proxy(web3, {
|
||||
get: (_web3, name) => {
|
||||
// get the time of use
|
||||
if (name !== '_used') _web3._used = Date.now()
|
||||
return _web3[name]
|
||||
},
|
||||
set: (_web3, name, value) => {
|
||||
_web3[name] = value
|
||||
},
|
||||
})
|
||||
var networkVersion
|
||||
|
||||
return handleResetRequest
|
||||
observable.subscribe(function (state) {
|
||||
// get the initial network
|
||||
const curentNetVersion = state.networkVersion
|
||||
if (!networkVersion) networkVersion = curentNetVersion
|
||||
|
||||
function handleResetRequest () {
|
||||
resetWasRequested = true
|
||||
// ignore if web3 was not used
|
||||
if (!pageIsUsingWeb3) return
|
||||
// reload after short timeout
|
||||
setTimeout(triggerReset, 500)
|
||||
}
|
||||
if (curentNetVersion !== networkVersion && web3._used) {
|
||||
const timeSinceUse = Date.now() - web3._used
|
||||
// if web3 was recently used then delay the reloading of the page
|
||||
timeSinceUse > 500 ? triggerReset() : setTimeout(triggerReset, 500)
|
||||
// prevent reentry into if statement if state updates again before
|
||||
// reload
|
||||
networkVersion = curentNetVersion
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// reload the page
|
||||
|
@ -1,6 +1,6 @@
|
||||
module.exports = getBuyEthUrl
|
||||
|
||||
function getBuyEthUrl({ network, amount, address }){
|
||||
function getBuyEthUrl ({ network, amount, address }) {
|
||||
let url
|
||||
switch (network) {
|
||||
case '1':
|
||||
@ -11,9 +11,13 @@ function getBuyEthUrl({ network, amount, address }){
|
||||
url = 'https://faucet.metamask.io/'
|
||||
break
|
||||
|
||||
case '4':
|
||||
url = 'https://www.rinkeby.io/'
|
||||
break
|
||||
|
||||
case '42':
|
||||
url = 'https://github.com/kovan-testnet/faucet'
|
||||
break
|
||||
}
|
||||
return url
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,12 @@
|
||||
const MetamaskConfig = require('../config.js')
|
||||
const ethUtil = require('ethereumjs-util')
|
||||
const normalize = require('eth-sig-util').normalize
|
||||
const MetamaskConfig = require('../config.js')
|
||||
|
||||
|
||||
const TESTNET_RPC = MetamaskConfig.network.testnet
|
||||
const MAINNET_RPC = MetamaskConfig.network.mainnet
|
||||
const MORDEN_RPC = MetamaskConfig.network.morden
|
||||
const ROPSTEN_RPC = MetamaskConfig.network.ropsten
|
||||
const KOVAN_RPC = MetamaskConfig.network.kovan
|
||||
const RINKEBY_RPC = MetamaskConfig.network.rinkeby
|
||||
|
||||
/* The config-manager is a convenience object
|
||||
* wrapping a pojo-migrator.
|
||||
@ -33,36 +34,6 @@ ConfigManager.prototype.getConfig = function () {
|
||||
return data.config
|
||||
}
|
||||
|
||||
ConfigManager.prototype.setRpcTarget = function (rpcUrl) {
|
||||
var config = this.getConfig()
|
||||
config.provider = {
|
||||
type: 'rpc',
|
||||
rpcTarget: rpcUrl,
|
||||
}
|
||||
this.setConfig(config)
|
||||
}
|
||||
|
||||
ConfigManager.prototype.setProviderType = function (type) {
|
||||
var config = this.getConfig()
|
||||
config.provider = {
|
||||
type: type,
|
||||
}
|
||||
this.setConfig(config)
|
||||
}
|
||||
|
||||
ConfigManager.prototype.useEtherscanProvider = function () {
|
||||
var config = this.getConfig()
|
||||
config.provider = {
|
||||
type: 'etherscan',
|
||||
}
|
||||
this.setConfig(config)
|
||||
}
|
||||
|
||||
ConfigManager.prototype.getProvider = function () {
|
||||
var config = this.getConfig()
|
||||
return config.provider
|
||||
}
|
||||
|
||||
ConfigManager.prototype.setData = function (data) {
|
||||
this.store.putState(data)
|
||||
}
|
||||
@ -136,6 +107,35 @@ ConfigManager.prototype.getSeedWords = function () {
|
||||
var data = this.getData()
|
||||
return data.seedWords
|
||||
}
|
||||
ConfigManager.prototype.setRpcTarget = function (rpcUrl) {
|
||||
var config = this.getConfig()
|
||||
config.provider = {
|
||||
type: 'rpc',
|
||||
rpcTarget: rpcUrl,
|
||||
}
|
||||
this.setConfig(config)
|
||||
}
|
||||
|
||||
ConfigManager.prototype.setProviderType = function (type) {
|
||||
var config = this.getConfig()
|
||||
config.provider = {
|
||||
type: type,
|
||||
}
|
||||
this.setConfig(config)
|
||||
}
|
||||
|
||||
ConfigManager.prototype.useEtherscanProvider = function () {
|
||||
var config = this.getConfig()
|
||||
config.provider = {
|
||||
type: 'etherscan',
|
||||
}
|
||||
this.setConfig(config)
|
||||
}
|
||||
|
||||
ConfigManager.prototype.getProvider = function () {
|
||||
var config = this.getConfig()
|
||||
return config.provider
|
||||
}
|
||||
|
||||
ConfigManager.prototype.getCurrentRpcAddress = function () {
|
||||
var provider = this.getProvider()
|
||||
@ -145,17 +145,17 @@ ConfigManager.prototype.getCurrentRpcAddress = function () {
|
||||
case 'mainnet':
|
||||
return MAINNET_RPC
|
||||
|
||||
case 'testnet':
|
||||
return TESTNET_RPC
|
||||
|
||||
case 'morden':
|
||||
return MORDEN_RPC
|
||||
case 'ropsten':
|
||||
return ROPSTEN_RPC
|
||||
|
||||
case 'kovan':
|
||||
return KOVAN_RPC
|
||||
|
||||
case 'rinkeby':
|
||||
return RINKEBY_RPC
|
||||
|
||||
default:
|
||||
return provider && provider.rpcTarget ? provider.rpcTarget : TESTNET_RPC
|
||||
return provider && provider.rpcTarget ? provider.rpcTarget : RINKEBY_RPC
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -10,7 +10,7 @@
|
||||
const async = require('async')
|
||||
const EthQuery = require('eth-query')
|
||||
const ObservableStore = require('obs-store')
|
||||
function noop() {}
|
||||
function noop () {}
|
||||
|
||||
|
||||
class EthereumStore extends ObservableStore {
|
||||
@ -21,6 +21,7 @@ class EthereumStore extends ObservableStore {
|
||||
transactions: {},
|
||||
currentBlockNumber: '0',
|
||||
currentBlockHash: '',
|
||||
currentBlockGasLimit: '',
|
||||
})
|
||||
this._provider = opts.provider
|
||||
this._query = new EthQuery(this._provider)
|
||||
@ -73,6 +74,7 @@ class EthereumStore extends ObservableStore {
|
||||
this._currentBlockNumber = blockNumber
|
||||
this.updateState({ currentBlockNumber: parseInt(blockNumber) })
|
||||
this.updateState({ currentBlockHash: `0x${block.hash.toString('hex')}`})
|
||||
this.updateState({ currentBlockGasLimit: `0x${block.gasLimit.toString('hex')}` })
|
||||
async.parallel([
|
||||
this._updateAccounts.bind(this),
|
||||
this._updateTransactions.bind(this, blockNumber),
|
||||
|
@ -34,6 +34,7 @@ function MetamaskInpageProvider (connectionStream) {
|
||||
asyncProvider,
|
||||
(err) => logStreamDisconnectWarning('MetaMask RpcProvider', err)
|
||||
)
|
||||
// start and stop polling to unblock first block lock
|
||||
|
||||
self.idMap = {}
|
||||
// handle sendAsync requests via asyncProvider
|
||||
@ -85,7 +86,7 @@ MetamaskInpageProvider.prototype.send = function (payload) {
|
||||
break
|
||||
|
||||
case 'net_version':
|
||||
let networkVersion = self.publicConfigStore.getState().networkVersion
|
||||
const networkVersion = self.publicConfigStore.getState().networkVersion
|
||||
result = networkVersion
|
||||
break
|
||||
|
||||
@ -125,7 +126,7 @@ function eachJsonMessage (payload, transformFn) {
|
||||
}
|
||||
}
|
||||
|
||||
function logStreamDisconnectWarning(remoteLabel, err){
|
||||
function logStreamDisconnectWarning (remoteLabel, err) {
|
||||
let warningMsg = `MetamaskInpageProvider - lost connection to ${remoteLabel}`
|
||||
if (err) warningMsg += '\n' + err.stack
|
||||
console.warn(warningMsg)
|
||||
|
@ -4,7 +4,7 @@ const ethUtil = require('ethereumjs-util')
|
||||
const createId = require('./random-id')
|
||||
|
||||
|
||||
module.exports = class MessageManager extends EventEmitter{
|
||||
module.exports = class MessageManager extends EventEmitter {
|
||||
constructor (opts) {
|
||||
super()
|
||||
this.memStore = new ObservableStore({
|
||||
@ -108,7 +108,7 @@ module.exports = class MessageManager extends EventEmitter{
|
||||
|
||||
}
|
||||
|
||||
function normalizeMsgData(data) {
|
||||
function normalizeMsgData (data) {
|
||||
if (data.slice(0, 2) === '0x') {
|
||||
// data is already hex
|
||||
return data
|
||||
|
@ -1,42 +1,35 @@
|
||||
const asyncQ = require('async-q')
|
||||
|
||||
class Migrator {
|
||||
|
||||
constructor (opts = {}) {
|
||||
let migrations = opts.migrations || []
|
||||
const migrations = opts.migrations || []
|
||||
// sort migrations by version
|
||||
this.migrations = migrations.sort((a, b) => a.version - b.version)
|
||||
let lastMigration = this.migrations.slice(-1)[0]
|
||||
// grab migration with highest version
|
||||
const lastMigration = this.migrations.slice(-1)[0]
|
||||
// use specified defaultVersion or highest migration version
|
||||
this.defaultVersion = opts.defaultVersion || (lastMigration && lastMigration.version) || 0
|
||||
}
|
||||
|
||||
// run all pending migrations on meta in place
|
||||
migrateData (versionedData = this.generateInitialState()) {
|
||||
let remaining = this.migrations.filter(migrationIsPending)
|
||||
|
||||
return (
|
||||
asyncQ.eachSeries(remaining, (migration) => this.runMigration(versionedData, migration))
|
||||
.then(() => versionedData)
|
||||
)
|
||||
async migrateData (versionedData = this.generateInitialState()) {
|
||||
const pendingMigrations = this.migrations.filter(migrationIsPending)
|
||||
|
||||
// migration is "pending" if hit has a higher
|
||||
for (const index in pendingMigrations) {
|
||||
const migration = pendingMigrations[index]
|
||||
versionedData = await migration.migrate(versionedData)
|
||||
if (!versionedData.data) throw new Error('Migrator - migration returned empty data')
|
||||
if (versionedData.version !== undefined && versionedData.meta.version !== migration.version) throw new Error('Migrator - Migration did not update version number correctly')
|
||||
}
|
||||
|
||||
return versionedData
|
||||
|
||||
// migration is "pending" if it has a higher
|
||||
// version number than currentVersion
|
||||
function migrationIsPending(migration) {
|
||||
function migrationIsPending (migration) {
|
||||
return migration.version > versionedData.meta.version
|
||||
}
|
||||
}
|
||||
|
||||
runMigration(versionedData, migration) {
|
||||
return (
|
||||
migration.migrate(versionedData)
|
||||
.then((versionedData) => {
|
||||
if (!versionedData.data) return Promise.reject(new Error('Migrator - Migration returned empty data'))
|
||||
if (migration.version !== undefined && versionedData.meta.version !== migration.version) return Promise.reject(new Error('Migrator - Migration did not update version number correctly'))
|
||||
return Promise.resolve(versionedData)
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
generateInitialState (initState) {
|
||||
return {
|
||||
meta: {
|
||||
|
@ -24,9 +24,6 @@ class NotificationManager {
|
||||
width,
|
||||
height,
|
||||
})
|
||||
.catch((reason) => {
|
||||
log.error('failed to create poupup', reason)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -71,4 +68,4 @@ class NotificationManager {
|
||||
|
||||
}
|
||||
|
||||
module.exports = NotificationManager
|
||||
module.exports = NotificationManager
|
||||
|
@ -5,7 +5,7 @@ const createId = require('./random-id')
|
||||
const hexRe = /^[0-9A-Fa-f]+$/g
|
||||
|
||||
|
||||
module.exports = class PersonalMessageManager extends EventEmitter{
|
||||
module.exports = class PersonalMessageManager extends EventEmitter {
|
||||
constructor (opts) {
|
||||
super()
|
||||
this.memStore = new ObservableStore({
|
||||
@ -108,7 +108,7 @@ module.exports = class PersonalMessageManager extends EventEmitter{
|
||||
this.emit('updateBadge')
|
||||
}
|
||||
|
||||
normalizeMsgData(data) {
|
||||
normalizeMsgData (data) {
|
||||
try {
|
||||
const stripped = ethUtil.stripHexPrefix(data)
|
||||
if (stripped.match(hexRe)) {
|
||||
|
@ -1,5 +1,4 @@
|
||||
const async = require('async')
|
||||
const EthQuery = require('eth-query')
|
||||
const ethUtil = require('ethereumjs-util')
|
||||
const Transaction = require('ethereumjs-tx')
|
||||
const normalize = require('eth-sig-util').normalize
|
||||
@ -7,15 +6,14 @@ const BN = ethUtil.BN
|
||||
|
||||
/*
|
||||
tx-utils are utility methods for Transaction manager
|
||||
its passed a provider and that is passed to ethquery
|
||||
its passed ethquery
|
||||
and used to do things like calculate gas of a tx.
|
||||
*/
|
||||
|
||||
module.exports = class txProviderUtils {
|
||||
|
||||
constructor (provider) {
|
||||
this.provider = provider
|
||||
this.query = new EthQuery(provider)
|
||||
constructor (ethQuery) {
|
||||
this.query = ethQuery
|
||||
}
|
||||
|
||||
analyzeGasUsage (txMeta, cb) {
|
||||
@ -35,7 +33,9 @@ module.exports = class txProviderUtils {
|
||||
txMeta.gasLimitSpecified = Boolean(txParams.gas)
|
||||
// if not, fallback to block gasLimit
|
||||
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
|
||||
this.query.estimateGas(txParams, cb)
|
||||
@ -75,14 +75,14 @@ module.exports = class txProviderUtils {
|
||||
}
|
||||
|
||||
fillInTxParams (txParams, cb) {
|
||||
let fromAddress = txParams.from
|
||||
let reqs = {}
|
||||
const fromAddress = txParams.from
|
||||
const reqs = {}
|
||||
|
||||
if (isUndef(txParams.gas)) reqs.gas = (cb) => this.query.estimateGas(txParams, cb)
|
||||
if (isUndef(txParams.gasPrice)) reqs.gasPrice = (cb) => this.query.gasPrice(cb)
|
||||
if (isUndef(txParams.nonce)) reqs.nonce = (cb) => this.query.getTransactionCount(fromAddress, 'pending', cb)
|
||||
|
||||
async.parallel(reqs, function(err, result) {
|
||||
async.parallel(reqs, function (err, result) {
|
||||
if (err) return cb(err)
|
||||
// write results to txParams obj
|
||||
Object.assign(txParams, result)
|
||||
@ -123,14 +123,20 @@ module.exports = class txProviderUtils {
|
||||
|
||||
// util
|
||||
|
||||
function isUndef(value) {
|
||||
function isUndef (value) {
|
||||
return value === undefined
|
||||
}
|
||||
|
||||
function bnToHex(inputBn) {
|
||||
function bnToHex (inputBn) {
|
||||
return ethUtil.addHexPrefix(inputBn.toString(16))
|
||||
}
|
||||
|
||||
function hexToBn(inputHex) {
|
||||
function hexToBn (inputHex) {
|
||||
return new BN(ethUtil.stripHexPrefix(inputHex), 16)
|
||||
}
|
||||
|
||||
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 Dnode = require('dnode')
|
||||
const ObservableStore = require('obs-store')
|
||||
const storeTransform = require('obs-store/lib/transform')
|
||||
const EthStore = require('./lib/eth-store')
|
||||
const EthQuery = require('eth-query')
|
||||
const streamIntoProvider = require('web3-stream-provider/handler')
|
||||
const MetaMaskProvider = require('web3-provider-engine/zero.js')
|
||||
const setupMultiplex = require('./lib/stream-utils.js').setupMultiplex
|
||||
const KeyringController = require('./keyring-controller')
|
||||
const NetworkController = require('./controllers/network')
|
||||
const PreferencesController = require('./controllers/preferences')
|
||||
const CurrencyController = require('./controllers/currency')
|
||||
const NoticeController = require('./notice-controller')
|
||||
@ -18,7 +17,7 @@ const ShapeShiftController = require('./controllers/shapeshift')
|
||||
const AddressBookController = require('./controllers/address-book')
|
||||
const MessageManager = require('./lib/message-manager')
|
||||
const PersonalMessageManager = require('./lib/personal-message-manager')
|
||||
const TxManager = require('./transaction-manager')
|
||||
const TransactionController = require('./controllers/transactions')
|
||||
const ConfigManager = require('./lib/config-manager')
|
||||
const autoFaucet = require('./lib/auto-faucet')
|
||||
const nodeify = require('./lib/nodeify')
|
||||
@ -32,7 +31,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
constructor (opts) {
|
||||
super()
|
||||
this.opts = opts
|
||||
let initState = opts.initState || {}
|
||||
const initState = opts.initState || {}
|
||||
|
||||
// platform-specific api
|
||||
this.platform = opts.platform
|
||||
@ -41,8 +40,8 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
this.store = new ObservableStore(initState)
|
||||
|
||||
// network store
|
||||
this.networkStore = new ObservableStore({ network: 'loading' })
|
||||
|
||||
this.networkController = new NetworkController(initState.NetworkController)
|
||||
// config manager
|
||||
this.configManager = new ConfigManager({
|
||||
store: this.store,
|
||||
@ -62,8 +61,6 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
|
||||
// rpc provider
|
||||
this.provider = this.initializeProvider()
|
||||
this.provider.on('block', this.logBlock.bind(this))
|
||||
this.provider.on('error', this.verifyNetwork.bind(this))
|
||||
|
||||
// eth data query tools
|
||||
this.ethQuery = new EthQuery(this.provider)
|
||||
@ -76,7 +73,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
this.keyringController = new KeyringController({
|
||||
initState: initState.KeyringController,
|
||||
ethStore: this.ethStore,
|
||||
getNetwork: this.getNetworkState.bind(this),
|
||||
getNetwork: this.networkController.getNetworkState.bind(this.networkController),
|
||||
})
|
||||
this.keyringController.on('newAccount', (address) => {
|
||||
this.preferencesController.setSelectedAddress(address)
|
||||
@ -91,15 +88,16 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
}, this.keyringController)
|
||||
|
||||
// tx mgmt
|
||||
this.txManager = new TxManager({
|
||||
initState: initState.TransactionManager,
|
||||
networkStore: this.networkStore,
|
||||
this.txController = new TransactionController({
|
||||
initState: initState.TransactionController || initState.TransactionManager,
|
||||
networkStore: this.networkController.networkStore,
|
||||
preferencesStore: this.preferencesController.store,
|
||||
txHistoryLimit: 40,
|
||||
getNetwork: this.getNetworkState.bind(this),
|
||||
getNetwork: this.networkController.getNetworkState.bind(this),
|
||||
signTransaction: this.keyringController.signTransaction.bind(this.keyringController),
|
||||
provider: this.provider,
|
||||
blockTracker: this.provider,
|
||||
ethQuery: this.ethQuery,
|
||||
})
|
||||
|
||||
// notices
|
||||
@ -114,14 +112,14 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
initState: initState.ShapeShiftController,
|
||||
})
|
||||
|
||||
this.lookupNetwork()
|
||||
this.networkController.lookupNetwork()
|
||||
this.messageManager = new MessageManager()
|
||||
this.personalMessageManager = new PersonalMessageManager()
|
||||
this.publicConfigStore = this.initPublicConfigStore()
|
||||
|
||||
// manual disk state subscriptions
|
||||
this.txManager.store.subscribe((state) => {
|
||||
this.store.updateState({ TransactionManager: state })
|
||||
this.txController.store.subscribe((state) => {
|
||||
this.store.updateState({ TransactionController: state })
|
||||
})
|
||||
this.keyringController.store.subscribe((state) => {
|
||||
this.store.updateState({ KeyringController: state })
|
||||
@ -141,11 +139,14 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
this.shapeshiftController.store.subscribe((state) => {
|
||||
this.store.updateState({ ShapeShiftController: state })
|
||||
})
|
||||
this.networkController.store.subscribe((state) => {
|
||||
this.store.updateState({ NetworkController: state })
|
||||
})
|
||||
|
||||
// manual mem state subscriptions
|
||||
this.networkStore.subscribe(this.sendUpdate.bind(this))
|
||||
this.networkController.store.subscribe(this.sendUpdate.bind(this))
|
||||
this.ethStore.subscribe(this.sendUpdate.bind(this))
|
||||
this.txManager.memStore.subscribe(this.sendUpdate.bind(this))
|
||||
this.txController.memStore.subscribe(this.sendUpdate.bind(this))
|
||||
this.messageManager.memStore.subscribe(this.sendUpdate.bind(this))
|
||||
this.personalMessageManager.memStore.subscribe(this.sendUpdate.bind(this))
|
||||
this.keyringController.memStore.subscribe(this.sendUpdate.bind(this))
|
||||
@ -161,17 +162,21 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
//
|
||||
|
||||
initializeProvider () {
|
||||
|
||||
let provider = MetaMaskProvider({
|
||||
return this.networkController.initializeProvider({
|
||||
static: {
|
||||
eth_syncing: false,
|
||||
web3_clientVersion: `MetaMask/v${version}`,
|
||||
},
|
||||
rpcUrl: this.configManager.getCurrentRpcAddress(),
|
||||
rpcUrl: this.networkController.getCurrentRpcAddress(),
|
||||
// account mgmt
|
||||
getAccounts: (cb) => {
|
||||
let selectedAddress = this.preferencesController.getSelectedAddress()
|
||||
let result = selectedAddress ? [selectedAddress] : []
|
||||
const isUnlocked = this.keyringController.memStore.getState().isUnlocked
|
||||
const result = []
|
||||
const selectedAddress = this.preferencesController.getSelectedAddress()
|
||||
// only show address if account is unlocked
|
||||
if (isUnlocked && selectedAddress) {
|
||||
result.push(selectedAddress)
|
||||
}
|
||||
cb(null, result)
|
||||
},
|
||||
// tx signing
|
||||
@ -182,26 +187,23 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
// new style msg signing
|
||||
processPersonalMessage: this.newUnsignedPersonalMessage.bind(this),
|
||||
})
|
||||
return provider
|
||||
}
|
||||
|
||||
initPublicConfigStore () {
|
||||
// get init state
|
||||
const publicConfigStore = new ObservableStore()
|
||||
|
||||
// sync publicConfigStore with transform
|
||||
pipe(
|
||||
this.store,
|
||||
storeTransform(selectPublicState.bind(this)),
|
||||
publicConfigStore
|
||||
)
|
||||
// memStore -> transform -> publicConfigStore
|
||||
this.on('update', (memState) => {
|
||||
const publicState = selectPublicState(memState)
|
||||
publicConfigStore.putState(publicState)
|
||||
})
|
||||
|
||||
function selectPublicState(state) {
|
||||
const result = { selectedAddress: undefined }
|
||||
try {
|
||||
result.selectedAddress = state.PreferencesController.selectedAddress
|
||||
result.networkVersion = this.getNetworkState()
|
||||
} catch (_) {}
|
||||
function selectPublicState (memState) {
|
||||
const result = {
|
||||
selectedAddress: memState.isUnlocked ? memState.selectedAddress : undefined,
|
||||
networkVersion: memState.network,
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
@ -220,9 +222,9 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
{
|
||||
isInitialized,
|
||||
},
|
||||
this.networkStore.getState(),
|
||||
this.networkController.store.getState(),
|
||||
this.ethStore.getState(),
|
||||
this.txManager.memStore.getState(),
|
||||
this.txController.memStore.getState(),
|
||||
this.messageManager.memStore.getState(),
|
||||
this.personalMessageManager.memStore.getState(),
|
||||
this.keyringController.memStore.getState(),
|
||||
@ -247,62 +249,61 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
getApi () {
|
||||
const keyringController = this.keyringController
|
||||
const preferencesController = this.preferencesController
|
||||
const txManager = this.txManager
|
||||
const txController = this.txController
|
||||
const noticeController = this.noticeController
|
||||
const addressBookController = this.addressBookController
|
||||
|
||||
return {
|
||||
// etc
|
||||
getState: (cb) => cb(null, this.getState()),
|
||||
setProviderType: this.setProviderType.bind(this),
|
||||
useEtherscanProvider: this.useEtherscanProvider.bind(this),
|
||||
setCurrentCurrency: this.setCurrentCurrency.bind(this),
|
||||
markAccountsFound: this.markAccountsFound.bind(this),
|
||||
getState: (cb) => cb(null, this.getState()),
|
||||
setProviderType: this.networkController.setProviderType.bind(this.networkController),
|
||||
setCurrentCurrency: this.setCurrentCurrency.bind(this),
|
||||
markAccountsFound: this.markAccountsFound.bind(this),
|
||||
// coinbase
|
||||
buyEth: this.buyEth.bind(this),
|
||||
// shapeshift
|
||||
createShapeShiftTx: this.createShapeShiftTx.bind(this),
|
||||
|
||||
// primary HD keyring management
|
||||
addNewAccount: this.addNewAccount.bind(this),
|
||||
placeSeedWords: this.placeSeedWords.bind(this),
|
||||
clearSeedWordCache: this.clearSeedWordCache.bind(this),
|
||||
importAccountWithStrategy: this.importAccountWithStrategy.bind(this),
|
||||
addNewAccount: this.addNewAccount.bind(this),
|
||||
placeSeedWords: this.placeSeedWords.bind(this),
|
||||
clearSeedWordCache: this.clearSeedWordCache.bind(this),
|
||||
importAccountWithStrategy: this.importAccountWithStrategy.bind(this),
|
||||
|
||||
// vault management
|
||||
submitPassword: this.submitPassword.bind(this),
|
||||
|
||||
// PreferencesController
|
||||
setSelectedAddress: nodeify(preferencesController.setSelectedAddress).bind(preferencesController),
|
||||
setDefaultRpc: nodeify(this.setDefaultRpc).bind(this),
|
||||
setCustomRpc: nodeify(this.setCustomRpc).bind(this),
|
||||
setSelectedAddress: nodeify(preferencesController.setSelectedAddress).bind(preferencesController),
|
||||
setDefaultRpc: nodeify(this.setDefaultRpc).bind(this),
|
||||
setCustomRpc: nodeify(this.setCustomRpc).bind(this),
|
||||
|
||||
// AddressController
|
||||
setAddressBook: nodeify(addressBookController.setAddressBook).bind(addressBookController),
|
||||
setAddressBook: nodeify(addressBookController.setAddressBook).bind(addressBookController),
|
||||
|
||||
// KeyringController
|
||||
setLocked: nodeify(keyringController.setLocked).bind(keyringController),
|
||||
setLocked: nodeify(keyringController.setLocked).bind(keyringController),
|
||||
createNewVaultAndKeychain: nodeify(keyringController.createNewVaultAndKeychain).bind(keyringController),
|
||||
createNewVaultAndRestore: nodeify(keyringController.createNewVaultAndRestore).bind(keyringController),
|
||||
addNewKeyring: nodeify(keyringController.addNewKeyring).bind(keyringController),
|
||||
saveAccountLabel: nodeify(keyringController.saveAccountLabel).bind(keyringController),
|
||||
exportAccount: nodeify(keyringController.exportAccount).bind(keyringController),
|
||||
createNewVaultAndRestore: nodeify(keyringController.createNewVaultAndRestore).bind(keyringController),
|
||||
addNewKeyring: nodeify(keyringController.addNewKeyring).bind(keyringController),
|
||||
saveAccountLabel: nodeify(keyringController.saveAccountLabel).bind(keyringController),
|
||||
exportAccount: nodeify(keyringController.exportAccount).bind(keyringController),
|
||||
|
||||
// txManager
|
||||
approveTransaction: txManager.approveTransaction.bind(txManager),
|
||||
cancelTransaction: txManager.cancelTransaction.bind(txManager),
|
||||
// txController
|
||||
approveTransaction: txController.approveTransaction.bind(txController),
|
||||
cancelTransaction: txController.cancelTransaction.bind(txController),
|
||||
updateAndApproveTransaction: this.updateAndApproveTx.bind(this),
|
||||
|
||||
// messageManager
|
||||
signMessage: nodeify(this.signMessage).bind(this),
|
||||
cancelMessage: this.cancelMessage.bind(this),
|
||||
signMessage: nodeify(this.signMessage).bind(this),
|
||||
cancelMessage: this.cancelMessage.bind(this),
|
||||
|
||||
// personalMessageManager
|
||||
signPersonalMessage: nodeify(this.signPersonalMessage).bind(this),
|
||||
cancelPersonalMessage: this.cancelPersonalMessage.bind(this),
|
||||
signPersonalMessage: nodeify(this.signPersonalMessage).bind(this),
|
||||
cancelPersonalMessage: this.cancelPersonalMessage.bind(this),
|
||||
|
||||
// notices
|
||||
checkNotices: noticeController.updateNoticesList.bind(noticeController),
|
||||
checkNotices: noticeController.updateNoticesList.bind(noticeController),
|
||||
markNoticeRead: noticeController.markNoticeRead.bind(noticeController),
|
||||
}
|
||||
}
|
||||
@ -342,9 +343,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
console.error('Error in RPC response:\n', response.error)
|
||||
}
|
||||
if (request.isMetamaskInternal) return
|
||||
if (global.METAMASK_DEBUG) {
|
||||
console.log(`RPC (${originDomain}):`, request, '->', response)
|
||||
}
|
||||
log.info(`RPC (${originDomain}):`, request, '->', response)
|
||||
}
|
||||
}
|
||||
|
||||
@ -422,12 +421,12 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
newUnapprovedTransaction (txParams, cb) {
|
||||
log.debug(`MetaMaskController newUnapprovedTransaction ${JSON.stringify(txParams)}`)
|
||||
const self = this
|
||||
self.txManager.addUnapprovedTransaction(txParams, (err, txMeta) => {
|
||||
self.txController.addUnapprovedTransaction(txParams, (err, txMeta) => {
|
||||
if (err) return cb(err)
|
||||
self.sendUpdate()
|
||||
self.opts.showUnapprovedTx(txMeta)
|
||||
// listen for tx completion (success, fail)
|
||||
self.txManager.once(`${txMeta.id}:finished`, (completedTx) => {
|
||||
self.txController.once(`${txMeta.id}:finished`, (completedTx) => {
|
||||
switch (completedTx.status) {
|
||||
case 'submitted':
|
||||
return cb(null, completedTx.hash)
|
||||
@ -441,7 +440,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
}
|
||||
|
||||
newUnsignedMessage (msgParams, cb) {
|
||||
let msgId = this.messageManager.addUnapprovedMessage(msgParams)
|
||||
const msgId = this.messageManager.addUnapprovedMessage(msgParams)
|
||||
this.sendUpdate()
|
||||
this.opts.showUnconfirmedMessage()
|
||||
this.messageManager.once(`${msgId}:finished`, (data) => {
|
||||
@ -461,7 +460,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
return cb(new Error('MetaMask Message Signature: from field is required.'))
|
||||
}
|
||||
|
||||
let msgId = this.personalMessageManager.addUnapprovedMessage(msgParams)
|
||||
const msgId = this.personalMessageManager.addUnapprovedMessage(msgParams)
|
||||
this.sendUpdate()
|
||||
this.opts.showUnconfirmedMessage()
|
||||
this.personalMessageManager.once(`${msgId}:finished`, (data) => {
|
||||
@ -476,11 +475,11 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
})
|
||||
}
|
||||
|
||||
updateAndApproveTx(txMeta, cb) {
|
||||
updateAndApproveTx (txMeta, cb) {
|
||||
log.debug(`MetaMaskController - updateAndApproveTx: ${JSON.stringify(txMeta)}`)
|
||||
const txManager = this.txManager
|
||||
txManager.updateTx(txMeta)
|
||||
txManager.approveTransaction(txMeta.id, cb)
|
||||
const txController = this.txController
|
||||
txController.updateTx(txMeta)
|
||||
txController.approveTransaction(txMeta.id, cb)
|
||||
}
|
||||
|
||||
signMessage (msgParams, cb) {
|
||||
@ -502,7 +501,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
})
|
||||
}
|
||||
|
||||
cancelMessage(msgId, cb) {
|
||||
cancelMessage (msgId, cb) {
|
||||
const messageManager = this.messageManager
|
||||
messageManager.rejectMsg(msgId)
|
||||
if (cb && typeof cb === 'function') {
|
||||
@ -512,7 +511,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
|
||||
// Prefixed Style Message Signing Methods:
|
||||
approvePersonalMessage (msgParams, cb) {
|
||||
let msgId = this.personalMessageManager.addUnapprovedMessage(msgParams)
|
||||
const msgId = this.personalMessageManager.addUnapprovedMessage(msgParams)
|
||||
this.sendUpdate()
|
||||
this.opts.showUnconfirmedMessage()
|
||||
this.personalMessageManager.once(`${msgId}:finished`, (data) => {
|
||||
@ -545,7 +544,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
})
|
||||
}
|
||||
|
||||
cancelPersonalMessage(msgId, cb) {
|
||||
cancelPersonalMessage (msgId, cb) {
|
||||
const messageManager = this.personalMessageManager
|
||||
messageManager.rejectMsg(msgId)
|
||||
if (cb && typeof cb === 'function') {
|
||||
@ -559,13 +558,13 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
cb(null, this.getState())
|
||||
}
|
||||
|
||||
restoreOldVaultAccounts(migratorOutput) {
|
||||
restoreOldVaultAccounts (migratorOutput) {
|
||||
const { serialized } = migratorOutput
|
||||
return this.keyringController.restoreKeyring(serialized)
|
||||
.then(() => migratorOutput)
|
||||
}
|
||||
|
||||
restoreOldLostAccounts(migratorOutput) {
|
||||
restoreOldLostAccounts (migratorOutput) {
|
||||
const { lostAccounts } = migratorOutput
|
||||
if (lostAccounts) {
|
||||
this.configManager.setLostAccounts(lostAccounts.map(acct => acct.address))
|
||||
@ -591,12 +590,6 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
//
|
||||
|
||||
// Log blocks
|
||||
logBlock (block) {
|
||||
if (global.METAMASK_DEBUG) {
|
||||
console.log(`BLOCK CHANGED: #${block.number.toString('hex')} 0x${block.hash.toString('hex')}`)
|
||||
}
|
||||
this.verifyNetwork()
|
||||
}
|
||||
|
||||
setCurrentCurrency (currencyCode, cb) {
|
||||
try {
|
||||
@ -615,7 +608,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
|
||||
buyEth (address, amount) {
|
||||
if (!amount) amount = '5'
|
||||
const network = this.getNetworkState()
|
||||
const network = this.networkController.getNetworkState()
|
||||
const url = getBuyEthUrl({ network, address, amount })
|
||||
if (url) this.platform.openWindow({ url })
|
||||
}
|
||||
@ -623,71 +616,21 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
createShapeShiftTx (depositAddress, depositType) {
|
||||
this.shapeshiftController.createShapeShiftTx(depositAddress, depositType)
|
||||
}
|
||||
|
||||
//
|
||||
// network
|
||||
//
|
||||
|
||||
verifyNetwork () {
|
||||
// Check network when restoring connectivity:
|
||||
if (this.isNetworkLoading()) this.lookupNetwork()
|
||||
}
|
||||
// network
|
||||
|
||||
setDefaultRpc () {
|
||||
this.configManager.setRpcTarget('http://localhost:8545')
|
||||
this.platform.reload()
|
||||
this.lookupNetwork()
|
||||
this.networkController.setRpcTarget('http://localhost:8545')
|
||||
return Promise.resolve('http://localhost:8545')
|
||||
}
|
||||
|
||||
setCustomRpc (rpcTarget, rpcList) {
|
||||
this.configManager.setRpcTarget(rpcTarget)
|
||||
this.networkController.setRpcTarget(rpcTarget)
|
||||
|
||||
return this.preferencesController.updateFrequentRpcList(rpcTarget)
|
||||
.then(() => {
|
||||
this.platform.reload()
|
||||
this.lookupNetwork()
|
||||
return Promise.resolve(rpcTarget)
|
||||
})
|
||||
}
|
||||
|
||||
setProviderType (type) {
|
||||
this.configManager.setProviderType(type)
|
||||
this.platform.reload()
|
||||
this.lookupNetwork()
|
||||
}
|
||||
|
||||
useEtherscanProvider () {
|
||||
this.configManager.useEtherscanProvider()
|
||||
this.platform.reload()
|
||||
}
|
||||
|
||||
getNetworkState () {
|
||||
return this.networkStore.getState().network
|
||||
}
|
||||
|
||||
setNetworkState (network) {
|
||||
return this.networkStore.updateState({ network })
|
||||
}
|
||||
|
||||
isNetworkLoading () {
|
||||
return this.getNetworkState() === 'loading'
|
||||
}
|
||||
|
||||
lookupNetwork (err) {
|
||||
if (err) {
|
||||
this.setNetworkState('loading')
|
||||
}
|
||||
|
||||
this.ethQuery.sendAsync({ method: 'net_version' }, (err, network) => {
|
||||
if (err) {
|
||||
this.setNetworkState('loading')
|
||||
return
|
||||
}
|
||||
if (global.METAMASK_DEBUG) {
|
||||
console.log('web3.getNetwork returned ' + network)
|
||||
}
|
||||
this.setNetworkState(network)
|
||||
.then(() => {
|
||||
return Promise.resolve(rpcTarget)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ module.exports = {
|
||||
version,
|
||||
|
||||
migrate: function (originalVersionedData) {
|
||||
let versionedData = clone(originalVersionedData)
|
||||
const versionedData = clone(originalVersionedData)
|
||||
versionedData.meta.version = version
|
||||
try {
|
||||
if (versionedData.data.config.provider.type === 'etherscan') {
|
||||
|
@ -8,7 +8,7 @@ module.exports = {
|
||||
version,
|
||||
|
||||
migrate: function (originalVersionedData) {
|
||||
let versionedData = clone(originalVersionedData)
|
||||
const versionedData = clone(originalVersionedData)
|
||||
versionedData.meta.version = version
|
||||
try {
|
||||
if (versionedData.data.config.provider.rpcTarget === oldTestRpc) {
|
||||
|
@ -6,7 +6,7 @@ module.exports = {
|
||||
version,
|
||||
|
||||
migrate: function (versionedData) {
|
||||
let safeVersionedData = clone(versionedData)
|
||||
const safeVersionedData = clone(versionedData)
|
||||
safeVersionedData.meta.version = version
|
||||
try {
|
||||
if (safeVersionedData.data.config.provider.type !== 'rpc') return Promise.resolve(safeVersionedData)
|
||||
|
@ -14,7 +14,7 @@ module.exports = {
|
||||
version,
|
||||
|
||||
migrate: function (originalVersionedData) {
|
||||
let versionedData = clone(originalVersionedData)
|
||||
const versionedData = clone(originalVersionedData)
|
||||
versionedData.meta.version = version
|
||||
try {
|
||||
const state = versionedData.data
|
||||
|
@ -13,7 +13,7 @@ module.exports = {
|
||||
version,
|
||||
|
||||
migrate: function (originalVersionedData) {
|
||||
let versionedData = clone(originalVersionedData)
|
||||
const versionedData = clone(originalVersionedData)
|
||||
versionedData.meta.version = version
|
||||
try {
|
||||
const state = versionedData.data
|
||||
|
@ -13,7 +13,7 @@ module.exports = {
|
||||
version,
|
||||
|
||||
migrate: function (originalVersionedData) {
|
||||
let versionedData = clone(originalVersionedData)
|
||||
const versionedData = clone(originalVersionedData)
|
||||
versionedData.meta.version = version
|
||||
try {
|
||||
const state = versionedData.data
|
||||
|
@ -13,7 +13,7 @@ module.exports = {
|
||||
version,
|
||||
|
||||
migrate: function (originalVersionedData) {
|
||||
let versionedData = clone(originalVersionedData)
|
||||
const versionedData = clone(originalVersionedData)
|
||||
versionedData.meta.version = version
|
||||
try {
|
||||
const state = versionedData.data
|
||||
|
@ -13,7 +13,7 @@ module.exports = {
|
||||
version,
|
||||
|
||||
migrate: function (originalVersionedData) {
|
||||
let versionedData = clone(originalVersionedData)
|
||||
const versionedData = clone(originalVersionedData)
|
||||
versionedData.meta.version = version
|
||||
try {
|
||||
const state = versionedData.data
|
||||
|
@ -13,7 +13,7 @@ module.exports = {
|
||||
version,
|
||||
|
||||
migrate: function (originalVersionedData) {
|
||||
let versionedData = clone(originalVersionedData)
|
||||
const versionedData = clone(originalVersionedData)
|
||||
versionedData.meta.version = version
|
||||
try {
|
||||
const state = versionedData.data
|
||||
|
@ -12,7 +12,7 @@ module.exports = {
|
||||
version,
|
||||
|
||||
migrate: function (originalVersionedData) {
|
||||
let versionedData = clone(originalVersionedData)
|
||||
const versionedData = clone(originalVersionedData)
|
||||
versionedData.meta.version = version
|
||||
try {
|
||||
const state = versionedData.data
|
||||
|
@ -12,7 +12,7 @@ module.exports = {
|
||||
version,
|
||||
|
||||
migrate: function (originalVersionedData) {
|
||||
let versionedData = clone(originalVersionedData)
|
||||
const versionedData = clone(originalVersionedData)
|
||||
versionedData.meta.version = version
|
||||
try {
|
||||
const state = versionedData.data
|
||||
|
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
|
||||
}
|
@ -15,15 +15,15 @@ const KeyringController = require('../../app/scripts/lib/keyring-controller')
|
||||
const password = 'obviously not correct'
|
||||
|
||||
module.exports = {
|
||||
version,
|
||||
version,
|
||||
|
||||
migrate: function (versionedData) {
|
||||
versionedData.meta.version = version
|
||||
|
||||
let store = new ObservableStore(versionedData.data)
|
||||
let configManager = new ConfigManager({ store })
|
||||
let idStoreMigrator = new IdentityStoreMigrator({ configManager })
|
||||
let keyringController = new KeyringController({
|
||||
const store = new ObservableStore(versionedData.data)
|
||||
const configManager = new ConfigManager({ store })
|
||||
const idStoreMigrator = new IdentityStoreMigrator({ configManager })
|
||||
const keyringController = new KeyringController({
|
||||
configManager: configManager,
|
||||
})
|
||||
|
||||
@ -46,6 +46,5 @@ module.exports = {
|
||||
return Promise.resolve(versionedData)
|
||||
})
|
||||
})
|
||||
|
||||
},
|
||||
}
|
||||
|
@ -23,4 +23,6 @@ module.exports = [
|
||||
require('./010'),
|
||||
require('./011'),
|
||||
require('./012'),
|
||||
require('./013'),
|
||||
require('./014'),
|
||||
]
|
||||
|
@ -1,7 +1,7 @@
|
||||
const EventEmitter = require('events').EventEmitter
|
||||
const async = require('async')
|
||||
const Dnode = require('dnode')
|
||||
const Web3 = require('web3')
|
||||
const EthQuery = require('eth-query')
|
||||
const launchMetamaskUi = require('../../ui')
|
||||
const StreamProvider = require('web3-stream-provider')
|
||||
const setupMultiplex = require('./lib/stream-utils.js').setupMultiplex
|
||||
@ -16,7 +16,6 @@ function initializePopup ({ container, connectionStream }, cb) {
|
||||
(cb) => connectToAccountManager(connectionStream, cb),
|
||||
(accountManager, cb) => launchMetamaskUi({ container, accountManager }, cb),
|
||||
], cb)
|
||||
|
||||
}
|
||||
|
||||
function connectToAccountManager (connectionStream, cb) {
|
||||
@ -33,7 +32,8 @@ function setupWeb3Connection (connectionStream) {
|
||||
providerStream.pipe(connectionStream).pipe(providerStream)
|
||||
connectionStream.on('error', console.error.bind(console))
|
||||
providerStream.on('error', console.error.bind(console))
|
||||
global.web3 = new Web3(providerStream)
|
||||
global.ethereumProvider = providerStream
|
||||
global.ethQuery = new EthQuery(providerStream)
|
||||
}
|
||||
|
||||
function setupControllerConnection (connectionStream, cb) {
|
||||
|
@ -41,7 +41,7 @@ function closePopupIfOpen (windowType) {
|
||||
}
|
||||
}
|
||||
|
||||
function displayCriticalError(err) {
|
||||
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)
|
||||
|
@ -1,6 +1,6 @@
|
||||
machine:
|
||||
node:
|
||||
version: 6.0.0
|
||||
version: 7.6.0
|
||||
dependencies:
|
||||
pre:
|
||||
- "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()
|
||||
|
||||
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)
|
||||
|
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',
|
||||
],
|
||||
}))
|
||||
gulp.task('copy:contractImages', copyTask({
|
||||
source: './node_modules/ethereum-contract-icons/images/',
|
||||
destinations: [
|
||||
'./dist/firefox/images/contract',
|
||||
'./dist/chrome/images/contract',
|
||||
'./dist/edge/images/contract',
|
||||
'./dist/opera/images/contract',
|
||||
],
|
||||
}))
|
||||
gulp.task('copy:fonts', copyTask({
|
||||
source: './app/fonts/',
|
||||
destinations: [
|
||||
@ -127,6 +136,7 @@ const staticFiles = [
|
||||
]
|
||||
|
||||
var copyStrings = staticFiles.map(staticFile => `copy:${staticFile}`)
|
||||
copyStrings.push('copy:contractImages')
|
||||
|
||||
if (!disableLiveReload) {
|
||||
copyStrings.push('copy:reload')
|
||||
@ -182,7 +192,7 @@ gulp.task('build:js', gulp.parallel(...jsBuildStrings))
|
||||
// disc bundle analyzer tasks
|
||||
|
||||
jsFiles.forEach((jsFile) => {
|
||||
gulp.task(`disc:${jsFile}`, bundleTask({ label: jsFile, filename: `${jsFile}.js` }))
|
||||
gulp.task(`disc:${jsFile}`, discTask({ label: jsFile, filename: `${jsFile}.js` }))
|
||||
})
|
||||
|
||||
gulp.task('disc', gulp.parallel(jsFiles.map(jsFile => `disc:${jsFile}`)))
|
||||
@ -296,8 +306,6 @@ function bundleTask(opts) {
|
||||
return (
|
||||
|
||||
bundler.bundle()
|
||||
// log errors if they happen
|
||||
.on('error', gutil.log.bind(gutil, 'Browserify Error'))
|
||||
// convert bundle stream to gulp vinyl stream
|
||||
.pipe(source(opts.filename))
|
||||
// inject variables into bundle
|
||||
|
@ -1,20 +1,33 @@
|
||||
start the dual servers (dapp + mascara)
|
||||
```
|
||||
node server.js
|
||||
npm run mascara
|
||||
```
|
||||
|
||||
## First time use:
|
||||
### First time use:
|
||||
|
||||
- navigate to: http://localhost:9001/popup/popup.html
|
||||
- navigate to: http://localhost:9001
|
||||
- Create an Account
|
||||
- go back to http://localhost:9002/
|
||||
- go back to http://localhost:9002
|
||||
- open devTools
|
||||
- click Sync Tx
|
||||
|
||||
### Todos
|
||||
### Tests:
|
||||
|
||||
- [ ] Figure out user flows and UI redesign
|
||||
- [ ] Figure out FireFox
|
||||
Standing problems:
|
||||
- [ ] IndexDb
|
||||
```
|
||||
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,4 +1,5 @@
|
||||
global.window = global
|
||||
const self = global
|
||||
const pipe = require('pump')
|
||||
|
||||
const SwGlobalListener = require('sw-stream/lib/sw-global-listener.js')
|
||||
@ -6,7 +7,7 @@ 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('./lib/index-db-controller')
|
||||
const DbController = require('idb-global')
|
||||
|
||||
const SwPlatform = require('../../app/scripts/platforms/sw')
|
||||
const MetamaskController = require('../../app/scripts/metamask-controller')
|
||||
@ -21,6 +22,7 @@ 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
|
||||
@ -40,7 +42,6 @@ console.log('inside:open')
|
||||
let diskStore
|
||||
const dbController = new DbController({
|
||||
key: STORAGE_KEY,
|
||||
version: 2,
|
||||
})
|
||||
loadStateFromPersistence()
|
||||
.then((initState) => setupController(initState))
|
||||
@ -107,6 +108,7 @@ function setupController (initState, client) {
|
||||
|
||||
connectionListener.on('remote', (portStream, messageEvent) => {
|
||||
console.log('REMOTE CONECTION FOUND***********')
|
||||
connectedClientCount += 1
|
||||
connectRemote(portStream, messageEvent.data.context)
|
||||
})
|
||||
|
||||
@ -142,4 +144,12 @@ function setupController (initState, client) {
|
||||
return Promise.resolve()
|
||||
|
||||
}
|
||||
|
||||
function sendMessageToAllClients (message) {
|
||||
self.clients.matchAll().then(function(clients) {
|
||||
clients.forEach(function(client) {
|
||||
client.postMessage(message)
|
||||
})
|
||||
})
|
||||
}
|
||||
function noop () {}
|
||||
|
@ -1,88 +0,0 @@
|
||||
const EventEmitter = require('events')
|
||||
module.exports = class IndexDbController extends EventEmitter {
|
||||
|
||||
constructor (opts) {
|
||||
super()
|
||||
this.migrations = opts.migrations
|
||||
this.key = opts.key
|
||||
this.dbObject = global.indexedDB
|
||||
this.IDBTransaction = global.IDBTransaction || global.webkitIDBTransaction || global.msIDBTransaction || {READ_WRITE: "readwrite"}; // This line should only be needed if it is needed to support the object's constants for older browsers
|
||||
this.IDBKeyRange = global.IDBKeyRange || global.webkitIDBKeyRange || global.msIDBKeyRange;
|
||||
this.version = opts.version
|
||||
this.logging = opts.logging
|
||||
this.initialState = opts.initialState
|
||||
if (this.logging) this.on('log', logger)
|
||||
}
|
||||
|
||||
// Opens the database connection and returns a promise
|
||||
open (version = this.version) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const dbOpenRequest = this.dbObject.open(this.key, version)
|
||||
dbOpenRequest.onerror = (event) => {
|
||||
return reject(event)
|
||||
}
|
||||
dbOpenRequest.onsuccess = (event) => {
|
||||
this.db = dbOpenRequest.result
|
||||
this.emit('success')
|
||||
resolve(this.db)
|
||||
}
|
||||
dbOpenRequest.onupgradeneeded = (event) => {
|
||||
this.db = event.target.result
|
||||
this.db.createObjectStore('dataStore')
|
||||
}
|
||||
})
|
||||
.then((openRequest) => {
|
||||
return this.get('dataStore')
|
||||
})
|
||||
.then((data) => {
|
||||
if (!data) {
|
||||
return this._add('dataStore', this.initialState)
|
||||
.then(() => this.get('dataStore'))
|
||||
.then((versionedData) => Promise.resolve(versionedData))
|
||||
}
|
||||
return Promise.resolve(data)
|
||||
})
|
||||
}
|
||||
|
||||
requestObjectStore (key, type = 'readonly') {
|
||||
return new Promise((resolve, reject) => {
|
||||
const dbReadWrite = this.db.transaction(key, type)
|
||||
const dataStore = dbReadWrite.objectStore(key)
|
||||
resolve(dataStore)
|
||||
})
|
||||
}
|
||||
|
||||
get (key = 'dataStore') {
|
||||
return this.requestObjectStore(key)
|
||||
.then((dataObject)=> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const getRequest = dataObject.get(key)
|
||||
getRequest.onsuccess = (event) => resolve(event.currentTarget.result)
|
||||
getRequest.onerror = (event) => reject(event)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
put (state) {
|
||||
return this.requestObjectStore('dataStore', 'readwrite')
|
||||
.then((dataObject)=> {
|
||||
const putRequest = dataObject.put(state, 'dataStore')
|
||||
putRequest.onsuccess = (event) => Promise.resolve(event.currentTarget.result)
|
||||
putRequest.onerror = (event) => Promise.reject(event)
|
||||
})
|
||||
}
|
||||
|
||||
_add (key, objStore, cb = logger) {
|
||||
return this.requestObjectStore(key, 'readwrite')
|
||||
.then((dataObject)=> {
|
||||
const addRequest = dataObject.add(objStore, key)
|
||||
addRequest.onsuccess = (event) => Promise.resolve(event.currentTarget.result)
|
||||
addRequest.onerror = (event) => Promise.reject(event)
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function logger (err, ress) {
|
||||
err ? console.error(`Logger says: ${err}`) : console.dir(`Logger says: ${ress}`)
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
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)
|
||||
|
||||
@ -14,8 +14,7 @@ const provider = setupProvider({
|
||||
instrumentForUserInteractionTriggers(provider)
|
||||
|
||||
const web3 = new Web3(provider)
|
||||
global.web3 = web3
|
||||
|
||||
setupDappAutoReload(web3, provider.publicConfigStore)
|
||||
//
|
||||
// ui stuff
|
||||
//
|
||||
|
@ -20,6 +20,7 @@ background.on('ready', (_) => {
|
||||
pageStream.pipe(swStream).pipe(pageStream)
|
||||
|
||||
})
|
||||
background.on('updatefound', () => window.location.reload())
|
||||
|
||||
background.on('error', console.error)
|
||||
background.startWorker()
|
||||
|
@ -24,10 +24,10 @@ const background = new SWcontroller({
|
||||
fileName: '/background.js',
|
||||
letBeIdle: false,
|
||||
intervalDelay,
|
||||
wakeUpInterval: 30000
|
||||
wakeUpInterval: 20000
|
||||
})
|
||||
// Setup listener for when the service worker is read
|
||||
background.on('ready', (readSw) => {
|
||||
const connectApp = function (readSw) {
|
||||
let connectionStream = SwStream({
|
||||
serviceWorker: background.controller,
|
||||
context: name,
|
||||
@ -39,6 +39,18 @@ background.on('ready', (readSw) => {
|
||||
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()
|
||||
}
|
||||
}
|
5
mascara/test/window-load.js
Normal file
5
mascara/test/window-load.js
Normal file
@ -0,0 +1,5 @@
|
||||
const Helper = require('./util/mascara-test-helper.js')
|
||||
|
||||
window.addEventListener('load', () => {
|
||||
require('../src/ui.js')
|
||||
})
|
@ -2,7 +2,8 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>MetaMask Plugin</title>
|
||||
<title>MetaMascara Alpha</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app-content"></div>
|
||||
|
@ -1 +0,0 @@
|
||||
MetaMask now lists a new network on our dropdown list: Kovan, a [Proof of Authority](https://github.com/paritytech/parity/wiki/Proof-of-Authority-Chains) testchain managed by several blockchain organizations such as Digix, Etherscan, and Parity. It is designed to be a more stable and reliable testnet alternative to Ropsten and was created in response to recent attacks that slowed down the Ropsten network. You can read more about Kovan [here](https://medium.com/@Digix/announcing-kovan-a-stable-ethereum-public-testnet-10ac7cb6c85f#.6o8sz8cct) and [here](https://medium.com/@Digix/letter-from-the-ceo-some-context-regarding-kovan-7b5121adb901#.kfv7zhw83). As with Ropsten, the default remote node to connect to Kovan is managed by Infura.
|
8
notices/archive/notice_2.md
Normal file
8
notices/archive/notice_2.md
Normal file
@ -0,0 +1,8 @@
|
||||
MetaMask is beta software.
|
||||
|
||||
When you log in to MetaMask, your current account is visible to every new site you visit.
|
||||
|
||||
For your privacy, for now, please sign out of MetaMask when you're done using a site.
|
||||
|
||||
Also, by default, you will be signed in to a test network. To use real Ether, you must connect to the main network manually in the top left network menu.
|
||||
|
@ -1 +1 @@
|
||||
2
|
||||
3
|
File diff suppressed because one or more lines are too long
35
package.json
35
package.json
@ -7,7 +7,7 @@
|
||||
"start": "npm run dev",
|
||||
"dev": "gulp dev --debug",
|
||||
"disc": "gulp disc --debug",
|
||||
"dist": "gulp dist --disableLiveReload",
|
||||
"dist": "npm install && gulp dist --disableLiveReload",
|
||||
"test": "npm run lint && npm run test-unit && npm run test-integration",
|
||||
"test-unit": "METAMASK_ENV=test mocha --require test/helper.js --recursive \"test/unit/**/*.js\"",
|
||||
"test-integration": "npm run buildMock && npm run buildCiUnits && testem ci -P 2",
|
||||
@ -21,7 +21,12 @@
|
||||
"testem": "npm run buildMock && testem",
|
||||
"announce": "node development/announcer.js",
|
||||
"generateNotice": "node notices/notice-generator.js",
|
||||
"deleteNotice": "node notices/notice-delete.js"
|
||||
"deleteNotice": "node notices/notice-delete.js",
|
||||
"mascara": "node ./mascara/example/server",
|
||||
"buildMascaraCi": "browserify mascara/test/window-load.js -o mascara/test/bundle.js",
|
||||
"buildMascaraSWCi": "browserify mascara/src/background.js -o mascara/test/background.js",
|
||||
"mascaraCi": "npm run buildMascaraCi && npm run buildMascaraSWCi && node mascara/test/index.js",
|
||||
"testMascara": "cd mascara/test && npm run mascaraCi && testem ci -P 3"
|
||||
},
|
||||
"browserify": {
|
||||
"transform": [
|
||||
@ -29,7 +34,8 @@
|
||||
"babelify",
|
||||
{
|
||||
"presets": [
|
||||
"es2015"
|
||||
"es2015",
|
||||
"stage-0"
|
||||
]
|
||||
}
|
||||
],
|
||||
@ -39,36 +45,39 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"async": "^1.5.2",
|
||||
"async-q": "^0.3.1",
|
||||
"babel-runtime": "^6.23.0",
|
||||
"bip39": "^2.2.0",
|
||||
"bluebird": "^3.5.0",
|
||||
"browser-passworder": "^2.0.3",
|
||||
"browserify-derequire": "^0.9.4",
|
||||
"client-sw-ready-event": "^3.0.1",
|
||||
"client-sw-ready-event": "^3.3.0",
|
||||
"clone": "^1.0.2",
|
||||
"copy-to-clipboard": "^2.0.0",
|
||||
"debounce": "^1.0.0",
|
||||
"deep-extend": "^0.4.1",
|
||||
"denodeify": "^1.2.1",
|
||||
"detect-node": "^2.0.3",
|
||||
"disc": "^1.3.2",
|
||||
"dnode": "^1.2.2",
|
||||
"end-of-stream": "^1.1.0",
|
||||
"ensnare": "^1.0.0",
|
||||
"eth-bin-to-ops": "^1.0.1",
|
||||
"eth-contract-metadata": "^1.0.0",
|
||||
"eth-hd-keyring": "^1.1.1",
|
||||
"eth-query": "^1.0.3",
|
||||
"eth-query": "^2.1.1",
|
||||
"eth-sig-util": "^1.1.1",
|
||||
"eth-simple-keyring": "^1.1.1",
|
||||
"eth-token-tracker": "^1.0.4",
|
||||
"ethereumjs-tx": "^1.2.5",
|
||||
"ethereumjs-tx": "^1.3.0",
|
||||
"ethereumjs-util": "ethereumjs/ethereumjs-util#ac5d0908536b447083ea422b435da27f26615de9",
|
||||
"ethereumjs-wallet": "^0.6.0",
|
||||
"ethjs-ens": "^1.0.2",
|
||||
"ethjs-ens": "^2.0.0",
|
||||
"express": "^4.14.0",
|
||||
"extension-link-enabler": "^1.0.0",
|
||||
"extensionizer": "^1.0.0",
|
||||
"gulp-eslint": "^2.0.0",
|
||||
"hat": "0.0.3",
|
||||
"idb-global": "^1.0.0",
|
||||
"identicon.js": "^1.2.1",
|
||||
"iframe": "^1.0.0",
|
||||
"iframe-stream": "^1.0.2",
|
||||
@ -80,6 +89,7 @@
|
||||
"mississippi": "^1.2.0",
|
||||
"mkdirp": "^0.5.1",
|
||||
"multiplex": "^6.7.0",
|
||||
"number-to-bn": "^1.7.0",
|
||||
"obs-store": "^2.3.1",
|
||||
"once": "^1.3.3",
|
||||
"ping-pong-stream": "^1.0.0",
|
||||
@ -114,7 +124,7 @@
|
||||
"valid-url": "^1.0.9",
|
||||
"vreme": "^3.0.2",
|
||||
"web3": "0.18.2",
|
||||
"web3-provider-engine": "^11.0.2",
|
||||
"web3-provider-engine": "^12.2.4",
|
||||
"web3-stream-provider": "^2.0.6",
|
||||
"xtend": "^4.0.1"
|
||||
},
|
||||
@ -135,6 +145,9 @@
|
||||
"deep-freeze-strict": "^1.1.1",
|
||||
"del": "^2.2.0",
|
||||
"envify": "^4.0.0",
|
||||
"enzyme": "^2.8.2",
|
||||
"eslint-plugin-chai": "0.0.1",
|
||||
"eslint-plugin-mocha": "^4.9.0",
|
||||
"fs-promise": "^1.0.0",
|
||||
"gulp": "github:gulpjs/gulp#4.0",
|
||||
"gulp-if": "^2.0.1",
|
||||
@ -159,6 +172,10 @@
|
||||
"prompt": "^1.0.0",
|
||||
"qs": "^6.2.0",
|
||||
"qunit": "^0.9.1",
|
||||
"react-addons-test-utils": "^15.5.1",
|
||||
"react-dom": "^15.5.4",
|
||||
"react-test-renderer": "^15.5.4",
|
||||
"react-testutils-additions": "^15.2.0",
|
||||
"sinon": "^1.17.3",
|
||||
"tape": "^4.5.1",
|
||||
"testem": "^1.10.3",
|
||||
|
@ -20,14 +20,12 @@ window.localStorage = {}
|
||||
if (!window.crypto) window.crypto = {}
|
||||
if (!window.crypto.getRandomValues) window.crypto.getRandomValues = require('polyfill-crypto.getrandomvalues')
|
||||
|
||||
|
||||
|
||||
function enableFailureOnUnhandledPromiseRejection() {
|
||||
function enableFailureOnUnhandledPromiseRejection () {
|
||||
// overwrite node's promise with the stricter Bluebird promise
|
||||
global.Promise = require('bluebird')
|
||||
|
||||
// modified from https://github.com/mochajs/mocha/issues/1926#issuecomment-180842722
|
||||
|
||||
|
||||
// rethrow unhandledRejections
|
||||
if (typeof process !== 'undefined') {
|
||||
process.on('unhandledRejection', function (reason) {
|
||||
@ -51,4 +49,4 @@ function enableFailureOnUnhandledPromiseRejection() {
|
||||
typeof (console.error || console.log) === 'function') {
|
||||
(console.error || console.log)('Unhandled rejections will be ignored!')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
function wait(time) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
setTimeout(function() {
|
||||
return new Promise(function (resolve, reject) {
|
||||
setTimeout(function () {
|
||||
resolve()
|
||||
}, time * 3 || 1500)
|
||||
})
|
||||
|
@ -1,10 +1,10 @@
|
||||
var fs = require('fs')
|
||||
var path = require('path')
|
||||
var browserify = require('browserify');
|
||||
var browserify = require('browserify')
|
||||
var tests = fs.readdirSync(path.join(__dirname, 'lib'))
|
||||
var bundlePath = path.join(__dirname, 'bundle.js')
|
||||
|
||||
var b = browserify();
|
||||
var b = browserify()
|
||||
|
||||
// Remove old bundle
|
||||
try {
|
||||
@ -13,9 +13,9 @@ try {
|
||||
|
||||
var writeStream = fs.createWriteStream(bundlePath)
|
||||
|
||||
tests.forEach(function(fileName) {
|
||||
tests.forEach(function (fileName) {
|
||||
b.add(path.join(__dirname, 'lib', fileName))
|
||||
})
|
||||
|
||||
b.bundle().pipe(writeStream);
|
||||
b.bundle().pipe(writeStream)
|
||||
|
||||
|
@ -11,7 +11,7 @@ QUnit.test('render init screen', function (assert) {
|
||||
|
||||
const recurseNotices = function () {
|
||||
let button = app.find('button')
|
||||
if (button.html() === 'Continue') {
|
||||
if (button.html() === 'Accept') {
|
||||
let termsPage = app.find('.markdown')[0]
|
||||
termsPage.scrollTop = termsPage.scrollHeight
|
||||
return wait().then(() => {
|
||||
|
@ -1 +1,14 @@
|
||||
{"version":0,"data":{"wallet":"{\"encSeed\":{\"encStr\":\"rT1C1jjkFRfmrwefscFcwZohl4f+HfIFlBZ9AM4ZD8atJmfKDIQCVK11NYDKYv8ZMIY03f3t8MuoZvfzBL8IJsWnZUhpzVTNNiARQJD2WpGA19eNBzgZm4vd0GwkIUruUDeJXu0iv2j9wU8hOQUqPbOePPy2Am5ro97iuvMAroRTnEKD60qFVg==\",\"nonce\":\"YUY2mwNq2v3FV0Fi94QnSiKFOLYfDR95\"},\"ksData\":{\"m/44'/60'/0'/0\":{\"info\":{\"curve\":\"secp256k1\",\"purpose\":\"sign\"},\"encHdPathPriv\":{\"encStr\":\"Iyi7ft4JQ9UtwrSXRT6ZIHPtZqJhe99rh0uWhNc6QLan6GanY2ZQeU0tt76CBealEWJyrJReSxGQdqDmSDYjpjH3m4JO5l0DfPLPseCqzXV/W+dzM0ubJ8lztLwpwi0L+vULNMqCx4dQtoNbNBq1QZUnjtpm6O8mWpScspboww==\",\"nonce\":\"Z7RqtjNjC6FrLUj5wVW1+HkjOW6Hib6K\"},\"hdIndex\":3,\"encPrivKeys\":{\"edb81c10122f34040cc4bef719a272fbbb1cf897\":{\"key\":\"8ab81tKBd4+CLAbzvS7SBFRTd6VWXBs86uBE43lgcmBu2U7UB22xdH64Q2hUf9eB\",\"nonce\":\"aGUEqI033FY39zKjWmZSI6PQrCLvkiRP\"},\"8bd7d5c000cf05284e98356370dc5ccaa3dbfc38\":{\"key\":\"+i3wmf4b+B898QtlOBfL0Ixirjg59/LLPX61vQ2L0xRPjXzNog0O4Wn15RemM5mY\",\"nonce\":\"imKrlkuoC5uuFkzJBbuDBluGCPJXNTKm\"},\"2340695474656e3124b8eba1172fbfb00eeac8f8\":{\"key\":\"pi+H9D8LYKsdCQKrfaJtsGFjE+X9s74xN675tsoIKrbPXhtpxMLOIQVtSqYveF62\",\"nonce\":\"49g80wDTovHwbguVVYf2FsYbp7Db5OAR\"}},\"addresses\":[\"edb81c10122f34040cc4bef719a272fbbb1cf897\",\"8bd7d5c000cf05284e98356370dc5ccaa3dbfc38\",\"2340695474656e3124b8eba1172fbfb00eeac8f8\"]}},\"version\":2}","config":{"provider":{"type":"etherscan"}}},"meta":{"version":0}}
|
||||
{
|
||||
"version": 0,
|
||||
"data": {
|
||||
"wallet": "{\"encSeed\":{\"encStr\":\"rT1C1jjkFRfmrwefscFcwZohl4f+HfIFlBZ9AM4ZD8atJmfKDIQCVK11NYDKYv8ZMIY03f3t8MuoZvfzBL8IJsWnZUhpzVTNNiARQJD2WpGA19eNBzgZm4vd0GwkIUruUDeJXu0iv2j9wU8hOQUqPbOePPy2Am5ro97iuvMAroRTnEKD60qFVg==\",\"nonce\":\"YUY2mwNq2v3FV0Fi94QnSiKFOLYfDR95\"},\"ksData\":{\"m/44'/60'/0'/0\":{\"info\":{\"curve\":\"secp256k1\",\"purpose\":\"sign\"},\"encHdPathPriv\":{\"encStr\":\"Iyi7ft4JQ9UtwrSXRT6ZIHPtZqJhe99rh0uWhNc6QLan6GanY2ZQeU0tt76CBealEWJyrJReSxGQdqDmSDYjpjH3m4JO5l0DfPLPseCqzXV/W+dzM0ubJ8lztLwpwi0L+vULNMqCx4dQtoNbNBq1QZUnjtpm6O8mWpScspboww==\",\"nonce\":\"Z7RqtjNjC6FrLUj5wVW1+HkjOW6Hib6K\"},\"hdIndex\":3,\"encPrivKeys\":{\"edb81c10122f34040cc4bef719a272fbbb1cf897\":{\"key\":\"8ab81tKBd4+CLAbzvS7SBFRTd6VWXBs86uBE43lgcmBu2U7UB22xdH64Q2hUf9eB\",\"nonce\":\"aGUEqI033FY39zKjWmZSI6PQrCLvkiRP\"},\"8bd7d5c000cf05284e98356370dc5ccaa3dbfc38\":{\"key\":\"+i3wmf4b+B898QtlOBfL0Ixirjg59/LLPX61vQ2L0xRPjXzNog0O4Wn15RemM5mY\",\"nonce\":\"imKrlkuoC5uuFkzJBbuDBluGCPJXNTKm\"},\"2340695474656e3124b8eba1172fbfb00eeac8f8\":{\"key\":\"pi+H9D8LYKsdCQKrfaJtsGFjE+X9s74xN675tsoIKrbPXhtpxMLOIQVtSqYveF62\",\"nonce\":\"49g80wDTovHwbguVVYf2FsYbp7Db5OAR\"}},\"addresses\":[\"edb81c10122f34040cc4bef719a272fbbb1cf897\",\"8bd7d5c000cf05284e98356370dc5ccaa3dbfc38\",\"2340695474656e3124b8eba1172fbfb00eeac8f8\"]}},\"version\":2}",
|
||||
"config": {
|
||||
"provider": {
|
||||
"type": "etherscan"
|
||||
}
|
||||
}
|
||||
},
|
||||
"meta": {
|
||||
"version": 0
|
||||
}
|
||||
}
|
@ -2,9 +2,8 @@ const ObservableStore = require('obs-store')
|
||||
const clone = require('clone')
|
||||
const ConfigManager = require('../../app/scripts/lib/config-manager')
|
||||
const firstTimeState = require('../../app/scripts/first-time-state')
|
||||
const STORAGE_KEY = 'metamask-config'
|
||||
|
||||
module.exports = function() {
|
||||
let store = new ObservableStore(clone(firstTimeState))
|
||||
module.exports = function () {
|
||||
const store = new ObservableStore(clone(firstTimeState))
|
||||
return new ConfigManager({ store })
|
||||
}
|
||||
}
|
||||
|
@ -4,28 +4,28 @@ let cacheVal
|
||||
|
||||
module.exports = {
|
||||
|
||||
encrypt(password, dataObj) {
|
||||
encrypt (password, dataObj) {
|
||||
cacheVal = dataObj
|
||||
return Promise.resolve(mockHex)
|
||||
},
|
||||
|
||||
decrypt(password, text) {
|
||||
decrypt (password, text) {
|
||||
return Promise.resolve(cacheVal || {})
|
||||
},
|
||||
|
||||
encryptWithKey(key, dataObj) {
|
||||
encryptWithKey (key, dataObj) {
|
||||
return this.encrypt(key, dataObj)
|
||||
},
|
||||
|
||||
decryptWithKey(key, text) {
|
||||
decryptWithKey (key, text) {
|
||||
return this.decrypt(key, text)
|
||||
},
|
||||
|
||||
keyFromPassword(password) {
|
||||
keyFromPassword (password) {
|
||||
return Promise.resolve(mockKey)
|
||||
},
|
||||
|
||||
generateSalt() {
|
||||
generateSalt () {
|
||||
return 'WHADDASALT!'
|
||||
},
|
||||
|
||||
|
@ -6,32 +6,32 @@ const type = 'Simple Key Pair'
|
||||
|
||||
module.exports = class MockSimpleKeychain {
|
||||
|
||||
static type() { return type }
|
||||
static type () { return type }
|
||||
|
||||
constructor(opts) {
|
||||
constructor (opts) {
|
||||
this.type = type
|
||||
this.opts = opts || {}
|
||||
this.wallets = []
|
||||
}
|
||||
|
||||
serialize() {
|
||||
serialize () {
|
||||
return [ fakeWallet.privKey ]
|
||||
}
|
||||
|
||||
deserialize(data) {
|
||||
deserialize (data) {
|
||||
if (!Array.isArray(data)) {
|
||||
throw new Error('Simple keychain deserialize requires a privKey array.')
|
||||
}
|
||||
this.wallets = [ fakeWallet ]
|
||||
}
|
||||
|
||||
addAccounts(n = 1) {
|
||||
for(var i = 0; i < n; i++) {
|
||||
addAccounts (n = 1) {
|
||||
for (var i = 0; i < n; i++) {
|
||||
this.wallets.push(fakeWallet)
|
||||
}
|
||||
}
|
||||
|
||||
getAccounts() {
|
||||
getAccounts () {
|
||||
return this.wallets.map(w => w.address)
|
||||
}
|
||||
|
||||
|
18
test/lib/mock-store.js
Normal file
18
test/lib/mock-store.js
Normal file
@ -0,0 +1,18 @@
|
||||
const createStore = require('redux').createStore
|
||||
const applyMiddleware = require('redux').applyMiddleware
|
||||
const thunkMiddleware = require('redux-thunk')
|
||||
const createLogger = require('redux-logger')
|
||||
const rootReducer = function () {}
|
||||
|
||||
module.exports = configureStore
|
||||
|
||||
const loggerMiddleware = createLogger()
|
||||
|
||||
const createStoreWithMiddleware = applyMiddleware(
|
||||
thunkMiddleware,
|
||||
loggerMiddleware
|
||||
)(createStore)
|
||||
|
||||
function configureStore (initialState) {
|
||||
return createStoreWithMiddleware(rootReducer, initialState)
|
||||
}
|
@ -1,18 +1,16 @@
|
||||
var assert = require('assert')
|
||||
var linkGen = require('../../ui/lib/account-link')
|
||||
|
||||
describe('account-link', function() {
|
||||
|
||||
it('adds ropsten prefix to ropsten test network', function() {
|
||||
describe('account-link', function () {
|
||||
it('adds ropsten prefix to ropsten test network', function () {
|
||||
var result = linkGen('account', '3')
|
||||
assert.notEqual(result.indexOf('ropsten'), -1, 'ropsten included')
|
||||
assert.notEqual(result.indexOf('account'), -1, 'account included')
|
||||
})
|
||||
|
||||
it('adds kovan prefix to kovan test network', function() {
|
||||
it('adds kovan prefix to kovan test network', function () {
|
||||
var result = linkGen('account', '42')
|
||||
assert.notEqual(result.indexOf('kovan'), -1, 'kovan included')
|
||||
assert.notEqual(result.indexOf('account'), -1, 'account included')
|
||||
})
|
||||
|
||||
})
|
||||
|
@ -1,36 +1,34 @@
|
||||
var jsdom = require('mocha-jsdom')
|
||||
// var jsdom = require('mocha-jsdom')
|
||||
var assert = require('assert')
|
||||
var freeze = require('deep-freeze-strict')
|
||||
var path = require('path')
|
||||
|
||||
var actions = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'actions.js'))
|
||||
var reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'reducers.js'))
|
||||
|
||||
describe ('config view actions', function() {
|
||||
var reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'reducers.js'))
|
||||
|
||||
describe('config view actions', function () {
|
||||
var initialState = {
|
||||
metamask: {
|
||||
rpcTarget: 'foo',
|
||||
frequentRpcList: []
|
||||
frequentRpcList: [],
|
||||
},
|
||||
appState: {
|
||||
currentView: {
|
||||
name: 'accounts',
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
freeze(initialState)
|
||||
|
||||
describe('SHOW_CONFIG_PAGE', function() {
|
||||
it('should set appState.currentView.name to config', function() {
|
||||
describe('SHOW_CONFIG_PAGE', function () {
|
||||
it('should set appState.currentView.name to config', function () {
|
||||
var result = reducers(initialState, actions.showConfigPage())
|
||||
assert.equal(result.appState.currentView.name, 'config')
|
||||
})
|
||||
})
|
||||
|
||||
describe('SET_RPC_TARGET', function() {
|
||||
|
||||
it('sets the state.metamask.rpcTarget property of the state to the action.value', function() {
|
||||
describe('SET_RPC_TARGET', function () {
|
||||
it('sets the state.metamask.rpcTarget property of the state to the action.value', function () {
|
||||
const action = {
|
||||
type: actions.SET_RPC_TARGET,
|
||||
value: 'foo',
|
||||
@ -41,5 +39,4 @@ describe ('config view actions', function() {
|
||||
assert.equal(result.metamask.provider.rpcTarget, 'foo')
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
|
@ -1,22 +1,21 @@
|
||||
var jsdom = require('mocha-jsdom')
|
||||
// var jsdom = require('mocha-jsdom')
|
||||
var assert = require('assert')
|
||||
var freeze = require('deep-freeze-strict')
|
||||
var path = require('path')
|
||||
|
||||
var actions = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'actions.js'))
|
||||
var reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'reducers.js'))
|
||||
var reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'reducers.js'))
|
||||
|
||||
describe('SAVE_ACCOUNT_LABEL', function() {
|
||||
|
||||
it('updates the state.metamask.identities[:i].name property of the state to the action.value.label', function() {
|
||||
describe('SAVE_ACCOUNT_LABEL', function () {
|
||||
it('updates the state.metamask.identities[:i].name property of the state to the action.value.label', function () {
|
||||
var initialState = {
|
||||
metamask: {
|
||||
identities: {
|
||||
foo: {
|
||||
name: 'bar'
|
||||
}
|
||||
name: 'bar',
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
freeze(initialState)
|
||||
|
||||
@ -24,13 +23,13 @@ describe('SAVE_ACCOUNT_LABEL', function() {
|
||||
type: actions.SAVE_ACCOUNT_LABEL,
|
||||
value: {
|
||||
account: 'foo',
|
||||
label: 'baz'
|
||||
label: 'baz',
|
||||
},
|
||||
}
|
||||
freeze(action)
|
||||
|
||||
var resultingState = reducers(initialState, action)
|
||||
assert.equal(resultingState.metamask.identities.foo.name, action.value.label)
|
||||
});
|
||||
});
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -1,18 +1,17 @@
|
||||
var jsdom = require('mocha-jsdom')
|
||||
// var jsdom = require('mocha-jsdom')
|
||||
var assert = require('assert')
|
||||
var freeze = require('deep-freeze-strict')
|
||||
var path = require('path')
|
||||
|
||||
var actions = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'actions.js'))
|
||||
var reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'reducers.js'))
|
||||
var reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'reducers.js'))
|
||||
|
||||
describe('SET_SELECTED_ACCOUNT', function() {
|
||||
|
||||
it('sets the state.appState.activeAddress property of the state to the action.value', function() {
|
||||
describe('SET_SELECTED_ACCOUNT', function () {
|
||||
it('sets the state.appState.activeAddress property of the state to the action.value', function () {
|
||||
var initialState = {
|
||||
appState: {
|
||||
activeAddress: 'foo',
|
||||
}
|
||||
},
|
||||
}
|
||||
freeze(initialState)
|
||||
|
||||
@ -24,15 +23,15 @@ describe('SET_SELECTED_ACCOUNT', function() {
|
||||
|
||||
var resultingState = reducers(initialState, action)
|
||||
assert.equal(resultingState.appState.activeAddress, action.value)
|
||||
});
|
||||
});
|
||||
})
|
||||
})
|
||||
|
||||
describe('SHOW_ACCOUNT_DETAIL', function() {
|
||||
it('updates metamask state', function() {
|
||||
describe('SHOW_ACCOUNT_DETAIL', function () {
|
||||
it('updates metamask state', function () {
|
||||
var initialState = {
|
||||
metamask: {
|
||||
selectedAddress: 'foo'
|
||||
}
|
||||
selectedAddress: 'foo',
|
||||
},
|
||||
}
|
||||
freeze(initialState)
|
||||
|
||||
|
@ -1,29 +1,27 @@
|
||||
var jsdom = require('mocha-jsdom')
|
||||
// var jsdom = require('mocha-jsdom')
|
||||
var assert = require('assert')
|
||||
var freeze = require('deep-freeze-strict')
|
||||
var path = require('path')
|
||||
var sinon = require('sinon')
|
||||
|
||||
var actions = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'actions.js'))
|
||||
var reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'reducers.js'))
|
||||
var reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'reducers.js'))
|
||||
|
||||
describe('tx confirmation screen', function() {
|
||||
describe('tx confirmation screen', function () {
|
||||
beforeEach(function () {
|
||||
this.sinon = sinon.sandbox.create()
|
||||
})
|
||||
|
||||
beforeEach(function() {
|
||||
this.sinon = sinon.sandbox.create();
|
||||
});
|
||||
|
||||
afterEach(function(){
|
||||
this.sinon.restore();
|
||||
});
|
||||
afterEach(function () {
|
||||
this.sinon.restore()
|
||||
})
|
||||
|
||||
var initialState, result
|
||||
|
||||
describe('when there is only one tx', function() {
|
||||
describe('when there is only one tx', function () {
|
||||
var firstTxId = 1457634084250832
|
||||
|
||||
beforeEach(function() {
|
||||
|
||||
beforeEach(function () {
|
||||
initialState = {
|
||||
appState: {
|
||||
currentView: {
|
||||
@ -34,70 +32,66 @@ describe('tx confirmation screen', function() {
|
||||
unapprovedTxs: {
|
||||
'1457634084250832': {
|
||||
id: 1457634084250832,
|
||||
status: "unconfirmed",
|
||||
status: 'unconfirmed',
|
||||
time: 1457634084250,
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
freeze(initialState)
|
||||
})
|
||||
|
||||
describe('cancelTx', function() {
|
||||
|
||||
before(function(done) {
|
||||
describe('cancelTx', function () {
|
||||
before(function (done) {
|
||||
actions._setBackgroundConnection({
|
||||
approveTransaction(txId, cb) { cb('An error!') },
|
||||
cancelTransaction(txId) { /* noop */ },
|
||||
clearSeedWordCache(cb) { cb() },
|
||||
approveTransaction (txId, cb) { cb('An error!') },
|
||||
cancelTransaction (txId) { /* noop */ },
|
||||
clearSeedWordCache (cb) { cb() },
|
||||
})
|
||||
|
||||
let action = actions.cancelTx({value: firstTxId})
|
||||
const action = actions.cancelTx({value: firstTxId})
|
||||
result = reducers(initialState, action)
|
||||
done()
|
||||
})
|
||||
|
||||
it('should transition to the account detail view', function() {
|
||||
it('should transition to the account detail view', function () {
|
||||
assert.equal(result.appState.currentView.name, 'accountDetail')
|
||||
})
|
||||
|
||||
it('should have no unconfirmed txs remaining', function() {
|
||||
it('should have no unconfirmed txs remaining', function () {
|
||||
var count = getUnconfirmedTxCount(result)
|
||||
assert.equal(count, 0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('sendTx', function() {
|
||||
describe('sendTx', function () {
|
||||
var result
|
||||
|
||||
describe('when there is an error', function() {
|
||||
|
||||
before(function(done) {
|
||||
alert = () => {/* noop */}
|
||||
|
||||
describe('when there is an error', function () {
|
||||
before(function (done) {
|
||||
actions._setBackgroundConnection({
|
||||
approveTransaction(txId, cb) { cb({message: 'An error!'}) },
|
||||
approveTransaction (txId, cb) { cb({message: 'An error!'}) },
|
||||
})
|
||||
|
||||
actions.sendTx({id: firstTxId})(function(action) {
|
||||
actions.sendTx({id: firstTxId})(function (action) {
|
||||
result = reducers(initialState, action)
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('should stay on the page', function() {
|
||||
it('should stay on the page', function () {
|
||||
assert.equal(result.appState.currentView.name, 'confTx')
|
||||
})
|
||||
|
||||
it('should set errorMessage on the currentView', function() {
|
||||
it('should set errorMessage on the currentView', function () {
|
||||
assert(result.appState.currentView.errorMessage)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when there is success', function() {
|
||||
it('should complete tx and go home', function() {
|
||||
describe('when there is success', function () {
|
||||
it('should complete tx and go home', function () {
|
||||
actions._setBackgroundConnection({
|
||||
approveTransaction(txId, cb) { cb() },
|
||||
approveTransaction (txId, cb) { cb() },
|
||||
})
|
||||
|
||||
var dispatchExpect = sinon.mock()
|
||||
@ -108,10 +102,10 @@ describe('tx confirmation screen', function() {
|
||||
})
|
||||
})
|
||||
|
||||
describe('when there are two pending txs', function() {
|
||||
describe('when there are two pending txs', function () {
|
||||
var firstTxId = 1457634084250832
|
||||
var result, initialState
|
||||
before(function(done) {
|
||||
before(function (done) {
|
||||
initialState = {
|
||||
appState: {
|
||||
currentView: {
|
||||
@ -122,42 +116,42 @@ describe('tx confirmation screen', function() {
|
||||
unapprovedTxs: {
|
||||
'1457634084250832': {
|
||||
id: firstTxId,
|
||||
status: "unconfirmed",
|
||||
status: 'unconfirmed',
|
||||
time: 1457634084250,
|
||||
},
|
||||
'1457634084250833': {
|
||||
id: 1457634084250833,
|
||||
status: "unconfirmed",
|
||||
status: 'unconfirmed',
|
||||
time: 1457634084255,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
freeze(initialState)
|
||||
|
||||
// Mocking a background connection:
|
||||
actions._setBackgroundConnection({
|
||||
approveTransaction(firstTxId, cb) { cb() },
|
||||
approveTransaction (firstTxId, cb) { cb() },
|
||||
})
|
||||
|
||||
let action = actions.sendTx({id: firstTxId})(function(action) {
|
||||
actions.sendTx({id: firstTxId})(function (action) {
|
||||
result = reducers(initialState, action)
|
||||
})
|
||||
done()
|
||||
})
|
||||
|
||||
it('should stay on the confTx view', function() {
|
||||
it('should stay on the confTx view', function () {
|
||||
assert.equal(result.appState.currentView.name, 'confTx')
|
||||
})
|
||||
|
||||
it('should transition to the first tx', function() {
|
||||
it('should transition to the first tx', function () {
|
||||
assert.equal(result.appState.currentView.context, 0)
|
||||
})
|
||||
})
|
||||
})
|
||||
});
|
||||
})
|
||||
|
||||
function getUnconfirmedTxCount(state) {
|
||||
function getUnconfirmedTxCount (state) {
|
||||
var txs = state.metamask.unapprovedTxs
|
||||
var count = Object.keys(txs).length
|
||||
return count
|
||||
|
@ -1,23 +1,22 @@
|
||||
var jsdom = require('mocha-jsdom')
|
||||
// var jsdom = require('mocha-jsdom')
|
||||
var assert = require('assert')
|
||||
var freeze = require('deep-freeze-strict')
|
||||
var path = require('path')
|
||||
|
||||
var actions = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'actions.js'))
|
||||
var reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'reducers.js'))
|
||||
var reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'reducers.js'))
|
||||
|
||||
describe('SHOW_INFO_PAGE', function() {
|
||||
|
||||
it('sets the state.appState.currentView.name property to info', function() {
|
||||
describe('SHOW_INFO_PAGE', function () {
|
||||
it('sets the state.appState.currentView.name property to info', function () {
|
||||
var initialState = {
|
||||
appState: {
|
||||
activeAddress: 'foo',
|
||||
}
|
||||
},
|
||||
}
|
||||
freeze(initialState)
|
||||
|
||||
const action = actions.showInfoPage()
|
||||
var resultingState = reducers(initialState, action)
|
||||
assert.equal(resultingState.appState.currentView.name, 'info')
|
||||
});
|
||||
});
|
||||
})
|
||||
})
|
||||
|
@ -1,14 +1,13 @@
|
||||
var jsdom = require('mocha-jsdom')
|
||||
// var jsdom = require('mocha-jsdom')
|
||||
var assert = require('assert')
|
||||
var freeze = require('deep-freeze-strict')
|
||||
var path = require('path')
|
||||
|
||||
var actions = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'actions.js'))
|
||||
var reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'reducers.js'))
|
||||
var reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'reducers.js'))
|
||||
|
||||
describe('action DISPLAY_WARNING', function() {
|
||||
|
||||
it('sets appState.warning to provided value', function() {
|
||||
describe('action DISPLAY_WARNING', function () {
|
||||
it('sets appState.warning to provided value', function () {
|
||||
var initialState = {
|
||||
appState: {},
|
||||
}
|
||||
@ -20,5 +19,5 @@ describe('action DISPLAY_WARNING', function() {
|
||||
const resultingState = reducers(initialState, action)
|
||||
|
||||
assert.equal(resultingState.appState.warning, warningText, 'warning text set')
|
||||
});
|
||||
});
|
||||
})
|
||||
})
|
||||
|
@ -1,5 +1,4 @@
|
||||
const assert = require('assert')
|
||||
const extend = require('xtend')
|
||||
const AddressBookController = require('../../app/scripts/controllers/address-book')
|
||||
|
||||
const mockKeyringController = {
|
||||
@ -7,21 +6,20 @@ const mockKeyringController = {
|
||||
getState: function () {
|
||||
return {
|
||||
identities: {
|
||||
'0x0aaa' : {
|
||||
'0x0aaa': {
|
||||
address: '0x0aaa',
|
||||
name: 'owned',
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
describe('address-book-controller', function() {
|
||||
describe('address-book-controller', function () {
|
||||
var addressBookController
|
||||
|
||||
beforeEach(function() {
|
||||
beforeEach(function () {
|
||||
addressBookController = new AddressBookController({}, mockKeyringController)
|
||||
})
|
||||
|
||||
|
@ -1,24 +1,22 @@
|
||||
var assert = require('assert')
|
||||
var BinaryRenderer = require('../../../ui/app/components/binary-renderer')
|
||||
|
||||
describe('BinaryRenderer', function() {
|
||||
|
||||
describe('BinaryRenderer', function () {
|
||||
let binaryRenderer
|
||||
const message = 'Hello, world!'
|
||||
const buffer = new Buffer(message, 'utf8')
|
||||
const hex = buffer.toString('hex')
|
||||
|
||||
beforeEach(function() {
|
||||
beforeEach(function () {
|
||||
binaryRenderer = new BinaryRenderer()
|
||||
})
|
||||
|
||||
it('recovers message', function() {
|
||||
it('recovers message', function () {
|
||||
const result = binaryRenderer.hexToText(hex)
|
||||
assert.equal(result, message)
|
||||
})
|
||||
|
||||
|
||||
it('recovers message with hex prefix', function() {
|
||||
it('recovers message with hex prefix', function () {
|
||||
const result = binaryRenderer.hexToText('0x' + hex)
|
||||
assert.equal(result, message)
|
||||
})
|
||||
|
51
test/unit/components/bn-as-decimal-input-test.js
Normal file
51
test/unit/components/bn-as-decimal-input-test.js
Normal file
@ -0,0 +1,51 @@
|
||||
var assert = require('assert')
|
||||
|
||||
const additions = require('react-testutils-additions')
|
||||
const h = require('react-hyperscript')
|
||||
const ReactTestUtils = require('react-addons-test-utils')
|
||||
const ethUtil = require('ethereumjs-util')
|
||||
const BN = ethUtil.BN
|
||||
|
||||
var BnInput = require('../../../ui/app/components/bn-as-decimal-input')
|
||||
|
||||
describe('BnInput', function () {
|
||||
it('can tolerate a gas decimal number at a high precision', function (done) {
|
||||
const renderer = ReactTestUtils.createRenderer()
|
||||
|
||||
let valueStr = '20'
|
||||
while (valueStr.length < 20) {
|
||||
valueStr += '0'
|
||||
}
|
||||
const value = new BN(valueStr, 10)
|
||||
|
||||
const inputStr = '2.3'
|
||||
|
||||
let targetStr = '23'
|
||||
while (targetStr.length < 19) {
|
||||
targetStr += '0'
|
||||
}
|
||||
const target = new BN(targetStr, 10)
|
||||
|
||||
const precision = 18 // ether precision
|
||||
const scale = 18
|
||||
|
||||
const props = {
|
||||
value,
|
||||
scale,
|
||||
precision,
|
||||
onChange: (newBn) => {
|
||||
assert.equal(newBn.toString(), target.toString(), 'should tolerate increase')
|
||||
done()
|
||||
},
|
||||
}
|
||||
|
||||
const inputComponent = h(BnInput, props)
|
||||
const component = additions.renderIntoDocument(inputComponent)
|
||||
renderer.render(inputComponent)
|
||||
const input = additions.find(component, 'input.hex-input')[0]
|
||||
ReactTestUtils.Simulate.change(input, { preventDefault () {}, target: {
|
||||
value: inputStr,
|
||||
checkValidity () { return true } },
|
||||
})
|
||||
})
|
||||
})
|
77
test/unit/components/pending-tx-test.js
Normal file
77
test/unit/components/pending-tx-test.js
Normal file
@ -0,0 +1,77 @@
|
||||
const assert = require('assert')
|
||||
const additions = require('react-testutils-additions')
|
||||
const h = require('react-hyperscript')
|
||||
const PendingTx = require('../../../ui/app/components/pending-tx')
|
||||
const ReactTestUtils = require('react-addons-test-utils')
|
||||
const ethUtil = require('ethereumjs-util')
|
||||
|
||||
describe('PendingTx', function () {
|
||||
const identities = {
|
||||
'0xfdea65c8e26263f6d9a1b5de9555d2931a33b826': {
|
||||
name: 'Main Account 1',
|
||||
balance: '0x00000000000000056bc75e2d63100000',
|
||||
},
|
||||
}
|
||||
|
||||
const gasPrice = '0x4A817C800' // 20 Gwei
|
||||
const txData = {
|
||||
'id': 5021615666270214,
|
||||
'time': 1494458763011,
|
||||
'status': 'unapproved',
|
||||
'metamaskNetworkId': '1494442339676',
|
||||
'txParams': {
|
||||
'from': '0xfdea65c8e26263f6d9a1b5de9555d2931a33b826',
|
||||
'to': '0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb',
|
||||
'value': '0xde0b6b3a7640000',
|
||||
gasPrice,
|
||||
'gas': '0x7b0c'},
|
||||
'gasLimitSpecified': false,
|
||||
'estimatedGas': '0x5208',
|
||||
}
|
||||
|
||||
|
||||
it('should use updated values when edited.', function (done) {
|
||||
const renderer = ReactTestUtils.createRenderer()
|
||||
const newGasPrice = '0x77359400'
|
||||
|
||||
const props = {
|
||||
identities,
|
||||
accounts: identities,
|
||||
txData,
|
||||
sendTransaction: (txMeta, event) => {
|
||||
// Assert changes:
|
||||
const result = ethUtil.addHexPrefix(txMeta.txParams.gasPrice)
|
||||
assert.notEqual(result, gasPrice, 'gas price should change')
|
||||
assert.equal(result, newGasPrice, 'gas price assigned.')
|
||||
done()
|
||||
},
|
||||
}
|
||||
|
||||
const pendingTxComponent = h(PendingTx, props)
|
||||
const component = additions.renderIntoDocument(pendingTxComponent)
|
||||
renderer.render(pendingTxComponent)
|
||||
const result = renderer.getRenderOutput()
|
||||
assert.equal(result.type, 'div', 'should create a div')
|
||||
|
||||
try {
|
||||
const input = additions.find(component, '.cell.row input[type="number"]')[1]
|
||||
ReactTestUtils.Simulate.change(input, {
|
||||
target: {
|
||||
value: 2,
|
||||
checkValidity () { return true },
|
||||
},
|
||||
})
|
||||
|
||||
const form = additions.find(component, 'form')[0]
|
||||
form.checkValidity = () => true
|
||||
form.getFormEl = () => { return { checkValidity () { return true } } }
|
||||
ReactTestUtils.Simulate.submit(form, { preventDefault () {}, target: { checkValidity () {
|
||||
return true
|
||||
} } })
|
||||
} catch (e) {
|
||||
console.log('WHAAAA')
|
||||
console.error(e)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user