mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 09:52:26 +01:00
commit
ee62a6a391
@ -1,2 +1 @@
|
||||
app/scripts/lib/extension-instance.js
|
||||
ui/app/conversion-util.js
|
||||
|
@ -127,9 +127,9 @@
|
||||
"no-whitespace-before-property": 2,
|
||||
"no-with": 2,
|
||||
"one-var": [2, { "initialized": "never" }],
|
||||
"operator-linebreak": [1, "after", { "overrides": { "?": "before", ":": "before" } }],
|
||||
"operator-linebreak": [1, "after", { "overrides": { "?": "ignore", ":": "ignore" } }],
|
||||
"padded-blocks": [1, "never"],
|
||||
"quotes": [2, "single", "avoid-escape"],
|
||||
"quotes": [2, "single", {"avoidEscape": true, "allowTemplateLiterals": true}],
|
||||
"semi": [2, "never"],
|
||||
"semi-spacing": [2, { "before": false, "after": true }],
|
||||
"space-before-blocks": [1, "always"],
|
||||
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,5 +1,4 @@
|
||||
dist
|
||||
|
||||
npm-debug.log
|
||||
node_modules
|
||||
temp
|
||||
@ -8,9 +7,9 @@ temp
|
||||
app/bower_components
|
||||
test/bower_components
|
||||
package
|
||||
|
||||
.DS_Store
|
||||
builds/
|
||||
disc/
|
||||
notes.txt
|
||||
app/.DS_Store
|
||||
development/bundle.js
|
||||
|
22
CHANGELOG.md
22
CHANGELOG.md
@ -2,9 +2,27 @@
|
||||
|
||||
## Current Master
|
||||
|
||||
## 3.0.0 2017-1-16
|
||||
|
||||
- Fix seed word account generation (https://medium.com/metamask/metamask-3-migration-guide-914b79533cdd#.t4i1qmmsz).
|
||||
- Fix Bug where you see a empty transaction flash by on the confirm transaction view.
|
||||
- Create visible difference in transaction history between a approved but not yet included in a block transaction and a transaction who has been confirmed.
|
||||
- Fix memory leak in RPC Cache
|
||||
- Override RPC commands eth_syncing and web3_clientVersion
|
||||
- Remove certain non-essential permissions from certain builds.
|
||||
- Add a check for when a tx is included in a block.
|
||||
- Fix bug where browser-solidity would sometimes warn of a contract creation error when there was none.
|
||||
- Minor modifications to network display.
|
||||
- Network now displays properly for pending transactions.
|
||||
- Implement replay attack protections allowed by EIP 155.
|
||||
- Fix bug where sometimes loading account data would fail by querying a future block.
|
||||
|
||||
## 2.14.1 2016-12-20
|
||||
|
||||
- Update Coinbase info. and increase the buy amount to $15
|
||||
- Fixed ropsten transaction links
|
||||
- Temporarily disable extension reload detection causing infinite reload bug.
|
||||
- Implemented basic checking for valid RPC URIs.
|
||||
|
||||
## 2.14.0 2016-12-16
|
||||
|
||||
@ -16,6 +34,7 @@
|
||||
## 2.13.11 2016-11-23
|
||||
|
||||
- Add support for synchronous RPC method "eth_uninstallFilter".
|
||||
- Forgotten password prompts now send users directly to seed word restoration.
|
||||
|
||||
## 2.13.10 2016-11-22
|
||||
|
||||
@ -28,6 +47,9 @@
|
||||
|
||||
- Add support for the new, default Ropsten Test Network.
|
||||
- Fix bug that would cause MetaMask to occasionally lose its StreamProvider connection and drop requests.
|
||||
- Fix bug that would cause the Custom RPC menu item to not appear when Localhost 8545 was selected.
|
||||
- Point ropsten faucet button to actual faucet.
|
||||
- Phase out ethereumjs-util from our encryptor module.
|
||||
|
||||
## 2.13.8 2016-11-16
|
||||
|
||||
|
55
README.md
55
README.md
@ -18,6 +18,12 @@ 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
|
||||
|
||||
To install your locally built extension on Chrome, [follow this guide](http://stackoverflow.com/a/24577660/272576).
|
||||
|
||||
The built extension is stored in `./dist/chrome/`.
|
||||
|
||||
## Architecture
|
||||
|
||||
[![Architecture Diagram](./docs/architecture.png)][1]
|
||||
@ -26,6 +32,13 @@ If you're a web dapp developer, we've got two types of guides for you:
|
||||
|
||||
```bash
|
||||
npm install
|
||||
npm start
|
||||
```
|
||||
|
||||
## Build for Publishing
|
||||
|
||||
```bash
|
||||
npm run dist
|
||||
```
|
||||
|
||||
#### In Chrome
|
||||
@ -78,7 +91,7 @@ To enjoy the live-reloading that `gulp dev` offers while working on the `web3-pr
|
||||
2. `npm install` in its folder.
|
||||
3. Run `npm link` in its folder.
|
||||
4. Run `npm link $DEP_NAME` in this project folder.
|
||||
5. Next time you `gulp dev` it will watch the dependency for changes as well!
|
||||
5. Next time you `npm start` it will watch the dependency for changes as well!
|
||||
|
||||
### Running Tests
|
||||
|
||||
@ -90,6 +103,10 @@ You can also test with a continuously watching process, via `npm run watch`.
|
||||
|
||||
You can run the linter by itself with `gulp lint`.
|
||||
|
||||
#### Writing Browser Tests
|
||||
|
||||
To write tests that will be run in the browser using QUnit, add your test files to `test/integration/lib`.
|
||||
|
||||
### Deploying the UI
|
||||
|
||||
You must be authorized already on the MetaMask plugin.
|
||||
@ -100,3 +117,39 @@ You can run the linter by itself with `gulp lint`.
|
||||
3. Upload the latest zip file from `builds/metamask-$PLATFORM-$VERSION.zip` as the updated package.
|
||||
|
||||
[1]: http://www.nomnoml.com/#view/%5B%3Cactor%3Euser%5D%0A%0A%5Bmetamask-ui%7C%0A%20%20%20%5Btools%7C%0A%20%20%20%20%20react%0A%20%20%20%20%20redux%0A%20%20%20%20%20thunk%0A%20%20%20%20%20ethUtils%0A%20%20%20%20%20jazzicon%0A%20%20%20%5D%0A%20%20%20%5Bcomponents%7C%0A%20%20%20%20%20app%0A%20%20%20%20%20account-detail%0A%20%20%20%20%20accounts%0A%20%20%20%20%20locked-screen%0A%20%20%20%20%20restore-vault%0A%20%20%20%20%20identicon%0A%20%20%20%20%20config%0A%20%20%20%20%20info%0A%20%20%20%5D%0A%20%20%20%5Breducers%7C%0A%20%20%20%20%20app%0A%20%20%20%20%20metamask%0A%20%20%20%20%20identities%0A%20%20%20%5D%0A%20%20%20%5Bactions%7C%0A%20%20%20%20%20%5BaccountManager%5D%0A%20%20%20%5D%0A%20%20%20%5Bcomponents%5D%3A-%3E%5Bactions%5D%0A%20%20%20%5Bactions%5D%3A-%3E%5Breducers%5D%0A%20%20%20%5Breducers%5D%3A-%3E%5Bcomponents%5D%0A%5D%0A%0A%5Bweb%20dapp%7C%0A%20%20%5Bui%20code%5D%0A%20%20%5Bweb3%5D%0A%20%20%5Bmetamask-inpage%5D%0A%20%20%0A%20%20%5B%3Cactor%3Eui%20developer%5D%0A%20%20%5Bui%20developer%5D-%3E%5Bui%20code%5D%0A%20%20%5Bui%20code%5D%3C-%3E%5Bweb3%5D%0A%20%20%5Bweb3%5D%3C-%3E%5Bmetamask-inpage%5D%0A%5D%0A%0A%5Bmetamask-background%7C%0A%20%20%5Bprovider-engine%5D%0A%20%20%5Bhooked%20wallet%20subprovider%5D%0A%20%20%5Bid%20store%5D%0A%20%20%0A%20%20%5Bprovider-engine%5D%3C-%3E%5Bhooked%20wallet%20subprovider%5D%0A%20%20%5Bhooked%20wallet%20subprovider%5D%3C-%3E%5Bid%20store%5D%0A%20%20%5Bconfig%20manager%7C%0A%20%20%20%20%5Brpc%20configuration%5D%0A%20%20%20%20%5Bencrypted%20keys%5D%0A%20%20%20%20%5Bwallet%20nicknames%5D%0A%20%20%5D%0A%20%20%0A%20%20%5Bprovider-engine%5D%3C-%5Bconfig%20manager%5D%0A%20%20%5Bid%20store%5D%3C-%3E%5Bconfig%20manager%5D%0A%5D%0A%0A%5Buser%5D%3C-%3E%5Bmetamask-ui%5D%0A%0A%5Buser%5D%3C%3A--%3A%3E%5Bweb%20dapp%5D%0A%0A%5Bmetamask-contentscript%7C%0A%20%20%5Bplugin%20restart%20detector%5D%0A%20%20%5Brpc%20passthrough%5D%0A%5D%0A%0A%5Brpc%20%7C%0A%20%20%5Bethereum%20blockchain%20%7C%0A%20%20%20%20%5Bcontracts%5D%0A%20%20%20%20%5Baccounts%5D%0A%20%20%5D%0A%5D%0A%0A%5Bweb%20dapp%5D%3C%3A--%3A%3E%5Bmetamask-contentscript%5D%0A%5Bmetamask-contentscript%5D%3C-%3E%5Bmetamask-background%5D%0A%5Bmetamask-background%5D%3C-%3E%5Bmetamask-ui%5D%0A%5Bmetamask-background%5D%3C-%3E%5Brpc%5D%0A
|
||||
|
||||
|
||||
### Generate Development Visualization
|
||||
|
||||
This will generate a video of the repo commit history.
|
||||
|
||||
Install preqs:
|
||||
```
|
||||
brew install gource
|
||||
brew install ffmpeg
|
||||
```
|
||||
|
||||
From the repo dir, pipe `gource` into `ffmpeg`:
|
||||
```
|
||||
gource \
|
||||
--seconds-per-day .1 \
|
||||
--user-scale 1.5 \
|
||||
--default-user-image "./images/icon-512.png" \
|
||||
--viewport 1280x720 \
|
||||
--auto-skip-seconds .1 \
|
||||
--multi-sampling \
|
||||
--stop-at-end \
|
||||
--highlight-users \
|
||||
--hide mouse,progress \
|
||||
--file-idle-time 0 \
|
||||
--max-files 0 \
|
||||
--background-colour 000000 \
|
||||
--font-size 18 \
|
||||
--date-format "%b %d, %Y" \
|
||||
--highlight-dirs \
|
||||
--user-friction 0.1 \
|
||||
--title "MetaMask Development History" \
|
||||
--output-ppm-stream - \
|
||||
--output-framerate 30 \
|
||||
| ffmpeg -y -r 30 -f image2pipe -vcodec ppm -i - -b 65536K metamask-dev-history.mp4
|
||||
```
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "MetaMask",
|
||||
"short_name": "Metamask",
|
||||
"version": "2.14.0",
|
||||
"version": "2.15.0",
|
||||
"manifest_version": 2,
|
||||
"author": "https://metamask.io",
|
||||
"description": "Ethereum Browser Extension",
|
||||
@ -56,9 +56,7 @@
|
||||
],
|
||||
"permissions": [
|
||||
"storage",
|
||||
"tabs",
|
||||
"clipboardWrite",
|
||||
"clipboardRead",
|
||||
"http://localhost:8545/"
|
||||
],
|
||||
"web_accessible_resources": [
|
||||
|
@ -10,26 +10,25 @@ const MetamaskController = require('./metamask-controller')
|
||||
const extension = require('./lib/extension')
|
||||
|
||||
const STORAGE_KEY = 'metamask-config'
|
||||
const METAMASK_DEBUG = 'GULP_METAMASK_DEBUG'
|
||||
var popupIsOpen = false
|
||||
|
||||
const controller = new MetamaskController({
|
||||
// User confirmation callbacks:
|
||||
showUnconfirmedMessage: triggerUi,
|
||||
unlockAccountMessage: triggerUi,
|
||||
showUnconfirmedTx: triggerUi,
|
||||
showUnapprovedTx: triggerUi,
|
||||
// Persistence Methods:
|
||||
setData,
|
||||
loadData,
|
||||
})
|
||||
const idStore = controller.idStore
|
||||
|
||||
function triggerUi () {
|
||||
if (!popupIsOpen) notification.show()
|
||||
}
|
||||
// On first install, open a window to MetaMask website to how-it-works.
|
||||
|
||||
extension.runtime.onInstalled.addListener(function (details) {
|
||||
if (details.reason === 'install') {
|
||||
if ((details.reason === 'install') && (!METAMASK_DEBUG)) {
|
||||
extension.tabs.create({url: 'https://metamask.io/#how-it-works'})
|
||||
}
|
||||
})
|
||||
@ -80,13 +79,11 @@ function setupControllerConnection (stream) {
|
||||
stream.pipe(dnode).pipe(stream)
|
||||
dnode.on('remote', (remote) => {
|
||||
// push updates to popup
|
||||
controller.ethStore.on('update', controller.sendUpdate.bind(controller))
|
||||
controller.listeners.push(remote)
|
||||
idStore.on('update', controller.sendUpdate.bind(controller))
|
||||
|
||||
var sendUpdate = remote.sendUpdate.bind(remote)
|
||||
controller.on('update', sendUpdate)
|
||||
// teardown on disconnect
|
||||
eos(stream, () => {
|
||||
controller.ethStore.removeListener('update', controller.sendUpdate.bind(controller))
|
||||
controller.removeListener('update', sendUpdate)
|
||||
popupIsOpen = false
|
||||
})
|
||||
})
|
||||
@ -96,15 +93,15 @@ function setupControllerConnection (stream) {
|
||||
// plugin badge text
|
||||
//
|
||||
|
||||
idStore.on('update', updateBadge)
|
||||
controller.txManager.on('updateBadge', updateBadge)
|
||||
updateBadge()
|
||||
|
||||
function updateBadge (state) {
|
||||
function updateBadge () {
|
||||
var label = ''
|
||||
var unconfTxs = controller.configManager.unconfirmedTxs()
|
||||
var unconfTxLen = Object.keys(unconfTxs).length
|
||||
var unapprovedTxCount = controller.txManager.unapprovedTxCount
|
||||
var unconfMsgs = messageManager.unconfirmedMsgs()
|
||||
var unconfMsgLen = Object.keys(unconfMsgs).length
|
||||
var count = unconfTxLen + unconfMsgLen
|
||||
var count = unapprovedTxCount + unconfMsgLen
|
||||
if (count) {
|
||||
label = String(count)
|
||||
}
|
||||
@ -112,6 +109,8 @@ function updateBadge (state) {
|
||||
extension.browserAction.setBadgeBackgroundColor({ color: '#506F8B' })
|
||||
}
|
||||
|
||||
// data :: setters/getters
|
||||
|
||||
function loadData () {
|
||||
var oldData = getOldStyleData()
|
||||
var newData
|
||||
|
@ -6,7 +6,7 @@ const extension = require('./lib/extension')
|
||||
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const inpageText = fs.readFileSync(path.join(__dirname + '/inpage.js')).toString()
|
||||
const inpageText = fs.readFileSync(path.join(__dirname, 'inpage.js')).toString()
|
||||
|
||||
// Eventually this streaming injection could be replaced with:
|
||||
// https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Language_Bindings/Components.utils.exportFunction
|
||||
@ -20,9 +20,8 @@ if (shouldInjectWeb3()) {
|
||||
setupStreams()
|
||||
}
|
||||
|
||||
function setupInjection(){
|
||||
function setupInjection () {
|
||||
try {
|
||||
|
||||
// inject in-page script
|
||||
var scriptTag = document.createElement('script')
|
||||
scriptTag.src = extension.extension.getURL('scripts/inpage.js')
|
||||
@ -31,14 +30,12 @@ function setupInjection(){
|
||||
var container = document.head || document.documentElement
|
||||
// append as first child
|
||||
container.insertBefore(scriptTag, container.children[0])
|
||||
|
||||
} catch (e) {
|
||||
console.error('Metamask injection failed.', e)
|
||||
}
|
||||
}
|
||||
|
||||
function setupStreams(){
|
||||
|
||||
function setupStreams () {
|
||||
// setup communication to page and plugin
|
||||
var pageStream = new LocalMessageDuplexStream({
|
||||
name: 'contentscript',
|
||||
@ -65,14 +62,13 @@ function setupStreams(){
|
||||
mx.ignoreStream('provider')
|
||||
mx.ignoreStream('publicConfig')
|
||||
mx.ignoreStream('reload')
|
||||
|
||||
}
|
||||
|
||||
function shouldInjectWeb3(){
|
||||
function shouldInjectWeb3 () {
|
||||
return isAllowedSuffix(window.location.href)
|
||||
}
|
||||
|
||||
function isAllowedSuffix(testCase) {
|
||||
function isAllowedSuffix (testCase) {
|
||||
var prohibitedTypes = ['xml', 'pdf']
|
||||
var currentUrl = window.location.href
|
||||
var currentRegex
|
||||
|
@ -43,6 +43,7 @@ reloadStream.once('data', triggerReload)
|
||||
// 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)
|
||||
@ -51,7 +52,7 @@ reloadStream.once('data', triggerReload)
|
||||
|
||||
// set web3 defaultAcount
|
||||
inpageProvider.publicConfigStore.subscribe(function (state) {
|
||||
web3.eth.defaultAccount = state.selectedAddress
|
||||
web3.eth.defaultAccount = state.selectedAccount
|
||||
})
|
||||
|
||||
//
|
||||
|
680
app/scripts/keyring-controller.js
Normal file
680
app/scripts/keyring-controller.js
Normal file
@ -0,0 +1,680 @@
|
||||
const ethUtil = require('ethereumjs-util')
|
||||
const bip39 = require('bip39')
|
||||
const EventEmitter = require('events').EventEmitter
|
||||
const filter = require('promise-filter')
|
||||
const encryptor = require('browser-passworder')
|
||||
|
||||
const normalize = require('./lib/sig-util').normalize
|
||||
const messageManager = require('./lib/message-manager')
|
||||
const BN = ethUtil.BN
|
||||
|
||||
// Keyrings:
|
||||
const SimpleKeyring = require('./keyrings/simple')
|
||||
const HdKeyring = require('./keyrings/hd')
|
||||
const keyringTypes = [
|
||||
SimpleKeyring,
|
||||
HdKeyring,
|
||||
]
|
||||
|
||||
const createId = require('./lib/random-id')
|
||||
|
||||
module.exports = class KeyringController extends EventEmitter {
|
||||
|
||||
// PUBLIC METHODS
|
||||
//
|
||||
// THE FIRST SECTION OF METHODS ARE PUBLIC-FACING,
|
||||
// MEANING THEY ARE USED BY CONSUMERS OF THIS CLASS.
|
||||
//
|
||||
// THEIR SURFACE AREA SHOULD BE CHANGED WITH GREAT CARE.
|
||||
|
||||
constructor (opts) {
|
||||
super()
|
||||
this.configManager = opts.configManager
|
||||
this.ethStore = opts.ethStore
|
||||
this.encryptor = encryptor
|
||||
this.keyringTypes = keyringTypes
|
||||
this.keyrings = []
|
||||
this.identities = {} // Essentially a name hash
|
||||
|
||||
this._unconfMsgCbs = {}
|
||||
|
||||
this.getNetwork = opts.getNetwork
|
||||
}
|
||||
|
||||
// Set Store
|
||||
//
|
||||
// Allows setting the ethStore after the constructor.
|
||||
// This is currently required because of the initialization order
|
||||
// of the ethStore and this class.
|
||||
//
|
||||
// Eventually would be nice to be able to add this in the constructor.
|
||||
setStore (ethStore) {
|
||||
this.ethStore = ethStore
|
||||
}
|
||||
|
||||
// Full Update
|
||||
// returns Promise( @object state )
|
||||
//
|
||||
// Emits the `update` event and
|
||||
// returns a Promise that resolves to the current state.
|
||||
//
|
||||
// Frequently used to end asynchronous chains in this class,
|
||||
// indicating consumers can often either listen for updates,
|
||||
// or accept a state-resolving promise to consume their results.
|
||||
//
|
||||
// Not all methods end with this, that might be a nice refactor.
|
||||
fullUpdate () {
|
||||
this.emit('update')
|
||||
return Promise.resolve(this.getState())
|
||||
}
|
||||
|
||||
// Get State
|
||||
// returns @object state
|
||||
//
|
||||
// This method returns a hash representing the current state
|
||||
// that the keyringController manages.
|
||||
//
|
||||
// It is extended in the MetamaskController along with the EthStore
|
||||
// state, and its own state, to create the metamask state branch
|
||||
// that is passed to the UI.
|
||||
//
|
||||
// This is currently a rare example of a synchronously resolving method
|
||||
// in this class, but will need to be Promisified when we move our
|
||||
// persistence to an async model.
|
||||
getState () {
|
||||
const configManager = this.configManager
|
||||
const address = configManager.getSelectedAccount()
|
||||
const wallet = configManager.getWallet() // old style vault
|
||||
const vault = configManager.getVault() // new style vault
|
||||
const keyrings = this.keyrings
|
||||
|
||||
return Promise.all(keyrings.map(this.displayForKeyring))
|
||||
.then((displayKeyrings) => {
|
||||
return {
|
||||
seedWords: this.configManager.getSeedWords(),
|
||||
isInitialized: (!!wallet || !!vault),
|
||||
isUnlocked: Boolean(this.password),
|
||||
isDisclaimerConfirmed: this.configManager.getConfirmedDisclaimer(),
|
||||
unconfMsgs: messageManager.unconfirmedMsgs(),
|
||||
messages: messageManager.getMsgList(),
|
||||
selectedAccount: address,
|
||||
shapeShiftTxList: this.configManager.getShapeShiftTxList(),
|
||||
currentFiat: this.configManager.getCurrentFiat(),
|
||||
conversionRate: this.configManager.getConversionRate(),
|
||||
conversionDate: this.configManager.getConversionDate(),
|
||||
keyringTypes: this.keyringTypes.map(krt => krt.type),
|
||||
identities: this.identities,
|
||||
keyrings: displayKeyrings,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Create New Vault And Keychain
|
||||
// @string password - The password to encrypt the vault with
|
||||
//
|
||||
// returns Promise( @object state )
|
||||
//
|
||||
// Destroys any old encrypted storage,
|
||||
// creates a new encrypted store with the given password,
|
||||
// randomly creates a new HD wallet with 1 account,
|
||||
// faucets that account on the testnet.
|
||||
createNewVaultAndKeychain (password) {
|
||||
return this.persistAllKeyrings(password)
|
||||
.then(this.createFirstKeyTree.bind(this))
|
||||
.then(this.fullUpdate.bind(this))
|
||||
}
|
||||
|
||||
// CreateNewVaultAndRestore
|
||||
// @string password - The password to encrypt the vault with
|
||||
// @string seed - The BIP44-compliant seed phrase.
|
||||
//
|
||||
// returns Promise( @object state )
|
||||
//
|
||||
// Destroys any old encrypted storage,
|
||||
// creates a new encrypted store with the given password,
|
||||
// creates a new HD wallet from the given seed with 1 account.
|
||||
createNewVaultAndRestore (password, seed) {
|
||||
if (typeof password !== 'string') {
|
||||
return Promise.reject('Password must be text.')
|
||||
}
|
||||
|
||||
if (!bip39.validateMnemonic(seed)) {
|
||||
return Promise.reject('Seed phrase is invalid.')
|
||||
}
|
||||
|
||||
this.clearKeyrings()
|
||||
|
||||
return this.persistAllKeyrings(password)
|
||||
.then(() => {
|
||||
return this.addNewKeyring('HD Key Tree', {
|
||||
mnemonic: seed,
|
||||
numberOfAccounts: 1,
|
||||
})
|
||||
}).then(() => {
|
||||
const firstKeyring = this.keyrings[0]
|
||||
return firstKeyring.getAccounts()
|
||||
})
|
||||
.then((accounts) => {
|
||||
const firstAccount = accounts[0]
|
||||
const hexAccount = normalize(firstAccount)
|
||||
this.configManager.setSelectedAccount(hexAccount)
|
||||
return this.setupAccounts(accounts)
|
||||
})
|
||||
.then(this.persistAllKeyrings.bind(this, password))
|
||||
.then(this.fullUpdate.bind(this))
|
||||
}
|
||||
|
||||
// PlaceSeedWords
|
||||
// returns Promise( @object state )
|
||||
//
|
||||
// Adds the current vault's seed words to the UI's state tree.
|
||||
//
|
||||
// Used when creating a first vault, to allow confirmation.
|
||||
// Also used when revealing the seed words in the confirmation view.
|
||||
placeSeedWords () {
|
||||
const firstKeyring = this.keyrings[0]
|
||||
return firstKeyring.serialize()
|
||||
.then((serialized) => {
|
||||
const seedWords = serialized.mnemonic
|
||||
this.configManager.setSeedWords(seedWords)
|
||||
return this.fullUpdate()
|
||||
})
|
||||
}
|
||||
|
||||
// ClearSeedWordCache
|
||||
//
|
||||
// returns Promise( @string currentSelectedAccount )
|
||||
//
|
||||
// Removes the current vault's seed words from the UI's state tree,
|
||||
// ensuring they are only ever available in the background process.
|
||||
clearSeedWordCache () {
|
||||
this.configManager.setSeedWords(null)
|
||||
return Promise.resolve(this.configManager.getSelectedAccount())
|
||||
}
|
||||
|
||||
// Set Locked
|
||||
// returns Promise( @object state )
|
||||
//
|
||||
// This method deallocates all secrets, and effectively locks metamask.
|
||||
setLocked () {
|
||||
this.password = null
|
||||
this.keyrings = []
|
||||
return this.fullUpdate()
|
||||
}
|
||||
|
||||
// Submit Password
|
||||
// @string password
|
||||
//
|
||||
// returns Promise( @object state )
|
||||
//
|
||||
// Attempts to decrypt the current vault and load its keyrings
|
||||
// into memory.
|
||||
//
|
||||
// Temporarily also migrates any old-style vaults first, as well.
|
||||
// (Pre MetaMask 3.0.0)
|
||||
submitPassword (password) {
|
||||
return this.unlockKeyrings(password)
|
||||
.then((keyrings) => {
|
||||
this.keyrings = keyrings
|
||||
return this.fullUpdate()
|
||||
})
|
||||
}
|
||||
|
||||
// Add New Keyring
|
||||
// @string type
|
||||
// @object opts
|
||||
//
|
||||
// returns Promise( @Keyring keyring )
|
||||
//
|
||||
// Adds a new Keyring of the given `type` to the vault
|
||||
// and the current decrypted Keyrings array.
|
||||
//
|
||||
// All Keyring classes implement a unique `type` string,
|
||||
// and this is used to retrieve them from the keyringTypes array.
|
||||
addNewKeyring (type, opts) {
|
||||
const Keyring = this.getKeyringClassForType(type)
|
||||
const keyring = new Keyring(opts)
|
||||
return keyring.getAccounts()
|
||||
.then((accounts) => {
|
||||
this.keyrings.push(keyring)
|
||||
return this.setupAccounts(accounts)
|
||||
})
|
||||
.then(() => { return this.password })
|
||||
.then(this.persistAllKeyrings.bind(this))
|
||||
.then(() => {
|
||||
return keyring
|
||||
})
|
||||
}
|
||||
|
||||
// Add New Account
|
||||
// @number keyRingNum
|
||||
//
|
||||
// returns Promise( @object state )
|
||||
//
|
||||
// Calls the `addAccounts` method on the Keyring
|
||||
// in the kryings array at index `keyringNum`,
|
||||
// and then saves those changes.
|
||||
addNewAccount (keyRingNum = 0) {
|
||||
const ring = this.keyrings[keyRingNum]
|
||||
return ring.addAccounts(1)
|
||||
.then(this.setupAccounts.bind(this))
|
||||
.then(this.persistAllKeyrings.bind(this))
|
||||
.then(this.fullUpdate.bind(this))
|
||||
}
|
||||
|
||||
// Set Selected Account
|
||||
// @string address
|
||||
//
|
||||
// returns Promise( @string address )
|
||||
//
|
||||
// Sets the state's `selectedAccount` value
|
||||
// to the specified address.
|
||||
setSelectedAccount (address) {
|
||||
var addr = normalize(address)
|
||||
this.configManager.setSelectedAccount(addr)
|
||||
return this.fullUpdate()
|
||||
}
|
||||
|
||||
// Save Account Label
|
||||
// @string account
|
||||
// @string label
|
||||
//
|
||||
// returns Promise( @string label )
|
||||
//
|
||||
// Persists a nickname equal to `label` for the specified account.
|
||||
saveAccountLabel (account, label) {
|
||||
const address = normalize(account)
|
||||
const configManager = this.configManager
|
||||
configManager.setNicknameForWallet(address, label)
|
||||
this.identities[address].name = label
|
||||
return Promise.resolve(label)
|
||||
}
|
||||
|
||||
// Export Account
|
||||
// @string address
|
||||
//
|
||||
// returns Promise( @string privateKey )
|
||||
//
|
||||
// Requests the private key from the keyring controlling
|
||||
// the specified address.
|
||||
//
|
||||
// Returns a Promise that may resolve with the private key string.
|
||||
exportAccount (address) {
|
||||
try {
|
||||
return this.getKeyringForAccount(address)
|
||||
.then((keyring) => {
|
||||
return keyring.exportAccount(normalize(address))
|
||||
})
|
||||
} catch (e) {
|
||||
return Promise.reject(e)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// SIGNING METHODS
|
||||
//
|
||||
// This method signs tx and returns a promise for
|
||||
// TX Manager to update the state after signing
|
||||
|
||||
signTransaction (ethTx, _fromAddress) {
|
||||
const fromAddress = normalize(_fromAddress)
|
||||
return this.getKeyringForAccount(fromAddress)
|
||||
.then((keyring) => {
|
||||
return keyring.signTransaction(fromAddress, ethTx)
|
||||
})
|
||||
}
|
||||
// Add Unconfirmed Message
|
||||
// @object msgParams
|
||||
// @function cb
|
||||
//
|
||||
// Does not call back, only emits an `update` event.
|
||||
//
|
||||
// Adds the given `msgParams` and `cb` to a local cache,
|
||||
// for displaying to a user for approval before signing or canceling.
|
||||
addUnconfirmedMessage (msgParams, cb) {
|
||||
// create txData obj with parameters and meta data
|
||||
var time = (new Date()).getTime()
|
||||
var msgId = createId()
|
||||
var msgData = {
|
||||
id: msgId,
|
||||
msgParams: msgParams,
|
||||
time: time,
|
||||
status: 'unconfirmed',
|
||||
}
|
||||
messageManager.addMsg(msgData)
|
||||
console.log('addUnconfirmedMessage:', msgData)
|
||||
|
||||
// keep the cb around for after approval (requires user interaction)
|
||||
// This cb fires completion to the Dapp's write operation.
|
||||
this._unconfMsgCbs[msgId] = cb
|
||||
|
||||
// signal update
|
||||
this.emit('update')
|
||||
return msgId
|
||||
}
|
||||
|
||||
// Cancel Message
|
||||
// @string msgId
|
||||
// @function cb (optional)
|
||||
//
|
||||
// Calls back to cached `unconfMsgCb`.
|
||||
// Calls back to `cb` if provided.
|
||||
//
|
||||
// Forgets any messages matching `msgId`.
|
||||
cancelMessage (msgId, cb) {
|
||||
var approvalCb = this._unconfMsgCbs[msgId] || noop
|
||||
|
||||
// reject tx
|
||||
approvalCb(null, false)
|
||||
// clean up
|
||||
messageManager.rejectMsg(msgId)
|
||||
delete this._unconfTxCbs[msgId]
|
||||
|
||||
if (cb && typeof cb === 'function') {
|
||||
cb()
|
||||
}
|
||||
}
|
||||
|
||||
// Sign Message
|
||||
// @object msgParams
|
||||
// @function cb
|
||||
//
|
||||
// returns Promise(@buffer rawSig)
|
||||
// calls back @function cb with @buffer rawSig
|
||||
// calls back cached Dapp's @function unconfMsgCb.
|
||||
//
|
||||
// Attempts to sign the provided @object msgParams.
|
||||
signMessage (msgParams, cb) {
|
||||
try {
|
||||
const msgId = msgParams.metamaskId
|
||||
delete msgParams.metamaskId
|
||||
const approvalCb = this._unconfMsgCbs[msgId] || noop
|
||||
|
||||
const address = normalize(msgParams.from)
|
||||
return this.getKeyringForAccount(address)
|
||||
.then((keyring) => {
|
||||
return keyring.signMessage(address, msgParams.data)
|
||||
}).then((rawSig) => {
|
||||
cb(null, rawSig)
|
||||
approvalCb(null, true)
|
||||
return rawSig
|
||||
})
|
||||
} catch (e) {
|
||||
cb(e)
|
||||
}
|
||||
}
|
||||
|
||||
// PRIVATE METHODS
|
||||
//
|
||||
// THESE METHODS ARE ONLY USED INTERNALLY TO THE KEYRING-CONTROLLER
|
||||
// AND SO MAY BE CHANGED MORE LIBERALLY THAN THE ABOVE METHODS.
|
||||
|
||||
// Create First Key Tree
|
||||
// returns @Promise
|
||||
//
|
||||
// Clears the vault,
|
||||
// creates a new one,
|
||||
// creates a random new HD Keyring with 1 account,
|
||||
// makes that account the selected account,
|
||||
// faucets that account on testnet,
|
||||
// puts the current seed words into the state tree.
|
||||
createFirstKeyTree () {
|
||||
this.clearKeyrings()
|
||||
return this.addNewKeyring('HD Key Tree', {numberOfAccounts: 1})
|
||||
.then(() => {
|
||||
return this.keyrings[0].getAccounts()
|
||||
})
|
||||
.then((accounts) => {
|
||||
const firstAccount = accounts[0]
|
||||
const hexAccount = normalize(firstAccount)
|
||||
this.configManager.setSelectedAccount(hexAccount)
|
||||
this.emit('newAccount', hexAccount)
|
||||
return this.setupAccounts(accounts)
|
||||
}).then(() => {
|
||||
return this.placeSeedWords()
|
||||
})
|
||||
.then(this.persistAllKeyrings.bind(this))
|
||||
}
|
||||
|
||||
// Setup Accounts
|
||||
// @array accounts
|
||||
//
|
||||
// returns @Promise(@object account)
|
||||
//
|
||||
// Initializes the provided account array
|
||||
// Gives them numerically incremented nicknames,
|
||||
// and adds them to the ethStore for regular balance checking.
|
||||
setupAccounts (accounts) {
|
||||
return this.getAccounts()
|
||||
.then((loadedAccounts) => {
|
||||
const arr = accounts || loadedAccounts
|
||||
return Promise.all(arr.map((account) => {
|
||||
return this.getBalanceAndNickname(account)
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
// Get Balance And Nickname
|
||||
// @string account
|
||||
//
|
||||
// returns Promise( @string label )
|
||||
//
|
||||
// Takes an account address and an iterator representing
|
||||
// the current number of named accounts.
|
||||
getBalanceAndNickname (account) {
|
||||
if (!account) {
|
||||
throw new Error('Problem loading account.')
|
||||
}
|
||||
const address = normalize(account)
|
||||
this.ethStore.addAccount(address)
|
||||
return this.createNickname(address)
|
||||
}
|
||||
|
||||
// Create Nickname
|
||||
// @string address
|
||||
//
|
||||
// returns Promise( @string label )
|
||||
//
|
||||
// Takes an address, and assigns it an incremented nickname, persisting it.
|
||||
createNickname (address) {
|
||||
const hexAddress = normalize(address)
|
||||
var i = Object.keys(this.identities).length
|
||||
const oldNickname = this.configManager.nicknameForWallet(address)
|
||||
const name = oldNickname || `Account ${++i}`
|
||||
this.identities[hexAddress] = {
|
||||
address: hexAddress,
|
||||
name,
|
||||
}
|
||||
return this.saveAccountLabel(hexAddress, name)
|
||||
}
|
||||
|
||||
// Persist All Keyrings
|
||||
// @password string
|
||||
//
|
||||
// returns Promise
|
||||
//
|
||||
// Iterates the current `keyrings` array,
|
||||
// serializes each one into a serialized array,
|
||||
// encrypts that array with the provided `password`,
|
||||
// and persists that encrypted string to storage.
|
||||
persistAllKeyrings (password = this.password) {
|
||||
if (typeof password === 'string') {
|
||||
this.password = password
|
||||
}
|
||||
return Promise.all(this.keyrings.map((keyring) => {
|
||||
return Promise.all([keyring.type, keyring.serialize()])
|
||||
.then((serializedKeyringArray) => {
|
||||
// Label the output values on each serialized Keyring:
|
||||
return {
|
||||
type: serializedKeyringArray[0],
|
||||
data: serializedKeyringArray[1],
|
||||
}
|
||||
})
|
||||
}))
|
||||
.then((serializedKeyrings) => {
|
||||
return this.encryptor.encrypt(this.password, serializedKeyrings)
|
||||
})
|
||||
.then((encryptedString) => {
|
||||
this.configManager.setVault(encryptedString)
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
// Unlock Keyrings
|
||||
// @string password
|
||||
//
|
||||
// returns Promise( @array keyrings )
|
||||
//
|
||||
// Attempts to unlock the persisted encrypted storage,
|
||||
// initializing the persisted keyrings to RAM.
|
||||
unlockKeyrings (password) {
|
||||
const encryptedVault = this.configManager.getVault()
|
||||
if (!encryptedVault) {
|
||||
throw new Error('Cannot unlock without a previous vault.')
|
||||
}
|
||||
|
||||
return this.encryptor.decrypt(password, encryptedVault)
|
||||
.then((vault) => {
|
||||
this.password = password
|
||||
vault.forEach(this.restoreKeyring.bind(this))
|
||||
return this.keyrings
|
||||
})
|
||||
}
|
||||
|
||||
// Restore Keyring
|
||||
// @object serialized
|
||||
//
|
||||
// returns Promise( @Keyring deserialized )
|
||||
//
|
||||
// Attempts to initialize a new keyring from the provided
|
||||
// serialized payload.
|
||||
//
|
||||
// On success, returns the resulting @Keyring instance.
|
||||
restoreKeyring (serialized) {
|
||||
const { type, data } = serialized
|
||||
|
||||
const Keyring = this.getKeyringClassForType(type)
|
||||
const keyring = new Keyring()
|
||||
return keyring.deserialize(data)
|
||||
.then(() => {
|
||||
return keyring.getAccounts()
|
||||
})
|
||||
.then((accounts) => {
|
||||
return this.setupAccounts(accounts)
|
||||
})
|
||||
.then(() => {
|
||||
this.keyrings.push(keyring)
|
||||
return keyring
|
||||
})
|
||||
}
|
||||
|
||||
// Get Keyring Class For Type
|
||||
// @string type
|
||||
//
|
||||
// Returns @class Keyring
|
||||
//
|
||||
// Searches the current `keyringTypes` array
|
||||
// for a Keyring class whose unique `type` property
|
||||
// matches the provided `type`,
|
||||
// returning it if it exists.
|
||||
getKeyringClassForType (type) {
|
||||
return this.keyringTypes.find(kr => kr.type === type)
|
||||
}
|
||||
|
||||
// Get Accounts
|
||||
// returns Promise( @Array[ @string accounts ] )
|
||||
//
|
||||
// Returns the public addresses of all current accounts
|
||||
// managed by all currently unlocked keyrings.
|
||||
getAccounts () {
|
||||
const keyrings = this.keyrings || []
|
||||
return Promise.all(keyrings.map(kr => kr.getAccounts()))
|
||||
.then((keyringArrays) => {
|
||||
return keyringArrays.reduce((res, arr) => {
|
||||
return res.concat(arr)
|
||||
}, [])
|
||||
})
|
||||
}
|
||||
|
||||
// Get Keyring For Account
|
||||
// @string address
|
||||
//
|
||||
// returns Promise(@Keyring keyring)
|
||||
//
|
||||
// Returns the currently initialized keyring that manages
|
||||
// the specified `address` if one exists.
|
||||
getKeyringForAccount (address) {
|
||||
const hexed = normalize(address)
|
||||
|
||||
return Promise.all(this.keyrings.map((keyring) => {
|
||||
return Promise.all([
|
||||
keyring,
|
||||
keyring.getAccounts(),
|
||||
])
|
||||
}))
|
||||
.then(filter((candidate) => {
|
||||
const accounts = candidate[1].map(normalize)
|
||||
return accounts.includes(hexed)
|
||||
}))
|
||||
.then((winners) => {
|
||||
if (winners && winners.length > 0) {
|
||||
return winners[0][0]
|
||||
} else {
|
||||
throw new Error('No keyring found for the requested account.')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Display For Keyring
|
||||
// @Keyring keyring
|
||||
//
|
||||
// returns Promise( @Object { type:String, accounts:Array } )
|
||||
//
|
||||
// Is used for adding the current keyrings to the state object.
|
||||
displayForKeyring (keyring) {
|
||||
return keyring.getAccounts()
|
||||
.then((accounts) => {
|
||||
return {
|
||||
type: keyring.type,
|
||||
accounts: accounts,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Add Gas Buffer
|
||||
// @string gas (as hexadecimal value)
|
||||
//
|
||||
// returns @string bufferedGas (as hexadecimal value)
|
||||
//
|
||||
// Adds a healthy buffer of gas to an initial gas estimate.
|
||||
addGasBuffer (gas) {
|
||||
const gasBuffer = new BN('100000', 10)
|
||||
const bnGas = new BN(ethUtil.stripHexPrefix(gas), 16)
|
||||
const correct = bnGas.add(gasBuffer)
|
||||
return ethUtil.addHexPrefix(correct.toString(16))
|
||||
}
|
||||
|
||||
// Clear Keyrings
|
||||
//
|
||||
// Deallocates all currently managed keyrings and accounts.
|
||||
// Used before initializing a new vault.
|
||||
clearKeyrings () {
|
||||
let accounts
|
||||
try {
|
||||
accounts = Object.keys(this.ethStore._currentState.accounts)
|
||||
} catch (e) {
|
||||
accounts = []
|
||||
}
|
||||
accounts.forEach((address) => {
|
||||
this.ethStore.removeAccount(address)
|
||||
})
|
||||
|
||||
this.keyrings = []
|
||||
this.identities = {}
|
||||
this.configManager.setSelectedAccount()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
function noop () {}
|
111
app/scripts/keyrings/hd.js
Normal file
111
app/scripts/keyrings/hd.js
Normal file
@ -0,0 +1,111 @@
|
||||
const EventEmitter = require('events').EventEmitter
|
||||
const hdkey = require('ethereumjs-wallet/hdkey')
|
||||
const bip39 = require('bip39')
|
||||
const ethUtil = require('ethereumjs-util')
|
||||
|
||||
// *Internal Deps
|
||||
const sigUtil = require('../lib/sig-util')
|
||||
|
||||
// Options:
|
||||
const hdPathString = `m/44'/60'/0'/0`
|
||||
const type = 'HD Key Tree'
|
||||
|
||||
class HdKeyring extends EventEmitter {
|
||||
|
||||
/* PUBLIC METHODS */
|
||||
|
||||
constructor (opts = {}) {
|
||||
super()
|
||||
this.type = type
|
||||
this.deserialize(opts)
|
||||
}
|
||||
|
||||
serialize () {
|
||||
return Promise.resolve({
|
||||
mnemonic: this.mnemonic,
|
||||
numberOfAccounts: this.wallets.length,
|
||||
})
|
||||
}
|
||||
|
||||
deserialize (opts = {}) {
|
||||
this.opts = opts || {}
|
||||
this.wallets = []
|
||||
this.mnemonic = null
|
||||
this.root = null
|
||||
|
||||
if (opts.mnemonic) {
|
||||
this._initFromMnemonic(opts.mnemonic)
|
||||
}
|
||||
|
||||
if (opts.numberOfAccounts) {
|
||||
return this.addAccounts(opts.numberOfAccounts)
|
||||
}
|
||||
|
||||
return Promise.resolve([])
|
||||
}
|
||||
|
||||
addAccounts (numberOfAccounts = 1) {
|
||||
if (!this.root) {
|
||||
this._initFromMnemonic(bip39.generateMnemonic())
|
||||
}
|
||||
|
||||
const oldLen = this.wallets.length
|
||||
const newWallets = []
|
||||
for (let i = oldLen; i < numberOfAccounts + oldLen; i++) {
|
||||
const child = this.root.deriveChild(i)
|
||||
const wallet = child.getWallet()
|
||||
newWallets.push(wallet)
|
||||
this.wallets.push(wallet)
|
||||
}
|
||||
const hexWallets = newWallets.map(w => w.getAddress().toString('hex'))
|
||||
return Promise.resolve(hexWallets)
|
||||
}
|
||||
|
||||
getAccounts () {
|
||||
return Promise.resolve(this.wallets.map(w => w.getAddress().toString('hex')))
|
||||
}
|
||||
|
||||
// tx is an instance of the ethereumjs-transaction class.
|
||||
signTransaction (address, tx) {
|
||||
const wallet = this._getWalletForAccount(address)
|
||||
var privKey = wallet.getPrivateKey()
|
||||
tx.sign(privKey)
|
||||
return Promise.resolve(tx)
|
||||
}
|
||||
|
||||
// For eth_sign, we need to sign transactions:
|
||||
signMessage (withAccount, data) {
|
||||
const wallet = this._getWalletForAccount(withAccount)
|
||||
const message = ethUtil.removeHexPrefix(data)
|
||||
var privKey = wallet.getPrivateKey()
|
||||
var msgSig = ethUtil.ecsign(new Buffer(message, 'hex'), privKey)
|
||||
var rawMsgSig = ethUtil.bufferToHex(sigUtil.concatSig(msgSig.v, msgSig.r, msgSig.s))
|
||||
return Promise.resolve(rawMsgSig)
|
||||
}
|
||||
|
||||
exportAccount (address) {
|
||||
const wallet = this._getWalletForAccount(address)
|
||||
return Promise.resolve(wallet.getPrivateKey().toString('hex'))
|
||||
}
|
||||
|
||||
|
||||
/* PRIVATE METHODS */
|
||||
|
||||
_initFromMnemonic (mnemonic) {
|
||||
this.mnemonic = mnemonic
|
||||
const seed = bip39.mnemonicToSeed(mnemonic)
|
||||
this.hdWallet = hdkey.fromMasterSeed(seed)
|
||||
this.root = this.hdWallet.derivePath(hdPathString)
|
||||
}
|
||||
|
||||
|
||||
_getWalletForAccount (account) {
|
||||
return this.wallets.find((w) => {
|
||||
const address = w.getAddress().toString('hex')
|
||||
return ((address === account) || (sigUtil.normalize(address) === account))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
HdKeyring.type = type
|
||||
module.exports = HdKeyring
|
82
app/scripts/keyrings/simple.js
Normal file
82
app/scripts/keyrings/simple.js
Normal file
@ -0,0 +1,82 @@
|
||||
const EventEmitter = require('events').EventEmitter
|
||||
const Wallet = require('ethereumjs-wallet')
|
||||
const ethUtil = require('ethereumjs-util')
|
||||
const type = 'Simple Key Pair'
|
||||
const sigUtil = require('../lib/sig-util')
|
||||
|
||||
class SimpleKeyring extends EventEmitter {
|
||||
|
||||
/* PUBLIC METHODS */
|
||||
|
||||
constructor (opts) {
|
||||
super()
|
||||
this.type = type
|
||||
this.opts = opts || {}
|
||||
this.wallets = []
|
||||
}
|
||||
|
||||
serialize () {
|
||||
return Promise.resolve(this.wallets.map(w => w.getPrivateKey().toString('hex')))
|
||||
}
|
||||
|
||||
deserialize (privateKeys = []) {
|
||||
this.wallets = privateKeys.map((privateKey) => {
|
||||
const stripped = ethUtil.stripHexPrefix(privateKey)
|
||||
const buffer = new Buffer(stripped, 'hex')
|
||||
const wallet = Wallet.fromPrivateKey(buffer)
|
||||
return wallet
|
||||
})
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
addAccounts (n = 1) {
|
||||
var newWallets = []
|
||||
for (var i = 0; i < n; i++) {
|
||||
newWallets.push(Wallet.generate())
|
||||
}
|
||||
this.wallets = this.wallets.concat(newWallets)
|
||||
const hexWallets = newWallets.map(w => ethUtil.bufferToHex(w.getAddress()))
|
||||
return Promise.resolve(hexWallets)
|
||||
}
|
||||
|
||||
getAccounts () {
|
||||
return Promise.resolve(this.wallets.map(w => ethUtil.bufferToHex(w.getAddress())))
|
||||
}
|
||||
|
||||
// tx is an instance of the ethereumjs-transaction class.
|
||||
signTransaction (address, tx) {
|
||||
const wallet = this._getWalletForAccount(address)
|
||||
var privKey = wallet.getPrivateKey()
|
||||
tx.sign(privKey)
|
||||
return Promise.resolve(tx)
|
||||
}
|
||||
|
||||
// For eth_sign, we need to sign transactions:
|
||||
signMessage (withAccount, data) {
|
||||
const wallet = this._getWalletForAccount(withAccount)
|
||||
|
||||
const message = ethUtil.removeHexPrefix(data)
|
||||
var privKey = wallet.getPrivateKey()
|
||||
var msgSig = ethUtil.ecsign(new Buffer(message, 'hex'), privKey)
|
||||
var rawMsgSig = ethUtil.bufferToHex(sigUtil.concatSig(msgSig.v, msgSig.r, msgSig.s))
|
||||
return Promise.resolve(rawMsgSig)
|
||||
}
|
||||
|
||||
exportAccount (address) {
|
||||
const wallet = this._getWalletForAccount(address)
|
||||
return Promise.resolve(wallet.getPrivateKey().toString('hex'))
|
||||
}
|
||||
|
||||
|
||||
/* PRIVATE METHODS */
|
||||
|
||||
_getWalletForAccount (account) {
|
||||
let wallet = this.wallets.find(w => ethUtil.bufferToHex(w.getAddress()) === account)
|
||||
if (!wallet) throw new Error('Simple Keyring - Unable to find matching address.')
|
||||
return wallet
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
SimpleKeyring.type = type
|
||||
module.exports = SimpleKeyring
|
@ -1,6 +1,9 @@
|
||||
var uri = 'https://faucet.metamask.io/'
|
||||
const uri = 'https://faucet.metamask.io/'
|
||||
const METAMASK_DEBUG = 'GULP_METAMASK_DEBUG'
|
||||
const env = process.env.METAMASK_ENV
|
||||
|
||||
module.exports = function (address) {
|
||||
if (METAMASK_DEBUG || env === 'test') return // Don't faucet in development or test
|
||||
var http = new XMLHttpRequest()
|
||||
var data = address
|
||||
http.open('POST', uri, true)
|
||||
|
@ -18,17 +18,16 @@ function setupDappAutoReload (web3) {
|
||||
|
||||
return handleResetRequest
|
||||
|
||||
function handleResetRequest() {
|
||||
function handleResetRequest () {
|
||||
resetWasRequested = true
|
||||
// ignore if web3 was not used
|
||||
if (!pageIsUsingWeb3) return
|
||||
// reload after short timeout
|
||||
setTimeout(triggerReset, 500)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// reload the page
|
||||
function triggerReset () {
|
||||
global.location.reload()
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,12 @@
|
||||
const Migrator = require('pojo-migrator')
|
||||
const MetamaskConfig = require('../config.js')
|
||||
const migrations = require('./migrations')
|
||||
const rp = require('request-promise')
|
||||
const ethUtil = require('ethereumjs-util')
|
||||
const normalize = require('./sig-util').normalize
|
||||
|
||||
const TESTNET_RPC = MetamaskConfig.network.testnet
|
||||
const MAINNET_RPC = MetamaskConfig.network.mainnet
|
||||
const MORDEN_RPC = MetamaskConfig.network.morden
|
||||
const txLimit = 40
|
||||
|
||||
/* The config-manager is a convenience object
|
||||
* wrapping a pojo-migrator.
|
||||
@ -17,8 +17,6 @@ const txLimit = 40
|
||||
*/
|
||||
module.exports = ConfigManager
|
||||
function ConfigManager (opts) {
|
||||
this.txLimit = txLimit
|
||||
|
||||
// ConfigManager is observable and will emit updates
|
||||
this._subs = []
|
||||
|
||||
@ -111,6 +109,27 @@ ConfigManager.prototype.setWallet = function (wallet) {
|
||||
this.setData(data)
|
||||
}
|
||||
|
||||
ConfigManager.prototype.setVault = function (encryptedString) {
|
||||
var data = this.getData()
|
||||
data.vault = encryptedString
|
||||
this.setData(data)
|
||||
}
|
||||
|
||||
ConfigManager.prototype.getVault = function () {
|
||||
var data = this.getData()
|
||||
return data.vault
|
||||
}
|
||||
|
||||
ConfigManager.prototype.getKeychains = function () {
|
||||
return this.migrator.getData().keychains || []
|
||||
}
|
||||
|
||||
ConfigManager.prototype.setKeychains = function (keychains) {
|
||||
var data = this.migrator.getData()
|
||||
data.keychains = keychains
|
||||
this.setData(data)
|
||||
}
|
||||
|
||||
ConfigManager.prototype.getSelectedAccount = function () {
|
||||
var config = this.getConfig()
|
||||
return config.selectedAccount
|
||||
@ -118,7 +137,7 @@ ConfigManager.prototype.getSelectedAccount = function () {
|
||||
|
||||
ConfigManager.prototype.setSelectedAccount = function (address) {
|
||||
var config = this.getConfig()
|
||||
config.selectedAccount = address
|
||||
config.selectedAccount = ethUtil.addHexPrefix(address)
|
||||
this.setConfig(config)
|
||||
}
|
||||
|
||||
@ -133,11 +152,23 @@ ConfigManager.prototype.setShowSeedWords = function (should) {
|
||||
this.setData(data)
|
||||
}
|
||||
|
||||
|
||||
ConfigManager.prototype.getShouldShowSeedWords = function () {
|
||||
var data = this.migrator.getData()
|
||||
return data.showSeedWords
|
||||
}
|
||||
|
||||
ConfigManager.prototype.setSeedWords = function (words) {
|
||||
var data = this.getData()
|
||||
data.seedWords = words
|
||||
this.setData(data)
|
||||
}
|
||||
|
||||
ConfigManager.prototype.getSeedWords = function () {
|
||||
var data = this.getData()
|
||||
return ('seedWords' in data) && data.seedWords
|
||||
}
|
||||
|
||||
ConfigManager.prototype.getCurrentRpcAddress = function () {
|
||||
var provider = this.getProvider()
|
||||
if (!provider) return null
|
||||
@ -174,61 +205,12 @@ ConfigManager.prototype.getTxList = function () {
|
||||
}
|
||||
}
|
||||
|
||||
ConfigManager.prototype.unconfirmedTxs = function () {
|
||||
var transactions = this.getTxList()
|
||||
return transactions.filter(tx => tx.status === 'unconfirmed')
|
||||
.reduce((result, tx) => { result[tx.id] = tx; return result }, {})
|
||||
}
|
||||
|
||||
ConfigManager.prototype._saveTxList = function (txList) {
|
||||
ConfigManager.prototype.setTxList = function (txList) {
|
||||
var data = this.migrator.getData()
|
||||
data.transactions = txList
|
||||
this.setData(data)
|
||||
}
|
||||
|
||||
ConfigManager.prototype.addTx = function (tx) {
|
||||
var transactions = this.getTxList()
|
||||
while (transactions.length > this.txLimit - 1) {
|
||||
transactions.shift()
|
||||
}
|
||||
transactions.push(tx)
|
||||
this._saveTxList(transactions)
|
||||
}
|
||||
|
||||
ConfigManager.prototype.getTx = function (txId) {
|
||||
var transactions = this.getTxList()
|
||||
var matching = transactions.filter(tx => tx.id === txId)
|
||||
return matching.length > 0 ? matching[0] : null
|
||||
}
|
||||
|
||||
ConfigManager.prototype.confirmTx = function (txId) {
|
||||
this._setTxStatus(txId, 'confirmed')
|
||||
}
|
||||
|
||||
ConfigManager.prototype.rejectTx = function (txId) {
|
||||
this._setTxStatus(txId, 'rejected')
|
||||
}
|
||||
|
||||
ConfigManager.prototype._setTxStatus = function (txId, status) {
|
||||
var tx = this.getTx(txId)
|
||||
tx.status = status
|
||||
this.updateTx(tx)
|
||||
}
|
||||
|
||||
ConfigManager.prototype.updateTx = function (tx) {
|
||||
var transactions = this.getTxList()
|
||||
var found, index
|
||||
transactions.forEach((otherTx, i) => {
|
||||
if (otherTx.id === tx.id) {
|
||||
found = true
|
||||
index = i
|
||||
}
|
||||
})
|
||||
if (found) {
|
||||
transactions[index] = tx
|
||||
}
|
||||
this._saveTxList(transactions)
|
||||
}
|
||||
|
||||
// wallet nickname methods
|
||||
|
||||
@ -239,13 +221,15 @@ ConfigManager.prototype.getWalletNicknames = function () {
|
||||
}
|
||||
|
||||
ConfigManager.prototype.nicknameForWallet = function (account) {
|
||||
const address = normalize(account)
|
||||
const nicknames = this.getWalletNicknames()
|
||||
return nicknames[account]
|
||||
return nicknames[address]
|
||||
}
|
||||
|
||||
ConfigManager.prototype.setNicknameForWallet = function (account, nickname) {
|
||||
const address = normalize(account)
|
||||
const nicknames = this.getWalletNicknames()
|
||||
nicknames[account] = nickname
|
||||
nicknames[address] = nickname
|
||||
var data = this.getData()
|
||||
data.walletNicknames = nicknames
|
||||
this.setData(data)
|
||||
@ -253,6 +237,17 @@ ConfigManager.prototype.setNicknameForWallet = function (account, nickname) {
|
||||
|
||||
// observable
|
||||
|
||||
ConfigManager.prototype.getSalt = function () {
|
||||
var data = this.getData()
|
||||
return ('salt' in data) && data.salt
|
||||
}
|
||||
|
||||
ConfigManager.prototype.setSalt = function (salt) {
|
||||
var data = this.getData()
|
||||
data.salt = salt
|
||||
this.setData(data)
|
||||
}
|
||||
|
||||
ConfigManager.prototype.subscribe = function (fn) {
|
||||
this._subs.push(fn)
|
||||
var unsubscribe = this.unsubscribe.bind(this, fn)
|
||||
@ -270,15 +265,15 @@ ConfigManager.prototype._emitUpdates = function (state) {
|
||||
})
|
||||
}
|
||||
|
||||
ConfigManager.prototype.setConfirmed = function (confirmed) {
|
||||
ConfigManager.prototype.setConfirmedDisclaimer = function (confirmed) {
|
||||
var data = this.getData()
|
||||
data.isConfirmed = confirmed
|
||||
data.isDisclaimerConfirmed = confirmed
|
||||
this.setData(data)
|
||||
}
|
||||
|
||||
ConfigManager.prototype.getConfirmed = function () {
|
||||
ConfigManager.prototype.getConfirmedDisclaimer = function () {
|
||||
var data = this.getData()
|
||||
return ('isConfirmed' in data) && data.isConfirmed
|
||||
return ('isDisclaimerConfirmed' in data) && data.isDisclaimerConfirmed
|
||||
}
|
||||
|
||||
ConfigManager.prototype.setTOSHash = function (hash) {
|
||||
@ -305,9 +300,9 @@ ConfigManager.prototype.getCurrentFiat = function () {
|
||||
|
||||
ConfigManager.prototype.updateConversionRate = function () {
|
||||
var data = this.getData()
|
||||
return rp(`https://www.cryptonator.com/api/ticker/eth-${data.fiatCurrency}`)
|
||||
.then((response) => {
|
||||
const parsedResponse = JSON.parse(response)
|
||||
return fetch(`https://www.cryptonator.com/api/ticker/eth-${data.fiatCurrency}`)
|
||||
.then(response => response.json())
|
||||
.then((parsedResponse) => {
|
||||
this.setConversionPrice(parsedResponse.ticker.price)
|
||||
this.setConversionDate(parsedResponse.timestamp)
|
||||
}).catch((err) => {
|
||||
@ -315,7 +310,6 @@ ConfigManager.prototype.updateConversionRate = function () {
|
||||
this.setConversionPrice(0)
|
||||
this.setConversionDate('N/A')
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
ConfigManager.prototype.setConversionPrice = function (price) {
|
||||
@ -340,21 +334,6 @@ ConfigManager.prototype.getConversionDate = function () {
|
||||
return (('conversionDate' in data) && data.conversionDate) || 'N/A'
|
||||
}
|
||||
|
||||
ConfigManager.prototype.setShouldntShowWarning = function () {
|
||||
var data = this.getData()
|
||||
if (data.isEthConfirmed) {
|
||||
data.isEthConfirmed = !data.isEthConfirmed
|
||||
} else {
|
||||
data.isEthConfirmed = true
|
||||
}
|
||||
this.setData(data)
|
||||
}
|
||||
|
||||
ConfigManager.prototype.getShouldntShowWarning = function () {
|
||||
var data = this.getData()
|
||||
return ('isEthConfirmed' in data) && data.isEthConfirmed
|
||||
}
|
||||
|
||||
ConfigManager.prototype.getShapeShiftTxList = function () {
|
||||
var data = this.getData()
|
||||
var shapeShiftTxList = data.shapeShiftTxList ? data.shapeShiftTxList : []
|
||||
@ -400,3 +379,14 @@ ConfigManager.prototype.setGasMultiplier = function (gasMultiplier) {
|
||||
data.gasMultiplier = gasMultiplier
|
||||
this.setData(data)
|
||||
}
|
||||
|
||||
ConfigManager.prototype.setLostAccounts = function (lostAccounts) {
|
||||
var data = this.getData()
|
||||
data.lostAccounts = lostAccounts
|
||||
this.setData(data)
|
||||
}
|
||||
|
||||
ConfigManager.prototype.getLostAccounts = function () {
|
||||
var data = this.getData()
|
||||
return data.lostAccounts || []
|
||||
}
|
||||
|
146
app/scripts/lib/eth-store.js
Normal file
146
app/scripts/lib/eth-store.js
Normal file
@ -0,0 +1,146 @@
|
||||
/* Ethereum Store
|
||||
*
|
||||
* This module is responsible for tracking any number of accounts
|
||||
* and caching their current balances & transaction counts.
|
||||
*
|
||||
* It also tracks transaction hashes, and checks their inclusion status
|
||||
* on each new block.
|
||||
*/
|
||||
|
||||
const EventEmitter = require('events').EventEmitter
|
||||
const inherits = require('util').inherits
|
||||
const async = require('async')
|
||||
const clone = require('clone')
|
||||
const EthQuery = require('eth-query')
|
||||
|
||||
module.exports = EthereumStore
|
||||
|
||||
|
||||
inherits(EthereumStore, EventEmitter)
|
||||
function EthereumStore(engine) {
|
||||
const self = this
|
||||
EventEmitter.call(self)
|
||||
self._currentState = {
|
||||
accounts: {},
|
||||
transactions: {},
|
||||
}
|
||||
self._query = new EthQuery(engine)
|
||||
|
||||
engine.on('block', self._updateForBlock.bind(self))
|
||||
}
|
||||
|
||||
//
|
||||
// public
|
||||
//
|
||||
|
||||
EthereumStore.prototype.getState = function () {
|
||||
const self = this
|
||||
return clone(self._currentState)
|
||||
}
|
||||
|
||||
EthereumStore.prototype.addAccount = function (address) {
|
||||
const self = this
|
||||
self._currentState.accounts[address] = {}
|
||||
self._didUpdate()
|
||||
if (!self.currentBlockNumber) return
|
||||
self._updateAccount(address, () => {
|
||||
self._didUpdate()
|
||||
})
|
||||
}
|
||||
|
||||
EthereumStore.prototype.removeAccount = function (address) {
|
||||
const self = this
|
||||
delete self._currentState.accounts[address]
|
||||
self._didUpdate()
|
||||
}
|
||||
|
||||
EthereumStore.prototype.addTransaction = function (txHash) {
|
||||
const self = this
|
||||
self._currentState.transactions[txHash] = {}
|
||||
self._didUpdate()
|
||||
if (!self.currentBlockNumber) return
|
||||
self._updateTransaction(self.currentBlockNumber, txHash, noop)
|
||||
}
|
||||
|
||||
EthereumStore.prototype.removeTransaction = function (address) {
|
||||
const self = this
|
||||
delete self._currentState.transactions[address]
|
||||
self._didUpdate()
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// private
|
||||
//
|
||||
|
||||
EthereumStore.prototype._didUpdate = function () {
|
||||
const self = this
|
||||
var state = self.getState()
|
||||
self.emit('update', state)
|
||||
}
|
||||
|
||||
EthereumStore.prototype._updateForBlock = function (block) {
|
||||
const self = this
|
||||
var blockNumber = '0x' + block.number.toString('hex')
|
||||
self.currentBlockNumber = blockNumber
|
||||
async.parallel([
|
||||
self._updateAccounts.bind(self),
|
||||
self._updateTransactions.bind(self, blockNumber),
|
||||
], function (err) {
|
||||
if (err) return console.error(err)
|
||||
self.emit('block', self.getState())
|
||||
self._didUpdate()
|
||||
})
|
||||
}
|
||||
|
||||
EthereumStore.prototype._updateAccounts = function (cb) {
|
||||
var accountsState = this._currentState.accounts
|
||||
var addresses = Object.keys(accountsState)
|
||||
async.each(addresses, this._updateAccount.bind(this), cb)
|
||||
}
|
||||
|
||||
EthereumStore.prototype._updateAccount = function (address, cb) {
|
||||
var accountsState = this._currentState.accounts
|
||||
this.getAccount(address, function (err, result) {
|
||||
if (err) return cb(err)
|
||||
result.address = address
|
||||
// only populate if the entry is still present
|
||||
if (accountsState[address]) {
|
||||
accountsState[address] = result
|
||||
}
|
||||
cb(null, result)
|
||||
})
|
||||
}
|
||||
|
||||
EthereumStore.prototype.getAccount = function (address, cb) {
|
||||
const query = this._query
|
||||
async.parallel({
|
||||
balance: query.getBalance.bind(query, address),
|
||||
nonce: query.getTransactionCount.bind(query, address),
|
||||
code: query.getCode.bind(query, address),
|
||||
}, cb)
|
||||
}
|
||||
|
||||
EthereumStore.prototype._updateTransactions = function (block, cb) {
|
||||
const self = this
|
||||
var transactionsState = self._currentState.transactions
|
||||
var txHashes = Object.keys(transactionsState)
|
||||
async.each(txHashes, self._updateTransaction.bind(self, block), cb)
|
||||
}
|
||||
|
||||
EthereumStore.prototype._updateTransaction = function (block, txHash, cb) {
|
||||
const self = this
|
||||
// would use the block here to determine how many confirmations the tx has
|
||||
var transactionsState = self._currentState.transactions
|
||||
self._query.getTransaction(txHash, function (err, result) {
|
||||
if (err) return cb(err)
|
||||
// only populate if the entry is still present
|
||||
if (transactionsState[txHash]) {
|
||||
transactionsState[txHash] = result
|
||||
self._didUpdate()
|
||||
}
|
||||
cb(null, result)
|
||||
})
|
||||
}
|
||||
|
||||
function noop() {}
|
80
app/scripts/lib/idStore-migrator.js
Normal file
80
app/scripts/lib/idStore-migrator.js
Normal file
@ -0,0 +1,80 @@
|
||||
const IdentityStore = require('./idStore')
|
||||
const HdKeyring = require('../keyrings/hd')
|
||||
const sigUtil = require('./sig-util')
|
||||
const normalize = sigUtil.normalize
|
||||
const denodeify = require('denodeify')
|
||||
|
||||
module.exports = class IdentityStoreMigrator {
|
||||
|
||||
constructor ({ configManager }) {
|
||||
this.configManager = configManager
|
||||
const hasOldVault = this.hasOldVault()
|
||||
if (!hasOldVault) {
|
||||
this.idStore = new IdentityStore({ configManager })
|
||||
}
|
||||
}
|
||||
|
||||
migratedVaultForPassword (password) {
|
||||
const hasOldVault = this.hasOldVault()
|
||||
const configManager = this.configManager
|
||||
|
||||
if (!this.idStore) {
|
||||
this.idStore = new IdentityStore({ configManager })
|
||||
}
|
||||
|
||||
if (!hasOldVault) {
|
||||
return Promise.resolve(null)
|
||||
}
|
||||
|
||||
const idStore = this.idStore
|
||||
const submitPassword = denodeify(idStore.submitPassword.bind(idStore))
|
||||
|
||||
return submitPassword(password)
|
||||
.then(() => {
|
||||
const serialized = this.serializeVault()
|
||||
return this.checkForLostAccounts(serialized)
|
||||
})
|
||||
}
|
||||
|
||||
serializeVault () {
|
||||
const mnemonic = this.idStore._idmgmt.getSeed()
|
||||
const numberOfAccounts = this.idStore._getAddresses().length
|
||||
|
||||
return {
|
||||
type: 'HD Key Tree',
|
||||
data: { mnemonic, numberOfAccounts },
|
||||
}
|
||||
}
|
||||
|
||||
checkForLostAccounts (serialized) {
|
||||
const hd = new HdKeyring()
|
||||
return hd.deserialize(serialized.data)
|
||||
.then((hexAccounts) => {
|
||||
const newAccounts = hexAccounts.map(normalize)
|
||||
const oldAccounts = this.idStore._getAddresses().map(normalize)
|
||||
const lostAccounts = oldAccounts.reduce((result, account) => {
|
||||
if (newAccounts.includes(account)) {
|
||||
return result
|
||||
} else {
|
||||
result.push(account)
|
||||
return result
|
||||
}
|
||||
}, [])
|
||||
|
||||
return {
|
||||
serialized,
|
||||
lostAccounts: lostAccounts.map((address) => {
|
||||
return {
|
||||
address,
|
||||
privateKey: this.idStore.exportAccount(address),
|
||||
}
|
||||
}),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
hasOldVault () {
|
||||
const wallet = this.configManager.getWallet()
|
||||
return wallet
|
||||
}
|
||||
}
|
@ -1,19 +1,14 @@
|
||||
const EventEmitter = require('events').EventEmitter
|
||||
const inherits = require('util').inherits
|
||||
const async = require('async')
|
||||
const ethUtil = require('ethereumjs-util')
|
||||
const BN = ethUtil.BN
|
||||
const EthQuery = require('eth-query')
|
||||
const KeyStore = require('eth-lightwallet').keystore
|
||||
const clone = require('clone')
|
||||
const extend = require('xtend')
|
||||
const createId = require('./random-id')
|
||||
const ethBinToOps = require('eth-bin-to-ops')
|
||||
const autoFaucet = require('./auto-faucet')
|
||||
const messageManager = require('./message-manager')
|
||||
const DEFAULT_RPC = 'https://testrpc.metamask.io/'
|
||||
const IdManagement = require('./id-management')
|
||||
|
||||
|
||||
module.exports = IdentityStore
|
||||
|
||||
inherits(IdentityStore, EventEmitter)
|
||||
@ -34,17 +29,14 @@ function IdentityStore (opts = {}) {
|
||||
selectedAddress: null,
|
||||
identities: {},
|
||||
}
|
||||
|
||||
// not part of serilized metamask state - only kept in memory
|
||||
this._unconfTxCbs = {}
|
||||
this._unconfMsgCbs = {}
|
||||
}
|
||||
|
||||
//
|
||||
// public
|
||||
//
|
||||
|
||||
IdentityStore.prototype.createNewVault = function (password, entropy, cb) {
|
||||
IdentityStore.prototype.createNewVault = function (password, cb) {
|
||||
delete this._keyStore
|
||||
var serializedKeystore = this.configManager.getWallet()
|
||||
|
||||
@ -53,7 +45,7 @@ IdentityStore.prototype.createNewVault = function (password, entropy, cb) {
|
||||
}
|
||||
|
||||
this.purgeCache()
|
||||
this._createVault(password, null, entropy, (err) => {
|
||||
this._createVault(password, null, (err) => {
|
||||
if (err) return cb(err)
|
||||
|
||||
this._autoFaucet()
|
||||
@ -77,7 +69,7 @@ IdentityStore.prototype.recoverSeed = function (cb) {
|
||||
IdentityStore.prototype.recoverFromSeed = function (password, seed, cb) {
|
||||
this.purgeCache()
|
||||
|
||||
this._createVault(password, seed, null, (err) => {
|
||||
this._createVault(password, seed, (err) => {
|
||||
if (err) return cb(err)
|
||||
|
||||
this._loadIdentities()
|
||||
@ -102,19 +94,13 @@ IdentityStore.prototype.getState = function () {
|
||||
isInitialized: !!configManager.getWallet() && !seedWords,
|
||||
isUnlocked: this._isUnlocked(),
|
||||
seedWords: seedWords,
|
||||
isConfirmed: configManager.getConfirmed(),
|
||||
isEthConfirmed: configManager.getShouldntShowWarning(),
|
||||
unconfTxs: configManager.unconfirmedTxs(),
|
||||
transactions: configManager.getTxList(),
|
||||
unconfMsgs: messageManager.unconfirmedMsgs(),
|
||||
messages: messageManager.getMsgList(),
|
||||
isDisclaimerConfirmed: configManager.getConfirmedDisclaimer(),
|
||||
selectedAddress: configManager.getSelectedAccount(),
|
||||
shapeShiftTxList: configManager.getShapeShiftTxList(),
|
||||
currentFiat: configManager.getCurrentFiat(),
|
||||
conversionRate: configManager.getConversionRate(),
|
||||
conversionDate: configManager.getConversionDate(),
|
||||
gasMultiplier: configManager.getGasMultiplier(),
|
||||
|
||||
}))
|
||||
}
|
||||
|
||||
@ -204,248 +190,10 @@ IdentityStore.prototype.submitPassword = function (password, cb) {
|
||||
|
||||
IdentityStore.prototype.exportAccount = function (address, cb) {
|
||||
var privateKey = this._idmgmt.exportPrivateKey(address)
|
||||
cb(null, privateKey)
|
||||
if (cb) cb(null, privateKey)
|
||||
return privateKey
|
||||
}
|
||||
|
||||
//
|
||||
// Transactions
|
||||
//
|
||||
|
||||
// comes from dapp via zero-client hooked-wallet provider
|
||||
IdentityStore.prototype.addUnconfirmedTransaction = function (txParams, onTxDoneCb, cb) {
|
||||
const configManager = this.configManager
|
||||
|
||||
var self = this
|
||||
// create txData obj with parameters and meta data
|
||||
var time = (new Date()).getTime()
|
||||
var txId = createId()
|
||||
txParams.metamaskId = txId
|
||||
txParams.metamaskNetworkId = self._currentState.network
|
||||
var txData = {
|
||||
id: txId,
|
||||
txParams: txParams,
|
||||
time: time,
|
||||
status: 'unconfirmed',
|
||||
gasMultiplier: configManager.getGasMultiplier() || 1,
|
||||
}
|
||||
|
||||
console.log('addUnconfirmedTransaction:', txData)
|
||||
|
||||
// keep the onTxDoneCb around for after approval/denial (requires user interaction)
|
||||
// This onTxDoneCb fires completion to the Dapp's write operation.
|
||||
self._unconfTxCbs[txId] = onTxDoneCb
|
||||
|
||||
var provider = self._ethStore._query.currentProvider
|
||||
var query = new EthQuery(provider)
|
||||
|
||||
// calculate metadata for tx
|
||||
async.parallel([
|
||||
analyzeForDelegateCall,
|
||||
estimateGas,
|
||||
], didComplete)
|
||||
|
||||
// perform static analyis on the target contract code
|
||||
function analyzeForDelegateCall(cb){
|
||||
if (txParams.to) {
|
||||
query.getCode(txParams.to, (err, result) => {
|
||||
if (err) return cb(err.message || err)
|
||||
var containsDelegateCall = self.checkForDelegateCall(result)
|
||||
txData.containsDelegateCall = containsDelegateCall
|
||||
cb()
|
||||
})
|
||||
} else {
|
||||
cb()
|
||||
}
|
||||
}
|
||||
|
||||
function estimateGas(cb){
|
||||
var estimationParams = extend(txParams)
|
||||
query.getBlockByNumber('latest', true, function(err, block){
|
||||
if (err) return cb(err)
|
||||
// check if gasLimit is already specified
|
||||
const gasLimitSpecified = Boolean(txParams.gas)
|
||||
// if not, fallback to block gasLimit
|
||||
if (!gasLimitSpecified) {
|
||||
estimationParams.gas = block.gasLimit
|
||||
}
|
||||
// run tx, see if it will OOG
|
||||
query.estimateGas(estimationParams, function(err, estimatedGasHex){
|
||||
if (err) return cb(err.message || err)
|
||||
// all gas used - must be an error
|
||||
if (estimatedGasHex === estimationParams.gas) {
|
||||
txData.simulationFails = true
|
||||
txData.estimatedGas = estimatedGasHex
|
||||
txData.txParams.gas = estimatedGasHex
|
||||
cb()
|
||||
return
|
||||
}
|
||||
// otherwise, did not use all gas, must be ok
|
||||
|
||||
// if specified gasLimit and no error, we're done
|
||||
if (gasLimitSpecified) {
|
||||
txData.estimatedGas = txParams.gas
|
||||
cb()
|
||||
return
|
||||
}
|
||||
|
||||
// try adding an additional gas buffer to our estimation for safety
|
||||
const estimatedGasBn = new BN(ethUtil.stripHexPrefix(estimatedGasHex), 16)
|
||||
const blockGasLimitBn = new BN(ethUtil.stripHexPrefix(block.gasLimit), 16)
|
||||
const estimationWithBuffer = self.addGasBuffer(estimatedGasBn)
|
||||
// added gas buffer is too high
|
||||
if (estimationWithBuffer.gt(blockGasLimitBn)) {
|
||||
txData.estimatedGas = estimatedGasHex
|
||||
txData.txParams.gas = estimatedGasHex
|
||||
// added gas buffer is safe
|
||||
} else {
|
||||
const gasWithBufferHex = ethUtil.intToHex(estimationWithBuffer)
|
||||
txData.estimatedGas = gasWithBufferHex
|
||||
txData.txParams.gas = gasWithBufferHex
|
||||
}
|
||||
cb()
|
||||
return
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function didComplete (err) {
|
||||
if (err) return cb(err.message || err)
|
||||
configManager.addTx(txData)
|
||||
// signal update
|
||||
self._didUpdate()
|
||||
// signal completion of add tx
|
||||
cb(null, txData)
|
||||
}
|
||||
}
|
||||
|
||||
IdentityStore.prototype.checkForDelegateCall = function (codeHex) {
|
||||
const code = ethUtil.toBuffer(codeHex)
|
||||
if (code !== '0x') {
|
||||
const ops = ethBinToOps(code)
|
||||
const containsDelegateCall = ops.some((op) => op.name === 'DELEGATECALL')
|
||||
return containsDelegateCall
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
IdentityStore.prototype.addGasBuffer = function (gasBn) {
|
||||
// add 20% to specified gas
|
||||
const gasBuffer = gasBn.div(new BN('5', 10))
|
||||
const gasWithBuffer = gasBn.add(gasBuffer)
|
||||
return gasWithBuffer
|
||||
}
|
||||
|
||||
// comes from metamask ui
|
||||
IdentityStore.prototype.approveTransaction = function (txId, cb) {
|
||||
const configManager = this.configManager
|
||||
var approvalCb = this._unconfTxCbs[txId] || noop
|
||||
|
||||
// accept tx
|
||||
cb()
|
||||
approvalCb(null, true)
|
||||
// clean up
|
||||
configManager.confirmTx(txId)
|
||||
delete this._unconfTxCbs[txId]
|
||||
this._didUpdate()
|
||||
}
|
||||
|
||||
// comes from metamask ui
|
||||
IdentityStore.prototype.cancelTransaction = function (txId) {
|
||||
const configManager = this.configManager
|
||||
var approvalCb = this._unconfTxCbs[txId] || noop
|
||||
|
||||
// reject tx
|
||||
approvalCb(null, false)
|
||||
// clean up
|
||||
configManager.rejectTx(txId)
|
||||
delete this._unconfTxCbs[txId]
|
||||
this._didUpdate()
|
||||
}
|
||||
|
||||
// performs the actual signing, no autofill of params
|
||||
IdentityStore.prototype.signTransaction = function (txParams, cb) {
|
||||
try {
|
||||
console.log('signing tx...', txParams)
|
||||
var rawTx = this._idmgmt.signTx(txParams)
|
||||
cb(null, rawTx)
|
||||
} catch (err) {
|
||||
cb(err)
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Messages
|
||||
//
|
||||
|
||||
// comes from dapp via zero-client hooked-wallet provider
|
||||
IdentityStore.prototype.addUnconfirmedMessage = function (msgParams, cb) {
|
||||
// create txData obj with parameters and meta data
|
||||
var time = (new Date()).getTime()
|
||||
var msgId = createId()
|
||||
var msgData = {
|
||||
id: msgId,
|
||||
msgParams: msgParams,
|
||||
time: time,
|
||||
status: 'unconfirmed',
|
||||
}
|
||||
messageManager.addMsg(msgData)
|
||||
console.log('addUnconfirmedMessage:', msgData)
|
||||
|
||||
// keep the cb around for after approval (requires user interaction)
|
||||
// This cb fires completion to the Dapp's write operation.
|
||||
this._unconfMsgCbs[msgId] = cb
|
||||
|
||||
// signal update
|
||||
this._didUpdate()
|
||||
|
||||
return msgId
|
||||
}
|
||||
|
||||
// comes from metamask ui
|
||||
IdentityStore.prototype.approveMessage = function (msgId, cb) {
|
||||
var approvalCb = this._unconfMsgCbs[msgId] || noop
|
||||
|
||||
// accept msg
|
||||
cb()
|
||||
approvalCb(null, true)
|
||||
// clean up
|
||||
messageManager.confirmMsg(msgId)
|
||||
delete this._unconfMsgCbs[msgId]
|
||||
this._didUpdate()
|
||||
}
|
||||
|
||||
// comes from metamask ui
|
||||
IdentityStore.prototype.cancelMessage = function (msgId) {
|
||||
var approvalCb = this._unconfMsgCbs[msgId] || noop
|
||||
|
||||
// reject tx
|
||||
approvalCb(null, false)
|
||||
// clean up
|
||||
messageManager.rejectMsg(msgId)
|
||||
delete this._unconfTxCbs[msgId]
|
||||
this._didUpdate()
|
||||
}
|
||||
|
||||
// performs the actual signing, no autofill of params
|
||||
IdentityStore.prototype.signMessage = function (msgParams, cb) {
|
||||
try {
|
||||
console.log('signing msg...', msgParams.data)
|
||||
var rawMsg = this._idmgmt.signMsg(msgParams.from, msgParams.data)
|
||||
if ('metamaskId' in msgParams) {
|
||||
var id = msgParams.metamaskId
|
||||
delete msgParams.metamaskId
|
||||
|
||||
this.approveMessage(id, cb)
|
||||
} else {
|
||||
cb(null, rawMsg)
|
||||
}
|
||||
} catch (err) {
|
||||
cb(err)
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// private
|
||||
//
|
||||
|
||||
@ -466,7 +214,9 @@ IdentityStore.prototype._loadIdentities = function () {
|
||||
var addresses = this._getAddresses()
|
||||
addresses.forEach((address, i) => {
|
||||
// // add to ethStore
|
||||
this._ethStore.addAccount(ethUtil.addHexPrefix(address))
|
||||
if (this._ethStore) {
|
||||
this._ethStore.addAccount(ethUtil.addHexPrefix(address))
|
||||
}
|
||||
// add to identities
|
||||
const defaultLabel = 'Account ' + (i + 1)
|
||||
const nickname = configManager.nicknameForWallet(address)
|
||||
@ -523,7 +273,7 @@ IdentityStore.prototype.tryPassword = function (password, cb) {
|
||||
})
|
||||
}
|
||||
|
||||
IdentityStore.prototype._createVault = function (password, seedPhrase, entropy, cb) {
|
||||
IdentityStore.prototype._createVault = function (password, seedPhrase, cb) {
|
||||
const opts = {
|
||||
password,
|
||||
hdPathString: this.hdPathString,
|
||||
@ -598,4 +348,3 @@ IdentityStore.prototype._autoFaucet = function () {
|
||||
|
||||
// util
|
||||
|
||||
function noop () {}
|
||||
|
@ -40,7 +40,7 @@ function MetamaskInpageProvider (connectionStream) {
|
||||
|
||||
self.idMap = {}
|
||||
// handle sendAsync requests via asyncProvider
|
||||
self.sendAsync = function(payload, cb){
|
||||
self.sendAsync = function (payload, cb) {
|
||||
// rewrite request ids
|
||||
var request = eachJsonMessage(payload, (message) => {
|
||||
var newId = createRandomId()
|
||||
@ -49,7 +49,7 @@ function MetamaskInpageProvider (connectionStream) {
|
||||
return message
|
||||
})
|
||||
// forward to asyncProvider
|
||||
asyncProvider.sendAsync(request, function(err, res){
|
||||
asyncProvider.sendAsync(request, function (err, res) {
|
||||
if (err) return cb(err)
|
||||
// transform messages to original ids
|
||||
eachJsonMessage(res, (message) => {
|
||||
@ -66,20 +66,20 @@ function MetamaskInpageProvider (connectionStream) {
|
||||
MetamaskInpageProvider.prototype.send = function (payload) {
|
||||
const self = this
|
||||
|
||||
let selectedAddress
|
||||
let selectedAccount
|
||||
let result = null
|
||||
switch (payload.method) {
|
||||
|
||||
case 'eth_accounts':
|
||||
// read from localStorage
|
||||
selectedAddress = self.publicConfigStore.get('selectedAddress')
|
||||
result = selectedAddress ? [selectedAddress] : []
|
||||
selectedAccount = self.publicConfigStore.get('selectedAccount')
|
||||
result = selectedAccount ? [selectedAccount] : []
|
||||
break
|
||||
|
||||
case 'eth_coinbase':
|
||||
// read from localStorage
|
||||
selectedAddress = self.publicConfigStore.get('selectedAddress')
|
||||
result = selectedAddress || '0x0000000000000000000000000000000000000000'
|
||||
selectedAccount = self.publicConfigStore.get('selectedAccount')
|
||||
result = selectedAccount || '0x0000000000000000000000000000000000000000'
|
||||
break
|
||||
|
||||
case 'eth_uninstallFilter':
|
||||
@ -111,6 +111,8 @@ MetamaskInpageProvider.prototype.isConnected = function () {
|
||||
return true
|
||||
}
|
||||
|
||||
MetamaskInpageProvider.prototype.isMetaMask = true
|
||||
|
||||
// util
|
||||
|
||||
function remoteStoreWithLocalStorageCache (storageKey) {
|
||||
@ -125,7 +127,7 @@ function remoteStoreWithLocalStorageCache (storageKey) {
|
||||
return store
|
||||
}
|
||||
|
||||
function eachJsonMessage(payload, transformFn){
|
||||
function eachJsonMessage (payload, transformFn) {
|
||||
if (Array.isArray(payload)) {
|
||||
return payload.map(transformFn)
|
||||
} else {
|
||||
|
@ -1,4 +1,4 @@
|
||||
module.exports = function isPopupOrNotification() {
|
||||
module.exports = function isPopupOrNotification () {
|
||||
const url = window.location.href
|
||||
if (url.match(/popup.html$/)) {
|
||||
return 'popup'
|
||||
|
24
app/scripts/lib/nodeify.js
Normal file
24
app/scripts/lib/nodeify.js
Normal file
@ -0,0 +1,24 @@
|
||||
module.exports = function (promiseFn) {
|
||||
return function () {
|
||||
var args = []
|
||||
for (var i = 0; i < arguments.length - 1; i++) {
|
||||
args.push(arguments[i])
|
||||
}
|
||||
var cb = arguments[arguments.length - 1]
|
||||
|
||||
const nodeified = promiseFn.apply(this, args)
|
||||
|
||||
if (!nodeified) {
|
||||
const methodName = String(promiseFn).split('(')[0]
|
||||
throw new Error(`The ${methodName} did not return a Promise, but was nodeified.`)
|
||||
}
|
||||
nodeified.then(function (result) {
|
||||
cb(null, result)
|
||||
})
|
||||
.catch(function (reason) {
|
||||
cb(reason)
|
||||
})
|
||||
|
||||
return nodeified
|
||||
}
|
||||
}
|
@ -15,12 +15,9 @@ function show () {
|
||||
if (err) throw err
|
||||
|
||||
if (popup) {
|
||||
|
||||
// bring focus to existing popup
|
||||
extension.windows.update(popup.id, { focused: true })
|
||||
|
||||
} else {
|
||||
|
||||
// create new popup
|
||||
extension.windows.create({
|
||||
url: 'notification.html',
|
||||
@ -29,12 +26,11 @@ function show () {
|
||||
width,
|
||||
height,
|
||||
})
|
||||
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function getWindows(cb) {
|
||||
function getWindows (cb) {
|
||||
// Ignore in test environment
|
||||
if (!extension.windows) {
|
||||
return cb()
|
||||
@ -45,14 +41,14 @@ function getWindows(cb) {
|
||||
})
|
||||
}
|
||||
|
||||
function getPopup(cb) {
|
||||
function getPopup (cb) {
|
||||
getWindows((err, windows) => {
|
||||
if (err) throw err
|
||||
cb(null, getPopupIn(windows))
|
||||
})
|
||||
}
|
||||
|
||||
function getPopupIn(windows) {
|
||||
function getPopupIn (windows) {
|
||||
return windows ? windows.find((win) => {
|
||||
return (win && win.type === 'popup' &&
|
||||
win.height === height &&
|
||||
@ -60,7 +56,7 @@ function getPopupIn(windows) {
|
||||
}) : null
|
||||
}
|
||||
|
||||
function closePopup() {
|
||||
function closePopup () {
|
||||
getPopup((err, popup) => {
|
||||
if (err) throw err
|
||||
if (!popup) return
|
||||
|
@ -51,11 +51,11 @@ PortDuplexStream.prototype._write = function (msg, encoding, cb) {
|
||||
// console.log('PortDuplexStream - sent message', msg)
|
||||
this._port.postMessage(msg)
|
||||
}
|
||||
cb()
|
||||
} catch (err) {
|
||||
// console.error(err)
|
||||
cb(new Error('PortDuplexStream - disconnected'))
|
||||
return cb(new Error('PortDuplexStream - disconnected'))
|
||||
}
|
||||
cb()
|
||||
}
|
||||
|
||||
// util
|
||||
|
@ -1,7 +1,7 @@
|
||||
const MAX = 1000000000
|
||||
const MAX = Number.MAX_SAFE_INTEGER
|
||||
|
||||
let idCounter = Math.round( Math.random() * MAX )
|
||||
function createRandomId() {
|
||||
let idCounter = Math.round(Math.random() * MAX)
|
||||
function createRandomId () {
|
||||
idCounter = idCounter % MAX
|
||||
return idCounter++
|
||||
}
|
||||
|
28
app/scripts/lib/sig-util.js
Normal file
28
app/scripts/lib/sig-util.js
Normal file
@ -0,0 +1,28 @@
|
||||
const ethUtil = require('ethereumjs-util')
|
||||
|
||||
module.exports = {
|
||||
|
||||
concatSig: function (v, r, s) {
|
||||
const rSig = ethUtil.fromSigned(r)
|
||||
const sSig = ethUtil.fromSigned(s)
|
||||
const vSig = ethUtil.bufferToInt(v)
|
||||
const rStr = padWithZeroes(ethUtil.toUnsigned(rSig).toString('hex'), 64)
|
||||
const sStr = padWithZeroes(ethUtil.toUnsigned(sSig).toString('hex'), 64)
|
||||
const vStr = ethUtil.stripHexPrefix(ethUtil.intToHex(vSig))
|
||||
return ethUtil.addHexPrefix(rStr.concat(sStr, vStr)).toString('hex')
|
||||
},
|
||||
|
||||
normalize: function (address) {
|
||||
if (!address) return
|
||||
return ethUtil.addHexPrefix(address.toLowerCase())
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
function padWithZeroes (number, length) {
|
||||
var myString = '' + number
|
||||
while (myString.length < length) {
|
||||
myString = '0' + myString
|
||||
}
|
||||
return myString
|
||||
}
|
132
app/scripts/lib/tx-utils.js
Normal file
132
app/scripts/lib/tx-utils.js
Normal file
@ -0,0 +1,132 @@
|
||||
const async = require('async')
|
||||
const EthQuery = require('eth-query')
|
||||
const ethUtil = require('ethereumjs-util')
|
||||
const Transaction = require('ethereumjs-tx')
|
||||
const normalize = require('./sig-util').normalize
|
||||
const BN = ethUtil.BN
|
||||
|
||||
/*
|
||||
tx-utils are utility methods for Transaction manager
|
||||
its passed a provider and that is passed to ethquery
|
||||
and used to do things like calculate gas of a tx.
|
||||
*/
|
||||
|
||||
module.exports = class txProviderUtils {
|
||||
constructor (provider) {
|
||||
this.provider = provider
|
||||
this.query = new EthQuery(provider)
|
||||
}
|
||||
|
||||
analyzeGasUsage (txData, cb) {
|
||||
var self = this
|
||||
this.query.getBlockByNumber('latest', true, (err, block) => {
|
||||
if (err) return cb(err)
|
||||
async.waterfall([
|
||||
self.estimateTxGas.bind(self, txData, block.gasLimit),
|
||||
self.setTxGas.bind(self, txData, block.gasLimit),
|
||||
], cb)
|
||||
})
|
||||
}
|
||||
|
||||
estimateTxGas (txData, blockGasLimitHex, cb) {
|
||||
const txParams = txData.txParams
|
||||
// check if gasLimit is already specified
|
||||
txData.gasLimitSpecified = Boolean(txParams.gas)
|
||||
// if not, fallback to block gasLimit
|
||||
if (!txData.gasLimitSpecified) {
|
||||
txParams.gas = blockGasLimitHex
|
||||
}
|
||||
// run tx, see if it will OOG
|
||||
this.query.estimateGas(txParams, cb)
|
||||
}
|
||||
|
||||
setTxGas (txData, blockGasLimitHex, estimatedGasHex, cb) {
|
||||
txData.estimatedGas = estimatedGasHex
|
||||
const txParams = txData.txParams
|
||||
|
||||
// if gasLimit was specified and doesnt OOG,
|
||||
// use original specified amount
|
||||
if (txData.gasLimitSpecified) {
|
||||
txData.estimatedGas = txParams.gas
|
||||
cb()
|
||||
return
|
||||
}
|
||||
// if gasLimit not originally specified,
|
||||
// try adding an additional gas buffer to our estimation for safety
|
||||
const estimatedGasBn = new BN(ethUtil.stripHexPrefix(txData.estimatedGas), 16)
|
||||
const blockGasLimitBn = new BN(ethUtil.stripHexPrefix(blockGasLimitHex), 16)
|
||||
const estimationWithBuffer = new BN(this.addGasBuffer(estimatedGasBn), 16)
|
||||
// added gas buffer is too high
|
||||
if (estimationWithBuffer.gt(blockGasLimitBn)) {
|
||||
txParams.gas = txData.estimatedGas
|
||||
// added gas buffer is safe
|
||||
} else {
|
||||
const gasWithBufferHex = ethUtil.intToHex(estimationWithBuffer)
|
||||
txParams.gas = gasWithBufferHex
|
||||
}
|
||||
cb()
|
||||
return
|
||||
}
|
||||
|
||||
addGasBuffer (gas) {
|
||||
const gasBuffer = new BN('100000', 10)
|
||||
const bnGas = new BN(ethUtil.stripHexPrefix(gas), 16)
|
||||
const correct = bnGas.add(gasBuffer)
|
||||
return ethUtil.addHexPrefix(correct.toString(16))
|
||||
}
|
||||
|
||||
fillInTxParams (txParams, cb) {
|
||||
let fromAddress = txParams.from
|
||||
let reqs = {}
|
||||
|
||||
if (isUndef(txParams.gas)) reqs.gas = (cb) => this.query.estimateGas(txParams, cb)
|
||||
if (isUndef(txParams.gasPrice)) reqs.gasPrice = (cb) => this.query.gasPrice(cb)
|
||||
if (isUndef(txParams.nonce)) reqs.nonce = (cb) => this.query.getTransactionCount(fromAddress, 'pending', cb)
|
||||
|
||||
async.parallel(reqs, function(err, result) {
|
||||
if (err) return cb(err)
|
||||
// write results to txParams obj
|
||||
Object.assign(txParams, result)
|
||||
cb()
|
||||
})
|
||||
}
|
||||
|
||||
// builds ethTx from txParams object
|
||||
buildEthTxFromParams (txParams, gasMultiplier = 1) {
|
||||
// apply gas multiplyer
|
||||
let gasPrice = new BN(ethUtil.stripHexPrefix(txParams.gasPrice), 16)
|
||||
// multiply and divide by 100 so as to add percision to integer mul
|
||||
gasPrice = gasPrice.mul(new BN(gasMultiplier * 100, 10)).div(new BN(100, 10))
|
||||
txParams.gasPrice = ethUtil.intToHex(gasPrice.toNumber())
|
||||
// normalize values
|
||||
txParams.to = normalize(txParams.to)
|
||||
txParams.from = normalize(txParams.from)
|
||||
txParams.value = normalize(txParams.value)
|
||||
txParams.data = normalize(txParams.data)
|
||||
txParams.gasLimit = normalize(txParams.gasLimit || txParams.gas)
|
||||
txParams.nonce = normalize(txParams.nonce)
|
||||
// build ethTx
|
||||
const ethTx = new Transaction(txParams)
|
||||
return ethTx
|
||||
}
|
||||
|
||||
publishTransaction (rawTx, cb) {
|
||||
this.query.sendRawTransaction(rawTx, cb)
|
||||
}
|
||||
|
||||
validateTxParams (txParams, cb) {
|
||||
if (('value' in txParams) && txParams.value.indexOf('-') === 0) {
|
||||
cb(new Error(`Invalid transaction value of ${txParams.value} not a positive number.`))
|
||||
} else {
|
||||
cb()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
// util
|
||||
|
||||
function isUndef(value) {
|
||||
return value === undefined
|
||||
}
|
@ -1,22 +1,30 @@
|
||||
const EventEmitter = require('events')
|
||||
const extend = require('xtend')
|
||||
const EthStore = require('eth-store')
|
||||
const EthStore = require('./lib/eth-store')
|
||||
const MetaMaskProvider = require('web3-provider-engine/zero.js')
|
||||
const IdentityStore = require('./lib/idStore')
|
||||
const KeyringController = require('./keyring-controller')
|
||||
const NoticeController = require('./notice-controller')
|
||||
const messageManager = require('./lib/message-manager')
|
||||
const TxManager = require('./transaction-manager')
|
||||
const HostStore = require('./lib/remote-store.js').HostStore
|
||||
const Web3 = require('web3')
|
||||
const ConfigManager = require('./lib/config-manager')
|
||||
const extension = require('./lib/extension')
|
||||
const autoFaucet = require('./lib/auto-faucet')
|
||||
const nodeify = require('./lib/nodeify')
|
||||
const IdStoreMigrator = require('./lib/idStore-migrator')
|
||||
const version = require('../manifest.json').version
|
||||
|
||||
module.exports = class MetamaskController {
|
||||
module.exports = class MetamaskController extends EventEmitter {
|
||||
|
||||
constructor (opts) {
|
||||
super()
|
||||
this.state = { network: 'loading' }
|
||||
this.opts = opts
|
||||
this.listeners = []
|
||||
this.configManager = new ConfigManager(opts)
|
||||
this.idStore = new IdentityStore({
|
||||
this.keyringController = new KeyringController({
|
||||
configManager: this.configManager,
|
||||
getNetwork: this.getStateNetwork.bind(this),
|
||||
})
|
||||
// notices
|
||||
this.noticeController = new NoticeController({
|
||||
@ -27,8 +35,20 @@ module.exports = class MetamaskController {
|
||||
// this.noticeController.startPolling()
|
||||
this.provider = this.initializeProvider(opts)
|
||||
this.ethStore = new EthStore(this.provider)
|
||||
this.idStore.setStore(this.ethStore)
|
||||
this.keyringController.setStore(this.ethStore)
|
||||
this.getNetwork()
|
||||
this.messageManager = messageManager
|
||||
this.txManager = new TxManager({
|
||||
txList: this.configManager.getTxList(),
|
||||
txHistoryLimit: 40,
|
||||
setTxList: this.configManager.setTxList.bind(this.configManager),
|
||||
getSelectedAccount: this.configManager.getSelectedAccount.bind(this.configManager),
|
||||
getGasMultiplier: this.configManager.getGasMultiplier.bind(this.configManager),
|
||||
getNetwork: this.getStateNetwork.bind(this),
|
||||
signTransaction: this.keyringController.signTransaction.bind(this.keyringController),
|
||||
provider: this.provider,
|
||||
blockTracker: this.provider,
|
||||
})
|
||||
this.publicConfigStore = this.initPublicConfigStore()
|
||||
|
||||
var currentFiat = this.configManager.getCurrentFiat() || 'USD'
|
||||
@ -38,50 +58,75 @@ module.exports = class MetamaskController {
|
||||
this.checkTOSChange()
|
||||
|
||||
this.scheduleConversionInterval()
|
||||
|
||||
// TEMPORARY UNTIL FULL DEPRECATION:
|
||||
this.idStoreMigrator = new IdStoreMigrator({
|
||||
configManager: this.configManager,
|
||||
})
|
||||
|
||||
this.ethStore.on('update', this.sendUpdate.bind(this))
|
||||
this.keyringController.on('update', this.sendUpdate.bind(this))
|
||||
this.txManager.on('update', this.sendUpdate.bind(this))
|
||||
}
|
||||
|
||||
getState () {
|
||||
return extend(
|
||||
this.ethStore.getState(),
|
||||
this.idStore.getState(),
|
||||
this.configManager.getConfig(),
|
||||
this.noticeController.getState()
|
||||
)
|
||||
return this.keyringController.getState()
|
||||
.then((keyringControllerState) => {
|
||||
return extend(
|
||||
this.state,
|
||||
this.ethStore.getState(),
|
||||
this.configManager.getConfig(),
|
||||
this.txManager.getState(),
|
||||
keyringControllerState,
|
||||
this.noticeController.getState(), {
|
||||
lostAccounts: this.configManager.getLostAccounts(),
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
getApi () {
|
||||
const idStore = this.idStore
|
||||
const keyringController = this.keyringController
|
||||
const txManager = this.txManager
|
||||
const noticeController = this.noticeController
|
||||
|
||||
return {
|
||||
getState: (cb) => { cb(null, this.getState()) },
|
||||
getState: nodeify(this.getState.bind(this)),
|
||||
setRpcTarget: this.setRpcTarget.bind(this),
|
||||
setProviderType: this.setProviderType.bind(this),
|
||||
useEtherscanProvider: this.useEtherscanProvider.bind(this),
|
||||
agreeToDisclaimer: this.agreeToDisclaimer.bind(this),
|
||||
resetDisclaimer: this.resetDisclaimer.bind(this),
|
||||
setCurrentFiat: this.setCurrentFiat.bind(this),
|
||||
agreeToEthWarning: this.agreeToEthWarning.bind(this),
|
||||
setTOSHash: this.setTOSHash.bind(this),
|
||||
checkTOSChange: this.checkTOSChange.bind(this),
|
||||
setGasMultiplier: this.setGasMultiplier.bind(this),
|
||||
markAccountsFound: this.markAccountsFound.bind(this),
|
||||
|
||||
// forward directly to keyringController
|
||||
createNewVaultAndKeychain: nodeify(keyringController.createNewVaultAndKeychain).bind(keyringController),
|
||||
createNewVaultAndRestore: nodeify(keyringController.createNewVaultAndRestore).bind(keyringController),
|
||||
placeSeedWords: nodeify(keyringController.placeSeedWords).bind(keyringController),
|
||||
clearSeedWordCache: nodeify(keyringController.clearSeedWordCache).bind(keyringController),
|
||||
setLocked: nodeify(keyringController.setLocked).bind(keyringController),
|
||||
submitPassword: (password, cb) => {
|
||||
this.migrateOldVaultIfAny(password)
|
||||
.then(keyringController.submitPassword.bind(keyringController, password))
|
||||
.then((newState) => { cb(null, newState) })
|
||||
.catch((reason) => { cb(reason) })
|
||||
},
|
||||
addNewKeyring: nodeify(keyringController.addNewKeyring).bind(keyringController),
|
||||
addNewAccount: nodeify(keyringController.addNewAccount).bind(keyringController),
|
||||
setSelectedAccount: nodeify(keyringController.setSelectedAccount).bind(keyringController),
|
||||
saveAccountLabel: nodeify(keyringController.saveAccountLabel).bind(keyringController),
|
||||
exportAccount: nodeify(keyringController.exportAccount).bind(keyringController),
|
||||
|
||||
// signing methods
|
||||
approveTransaction: txManager.approveTransaction.bind(txManager),
|
||||
cancelTransaction: txManager.cancelTransaction.bind(txManager),
|
||||
signMessage: keyringController.signMessage.bind(keyringController),
|
||||
cancelMessage: keyringController.cancelMessage.bind(keyringController),
|
||||
|
||||
// forward directly to idStore
|
||||
createNewVault: idStore.createNewVault.bind(idStore),
|
||||
recoverFromSeed: idStore.recoverFromSeed.bind(idStore),
|
||||
submitPassword: idStore.submitPassword.bind(idStore),
|
||||
setSelectedAddress: idStore.setSelectedAddress.bind(idStore),
|
||||
approveTransaction: idStore.approveTransaction.bind(idStore),
|
||||
cancelTransaction: idStore.cancelTransaction.bind(idStore),
|
||||
signMessage: idStore.signMessage.bind(idStore),
|
||||
cancelMessage: idStore.cancelMessage.bind(idStore),
|
||||
setLocked: idStore.setLocked.bind(idStore),
|
||||
clearSeedWordCache: idStore.clearSeedWordCache.bind(idStore),
|
||||
exportAccount: idStore.exportAccount.bind(idStore),
|
||||
revealAccount: idStore.revealAccount.bind(idStore),
|
||||
saveAccountLabel: idStore.saveAccountLabel.bind(idStore),
|
||||
tryPassword: idStore.tryPassword.bind(idStore),
|
||||
recoverSeed: idStore.recoverSeed.bind(idStore),
|
||||
// coinbase
|
||||
buyEth: this.buyEth.bind(this),
|
||||
// shapeshift
|
||||
@ -97,23 +142,6 @@ module.exports = class MetamaskController {
|
||||
}
|
||||
|
||||
onRpcRequest (stream, originDomain, request) {
|
||||
|
||||
/* Commented out for Parity compliance
|
||||
* Parity does not permit additional keys, like `origin`,
|
||||
* and Infura is not currently filtering this key out.
|
||||
var payloads = Array.isArray(request) ? request : [request]
|
||||
payloads.forEach(function (payload) {
|
||||
// Append origin to rpc payload
|
||||
payload.origin = originDomain
|
||||
// Append origin to signature request
|
||||
if (payload.method === 'eth_sendTransaction') {
|
||||
payload.params[0].origin = originDomain
|
||||
} else if (payload.method === 'eth_sign') {
|
||||
payload.params.push({ origin: originDomain })
|
||||
}
|
||||
})
|
||||
*/
|
||||
|
||||
// handle rpc request
|
||||
this.provider.sendAsync(request, function onPayloadHandled (err, response) {
|
||||
logger(err, request, response)
|
||||
@ -140,76 +168,65 @@ module.exports = class MetamaskController {
|
||||
}
|
||||
|
||||
sendUpdate () {
|
||||
this.listeners.forEach((remote) => {
|
||||
remote.sendUpdate(this.getState())
|
||||
this.getState()
|
||||
.then((state) => {
|
||||
this.emit('update', state)
|
||||
})
|
||||
}
|
||||
|
||||
initializeProvider (opts) {
|
||||
const idStore = this.idStore
|
||||
const keyringController = this.keyringController
|
||||
|
||||
var providerOpts = {
|
||||
static: {
|
||||
eth_syncing: false,
|
||||
web3_clientVersion: `MetaMask/v${version}`,
|
||||
},
|
||||
rpcUrl: this.configManager.getCurrentRpcAddress(),
|
||||
// account mgmt
|
||||
getAccounts: (cb) => {
|
||||
var selectedAddress = idStore.getSelectedAddress()
|
||||
var result = selectedAddress ? [selectedAddress] : []
|
||||
var selectedAccount = this.configManager.getSelectedAccount()
|
||||
var result = selectedAccount ? [selectedAccount] : []
|
||||
cb(null, result)
|
||||
},
|
||||
// tx signing
|
||||
approveTransaction: this.newUnsignedTransaction.bind(this),
|
||||
signTransaction: (...args) => {
|
||||
idStore.signTransaction(...args)
|
||||
this.sendUpdate()
|
||||
},
|
||||
|
||||
processTransaction: (txParams, cb) => this.newUnapprovedTransaction(txParams, cb),
|
||||
// msg signing
|
||||
approveMessage: this.newUnsignedMessage.bind(this),
|
||||
signMessage: (...args) => {
|
||||
idStore.signMessage(...args)
|
||||
keyringController.signMessage(...args)
|
||||
this.sendUpdate()
|
||||
},
|
||||
}
|
||||
|
||||
var provider = MetaMaskProvider(providerOpts)
|
||||
var web3 = new Web3(provider)
|
||||
idStore.web3 = web3
|
||||
idStore.getNetwork()
|
||||
|
||||
this.web3 = web3
|
||||
keyringController.web3 = web3
|
||||
provider.on('block', this.processBlock.bind(this))
|
||||
provider.on('error', idStore.getNetwork.bind(idStore))
|
||||
provider.on('error', this.getNetwork.bind(this))
|
||||
|
||||
return provider
|
||||
}
|
||||
|
||||
initPublicConfigStore () {
|
||||
// get init state
|
||||
var initPublicState = extend(
|
||||
idStoreToPublic(this.idStore.getState()),
|
||||
configToPublic(this.configManager.getConfig())
|
||||
)
|
||||
|
||||
var initPublicState = configToPublic(this.configManager.getConfig())
|
||||
var publicConfigStore = new HostStore(initPublicState)
|
||||
|
||||
// subscribe to changes
|
||||
this.configManager.subscribe(function (state) {
|
||||
storeSetFromObj(publicConfigStore, configToPublic(state))
|
||||
})
|
||||
this.idStore.on('update', function (state) {
|
||||
storeSetFromObj(publicConfigStore, idStoreToPublic(state))
|
||||
|
||||
this.keyringController.on('newAccount', (account) => {
|
||||
autoFaucet(account)
|
||||
})
|
||||
|
||||
// idStore substate
|
||||
function idStoreToPublic (state) {
|
||||
return {
|
||||
selectedAddress: state.selectedAddress,
|
||||
}
|
||||
}
|
||||
// config substate
|
||||
function configToPublic (state) {
|
||||
return {
|
||||
provider: state.provider,
|
||||
selectedAddress: state.selectedAccount,
|
||||
selectedAccount: state.selectedAccount,
|
||||
}
|
||||
}
|
||||
// dump obj into store
|
||||
@ -222,28 +239,30 @@ module.exports = class MetamaskController {
|
||||
return publicConfigStore
|
||||
}
|
||||
|
||||
newUnsignedTransaction (txParams, onTxDoneCb) {
|
||||
const idStore = this.idStore
|
||||
let err = this.enforceTxValidations(txParams)
|
||||
if (err) return onTxDoneCb(err)
|
||||
idStore.addUnconfirmedTransaction(txParams, onTxDoneCb, (err, txData) => {
|
||||
if (err) return onTxDoneCb(err)
|
||||
this.sendUpdate()
|
||||
this.opts.showUnconfirmedTx(txParams, txData, onTxDoneCb)
|
||||
newUnapprovedTransaction (txParams, cb) {
|
||||
const self = this
|
||||
self.txManager.addUnapprovedTransaction(txParams, (err, txMeta) => {
|
||||
if (err) return cb(err)
|
||||
self.sendUpdate()
|
||||
self.opts.showUnapprovedTx(txMeta)
|
||||
// listen for tx completion (success, fail)
|
||||
self.txManager.once(`${txMeta.id}:finished`, (status) => {
|
||||
switch (status) {
|
||||
case 'submitted':
|
||||
return cb(null, txMeta.hash)
|
||||
case 'rejected':
|
||||
return cb(new Error('MetaMask Tx Signature: User denied transaction signature.'))
|
||||
default:
|
||||
return cb(new Error(`MetaMask Tx Signature: Unknown problem: ${JSON.stringify(txMeta.txParams)}`))
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
enforceTxValidations (txParams) {
|
||||
if (('value' in txParams) && txParams.value.indexOf('-') === 0) {
|
||||
const msg = `Invalid transaction value of ${txParams.value} not a positive number.`
|
||||
return new Error(msg)
|
||||
}
|
||||
}
|
||||
|
||||
newUnsignedMessage (msgParams, cb) {
|
||||
var state = this.idStore.getState()
|
||||
var state = this.keyringController.getState()
|
||||
if (!state.isUnlocked) {
|
||||
this.idStore.addUnconfirmedMessage(msgParams, cb)
|
||||
this.keyringController.addUnconfirmedMessage(msgParams, cb)
|
||||
this.opts.unlockAccountMessage()
|
||||
} else {
|
||||
this.addUnconfirmedMessage(msgParams, cb)
|
||||
@ -252,8 +271,8 @@ module.exports = class MetamaskController {
|
||||
}
|
||||
|
||||
addUnconfirmedMessage (msgParams, cb) {
|
||||
const idStore = this.idStore
|
||||
const msgId = idStore.addUnconfirmedMessage(msgParams, cb)
|
||||
const keyringController = this.keyringController
|
||||
const msgId = keyringController.addUnconfirmedMessage(msgParams, cb)
|
||||
this.opts.showUnconfirmedMessage(msgParams, msgId)
|
||||
}
|
||||
|
||||
@ -272,8 +291,8 @@ module.exports = class MetamaskController {
|
||||
|
||||
verifyNetwork () {
|
||||
// Check network when restoring connectivity:
|
||||
if (this.idStore._currentState.network === 'loading') {
|
||||
this.idStore.getNetwork()
|
||||
if (this.state.network === 'loading') {
|
||||
this.getNetwork()
|
||||
}
|
||||
}
|
||||
|
||||
@ -298,14 +317,13 @@ module.exports = class MetamaskController {
|
||||
} catch (err) {
|
||||
console.error('Error in checking TOS change.')
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// disclaimer
|
||||
|
||||
agreeToDisclaimer (cb) {
|
||||
try {
|
||||
this.configManager.setConfirmed(true)
|
||||
this.configManager.setConfirmedDisclaimer(true)
|
||||
cb()
|
||||
} catch (err) {
|
||||
cb(err)
|
||||
@ -314,9 +332,9 @@ module.exports = class MetamaskController {
|
||||
|
||||
resetDisclaimer () {
|
||||
try {
|
||||
this.configManager.setConfirmed(false)
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
this.configManager.setConfirmedDisclaimer(false)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
|
||||
@ -345,26 +363,17 @@ module.exports = class MetamaskController {
|
||||
}, 300000)
|
||||
}
|
||||
|
||||
agreeToEthWarning (cb) {
|
||||
try {
|
||||
this.configManager.setShouldntShowWarning()
|
||||
cb()
|
||||
} catch (err) {
|
||||
cb(err)
|
||||
}
|
||||
}
|
||||
|
||||
// called from popup
|
||||
setRpcTarget (rpcTarget) {
|
||||
this.configManager.setRpcTarget(rpcTarget)
|
||||
extension.runtime.reload()
|
||||
this.idStore.getNetwork()
|
||||
this.getNetwork()
|
||||
}
|
||||
|
||||
setProviderType (type) {
|
||||
this.configManager.setProviderType(type)
|
||||
extension.runtime.reload()
|
||||
this.idStore.getNetwork()
|
||||
this.getNetwork()
|
||||
}
|
||||
|
||||
useEtherscanProvider () {
|
||||
@ -375,7 +384,7 @@ module.exports = class MetamaskController {
|
||||
buyEth (address, amount) {
|
||||
if (!amount) amount = '5'
|
||||
|
||||
var network = this.idStore._currentState.network
|
||||
var network = this.state.network
|
||||
var url = `https://buy.coinbase.com/?code=9ec56d01-7e81-5017-930c-513daa27bb6a&amount=${amount}&address=${address}&crypto_currency=ETH`
|
||||
|
||||
if (network === '3') {
|
||||
@ -391,6 +400,25 @@ module.exports = class MetamaskController {
|
||||
this.configManager.createShapeShiftTx(depositAddress, depositType)
|
||||
}
|
||||
|
||||
getNetwork (err) {
|
||||
if (err) {
|
||||
this.state.network = 'loading'
|
||||
this.sendUpdate()
|
||||
}
|
||||
|
||||
this.web3.version.getNetwork((err, network) => {
|
||||
if (err) {
|
||||
this.state.network = 'loading'
|
||||
return this.sendUpdate()
|
||||
}
|
||||
if (global.METAMASK_DEBUG) {
|
||||
console.log('web3.getNetwork returned ' + network)
|
||||
}
|
||||
this.state.network = network
|
||||
this.sendUpdate()
|
||||
})
|
||||
}
|
||||
|
||||
setGasMultiplier (gasMultiplier, cb) {
|
||||
try {
|
||||
this.configManager.setGasMultiplier(gasMultiplier)
|
||||
@ -399,4 +427,70 @@ module.exports = class MetamaskController {
|
||||
cb(err)
|
||||
}
|
||||
}
|
||||
|
||||
getStateNetwork () {
|
||||
return this.state.network
|
||||
}
|
||||
|
||||
markAccountsFound (cb) {
|
||||
this.configManager.setLostAccounts([])
|
||||
this.sendUpdate()
|
||||
cb(null, this.getState())
|
||||
}
|
||||
|
||||
// Migrate Old Vault If Any
|
||||
// @string password
|
||||
//
|
||||
// returns Promise()
|
||||
//
|
||||
// Temporary step used when logging in.
|
||||
// Checks if old style (pre-3.0.0) Metamask Vault exists.
|
||||
// If so, persists that vault in the new vault format
|
||||
// with the provided password, so the other unlock steps
|
||||
// may be completed without interruption.
|
||||
migrateOldVaultIfAny (password) {
|
||||
|
||||
if (!this.checkIfShouldMigrate()) {
|
||||
return Promise.resolve(password)
|
||||
}
|
||||
|
||||
const keyringController = this.keyringController
|
||||
|
||||
return this.idStoreMigrator.migratedVaultForPassword(password)
|
||||
.then(this.restoreOldVaultAccounts.bind(this))
|
||||
.then(this.restoreOldLostAccounts.bind(this))
|
||||
.then(keyringController.persistAllKeyrings.bind(keyringController, password))
|
||||
.then(() => password)
|
||||
}
|
||||
|
||||
checkIfShouldMigrate() {
|
||||
return !!this.configManager.getWallet() && !this.configManager.getVault()
|
||||
}
|
||||
|
||||
restoreOldVaultAccounts(migratorOutput) {
|
||||
const { serialized } = migratorOutput
|
||||
return this.keyringController.restoreKeyring(serialized)
|
||||
.then(() => migratorOutput)
|
||||
}
|
||||
|
||||
restoreOldLostAccounts(migratorOutput) {
|
||||
const { lostAccounts } = migratorOutput
|
||||
if (lostAccounts) {
|
||||
this.configManager.setLostAccounts(lostAccounts.map(acct => acct.address))
|
||||
return this.importLostAccounts(migratorOutput)
|
||||
}
|
||||
return Promise.resolve(migratorOutput)
|
||||
}
|
||||
|
||||
// IMPORT LOST ACCOUNTS
|
||||
// @Object with key lostAccounts: @Array accounts <{ address, privateKey }>
|
||||
// Uses the array's private keys to create a new Simple Key Pair keychain
|
||||
// and add it to the keyring controller.
|
||||
importLostAccounts ({ lostAccounts }) {
|
||||
const privKeys = lostAccounts.map(acct => acct.privateKey)
|
||||
return this.keyringController.restoreKeyring({
|
||||
type: 'Simple Key Pair',
|
||||
data: privKeys,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ module.exports = class NoticeController extends EventEmitter {
|
||||
this.noticePoller = null
|
||||
}
|
||||
|
||||
getState() {
|
||||
getState () {
|
||||
var lastUnreadNotice = this.getLatestUnreadNotice()
|
||||
|
||||
return {
|
||||
@ -18,7 +18,7 @@ module.exports = class NoticeController extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
getNoticesList() {
|
||||
getNoticesList () {
|
||||
var data = this.configManager.getData()
|
||||
if ('noticesList' in data) {
|
||||
return data.noticesList
|
||||
@ -27,28 +27,28 @@ module.exports = class NoticeController extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
setNoticesList(list) {
|
||||
setNoticesList (list) {
|
||||
var data = this.configManager.getData()
|
||||
data.noticesList = list
|
||||
this.configManager.setData(data)
|
||||
return Promise.resolve(true)
|
||||
}
|
||||
|
||||
markNoticeRead(notice, cb) {
|
||||
cb = cb || function(err){ if (err) throw err }
|
||||
markNoticeRead (notice, cb) {
|
||||
cb = cb || function (err) { if (err) throw err }
|
||||
try {
|
||||
var notices = this.getNoticesList()
|
||||
var id = notice.id
|
||||
notices[id].read = true
|
||||
this.setNoticesList(notices)
|
||||
let latestNotice = this.getLatestUnreadNotice()
|
||||
const latestNotice = this.getLatestUnreadNotice()
|
||||
cb(null, latestNotice)
|
||||
} catch (err) {
|
||||
cb(err)
|
||||
}
|
||||
}
|
||||
|
||||
updateNoticesList() {
|
||||
updateNoticesList () {
|
||||
return this._retrieveNoticeData().then((newNotices) => {
|
||||
var oldNotices = this.getNoticesList()
|
||||
var combinedNotices = this._mergeNotices(oldNotices, newNotices)
|
||||
@ -56,7 +56,7 @@ module.exports = class NoticeController extends EventEmitter {
|
||||
})
|
||||
}
|
||||
|
||||
getLatestUnreadNotice() {
|
||||
getLatestUnreadNotice () {
|
||||
var notices = this.getNoticesList()
|
||||
var filteredNotices = notices.filter((notice) => {
|
||||
return notice.read === false
|
||||
@ -73,7 +73,7 @@ module.exports = class NoticeController extends EventEmitter {
|
||||
}, 300000)
|
||||
}
|
||||
|
||||
_mergeNotices(oldNotices, newNotices) {
|
||||
_mergeNotices (oldNotices, newNotices) {
|
||||
var noticeMap = this._mapNoticeIds(oldNotices)
|
||||
newNotices.forEach((notice) => {
|
||||
if (noticeMap.indexOf(notice.id) === -1) {
|
||||
@ -83,11 +83,11 @@ module.exports = class NoticeController extends EventEmitter {
|
||||
return oldNotices
|
||||
}
|
||||
|
||||
_mapNoticeIds(notices) {
|
||||
_mapNoticeIds (notices) {
|
||||
return notices.map((notice) => notice.id)
|
||||
}
|
||||
|
||||
_retrieveNoticeData() {
|
||||
_retrieveNoticeData () {
|
||||
// Placeholder for the API.
|
||||
return Promise.resolve(hardCodedNotices)
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ const setupMultiplex = require('./lib/stream-utils.js').setupMultiplex
|
||||
module.exports = initializePopup
|
||||
|
||||
|
||||
function initializePopup(connectionStream){
|
||||
function initializePopup (connectionStream) {
|
||||
// setup app
|
||||
connectToAccountManager(connectionStream, setupApp)
|
||||
}
|
||||
@ -32,7 +32,7 @@ function setupWeb3Connection (connectionStream) {
|
||||
}
|
||||
|
||||
function setupControllerConnection (connectionStream, cb) {
|
||||
// this is a really sneaky way of adding EventEmitter api
|
||||
// this is a really sneaky way of adding EventEmitter api
|
||||
// to a bi-directional dnode instance
|
||||
var eventEmitter = new EventEmitter()
|
||||
var accountManagerDnode = Dnode({
|
||||
|
@ -18,7 +18,7 @@ var portStream = new PortStream(pluginPort)
|
||||
|
||||
startPopup(portStream)
|
||||
|
||||
function closePopupIfOpen(name) {
|
||||
function closePopupIfOpen (name) {
|
||||
if (name !== 'notification') {
|
||||
notification.closePopup()
|
||||
}
|
||||
|
362
app/scripts/transaction-manager.js
Normal file
362
app/scripts/transaction-manager.js
Normal file
@ -0,0 +1,362 @@
|
||||
const EventEmitter = require('events')
|
||||
const async = require('async')
|
||||
const extend = require('xtend')
|
||||
const Semaphore = require('semaphore')
|
||||
const ethUtil = require('ethereumjs-util')
|
||||
const BN = require('ethereumjs-util').BN
|
||||
const TxProviderUtil = require('./lib/tx-utils')
|
||||
const createId = require('./lib/random-id')
|
||||
|
||||
module.exports = class TransactionManager extends EventEmitter {
|
||||
constructor (opts) {
|
||||
super()
|
||||
this.txList = opts.txList || []
|
||||
this._setTxList = opts.setTxList
|
||||
this.txHistoryLimit = opts.txHistoryLimit
|
||||
this.getSelectedAccount = opts.getSelectedAccount
|
||||
this.provider = opts.provider
|
||||
this.blockTracker = opts.blockTracker
|
||||
this.txProviderUtils = new TxProviderUtil(this.provider)
|
||||
this.blockTracker.on('block', this.checkForTxInBlock.bind(this))
|
||||
this.getGasMultiplier = opts.getGasMultiplier
|
||||
this.getNetwork = opts.getNetwork
|
||||
this.signEthTx = opts.signTransaction
|
||||
this.nonceLock = Semaphore(1)
|
||||
}
|
||||
|
||||
getState () {
|
||||
var selectedAccount = this.getSelectedAccount()
|
||||
return {
|
||||
transactions: this.getTxList(),
|
||||
unconfTxs: this.getUnapprovedTxList(),
|
||||
selectedAccountTxList: this.getFilteredTxList({metamaskNetworkId: this.getNetwork(), from: selectedAccount}),
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the tx list
|
||||
getTxList () {
|
||||
let network = this.getNetwork()
|
||||
return this.txList.filter(txMeta => txMeta.metamaskNetworkId === network)
|
||||
}
|
||||
|
||||
// Adds a tx to the txlist
|
||||
addTx (txMeta) {
|
||||
var txList = this.getTxList()
|
||||
var txHistoryLimit = this.txHistoryLimit
|
||||
|
||||
// checks if the length of th tx history is
|
||||
// longer then desired persistence limit
|
||||
// and then if it is removes only confirmed
|
||||
// or rejected tx's.
|
||||
// not tx's that are pending or unapproved
|
||||
if (txList.length > txHistoryLimit - 1) {
|
||||
var index = txList.findIndex((metaTx) => metaTx.status === 'confirmed' || metaTx.status === 'rejected')
|
||||
txList.splice(index, 1)
|
||||
}
|
||||
txList.push(txMeta)
|
||||
|
||||
this._saveTxList(txList)
|
||||
this.once(`${txMeta.id}:signed`, function (txId) {
|
||||
this.removeAllListeners(`${txMeta.id}:rejected`)
|
||||
})
|
||||
this.once(`${txMeta.id}:rejected`, function (txId) {
|
||||
this.removeAllListeners(`${txMeta.id}:signed`)
|
||||
})
|
||||
|
||||
this.emit('updateBadge')
|
||||
this.emit(`${txMeta.id}:unapproved`, txMeta)
|
||||
}
|
||||
|
||||
// gets tx by Id and returns it
|
||||
getTx (txId, cb) {
|
||||
var txList = this.getTxList()
|
||||
var txMeta = txList.find(txData => txData.id === txId)
|
||||
return cb ? cb(txMeta) : txMeta
|
||||
}
|
||||
|
||||
//
|
||||
updateTx (txMeta) {
|
||||
var txId = txMeta.id
|
||||
var txList = this.getTxList()
|
||||
var index = txList.findIndex(txData => txData.id === txId)
|
||||
txList[index] = txMeta
|
||||
this._saveTxList(txList)
|
||||
this.emit('update')
|
||||
}
|
||||
|
||||
get unapprovedTxCount () {
|
||||
return Object.keys(this.getUnapprovedTxList()).length
|
||||
}
|
||||
|
||||
get pendingTxCount () {
|
||||
return this.getTxsByMetaData('status', 'signed').length
|
||||
}
|
||||
|
||||
addUnapprovedTransaction (txParams, done) {
|
||||
let txMeta
|
||||
async.waterfall([
|
||||
// validate
|
||||
(cb) => this.txProviderUtils.validateTxParams(txParams, cb),
|
||||
// prepare txMeta
|
||||
(cb) => {
|
||||
// create txMeta obj with parameters and meta data
|
||||
let time = (new Date()).getTime()
|
||||
let txId = createId()
|
||||
txParams.metamaskId = txId
|
||||
txParams.metamaskNetworkId = this.getNetwork()
|
||||
txMeta = {
|
||||
id: txId,
|
||||
time: time,
|
||||
status: 'unapproved',
|
||||
gasMultiplier: this.getGasMultiplier() || 1,
|
||||
metamaskNetworkId: this.getNetwork(),
|
||||
txParams: txParams,
|
||||
}
|
||||
// calculate metadata for tx
|
||||
this.txProviderUtils.analyzeGasUsage(txMeta, cb)
|
||||
},
|
||||
// save txMeta
|
||||
(cb) => {
|
||||
this.addTx(txMeta)
|
||||
this.setMaxTxCostAndFee(txMeta)
|
||||
cb(null, txMeta)
|
||||
},
|
||||
], done)
|
||||
}
|
||||
|
||||
setMaxTxCostAndFee (txMeta) {
|
||||
var txParams = txMeta.txParams
|
||||
var gasMultiplier = txMeta.gasMultiplier
|
||||
var gasCost = new BN(ethUtil.stripHexPrefix(txParams.gas || txMeta.estimatedGas), 16)
|
||||
var gasPrice = new BN(ethUtil.stripHexPrefix(txParams.gasPrice || '0x4a817c800'), 16)
|
||||
gasPrice = gasPrice.mul(new BN(gasMultiplier * 100), 10).div(new BN(100, 10))
|
||||
var txFee = gasCost.mul(gasPrice)
|
||||
var txValue = new BN(ethUtil.stripHexPrefix(txParams.value || '0x0'), 16)
|
||||
var maxCost = txValue.add(txFee)
|
||||
txMeta.txFee = txFee
|
||||
txMeta.txValue = txValue
|
||||
txMeta.maxCost = maxCost
|
||||
this.updateTx(txMeta)
|
||||
}
|
||||
|
||||
getUnapprovedTxList () {
|
||||
var txList = this.getTxList()
|
||||
return txList.filter((txMeta) => txMeta.status === 'unapproved')
|
||||
.reduce((result, tx) => {
|
||||
result[tx.id] = tx
|
||||
return result
|
||||
}, {})
|
||||
}
|
||||
|
||||
approveTransaction (txId, cb = warn) {
|
||||
const self = this
|
||||
// approve
|
||||
self.setTxStatusApproved(txId)
|
||||
// only allow one tx at a time for atomic nonce usage
|
||||
self.nonceLock.take(() => {
|
||||
// begin signature process
|
||||
async.waterfall([
|
||||
(cb) => self.fillInTxParams(txId, cb),
|
||||
(cb) => self.signTransaction(txId, cb),
|
||||
(rawTx, cb) => self.publishTransaction(txId, rawTx, cb),
|
||||
], (err) => {
|
||||
self.nonceLock.leave()
|
||||
if (err) {
|
||||
this.setTxStatusFailed(txId)
|
||||
return cb(err)
|
||||
}
|
||||
cb()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
cancelTransaction (txId, cb = warn) {
|
||||
this.setTxStatusRejected(txId)
|
||||
cb()
|
||||
}
|
||||
|
||||
fillInTxParams (txId, cb) {
|
||||
let txMeta = this.getTx(txId)
|
||||
this.txProviderUtils.fillInTxParams(txMeta.txParams, (err) => {
|
||||
if (err) return cb(err)
|
||||
this.updateTx(txMeta)
|
||||
cb()
|
||||
})
|
||||
}
|
||||
|
||||
signTransaction (txId, cb) {
|
||||
let txMeta = this.getTx(txId)
|
||||
let txParams = txMeta.txParams
|
||||
let fromAddress = txParams.from
|
||||
let ethTx = this.txProviderUtils.buildEthTxFromParams(txParams, txMeta.gasMultiplier)
|
||||
this.signEthTx(ethTx, fromAddress).then(() => {
|
||||
this.updateTxAsSigned(txMeta.id, ethTx)
|
||||
cb(null, ethUtil.bufferToHex(ethTx.serialize()))
|
||||
}).catch((err) => {
|
||||
cb(err)
|
||||
})
|
||||
}
|
||||
|
||||
publishTransaction (txId, rawTx, cb) {
|
||||
this.txProviderUtils.publishTransaction(rawTx, (err) => {
|
||||
if (err) return cb(err)
|
||||
this.setTxStatusSubmitted(txId)
|
||||
cb()
|
||||
})
|
||||
}
|
||||
|
||||
// receives a signed tx object and updates the tx hash
|
||||
updateTxAsSigned (txId, ethTx) {
|
||||
// Add the tx hash to the persisted meta-tx object
|
||||
let txHash = ethUtil.bufferToHex(ethTx.hash())
|
||||
let txMeta = this.getTx(txId)
|
||||
txMeta.hash = txHash
|
||||
this.updateTx(txMeta)
|
||||
this.setTxStatusSigned(txMeta.id)
|
||||
}
|
||||
|
||||
/*
|
||||
Takes an object of fields to search for eg:
|
||||
var thingsToLookFor = {
|
||||
to: '0x0..',
|
||||
from: '0x0..',
|
||||
status: 'signed',
|
||||
}
|
||||
and returns a list of tx with all
|
||||
options matching
|
||||
|
||||
this is for things like filtering a the tx list
|
||||
for only tx's from 1 account
|
||||
or for filltering for all txs from one account
|
||||
and that have been 'confirmed'
|
||||
*/
|
||||
getFilteredTxList (opts) {
|
||||
var filteredTxList
|
||||
Object.keys(opts).forEach((key) => {
|
||||
filteredTxList = this.getTxsByMetaData(key, opts[key], filteredTxList)
|
||||
})
|
||||
return filteredTxList
|
||||
}
|
||||
|
||||
getTxsByMetaData (key, value, txList = this.getTxList()) {
|
||||
return txList.filter((txMeta) => {
|
||||
if (key in txMeta.txParams) {
|
||||
return txMeta.txParams[key] === value
|
||||
} else {
|
||||
return txMeta[key] === value
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// STATUS METHODS
|
||||
// get::set status
|
||||
|
||||
// should return the status of the tx.
|
||||
getTxStatus (txId) {
|
||||
const txMeta = this.getTx(txId)
|
||||
return txMeta.status
|
||||
}
|
||||
|
||||
// should update the status of the tx to 'rejected'.
|
||||
setTxStatusRejected (txId) {
|
||||
this._setTxStatus(txId, 'rejected')
|
||||
}
|
||||
|
||||
// should update the status of the tx to 'approved'.
|
||||
setTxStatusApproved (txId) {
|
||||
this._setTxStatus(txId, 'approved')
|
||||
}
|
||||
|
||||
// should update the status of the tx to 'signed'.
|
||||
setTxStatusSigned (txId) {
|
||||
this._setTxStatus(txId, 'signed')
|
||||
}
|
||||
|
||||
// should update the status of the tx to 'submitted'.
|
||||
setTxStatusSubmitted (txId) {
|
||||
this._setTxStatus(txId, 'submitted')
|
||||
}
|
||||
|
||||
// should update the status of the tx to 'confirmed'.
|
||||
setTxStatusConfirmed (txId) {
|
||||
this._setTxStatus(txId, 'confirmed')
|
||||
}
|
||||
|
||||
setTxStatusFailed (txId) {
|
||||
this._setTxStatus(txId, 'failed')
|
||||
}
|
||||
|
||||
// merges txParams obj onto txData.txParams
|
||||
// use extend to ensure that all fields are filled
|
||||
updateTxParams (txId, txParams) {
|
||||
var txMeta = this.getTx(txId)
|
||||
txMeta.txParams = extend(txMeta.txParams, txParams)
|
||||
this.updateTx(txMeta)
|
||||
}
|
||||
|
||||
// checks if a signed tx is in a block and
|
||||
// if included sets the tx status as 'confirmed'
|
||||
checkForTxInBlock () {
|
||||
var signedTxList = this.getFilteredTxList({status: 'signed'})
|
||||
if (!signedTxList.length) return
|
||||
signedTxList.forEach((txMeta) => {
|
||||
var txHash = txMeta.hash
|
||||
var txId = txMeta.id
|
||||
if (!txHash) {
|
||||
txMeta.err = {
|
||||
errCode: 'No hash was provided',
|
||||
message: 'We had an error while submitting this transaction, please try again.',
|
||||
}
|
||||
this.updateTx(txMeta)
|
||||
return this.setTxStatusFailed(txId)
|
||||
}
|
||||
this.txProviderUtils.query.getTransactionByHash(txHash, (err, txParams) => {
|
||||
if (err || !txParams) {
|
||||
if (!txParams) return
|
||||
txMeta.err = {
|
||||
isWarning: true,
|
||||
errorCode: err,
|
||||
message: 'There was a problem loading this transaction.',
|
||||
}
|
||||
this.updateTx(txMeta)
|
||||
return console.error(err)
|
||||
}
|
||||
if (txParams.blockNumber) {
|
||||
this.setTxStatusConfirmed(txId)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// PRIVATE METHODS
|
||||
|
||||
// Should find the tx in the tx list and
|
||||
// update it.
|
||||
// should set the status in txData
|
||||
// - `'unapproved'` the user has not responded
|
||||
// - `'rejected'` the user has responded no!
|
||||
// - `'approved'` the user has approved the tx
|
||||
// - `'signed'` the tx is signed
|
||||
// - `'submitted'` the tx is sent to a server
|
||||
// - `'confirmed'` the tx has been included in a block.
|
||||
_setTxStatus (txId, status) {
|
||||
var txMeta = this.getTx(txId)
|
||||
txMeta.status = status
|
||||
this.emit(`${txMeta.id}:${status}`, txId)
|
||||
if (status === 'submitted' || status === 'rejected') {
|
||||
this.emit(`${txMeta.id}:finished`, status)
|
||||
}
|
||||
this.updateTx(txMeta)
|
||||
this.emit('updateBadge')
|
||||
}
|
||||
|
||||
// Saves the new/updated txList.
|
||||
// Function is intended only for internal use
|
||||
_saveTxList (txList) {
|
||||
this.txList = txList
|
||||
this._setTxList(txList)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const warn = () => console.warn('warn was used no cb provided')
|
File diff suppressed because one or more lines are too long
@ -133,10 +133,10 @@
|
||||
"address": "0x704107d04affddd9b66ab9de3dd7b095852e9b69"
|
||||
}
|
||||
},
|
||||
"selectedAddress": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc",
|
||||
"selectedAccount": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc",
|
||||
"network": "1",
|
||||
"seedWords": null,
|
||||
"isConfirmed": true,
|
||||
"isDisclaimerConfirmed": true,
|
||||
"unconfMsgs": {},
|
||||
"messages": [],
|
||||
"provider": {
|
||||
|
@ -99,10 +99,10 @@
|
||||
"status": "confirmed",
|
||||
"containsDelegateCall": false
|
||||
}],
|
||||
"selectedAddress": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc",
|
||||
"selectedAccount": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc",
|
||||
"network": "2",
|
||||
"seedWords": null,
|
||||
"isConfirmed": true,
|
||||
"isDisclaimerConfirmed": true,
|
||||
"unconfMsgs": {},
|
||||
"messages": [],
|
||||
"provider": {
|
||||
|
@ -57,10 +57,10 @@
|
||||
}
|
||||
},
|
||||
"transactions": [],
|
||||
"selectedAddress": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc",
|
||||
"selectedAccount": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc",
|
||||
"network": "2",
|
||||
"seedWords": null,
|
||||
"isConfirmed": true,
|
||||
"isDisclaimerConfirmed": true,
|
||||
"unconfMsgs": {},
|
||||
"messages": [],
|
||||
"provider": {
|
||||
|
126
development/states/accounts-loose.json
Normal file
126
development/states/accounts-loose.json
Normal file
@ -0,0 +1,126 @@
|
||||
{
|
||||
"metamask": {
|
||||
"isInitialized": true,
|
||||
"isUnlocked": true,
|
||||
"rpcTarget": "https://rawtestrpc.metamask.io/",
|
||||
"identities": {
|
||||
"0xac39b311dceb2a4b2f5d8461c1cdaf756f4f7ae9": {
|
||||
"address": "0xac39b311dceb2a4b2f5d8461c1cdaf756f4f7ae9",
|
||||
"name": "Account 1"
|
||||
},
|
||||
"0xd7c0cd9e7d2701c710d64fc492c7086679bdf7b4": {
|
||||
"address": "0xd7c0cd9e7d2701c710d64fc492c7086679bdf7b4",
|
||||
"name": "Account 2"
|
||||
},
|
||||
"0x1acfb961c5a8268eac8e09d6241a26cbeff42241": {
|
||||
"address": "0x1acfb961c5a8268eac8e09d6241a26cbeff42241",
|
||||
"name": "Account 3"
|
||||
},
|
||||
"0xe15d894becb0354c501ae69429b05143679f39e0": {
|
||||
"address": "0xe15d894becb0354c501ae69429b05143679f39e0",
|
||||
"name": "Account 4"
|
||||
},
|
||||
"0x87658c15aefe7448008a28513a11b6b130ef4cd0": {
|
||||
"address": "0x87658c15aefe7448008a28513a11b6b130ef4cd0",
|
||||
"name": "Account 5"
|
||||
},
|
||||
"0xaa25854c0379e53c957ac9382e720c577fa31fd5": {
|
||||
"address": "0xaa25854c0379e53c957ac9382e720c577fa31fd5",
|
||||
"name": "Account 6"
|
||||
}
|
||||
},
|
||||
"unconfTxs": {},
|
||||
"currentFiat": "USD",
|
||||
"conversionRate": 0,
|
||||
"conversionDate": "N/A",
|
||||
"noActiveNotices": true,
|
||||
"network": "3",
|
||||
"accounts": {
|
||||
"0xac39b311dceb2a4b2f5d8461c1cdaf756f4f7ae9": {
|
||||
"code": "0x",
|
||||
"balance": "0x11f646fe14c9c000",
|
||||
"nonce": "0x3",
|
||||
"address": "0xac39b311dceb2a4b2f5d8461c1cdaf756f4f7ae9"
|
||||
},
|
||||
"0xd7c0cd9e7d2701c710d64fc492c7086679bdf7b4": {
|
||||
"code": "0x",
|
||||
"balance": "0x0",
|
||||
"nonce": "0x0",
|
||||
"address": "0xd7c0cd9e7d2701c710d64fc492c7086679bdf7b4"
|
||||
},
|
||||
"0x1acfb961c5a8268eac8e09d6241a26cbeff42241": {
|
||||
"code": "0x",
|
||||
"balance": "0x0",
|
||||
"nonce": "0x0",
|
||||
"address": "0x1acfb961c5a8268eac8e09d6241a26cbeff42241"
|
||||
},
|
||||
"0xe15d894becb0354c501ae69429b05143679f39e0": {
|
||||
"code": "0x",
|
||||
"balance": "0x0",
|
||||
"nonce": "0x0",
|
||||
"address": "0xe15d894becb0354c501ae69429b05143679f39e0"
|
||||
},
|
||||
"0x87658c15aefe7448008a28513a11b6b130ef4cd0": {
|
||||
"code": "0x",
|
||||
"balance": "0x0",
|
||||
"nonce": "0x0",
|
||||
"address": "0x87658c15aefe7448008a28513a11b6b130ef4cd0"
|
||||
},
|
||||
"0xaa25854c0379e53c957ac9382e720c577fa31fd5": {
|
||||
"code": "0x",
|
||||
"balance": "0x0",
|
||||
"nonce": "0x0",
|
||||
"address": "0xaa25854c0379e53c957ac9382e720c577fa31fd5"
|
||||
}
|
||||
},
|
||||
"transactions": [],
|
||||
"provider": {
|
||||
"type": "testnet"
|
||||
},
|
||||
"selectedAccount": "0x87658c15aefe7448008a28513a11b6b130ef4cd0",
|
||||
"isDisclaimerConfirmed": true,
|
||||
"unconfMsgs": {},
|
||||
"messages": [],
|
||||
"shapeShiftTxList": [],
|
||||
"keyringTypes": [
|
||||
"Simple Key Pair",
|
||||
"HD Key Tree"
|
||||
],
|
||||
"keyrings": [
|
||||
{
|
||||
"type": "HD Key Tree",
|
||||
"accounts": [
|
||||
"ac39b311dceb2a4b2f5d8461c1cdaf756f4f7ae9",
|
||||
"d7c0cd9e7d2701c710d64fc492c7086679bdf7b4",
|
||||
"1acfb961c5a8268eac8e09d6241a26cbeff42241"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "Simple Key Pair",
|
||||
"accounts": [
|
||||
"e15d894becb0354c501ae69429b05143679f39e0",
|
||||
"87658c15aefe7448008a28513a11b6b130ef4cd0",
|
||||
"aa25854c0379e53c957ac9382e720c577fa31fd5"
|
||||
]
|
||||
}
|
||||
],
|
||||
"lostAccounts": []
|
||||
},
|
||||
"appState": {
|
||||
"menuOpen": false,
|
||||
"currentView": {
|
||||
"name": "accounts"
|
||||
},
|
||||
"accountDetail": {
|
||||
"subview": "transactions",
|
||||
"accountExport": "none",
|
||||
"privateKey": ""
|
||||
},
|
||||
"transForward": true,
|
||||
"isLoading": false,
|
||||
"warning": null,
|
||||
"scrollToBottom": false,
|
||||
"forgottenPassword": false
|
||||
},
|
||||
"identities": {}
|
||||
}
|
@ -2,7 +2,6 @@
|
||||
"metamask": {
|
||||
"isInitialized": true,
|
||||
"isUnlocked": true,
|
||||
"isEthConfirmed": true,
|
||||
"rpcTarget": "https://rawtestrpc.metamask.io/",
|
||||
"identities": {
|
||||
"0x0abdd95cafcabec9b3e99dcd09fc4b441037cb80": {
|
||||
@ -90,9 +89,9 @@
|
||||
}
|
||||
},
|
||||
"transactions": [],
|
||||
"selectedAddress": "0x0abdd95cafcabec9b3e99dcd09fc4b441037cb80",
|
||||
"selectedAccount": "0x0abdd95cafcabec9b3e99dcd09fc4b441037cb80",
|
||||
"network": "2",
|
||||
"isConfirmed": true,
|
||||
"isDisclaimerConfirmed": true,
|
||||
"unconfMsgs": {},
|
||||
"messages": [],
|
||||
"shapeShiftTxList": [],
|
||||
@ -116,4 +115,4 @@
|
||||
"scrollToBottom": true
|
||||
},
|
||||
"identities": {}
|
||||
}
|
||||
}
|
||||
|
@ -57,9 +57,9 @@
|
||||
}
|
||||
},
|
||||
"transactions": [],
|
||||
"selectedAddress": "0x843963b837841dad3b0f5969ff271108776616df",
|
||||
"selectedAccount": "0x843963b837841dad3b0f5969ff271108776616df",
|
||||
"network": "2",
|
||||
"isConfirmed": true,
|
||||
"isDisclaimerConfirmed": true,
|
||||
"unconfMsgs": {},
|
||||
"messages": [],
|
||||
"provider": {
|
||||
|
@ -10,7 +10,7 @@
|
||||
"transactions": [],
|
||||
"network": "2",
|
||||
"seedWords": null,
|
||||
"isConfirmed": true,
|
||||
"isDisclaimerConfirmed": true,
|
||||
"unconfMsgs": {},
|
||||
"messages": [],
|
||||
"provider": {
|
||||
|
@ -157,11 +157,10 @@
|
||||
"estimatedGas": "0x5208"
|
||||
}
|
||||
],
|
||||
"selectedAddress": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
|
||||
"selectedAccount": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
|
||||
"network": "166",
|
||||
"seedWords": null,
|
||||
"isConfirmed": true,
|
||||
"isEthConfirmed": true,
|
||||
"isDisclaimerConfirmed": true,
|
||||
"unconfMsgs": {},
|
||||
"messages": [],
|
||||
"provider": {
|
||||
|
@ -54,10 +54,10 @@
|
||||
}
|
||||
},
|
||||
"transactions": [],
|
||||
"selectedAddress": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc",
|
||||
"selectedAccount": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc",
|
||||
"network": "2",
|
||||
"seedWords": null,
|
||||
"isConfirmed": true,
|
||||
"isDisclaimerConfirmed": true,
|
||||
"unconfMsgs": {},
|
||||
"messages": [],
|
||||
"provider": {
|
||||
|
@ -2,18 +2,22 @@
|
||||
"metamask": {
|
||||
"isInitialized": false,
|
||||
"isUnlocked": false,
|
||||
"currentDomain": "example.com",
|
||||
"rpcTarget": "https://rawtestrpc.metamask.io/",
|
||||
"identities": {},
|
||||
"unconfTxs": {},
|
||||
"currentFiat": "USD",
|
||||
"conversionRate": 11.47635827,
|
||||
"conversionDate": 1477606503,
|
||||
"network": null,
|
||||
"accounts": {},
|
||||
"transactions": [],
|
||||
"network": "2",
|
||||
"seedWords": null,
|
||||
"isConfirmed": false,
|
||||
"isEthConfirmed": false,
|
||||
"isDisclaimerConfirmed": true,
|
||||
"unconfMsgs": {},
|
||||
"messages": [],
|
||||
"shapeShiftTxList": [],
|
||||
"keyringTypes": [
|
||||
"Simple Key Pair"
|
||||
],
|
||||
"provider": {
|
||||
"type": "testnet"
|
||||
}
|
||||
@ -21,15 +25,15 @@
|
||||
"appState": {
|
||||
"menuOpen": false,
|
||||
"currentView": {
|
||||
"name": "EthStoreWarning"
|
||||
"name": "accounts",
|
||||
"detailView": null
|
||||
},
|
||||
"accountDetail": {
|
||||
"subview": "transactions"
|
||||
},
|
||||
"currentDomain": "127.0.0.1:9966",
|
||||
"transForward": true,
|
||||
"isLoading": false,
|
||||
"warning": null
|
||||
},
|
||||
"identities": {}
|
||||
}
|
||||
}
|
@ -55,14 +55,14 @@
|
||||
},
|
||||
"transactions": [],
|
||||
"network": "2",
|
||||
"isConfirmed": true,
|
||||
"isDisclaimerConfirmed": true,
|
||||
"unconfMsgs": {},
|
||||
"messages": [],
|
||||
"provider": {
|
||||
"type": "testnet"
|
||||
},
|
||||
"selectedAccount": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc",
|
||||
"selectedAddress": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc",
|
||||
"selectedAccount": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc",
|
||||
"seedWords": null
|
||||
},
|
||||
"appState": {
|
||||
|
@ -2,7 +2,6 @@
|
||||
"metamask": {
|
||||
"isInitialized": true,
|
||||
"isUnlocked": false,
|
||||
"isEthConfirmed": true,
|
||||
"currentDomain": "example.com",
|
||||
"rpcTarget": "https://rawtestrpc.metamask.io/",
|
||||
"identities": {},
|
||||
@ -12,10 +11,10 @@
|
||||
"conversionDate": 1473358355,
|
||||
"accounts": {},
|
||||
"transactions": [],
|
||||
"selectedAddress": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
|
||||
"selectedAccount": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
|
||||
"network": "1473186153102",
|
||||
"seedWords": null,
|
||||
"isConfirmed": true,
|
||||
"isDisclaimerConfirmed": true,
|
||||
"unconfMsgs": {},
|
||||
"messages": [],
|
||||
"shapeShiftTxList": [],
|
||||
|
91
development/states/lost-accounts.json
Normal file
91
development/states/lost-accounts.json
Normal file
@ -0,0 +1,91 @@
|
||||
{
|
||||
"metamask": {
|
||||
"currentFiat": "USD",
|
||||
"lostAccounts": [
|
||||
"0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc",
|
||||
"0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b"
|
||||
],
|
||||
"conversionRate": 11.06608791,
|
||||
"conversionDate": 1470421024,
|
||||
"isInitialized": true,
|
||||
"isUnlocked": true,
|
||||
"currentDomain": "example.com",
|
||||
"rpcTarget": "https://rawtestrpc.metamask.io/",
|
||||
"identities": {
|
||||
"0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc": {
|
||||
"name": "Wallet 1",
|
||||
"address": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc",
|
||||
"mayBeFauceting": false
|
||||
},
|
||||
"0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b": {
|
||||
"name": "Wallet 2",
|
||||
"address": "0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b",
|
||||
"mayBeFauceting": false
|
||||
},
|
||||
"0xeb9e64b93097bc15f01f13eae97015c57ab64823": {
|
||||
"name": "Wallet 3",
|
||||
"address": "0xeb9e64b93097bc15f01f13eae97015c57ab64823",
|
||||
"mayBeFauceting": false
|
||||
},
|
||||
"0x704107d04affddd9b66ab9de3dd7b095852e9b69": {
|
||||
"name": "Wallet 4",
|
||||
"address": "0x704107d04affddd9b66ab9de3dd7b095852e9b69",
|
||||
"mayBeFauceting": false
|
||||
}
|
||||
},
|
||||
"unconfTxs": {},
|
||||
"accounts": {
|
||||
"0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc": {
|
||||
"code": "0x",
|
||||
"balance": "0x100000000000",
|
||||
"nonce": "0x0",
|
||||
"address": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc"
|
||||
},
|
||||
"0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b": {
|
||||
"code": "0x",
|
||||
"nonce": "0x0",
|
||||
"balance": "0x100000000000",
|
||||
"address": "0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b"
|
||||
},
|
||||
"0xeb9e64b93097bc15f01f13eae97015c57ab64823": {
|
||||
"code": "0x",
|
||||
"nonce": "0x0",
|
||||
"balance": "0x100000000000",
|
||||
"address": "0xeb9e64b93097bc15f01f13eae97015c57ab64823"
|
||||
},
|
||||
"0x704107d04affddd9b66ab9de3dd7b095852e9b69": {
|
||||
"code": "0x",
|
||||
"balance": "0x0",
|
||||
"nonce": "0x0",
|
||||
"address": "0x704107d04affddd9b66ab9de3dd7b095852e9b69"
|
||||
}
|
||||
},
|
||||
"transactions": [],
|
||||
"selectedAccount": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc",
|
||||
"network": "2",
|
||||
"seedWords": null,
|
||||
"isDisclaimerConfirmed": true,
|
||||
"unconfMsgs": {},
|
||||
"messages": [],
|
||||
"provider": {
|
||||
"type": "testnet"
|
||||
},
|
||||
"selectedAccount": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc"
|
||||
},
|
||||
"appState": {
|
||||
"menuOpen": false,
|
||||
"currentView": {
|
||||
"name": "accountDetail",
|
||||
"detailView": null,
|
||||
"context": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc"
|
||||
},
|
||||
"accountDetail": {
|
||||
"subview": "transactions"
|
||||
},
|
||||
"currentDomain": "127.0.0.1:9966",
|
||||
"transForward": true,
|
||||
"isLoading": false,
|
||||
"warning": null
|
||||
},
|
||||
"identities": {}
|
||||
}
|
@ -10,7 +10,7 @@
|
||||
"transactions": [],
|
||||
"network": "2",
|
||||
"seedWords": null,
|
||||
"isConfirmed": false,
|
||||
"isDisclaimerConfirmed": false,
|
||||
"unconfMsgs": {},
|
||||
"messages": [],
|
||||
"provider": {
|
||||
|
File diff suppressed because one or more lines are too long
@ -2,7 +2,6 @@
|
||||
"metamask": {
|
||||
"isInitialized": true,
|
||||
"isUnlocked": true,
|
||||
"isEthConfirmed": true,
|
||||
"currentDomain": "example.com",
|
||||
"rpcTarget": "https://rawtestrpc.metamask.io/",
|
||||
"identities": {
|
||||
@ -352,10 +351,10 @@
|
||||
"hash": "0xb6e6ff57e7b5f6bd7f2e6dc44c39f4e858a227c9509586634ca547179345a13e"
|
||||
}
|
||||
],
|
||||
"selectedAddress": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
|
||||
"selectedAccount": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
|
||||
"network": "1471904489432",
|
||||
"seedWords": null,
|
||||
"isConfirmed": true,
|
||||
"isDisclaimerConfirmed": true,
|
||||
"unconfMsgs": {
|
||||
"1472076978535283": {
|
||||
"id": 1472076978535283,
|
||||
@ -403,4 +402,4 @@
|
||||
"warning": null
|
||||
},
|
||||
"identities": {}
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because one or more lines are too long
@ -1 +1 @@
|
||||
{"metamask":{"isInitialized":true,"isUnlocked":true,"currentDomain":"example.com","rpcTarget":"https://rawtestrpc.metamask.io/","identities":{"0xfdea65c8e26263f6d9a1b5de9555d2931a33b825":{"name":"Wallet 1","address":"0xfdea65c8e26263f6d9a1b5de9555d2931a33b825","mayBeFauceting":false},"0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb":{"name":"Wallet 2","address":"0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb","mayBeFauceting":false},"0x2f8d4a878cfa04a6e60d46362f5644deab66572d":{"name":"Wallet 3","address":"0x2f8d4a878cfa04a6e60d46362f5644deab66572d","mayBeFauceting":false}},"unconfTxs":{"1467868023090690":{"id":1467868023090690,"txParams":{"data":"0xa9059cbb0000000000000000000000008deb4d106090c3eb8f1950f727e87c4f884fb06f0000000000000000000000000000000000000000000000000000000000000064","from":"0xfdea65c8e26263f6d9a1b5de9555d2931a33b825","value":"0x16345785d8a0000","to":"0xbeb0ed3034c4155f3d16a64a5c5e7c8d4ea9e9c9","origin":"MetaMask","metamaskId":1467868023090690,"metamaskNetworkId":"2"},"time":1467868023090,"status":"unconfirmed","containsDelegateCall":false}},"accounts":{"0xfdea65c8e26263f6d9a1b5de9555d2931a33b825":{"code":"0x","balance":"0x38326dc32cf80800","nonce":"0x10000c","address":"0xfdea65c8e26263f6d9a1b5de9555d2931a33b825"},"0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb":{"code":"0x","balance":"0x15e578bd8e9c8000","nonce":"0x100000","address":"0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb"},"0x2f8d4a878cfa04a6e60d46362f5644deab66572d":{"code":"0x","nonce":"0x100000","balance":"0x2386f26fc10000","address":"0x2f8d4a878cfa04a6e60d46362f5644deab66572d"}},"transactions":[],"selectedAddress":"0xfdea65c8e26263f6d9a1b5de9555d2931a33b825","network":"2","seedWords":null,"isConfirmed":true,"unconfMsgs":{},"messages":[],"provider":{"type":"testnet"},"selectedAccount":"0xfdea65c8e26263f6d9a1b5de9555d2931a33b825"},"appState":{"menuOpen":false,"currentView":{"name":"confTx","context":0},"accountDetail":{"subview":"transactions"},"currentDomain":"extensions","transForward":true,"isLoading":false,"warning":null},"identities":{}}
|
||||
{"metamask":{"isInitialized":true,"isUnlocked":true,"currentDomain":"example.com","rpcTarget":"https://rawtestrpc.metamask.io/","identities":{"0xfdea65c8e26263f6d9a1b5de9555d2931a33b825":{"name":"Wallet 1","address":"0xfdea65c8e26263f6d9a1b5de9555d2931a33b825","mayBeFauceting":false},"0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb":{"name":"Wallet 2","address":"0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb","mayBeFauceting":false},"0x2f8d4a878cfa04a6e60d46362f5644deab66572d":{"name":"Wallet 3","address":"0x2f8d4a878cfa04a6e60d46362f5644deab66572d","mayBeFauceting":false}},"unconfTxs":{"1467868023090690":{"id":1467868023090690,"txParams":{"data":"0xa9059cbb0000000000000000000000008deb4d106090c3eb8f1950f727e87c4f884fb06f0000000000000000000000000000000000000000000000000000000000000064","from":"0xfdea65c8e26263f6d9a1b5de9555d2931a33b825","value":"0x16345785d8a0000","to":"0xbeb0ed3034c4155f3d16a64a5c5e7c8d4ea9e9c9","origin":"MetaMask","metamaskId":1467868023090690,"metamaskNetworkId":"2"},"time":1467868023090,"status":"unconfirmed","containsDelegateCall":false}},"accounts":{"0xfdea65c8e26263f6d9a1b5de9555d2931a33b825":{"code":"0x","balance":"0x38326dc32cf80800","nonce":"0x10000c","address":"0xfdea65c8e26263f6d9a1b5de9555d2931a33b825"},"0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb":{"code":"0x","balance":"0x15e578bd8e9c8000","nonce":"0x100000","address":"0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb"},"0x2f8d4a878cfa04a6e60d46362f5644deab66572d":{"code":"0x","nonce":"0x100000","balance":"0x2386f26fc10000","address":"0x2f8d4a878cfa04a6e60d46362f5644deab66572d"}},"transactions":[],"selectedAccount":"0xfdea65c8e26263f6d9a1b5de9555d2931a33b825","network":"2","seedWords":null,"isDisclaimerConfirmed":true,"unconfMsgs":{},"messages":[],"provider":{"type":"testnet"},"selectedAccount":"0xfdea65c8e26263f6d9a1b5de9555d2931a33b825"},"appState":{"menuOpen":false,"currentView":{"name":"confTx","context":0},"accountDetail":{"subview":"transactions"},"currentDomain":"extensions","transForward":true,"isLoading":false,"warning":null},"identities":{}}
|
||||
|
File diff suppressed because one or more lines are too long
86
development/states/private-network.json
Normal file
86
development/states/private-network.json
Normal file
@ -0,0 +1,86 @@
|
||||
{
|
||||
"metamask": {
|
||||
"isInitialized": true,
|
||||
"isUnlocked": true,
|
||||
"rpcTarget": "https://rawtestrpc.metamask.io/",
|
||||
"identities": {
|
||||
"0xfdea65c8e26263f6d9a1b5de9555d2931a33b825": {
|
||||
"name": "Account 1",
|
||||
"address": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
|
||||
"mayBeFauceting": false
|
||||
},
|
||||
"0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb": {
|
||||
"name": "Account 2",
|
||||
"address": "0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb",
|
||||
"mayBeFauceting": false
|
||||
}
|
||||
},
|
||||
"unconfTxs": {},
|
||||
"currentFiat": "USD",
|
||||
"conversionRate": 9.52855776,
|
||||
"conversionDate": 1479756513,
|
||||
"accounts": {
|
||||
"0xfdea65c8e26263f6d9a1b5de9555d2931a33b825": {
|
||||
"balance": "0x0",
|
||||
"nonce": "0x0",
|
||||
"code": "0x0",
|
||||
"address": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825"
|
||||
},
|
||||
"0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb": {
|
||||
"balance": "0x0",
|
||||
"nonce": "0x0",
|
||||
"code": "0x0",
|
||||
"address": "0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb"
|
||||
}
|
||||
},
|
||||
"transactions": [
|
||||
{
|
||||
"id": 5551995700357153,
|
||||
"txParams": {
|
||||
"from": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
|
||||
"to": "0x48ff0cbac0acefedf152281ee80e9a0a01d5da63",
|
||||
"data": "0x90b98a11000000000000000000000000c5b8dbac4c1d3f152cdeb400e2313f309c410acb000000000000000000000000000000000000000000000000000000000000000a",
|
||||
"metamaskId": 5551995700357153,
|
||||
"metamaskNetworkId": "1479490588308"
|
||||
},
|
||||
"time": 1479498745949,
|
||||
"status": "confirmed",
|
||||
"gasMultiplier": 1,
|
||||
"metamaskNetworkId": "1479490588308",
|
||||
"containsDelegateCall": true,
|
||||
"estimatedGas": "0x24b33",
|
||||
"hash": "0xad609a6931f54a575ad71222ffc27cd6746017106d5b89f4ad300b37b273f8ac"
|
||||
}
|
||||
],
|
||||
"selectedAccount": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
|
||||
"network": "1479753732793",
|
||||
"isConfirmed": true,
|
||||
"isEthConfirmed": true,
|
||||
"unconfMsgs": {},
|
||||
"messages": [],
|
||||
"shapeShiftTxList": [],
|
||||
"gasMultiplier": false,
|
||||
"provider": {
|
||||
"type": "rpc",
|
||||
"rpcTarget": "http://localhost:8545"
|
||||
},
|
||||
"selectedAccount": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
|
||||
"isDisclaimerConfirmed": true
|
||||
},
|
||||
"appState": {
|
||||
"menuOpen": false,
|
||||
"currentView": {
|
||||
"name": "accountDetail"
|
||||
},
|
||||
"accountDetail": {
|
||||
"subview": "transactions",
|
||||
"accountExport": "none",
|
||||
"privateKey": ""
|
||||
},
|
||||
"transForward": false,
|
||||
"isLoading": false,
|
||||
"warning": null,
|
||||
"scrollToBottom": false
|
||||
},
|
||||
"identities": {}
|
||||
}
|
@ -2,7 +2,6 @@
|
||||
"metamask": {
|
||||
"isInitialized": false,
|
||||
"isUnlocked": false,
|
||||
"isEthConfirmed": false,
|
||||
"currentDomain": "example.com",
|
||||
"rpcTarget": "https://rawtestrpc.metamask.io/",
|
||||
"identities": {},
|
||||
@ -13,7 +12,7 @@
|
||||
"accounts": {},
|
||||
"transactions": [],
|
||||
"seedWords": null,
|
||||
"isConfirmed": true,
|
||||
"isDisclaimerConfirmed": true,
|
||||
"unconfMsgs": {},
|
||||
"messages": [],
|
||||
"shapeShiftTxList": [],
|
||||
@ -36,4 +35,4 @@
|
||||
"warning": null
|
||||
},
|
||||
"identities": {}
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,6 @@
|
||||
"metamask": {
|
||||
"isInitialized": true,
|
||||
"isUnlocked": true,
|
||||
"isEthConfirmed": false,
|
||||
"currentDomain": "example.com",
|
||||
"rpcTarget": "https://rawtestrpc.metamask.io/",
|
||||
"identities": {
|
||||
@ -49,7 +48,7 @@
|
||||
"transactions": [],
|
||||
"network": "2",
|
||||
"seedWords": null,
|
||||
"isConfirmed": true,
|
||||
"isDisclaimerConfirmed": true,
|
||||
"unconfMsgs": {},
|
||||
"messages": [],
|
||||
"shapeShiftTxList": [],
|
||||
@ -73,4 +72,4 @@
|
||||
"detailView": {}
|
||||
},
|
||||
"identities": {}
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,6 @@
|
||||
"metamask": {
|
||||
"isInitialized": true,
|
||||
"isUnlocked": true,
|
||||
"isEthConfirmed": true,
|
||||
"currentDomain": "example.com",
|
||||
"rpcTarget": "https://rawtestrpc.metamask.io/",
|
||||
"identities": {
|
||||
@ -47,10 +46,10 @@
|
||||
}
|
||||
},
|
||||
"transactions": [],
|
||||
"selectedAddress": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
|
||||
"selectedAccount": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
|
||||
"network": "1",
|
||||
"seedWords": null,
|
||||
"isConfirmed": true,
|
||||
"isDisclaimerConfirmed": true,
|
||||
"unconfMsgs": {},
|
||||
"messages": [],
|
||||
"shapeShiftTxList": [],
|
||||
@ -345,4 +344,4 @@
|
||||
"isSubLoading": false
|
||||
},
|
||||
"identities": {}
|
||||
}
|
||||
}
|
||||
|
@ -45,7 +45,7 @@
|
||||
"transactions": [],
|
||||
"network": "2",
|
||||
"seedWords": "debris dizzy just program just float decrease vacant alarm reduce speak stadium",
|
||||
"isConfirmed": true,
|
||||
"isDisclaimerConfirmed": true,
|
||||
"unconfMsgs": {},
|
||||
"messages": [],
|
||||
"provider": {
|
||||
|
31
development/test.html
Normal file
31
development/test.html
Normal file
@ -0,0 +1,31 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>MetaMask</title>
|
||||
|
||||
<script>
|
||||
window.METAMASK_DEBUG = true
|
||||
window.TEST_MODE = true
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- app content -->
|
||||
<div id="app-content" style="height: 100%"></div>
|
||||
<script src="./bundle.js" type="text/javascript" charset="utf-8"></script>
|
||||
|
||||
</body>
|
||||
|
||||
<style>
|
||||
html, body, #app-content, .super-dev-container {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
background: white;
|
||||
}
|
||||
.mock-app-root {
|
||||
background: #F7F7F7;
|
||||
}
|
||||
</style>
|
||||
</html>
|
188
docs/multi_vault_planning.md
Normal file
188
docs/multi_vault_planning.md
Normal file
@ -0,0 +1,188 @@
|
||||
https://hackmd.io/JwIwDMDGKQZgtAFgKZjEgbARhPAhgKxZbwAcA7LAWOQCaKEgFA==?edit
|
||||
|
||||
Subscribablez(initState)
|
||||
.subscribe()
|
||||
.emitUpdate(newState)
|
||||
//.getState()
|
||||
|
||||
|
||||
var initState = fromDisk()
|
||||
ReduxStore(reducer, initState)
|
||||
.reduce(action) -> .emitUpdate()
|
||||
|
||||
ReduxStore.subscribe(toDisk)
|
||||
|
||||
|
||||
### KeyChainManager / idStore 2.0 (maybe just in MetaMaskController)
|
||||
keychains: []
|
||||
getAllAccounts(cb)
|
||||
getAllKeychainViewStates(cb) -> returns [ KeyChainViewState]
|
||||
|
||||
#### Old idStore external methods, for feature parity:
|
||||
|
||||
- init(configManager)
|
||||
- setStore(ethStore)
|
||||
- getState()
|
||||
- getSelectedAddres()
|
||||
- setSelectedAddress()
|
||||
- createNewVault()
|
||||
- recoverFromSeed()
|
||||
- submitPassword()
|
||||
- approveTransaction()
|
||||
- cancelTransaction()
|
||||
- addUnconfirmedMessage(msgParams, cb)
|
||||
- signMessage()
|
||||
- cancelMessage()
|
||||
- setLocked()
|
||||
- clearSeedWordCache()
|
||||
- exportAccount()
|
||||
- revealAccount()
|
||||
- saveAccountLabel()
|
||||
- tryPassword()
|
||||
- recoverSeed()
|
||||
- getNetwork()
|
||||
|
||||
##### Of those methods
|
||||
|
||||
Where they should end up:
|
||||
|
||||
##### MetaMaskController
|
||||
|
||||
- getNetwork()
|
||||
|
||||
##### KeyChainManager
|
||||
|
||||
- init(configManager)
|
||||
- setStore(ethStore)
|
||||
- getState() // Deprecate for unidirectional flow
|
||||
- on('update', cb)
|
||||
- createNewVault(password)
|
||||
- getSelectedAddres()
|
||||
- setSelectedAddress()
|
||||
- submitPassword()
|
||||
- tryPassword()
|
||||
- approveTransaction()
|
||||
- cancelTransaction()
|
||||
- signMessage()
|
||||
- cancelMessage()
|
||||
- setLocked()
|
||||
- exportAccount()
|
||||
|
||||
##### Bip44 KeyChain
|
||||
|
||||
- getState() // Deprecate for unidirectional flow
|
||||
- on('update', cb)
|
||||
|
||||
If we adopt a ReactStore style unidirectional action dispatching data flow, these methods will be unified under a `dispatch` method, and rather than having a cb will emit an update to the UI:
|
||||
|
||||
- createNewKeyChain(entropy)
|
||||
- recoverFromSeed()
|
||||
- approveTransaction()
|
||||
- signMessage()
|
||||
- clearSeedWordCache()
|
||||
- exportAccount()
|
||||
- revealAccount()
|
||||
- saveAccountLabel()
|
||||
- recoverSeed()
|
||||
|
||||
Additional methods, new to this:
|
||||
- serialize()
|
||||
- Returns pojo with optional `secret` key whose contents will be encrypted with the users' password and salt when written to disk.
|
||||
- The isolation of secrets is to preserve performance when decrypting user data.
|
||||
- deserialize(pojo)
|
||||
|
||||
### KeyChain (ReduxStore?)
|
||||
// attributes
|
||||
@name
|
||||
|
||||
signTx(txParams, cb)
|
||||
signMsg(msg, cb)
|
||||
|
||||
getAddressList(cb)
|
||||
|
||||
getViewState(cb) -> returns KeyChainViewState
|
||||
|
||||
serialize(cb) -> obj
|
||||
deserialize(obj)
|
||||
|
||||
dispatch({ type: <str>, value: <pojo> })
|
||||
|
||||
|
||||
### KeyChainViewState
|
||||
// The serialized, renderable keychain data
|
||||
accountList: [],
|
||||
typeName: 'uPort',
|
||||
iconAddress: 'uport.gif',
|
||||
internal: {} // Subclass-defined metadata
|
||||
|
||||
### KeyChainReactComponent
|
||||
// takes a KeyChainViewState
|
||||
|
||||
// Subclasses of this:
|
||||
- KeyChainListItemComponent
|
||||
- KeyChainInitComponent - Maybe part of the List Item
|
||||
- KeyChainAccountHeaderComponent
|
||||
- KeyChainConfirmationComponent
|
||||
// Account list item, tx confirmation extra data (like a QR code),
|
||||
// Maybe an options screen, init screen,
|
||||
|
||||
how to send actions?
|
||||
emitAction(keychains.<id>.didInit)
|
||||
|
||||
|
||||
gimmeRemoteKeychain((err, remoteKeychain)=>
|
||||
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
KeyChainReactComponent({
|
||||
keychain
|
||||
})
|
||||
|
||||
Keychain:
|
||||
methods:{},
|
||||
cachedAccountList: [],
|
||||
name: '',
|
||||
|
||||
|
||||
CoinbaseKeychain
|
||||
getAccountList
|
||||
|
||||
|
||||
CoinbaseKeychainComponent
|
||||
isLoading = true
|
||||
keychain.getAccountList(()=>{
|
||||
isLoading=false
|
||||
accountList=accounts
|
||||
})
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
KeyChainViewState {
|
||||
attributes: {
|
||||
//mandatory:
|
||||
accountList: [],
|
||||
typeName: 'uPort',
|
||||
iconAddress: 'uport.gif',
|
||||
|
||||
internal: {
|
||||
// keychain-specific metadata
|
||||
proxyAddresses: {
|
||||
0xReal: '0xProxy'
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
// arbitrary, internal
|
||||
}
|
||||
}
|
||||
|
||||
## A note on the security of arbitrary action dispatchers
|
||||
|
||||
Since keychains will be dispatching actions that are then passed through the background process to be routed, we should not trust or require them to include their own keychain ID as a prefix to their action, but we should tack it on ourselves, so that no action dispatched by a KeyChainComponent ever reaches any KeyChain other than its own.
|
||||
|
137
gulpfile.js
137
gulpfile.js
@ -1,5 +1,6 @@
|
||||
var watchify = require('watchify')
|
||||
var browserify = require('browserify')
|
||||
var disc = require('disc')
|
||||
var gulp = require('gulp')
|
||||
var source = require('vinyl-source-stream')
|
||||
var buffer = require('vinyl-buffer')
|
||||
@ -10,7 +11,6 @@ var jsoneditor = require('gulp-json-editor')
|
||||
var zip = require('gulp-zip')
|
||||
var assign = require('lodash.assign')
|
||||
var livereload = require('gulp-livereload')
|
||||
var brfs = require('gulp-brfs')
|
||||
var del = require('del')
|
||||
var eslint = require('gulp-eslint')
|
||||
var fs = require('fs')
|
||||
@ -21,6 +21,7 @@ var replace = require('gulp-replace')
|
||||
var disclaimer = fs.readFileSync(path.join(__dirname, 'USER_AGREEMENT.md')).toString()
|
||||
var crypto = require('crypto')
|
||||
var hash = crypto.createHash('sha256')
|
||||
var mkdirp = require('mkdirp')
|
||||
|
||||
hash.update(disclaimer)
|
||||
var tosHash = hash.digest('hex')
|
||||
@ -33,7 +34,6 @@ var debug = gutil.env.debug
|
||||
gulp.task('dev:reload', function() {
|
||||
livereload.listen({
|
||||
port: 35729,
|
||||
// basePath: './dist/firefox/'
|
||||
})
|
||||
})
|
||||
|
||||
@ -46,6 +46,7 @@ gulp.task('copy:locales', copyTask({
|
||||
'./dist/firefox/_locales',
|
||||
'./dist/chrome/_locales',
|
||||
'./dist/edge/_locales',
|
||||
'./dist/opera/_locales',
|
||||
]
|
||||
}))
|
||||
gulp.task('copy:images', copyTask({
|
||||
@ -54,6 +55,7 @@ gulp.task('copy:images', copyTask({
|
||||
'./dist/firefox/images',
|
||||
'./dist/chrome/images',
|
||||
'./dist/edge/images',
|
||||
'./dist/opera/images',
|
||||
],
|
||||
}))
|
||||
gulp.task('copy:fonts', copyTask({
|
||||
@ -62,6 +64,7 @@ gulp.task('copy:fonts', copyTask({
|
||||
'./dist/firefox/fonts',
|
||||
'./dist/chrome/fonts',
|
||||
'./dist/edge/fonts',
|
||||
'./dist/opera/fonts',
|
||||
],
|
||||
}))
|
||||
gulp.task('copy:reload', copyTask({
|
||||
@ -70,6 +73,7 @@ gulp.task('copy:reload', copyTask({
|
||||
'./dist/firefox/scripts',
|
||||
'./dist/chrome/scripts',
|
||||
'./dist/edge/scripts',
|
||||
'./dist/opera/scripts',
|
||||
],
|
||||
pattern: '/chromereload.js',
|
||||
}))
|
||||
@ -79,6 +83,7 @@ gulp.task('copy:root', copyTask({
|
||||
'./dist/firefox',
|
||||
'./dist/chrome',
|
||||
'./dist/edge',
|
||||
'./dist/opera',
|
||||
],
|
||||
pattern: '/*',
|
||||
}))
|
||||
@ -92,6 +97,21 @@ gulp.task('manifest:chrome', function() {
|
||||
.pipe(gulp.dest('./dist/chrome', { overwrite: true }))
|
||||
})
|
||||
|
||||
gulp.task('manifest:opera', function() {
|
||||
return gulp.src('./dist/opera/manifest.json')
|
||||
.pipe(jsoneditor(function(json) {
|
||||
json.permissions = [
|
||||
"storage",
|
||||
"tabs",
|
||||
"clipboardWrite",
|
||||
"clipboardRead",
|
||||
"http://localhost:8545/"
|
||||
]
|
||||
return json
|
||||
}))
|
||||
.pipe(gulp.dest('./dist/opera', { overwrite: true }))
|
||||
})
|
||||
|
||||
gulp.task('manifest:production', function() {
|
||||
return gulp.src([
|
||||
'./dist/firefox/manifest.json',
|
||||
@ -118,7 +138,7 @@ if (!disableLiveReload) {
|
||||
copyStrings.push('copy:reload')
|
||||
}
|
||||
|
||||
gulp.task('copy', gulp.series(gulp.parallel(...copyStrings), 'manifest:production', 'manifest:chrome'))
|
||||
gulp.task('copy', gulp.series(gulp.parallel(...copyStrings), 'manifest:production', 'manifest:chrome', 'manifest:opera'))
|
||||
gulp.task('copy:watch', function(){
|
||||
gulp.watch(['./app/{_locales,images}/*', './app/scripts/chromereload.js', './app/*.{html,json}'], gulp.series('copy'))
|
||||
})
|
||||
@ -152,18 +172,27 @@ const jsFiles = [
|
||||
'popup',
|
||||
]
|
||||
|
||||
// bundle tasks
|
||||
|
||||
var jsDevStrings = jsFiles.map(jsFile => `dev:js:${jsFile}`)
|
||||
var jsBuildStrings = jsFiles.map(jsFile => `build:js:${jsFile}`)
|
||||
|
||||
jsFiles.forEach((jsFile) => {
|
||||
gulp.task(`dev:js:${jsFile}`, bundleTask({ watch: true, filename: `${jsFile}.js` }))
|
||||
gulp.task(`build:js:${jsFile}`, bundleTask({ watch: false, filename: `${jsFile}.js` }))
|
||||
gulp.task(`dev:js:${jsFile}`, bundleTask({ watch: true, label: jsFile, filename: `${jsFile}.js` }))
|
||||
gulp.task(`build:js:${jsFile}`, bundleTask({ watch: false, label: jsFile, filename: `${jsFile}.js` }))
|
||||
})
|
||||
|
||||
gulp.task('dev:js', gulp.parallel(...jsDevStrings))
|
||||
|
||||
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', gulp.parallel(jsFiles.map(jsFile => `disc:${jsFile}`)))
|
||||
|
||||
|
||||
// clean dist
|
||||
|
||||
@ -173,22 +202,11 @@ gulp.task('clean', function clean() {
|
||||
})
|
||||
|
||||
// zip tasks for distribution
|
||||
gulp.task('zip:chrome', () => {
|
||||
return gulp.src('dist/chrome/**')
|
||||
.pipe(zip(`metamask-chrome-${manifest.version}.zip`))
|
||||
.pipe(gulp.dest('builds'));
|
||||
})
|
||||
gulp.task('zip:firefox', () => {
|
||||
return gulp.src('dist/firefox/**')
|
||||
.pipe(zip(`metamask-firefox-${manifest.version}.zip`))
|
||||
.pipe(gulp.dest('builds'));
|
||||
})
|
||||
gulp.task('zip:edge', () => {
|
||||
return gulp.src('dist/edge/**')
|
||||
.pipe(zip(`metamask-edge-${manifest.version}.zip`))
|
||||
.pipe(gulp.dest('builds'));
|
||||
})
|
||||
gulp.task('zip', gulp.parallel('zip:chrome', 'zip:firefox', 'zip:edge'))
|
||||
gulp.task('zip:chrome', zipTask('chrome'))
|
||||
gulp.task('zip:firefox', zipTask('firefox'))
|
||||
gulp.task('zip:edge', zipTask('edge'))
|
||||
gulp.task('zip:opera', zipTask('opera'))
|
||||
gulp.task('zip', gulp.parallel('zip:chrome', 'zip:firefox', 'zip:edge', 'zip:opera'))
|
||||
|
||||
// high level tasks
|
||||
|
||||
@ -218,21 +236,65 @@ function copyTask(opts){
|
||||
}
|
||||
}
|
||||
|
||||
function bundleTask(opts) {
|
||||
function zipTask(target) {
|
||||
return () => {
|
||||
return gulp.src(`dist/${target}/**`)
|
||||
.pipe(zip(`metamask-${target}-${manifest.version}.zip`))
|
||||
.pipe(gulp.dest('builds'));
|
||||
}
|
||||
}
|
||||
|
||||
function generateBundler(opts) {
|
||||
var browserifyOpts = assign({}, watchify.args, {
|
||||
entries: ['./app/scripts/'+opts.filename],
|
||||
debug: true,
|
||||
plugin: 'browserify-derequire',
|
||||
debug: debug,
|
||||
fullPaths: debug,
|
||||
})
|
||||
|
||||
var bundler = browserify(browserifyOpts)
|
||||
bundler.transform('brfs')
|
||||
return browserify(browserifyOpts)
|
||||
}
|
||||
|
||||
function discTask(opts) {
|
||||
let bundler = generateBundler(opts)
|
||||
|
||||
if (opts.watch) {
|
||||
bundler = watchify(bundler)
|
||||
bundler.on('update', performBundle) // on any dep update, runs the bundler
|
||||
// on any dep update, runs the bundler
|
||||
bundler.on('update', performBundle)
|
||||
}
|
||||
|
||||
bundler.on('log', gutil.log) // output build logs to terminal
|
||||
// output build logs to terminal
|
||||
bundler.on('log', gutil.log)
|
||||
|
||||
return performBundle
|
||||
|
||||
function performBundle(){
|
||||
// start "disc" build
|
||||
let discDir = path.join(__dirname, 'disc')
|
||||
mkdirp.sync(discDir)
|
||||
let discPath = path.join(discDir, `${opts.label}.html`)
|
||||
|
||||
return (
|
||||
bundler.bundle()
|
||||
.pipe(disc())
|
||||
.pipe(fs.createWriteStream(discPath))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function bundleTask(opts) {
|
||||
let bundler = generateBundler(opts)
|
||||
|
||||
if (opts.watch) {
|
||||
bundler = watchify(bundler)
|
||||
// on any file update, re-runs the bundler
|
||||
bundler.on('update', performBundle)
|
||||
}
|
||||
|
||||
// output build logs to terminal
|
||||
bundler.on('log', gutil.log)
|
||||
|
||||
return performBundle
|
||||
|
||||
@ -242,20 +304,25 @@ function bundleTask(opts) {
|
||||
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))
|
||||
.pipe(brfs())
|
||||
// inject variables into bundle
|
||||
.pipe(replace('GULP_TOS_HASH', tosHash))
|
||||
.pipe(replace('\'GULP_METAMASK_DEBUG\'', debug))
|
||||
// optional, remove if you don't need to buffer file contents
|
||||
// buffer file contents (?)
|
||||
.pipe(buffer())
|
||||
// optional, remove if you dont want sourcemaps
|
||||
.pipe(sourcemaps.init({loadMaps: true})) // loads map from browserify file
|
||||
// Add transformation tasks to the pipeline here.
|
||||
.pipe(sourcemaps.write('./')) // writes .map file
|
||||
// sourcemaps
|
||||
// loads map from browserify file
|
||||
.pipe(sourcemaps.init({loadMaps: true}))
|
||||
// writes .map file
|
||||
.pipe(sourcemaps.write('./'))
|
||||
// write completed bundles
|
||||
.pipe(gulp.dest('./dist/firefox/scripts'))
|
||||
.pipe(gulp.dest('./dist/chrome/scripts'))
|
||||
.pipe(gulp.dest('./dist/edge/scripts'))
|
||||
.pipe(gulpif(!disableLiveReload,livereload()))
|
||||
.pipe(gulp.dest('./dist/opera/scripts'))
|
||||
// finally, trigger live reload
|
||||
.pipe(gulpif(!disableLiveReload, livereload()))
|
||||
|
||||
)
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ function initializeZeroClient() {
|
||||
// User confirmation callbacks:
|
||||
showUnconfirmedMessage,
|
||||
unlockAccountMessage,
|
||||
showUnconfirmedTx,
|
||||
showUnapprovedTx,
|
||||
// Persistence Methods:
|
||||
setData,
|
||||
loadData,
|
||||
@ -36,8 +36,8 @@ function initializeZeroClient() {
|
||||
console.log('notif stub - showUnconfirmedMessage')
|
||||
}
|
||||
|
||||
function showUnconfirmedTx (txParams, txData, onTxDoneCb) {
|
||||
console.log('notif stub - showUnconfirmedTx')
|
||||
function showUnapprovedTx (txParams, txData, onTxDoneCb) {
|
||||
console.log('notif stub - showUnapprovedTx')
|
||||
}
|
||||
|
||||
//
|
||||
@ -156,4 +156,4 @@ function initializeZeroClient() {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ const extension = require('./development/mockExtension')
|
||||
// Query String
|
||||
const qs = require('qs')
|
||||
let queryString = qs.parse(window.location.href.split('#')[1])
|
||||
let selectedView = queryString.view || 'terms'
|
||||
let selectedView = queryString.view || 'first time'
|
||||
const firstState = states[selectedView]
|
||||
updateQueryParams(selectedView)
|
||||
|
||||
@ -46,7 +46,7 @@ const controller = new MetamaskController({
|
||||
// User confirmation callbacks:
|
||||
showUnconfirmedMessage: noop,
|
||||
unlockAccountMessage: noop,
|
||||
showUnconfirmedTx: noop,
|
||||
showUnapprovedTx: noop,
|
||||
// Persistence Methods:
|
||||
setData,
|
||||
loadData,
|
||||
@ -107,7 +107,7 @@ function getOldStyleData () {
|
||||
return result
|
||||
}
|
||||
|
||||
actions._setAccountManager(controller.getApi())
|
||||
actions._setBackgroundConnection(controller.getApi())
|
||||
actions.update = function(stateName) {
|
||||
selectedView = stateName
|
||||
updateQueryParams(stateName)
|
||||
|
27
package.json
27
package.json
@ -4,19 +4,21 @@
|
||||
"public": false,
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "gulp dev",
|
||||
"start": "npm run dev",
|
||||
"lint": "gulp lint",
|
||||
"dev": "gulp dev",
|
||||
"buildCiUnits": "node test/integration/index.js",
|
||||
"dev": "gulp dev --debug",
|
||||
"disc": "gulp disc --debug",
|
||||
"dist": "gulp dist --disableLiveReload",
|
||||
"test": "npm run fastTest && npm run ci && npm run lint",
|
||||
"fastTest": "mocha --require test/helper.js --compilers js:babel-register --recursive \"test/unit/**/*.js\"",
|
||||
"fastTest": "METAMASK_ENV=test mocha --require test/helper.js --compilers js:babel-register --recursive \"test/unit/**/*.js\"",
|
||||
"watch": "mocha watch --compilers js:babel-register --recursive \"test/unit/**/*.js\"",
|
||||
"genStates": "node development/genStates.js",
|
||||
"ui": "npm run genStates && beefy ui-dev.js:bundle.js --live --open --index=./development/index.html --cwd ./",
|
||||
"mock": "beefy mock-dev.js:bundle.js --live --open --index=./development/index.html --cwd ./",
|
||||
"buildMock": "npm run genStates && browserify ./mock-dev.js -o ./development/bundle.js",
|
||||
"testem": "npm run buildMock && testem",
|
||||
"ci": "npm run buildMock && testem ci -P 2",
|
||||
"ci": "npm run buildMock && npm run buildCiUnits && testem ci -P 2",
|
||||
"announce": "node development/announcer.js",
|
||||
"generateNotice": "node development/notice-generator.js"
|
||||
},
|
||||
@ -35,19 +37,23 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"async": "^1.5.2",
|
||||
"bip39": "^2.2.0",
|
||||
"browser-passworder": "^2.0.3",
|
||||
"browserify-derequire": "^0.9.4",
|
||||
"clone": "^1.0.2",
|
||||
"copy-to-clipboard": "^2.0.0",
|
||||
"debounce": "^1.0.0",
|
||||
"denodeify": "^1.2.1",
|
||||
"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-lightwallet": "^2.3.3",
|
||||
"eth-query": "^1.0.3",
|
||||
"eth-store": "^1.1.0",
|
||||
"ethereumjs-tx": "^1.0.0",
|
||||
"ethereumjs-util": "^4.4.0",
|
||||
"ethereumjs-wallet": "^0.6.0",
|
||||
"express": "^4.14.0",
|
||||
"extension-link-enabler": "^1.0.0",
|
||||
"extensionizer": "^1.0.0",
|
||||
@ -57,16 +63,18 @@
|
||||
"iframe": "^1.0.0",
|
||||
"iframe-stream": "^1.0.2",
|
||||
"inject-css": "^0.1.1",
|
||||
"jazzicon": "1.1.5",
|
||||
"jazzicon": "^1.2.0",
|
||||
"menu-droppo": "^1.1.0",
|
||||
"metamask-logo": "^2.1.2",
|
||||
"mississippi": "^1.2.0",
|
||||
"mkdirp": "^0.5.1",
|
||||
"multiplex": "^6.7.0",
|
||||
"once": "^1.3.3",
|
||||
"ping-pong-stream": "^1.0.0",
|
||||
"pojo-migrator": "^2.1.0",
|
||||
"polyfill-crypto.getrandomvalues": "^1.0.0",
|
||||
"post-message-stream": "^1.0.0",
|
||||
"promise-filter": "^1.1.0",
|
||||
"pumpify": "^1.3.4",
|
||||
"qrcode-npm": "0.0.3",
|
||||
"react": "^15.0.2",
|
||||
@ -80,14 +88,15 @@
|
||||
"redux": "^3.0.5",
|
||||
"redux-logger": "^2.3.1",
|
||||
"redux-thunk": "^1.0.2",
|
||||
"request-promise": "^4.1.1",
|
||||
"sandwich-expando": "^1.0.5",
|
||||
"semaphore": "^1.0.5",
|
||||
"textarea-caret": "^3.0.1",
|
||||
"three.js": "^0.73.2",
|
||||
"through2": "^2.0.1",
|
||||
"valid-url": "^1.0.9",
|
||||
"vreme": "^3.0.2",
|
||||
"web3": "0.17.0-beta",
|
||||
"web3-provider-engine": "^8.1.14",
|
||||
"web3-provider-engine": "^8.4.0",
|
||||
"web3-stream-provider": "^2.0.6",
|
||||
"xtend": "^4.0.1"
|
||||
},
|
||||
@ -103,7 +112,6 @@
|
||||
"del": "^2.2.0",
|
||||
"fs-promise": "^1.0.0",
|
||||
"gulp": "github:gulpjs/gulp#4.0",
|
||||
"gulp-brfs": "^0.1.0",
|
||||
"gulp-if": "^2.0.1",
|
||||
"gulp-json-editor": "^2.2.1",
|
||||
"gulp-livereload": "^3.8.1",
|
||||
@ -112,6 +120,7 @@
|
||||
"gulp-util": "^3.0.7",
|
||||
"gulp-watch": "^4.3.5",
|
||||
"gulp-zip": "^3.2.0",
|
||||
"isomorphic-fetch": "^2.2.1",
|
||||
"jsdom": "^8.1.0",
|
||||
"jsdom-global": "^1.7.0",
|
||||
"jshint-stylish": "~0.1.5",
|
||||
|
@ -1,7 +1,7 @@
|
||||
function wait() {
|
||||
function wait(time) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
setTimeout(function() {
|
||||
resolve()
|
||||
}, 500)
|
||||
}, time * 3 || 1500)
|
||||
})
|
||||
}
|
||||
|
@ -12,10 +12,10 @@
|
||||
<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="tests.js"></script>
|
||||
<script src="bundle.js"></script>
|
||||
<script src="/testem.js"></script>
|
||||
|
||||
<iframe src="/development/index.html" height="500px" width="360px">
|
||||
<iframe src="/development/test.html" height="500px" width="360px">
|
||||
<p>Your browser does not support iframes</p>
|
||||
</iframe>
|
||||
</body>
|
||||
|
21
test/integration/index.js
Normal file
21
test/integration/index.js
Normal file
@ -0,0 +1,21 @@
|
||||
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, 'bundle.js')
|
||||
|
||||
var b = browserify();
|
||||
|
||||
// Remove old bundle
|
||||
try {
|
||||
fs.unlinkSync(bundlePath)
|
||||
} catch (e) {}
|
||||
|
||||
var writeStream = fs.createWriteStream(bundlePath)
|
||||
|
||||
tests.forEach(function(fileName) {
|
||||
b.add(path.join(__dirname, 'lib', fileName))
|
||||
})
|
||||
|
||||
b.bundle().pipe(writeStream);
|
||||
|
90
test/integration/lib/first-time.js
Normal file
90
test/integration/lib/first-time.js
Normal file
@ -0,0 +1,90 @@
|
||||
const PASSWORD = 'password123'
|
||||
|
||||
QUnit.module('first time usage')
|
||||
|
||||
QUnit.test('agree to terms', function (assert) {
|
||||
var done = assert.async()
|
||||
let app
|
||||
|
||||
wait().then(function() {
|
||||
app = $('iframe').contents().find('#app-content .mock-app-root')
|
||||
app.find('.markdown').prop('scrollTop', 100000000)
|
||||
return wait()
|
||||
|
||||
}).then(function() {
|
||||
|
||||
var title = app.find('h1').text()
|
||||
assert.equal(title, 'MetaMask', 'title screen')
|
||||
|
||||
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() {
|
||||
|
||||
var createButton = app.find('button.primary')[0]
|
||||
createButton.click()
|
||||
|
||||
return wait(1500)
|
||||
}).then(function() {
|
||||
|
||||
var terms = app.find('h3.terms-header')[0]
|
||||
assert.equal(terms.textContent, 'MetaMask Terms & Conditions', 'Showing TOS')
|
||||
|
||||
// Scroll through terms
|
||||
var scrollable = app.find('.markdown')[0]
|
||||
scrollable.scrollTop = scrollable.scrollHeight
|
||||
|
||||
return wait(10)
|
||||
}).then(function() {
|
||||
|
||||
var button = app.find('button')[0] // Agree button
|
||||
button.click()
|
||||
|
||||
return wait(1000)
|
||||
}).then(function() {
|
||||
|
||||
var created = app.find('h3')[0]
|
||||
assert.equal(created.textContent, 'Vault Created', 'Vault created screen')
|
||||
|
||||
var button = app.find('button')[0] // Agree button
|
||||
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 lock = sandwich.children[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.')
|
||||
|
||||
done()
|
||||
})
|
||||
})
|
91
test/integration/lib/idStore-migrator-test.js
Normal file
91
test/integration/lib/idStore-migrator-test.js
Normal file
@ -0,0 +1,91 @@
|
||||
var ConfigManager = require('../../../app/scripts/lib/config-manager')
|
||||
var IdStoreMigrator = require('../../../app/scripts/lib/idStore-migrator')
|
||||
var SimpleKeyring = require('../../../app/scripts/keyrings/simple')
|
||||
var normalize = require('../../../app/scripts/lib/sig-util').normalize
|
||||
|
||||
var oldStyleVault = require('../mocks/oldVault.json')
|
||||
var badStyleVault = require('../mocks/badVault.json')
|
||||
|
||||
var STORAGE_KEY = 'metamask-config'
|
||||
var PASSWORD = '12345678'
|
||||
var FIRST_ADDRESS = '0x4dd5d356c5A016A220bCD69e82e5AF680a430d00'.toLowerCase()
|
||||
var SEED = 'fringe damage bounce extend tunnel afraid alert sound all soldier all dinner'
|
||||
|
||||
var BAD_STYLE_FIRST_ADDRESS = '0xac39b311dceb2a4b2f5d8461c1cdaf756f4f7ae9'
|
||||
|
||||
QUnit.module('Old Style Vaults', {
|
||||
beforeEach: function () {
|
||||
window.localStorage[STORAGE_KEY] = JSON.stringify(oldStyleVault)
|
||||
|
||||
this.configManager = new ConfigManager({
|
||||
loadData: () => { return JSON.parse(window.localStorage[STORAGE_KEY]) },
|
||||
setData: (data) => { window.localStorage[STORAGE_KEY] = JSON.stringify(data) },
|
||||
})
|
||||
|
||||
this.migrator = new IdStoreMigrator({
|
||||
configManager: this.configManager,
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
QUnit.test('migrator:isInitialized', function (assert) {
|
||||
assert.ok(this.migrator)
|
||||
})
|
||||
|
||||
QUnit.test('migrator:migratedVaultForPassword', function (assert) {
|
||||
var done = assert.async()
|
||||
|
||||
this.migrator.migratedVaultForPassword(PASSWORD)
|
||||
.then((result) => {
|
||||
const { serialized, lostAccounts } = result
|
||||
assert.equal(serialized.data.mnemonic, SEED, 'seed phrase recovered')
|
||||
assert.equal(lostAccounts.length, 0, 'no lost accounts')
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
QUnit.module('Old Style Vaults with bad HD seed', {
|
||||
beforeEach: function () {
|
||||
window.localStorage[STORAGE_KEY] = JSON.stringify(badStyleVault)
|
||||
|
||||
this.configManager = new ConfigManager({
|
||||
loadData: () => { return JSON.parse(window.localStorage[STORAGE_KEY]) },
|
||||
setData: (data) => { window.localStorage[STORAGE_KEY] = JSON.stringify(data) },
|
||||
})
|
||||
|
||||
this.migrator = new IdStoreMigrator({
|
||||
configManager: this.configManager,
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
QUnit.test('migrator:migratedVaultForPassword', function (assert) {
|
||||
var done = assert.async()
|
||||
|
||||
this.migrator.migratedVaultForPassword(PASSWORD)
|
||||
.then((result) => {
|
||||
const { serialized, lostAccounts } = result
|
||||
|
||||
assert.equal(lostAccounts.length, 1, 'one lost account')
|
||||
assert.equal(lostAccounts[0].address, '0xe15D894BeCB0354c501AE69429B05143679F39e0'.toLowerCase())
|
||||
assert.ok(lostAccounts[0].privateKey, 'private key exported')
|
||||
|
||||
var lostAccount = lostAccounts[0]
|
||||
var privateKey = lostAccount.privateKey
|
||||
|
||||
var simple = new SimpleKeyring()
|
||||
simple.deserialize([privateKey])
|
||||
.then(() => {
|
||||
return simple.getAccounts()
|
||||
})
|
||||
.then((accounts) => {
|
||||
assert.equal(normalize(accounts[0]), lostAccount.address, 'recovered address.')
|
||||
done()
|
||||
})
|
||||
.catch((reason) => {
|
||||
assert.ifError(reason)
|
||||
done(reason)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
1
test/integration/mocks/badVault.json
Normal file
1
test/integration/mocks/badVault.json
Normal file
@ -0,0 +1 @@
|
||||
{"meta":{"version":4},"data":{"fiatCurrency":"USD","conversionRate":8.34908448,"conversionDate":1481227505,"isConfirmed":true,"wallet":"{\"encSeed\":{\"encStr\":\"Te2KyAGY3S01bgUJ+7d4y3BOvr/8TKrXrkRZ29cGI6dgyedtN+YgTQxElC2td/pzuoXm7KeSfr+yAoFCvMgqFAJwRcX3arHOsMFQie8kp8mL5I65zwdg/HB2QecB4OJHytrxgApv2zZiKEo0kbu2cs8zYIn5wNlCBIHwgylYmHpUDIJcO1B4zg==\",\"nonce\":\"xnxqk4iy70bjt721F+KPLV4PNfBFNyct\"},\"ksData\":{\"m/44'/60'/0'/0\":{\"info\":{\"curve\":\"secp256k1\",\"purpose\":\"sign\"},\"encHdPathPriv\":{\"encStr\":\"vNrSjekRKLmaGFf77Uca9+aAebmDlvrBwtAV8YthpQ4OX/mXtLSycmnLsYdk4schaByfJvrm6/Mf9fxzOSaScJk+XvKw5XqNXedkDHtbWrmNnxFpuT+9tuB8Nupr3D9GZK9PgXhJD99/7Bn6Wk7/ne+PIDmbtdmx/SWmrdo3pg==\",\"nonce\":\"zqWq/gtJ5zfUVRWQQJkP/zoYjer6Rozj\"},\"hdIndex\":1,\"encPrivKeys\":{\"e15d894becb0354c501ae69429b05143679f39e0\":{\"key\":\"jBLQ9v1l5LOEY1C3kI8z7LpbJKHP1vpVfPAlz90MNSfa8Oe+XlxKQAGYs8Zb4fWm\",\"nonce\":\"fJyrSRo1t0RMNqp2MsneoJnYJWHQnSVY\"}},\"addresses\":[\"e15d894becb0354c501ae69429b05143679f39e0\"]}},\"encHdRootPriv\":{\"encStr\":\"mbvwiFBQGbjj4BJLmdeYzfYi8jb7gtFtwiCQOPfvmyz4h2/KMbHNGzumM16qRKpifioQXkhnBulMIQHaYg0Jwv1MoFsqHxHmuIAT+QP5XvJjz0MRl6708pHowmIVG+R8CZNTLqzE7XS8YkZ4ElRpTvLEM8Wngi5Sg287mQMP9w==\",\"nonce\":\"i5Tp2lQe92rXQzNhjZcu9fNNhfux6Wf4\"},\"salt\":\"FQpA8D9R/5qSp9WtQ94FILyfWZHMI6YZw6RmBYqK0N0=\",\"version\":2}","config":{"provider":{"type":"testnet"},"selectedAccount":"0xe15d894becb0354c501ae69429b05143679f39e0"},"isEthConfirmed":true,"transactions":[],"TOSHash":"a4f4e23f823a7ac51783e7ffba7914a911b09acdb97263296b7e14b527f80c5b","gasMultiplier":1}}
|
1
test/integration/mocks/badVault2.json
Normal file
1
test/integration/mocks/badVault2.json
Normal file
@ -0,0 +1 @@
|
||||
{"meta":{"version":4},"data":{"fiatCurrency":"USD","isConfirmed":true,"TOSHash":"a4f4e23f823a7ac51783e7ffba7914a911b09acdb97263296b7e14b527f80c5b","noticesList":[{"read":true,"date":"Fri Dec 16 2016","title":"Ending Morden Support","body":"Due to [recent events](https://blog.ethereum.org/2016/11/20/from-morden-to-ropsten/), MetaMask is now deprecating support for the Morden Test Network.\n\nUsers will still be able to access Morden through a locally hosted node, but we will no longer be providing hosted access to this network through [Infura](http://infura.io/).\n\nPlease use the new Ropsten Network as your new default test network.\n\nYou can fund your Ropsten account using the buy button on your account page.\n\nBest wishes!\nThe MetaMask Team\n\n","id":0}],"conversionRate":7.07341909,"conversionDate":1482539284,"wallet":"{\"encSeed\":{\"encStr\":\"LZsdN8lJzYkUe1UpmAalnERdgkBFt25gWDdK8kfQUwMAk/27XR+dc+8n5swgoF5qgwhc9LBgliEGNDs1Q/lnuld3aQLabkOeAW4BHS1vS7FxqKrzDS3iyzSuQO6wDQmGno/buuknVgDsKiyjW22hpt7vtVVWA+ZL1P3x6M0+AxGJjeGVrG+E8Q==\",\"nonce\":\"T6O9BmwmTj214XUK3KF0s3iCKo3OlrUD\"},\"ksData\":{\"m/44'/60'/0'/0\":{\"info\":{\"curve\":\"secp256k1\",\"purpose\":\"sign\"},\"encHdPathPriv\":{\"encStr\":\"GNNfZevCMlgMVh9y21y1UwrC9qcmH6XYq7v+9UoqbHnzPQJFlxidN5+x/Sldo72a6+5zJpQkkdZ+Q0lePrzvXfuSd3D/RO7WKFIKo9nAQI5+JWwz4INuCmVcmqCv2J4BTLGjrG8fp5pDJ62Bn0XHqkJo3gx3fpvs3cS66+ZKwg==\",\"nonce\":\"HRTlGj44khQs2veYHEF/GqTI1t0yYvyd\"},\"hdIndex\":3,\"encPrivKeys\":{\"e15d894becb0354c501ae69429b05143679f39e0\":{\"key\":\"ZAeZL9VcRUtiiO4VXOQKBFg787PR5R3iymjUeU5vpDRIqOXbjWN6N4ZNR8YpSXl+\",\"nonce\":\"xLsADagS8uqDYae6cImyhxF7o1kBDbPe\"},\"87658c15aefe7448008a28513a11b6b130ef4cd0\":{\"key\":\"ku0mm5s1agRJNAMYIJO0qeoDe+FqcbqdQI6azXF3GL1OLo6uMlt6I4qS+eeravFi\",\"nonce\":\"xdGfSUPKtkW8ge0SWIbbpahs/NyEMzn5\"},\"aa25854c0379e53c957ac9382e720c577fa31fd5\":{\"key\":\"NjpYC9FbiC95CTx/1kwgOHk5LSN9vl4RULEBbvwfVOjqSH8WixNoP3R6I/QyNIs2\",\"nonce\":\"M/HWpXXA9QvuZxEykkGQPJKKdz33ovQr\"}},\"addresses\":[\"e15d894becb0354c501ae69429b05143679f39e0\",\"87658c15aefe7448008a28513a11b6b130ef4cd0\",\"aa25854c0379e53c957ac9382e720c577fa31fd5\"]}},\"encHdRootPriv\":{\"encStr\":\"f+3prUOzl+95aNAV+ad6lZdsYZz120ZsL67ucjj3tiMXf/CC4X8XB9N2QguhoMy6fW+fATUsTdJe8+CbAAyb79V9HY0Pitzq9Yw/g1g0/Ii2JzsdGBriuMsPdwZSVqz+rvQFw/6Qms1xjW6cqa8S7kM2WA5l8RB1Ck6r5zaqbA==\",\"nonce\":\"oGahxNFekVxH9sg6PUCCHIByvo4WFSqm\"},\"salt\":\"N7xYoEA53yhSweOsEphku1UKkIEuZtX2MwLBhVM6RR8=\",\"version\":2}","config":{"provider":{"type":"testnet"},"selectedAccount":"0xe15d894becb0354c501ae69429b05143679f39e0"},"isDisclaimerConfirmed":true,"walletNicknames":{"0xac39b311dceb2a4b2f5d8461c1cdaf756f4f7ae9":"Account 1","0xd7c0cd9e7d2701c710d64fc492c7086679bdf7b4":"Account 2","0x1acfb961c5a8268eac8e09d6241a26cbeff42241":"Account 3"},"lostAccounts":["0xe15d894becb0354c501ae69429b05143679f39e0","0x87658c15aefe7448008a28513a11b6b130ef4cd0","0xaa25854c0379e53c957ac9382e720c577fa31fd5"]}}
|
21
test/integration/mocks/oldVault.json
Normal file
21
test/integration/mocks/oldVault.json
Normal file
@ -0,0 +1,21 @@
|
||||
{
|
||||
"meta": {
|
||||
"version": 4
|
||||
},
|
||||
"data": {
|
||||
"fiatCurrency": "USD",
|
||||
"isConfirmed": true,
|
||||
"TOSHash": "a4f4e23f823a7ac51783e7ffba7914a911b09acdb97263296b7e14b527f80c5b",
|
||||
"conversionRate": 9.47316629,
|
||||
"conversionDate": 1479510994,
|
||||
"wallet": "{\"encSeed\":{\"encStr\":\"a5tjKtDGlHkua+6Ta5s3wMFWPmsBqaPdMKGmqeI2z1kMbNs3V03HBaCptU7NtMra1DjHKbSNsUToxFUrmrvWBmUejamN16+l1CviwqASsv7kKzpot00/dfyyJgtZwwFP5Je+TAB1V231nRbPidOfeE1cDec5V8KTF8epl6qzsbA25pjeW76Dfw==\",\"nonce\":\"RzID6bAhWfGTSR74xdIh3RaT1+1sLk6F\"},\"ksData\":{\"m/44'/60'/0'/0\":{\"info\":{\"curve\":\"secp256k1\",\"purpose\":\"sign\"},\"encHdPathPriv\":{\"encStr\":\"6nlYAopRbmGcqerRZO08XwgeYaCJg9XRhh4oiYiVVdQtyNPdxvOI9TcE/mqvBiatMwBwA+TmsqTV6eZZe/VDZKYIGajKulQbScd0xQ71JhYfqqmzSG6EH2Pnzwa+aSAsfARgN1JJSaff2+p6wV6Zg5BUDtl72OGEIEfXhcUGwg==\",\"nonce\":\"Ee1KiDqtx7NvYToQUFvjEhKNinNQcXlK\"},\"hdIndex\":1,\"encPrivKeys\":{\"4dd5d356c5a016a220bcd69e82e5af680a430d00\":{\"key\":\"htGRGAH10lGF4M+fvioznmYVIUSWAzwp/yWSIo85psgZZwmCdJY72oyGanYsrFO8\",\"nonce\":\"PkP8XeZ+ok215rzEorvJu9nYTWzkOVr0\"}},\"addresses\":[\"4dd5d356c5a016a220bcd69e82e5af680a430d00\"]}},\"encHdRootPriv\":{\"encStr\":\"TAZAo71a+4IlAaoA66f0w4ts2f+V7ArTSUHRIrMltfAPXz7GfJBmKXNtHPORUYAjRiKqWK6FZnhKLf7Vcng2LG7VnDQwC4xPxzSRZzSEilnoY3V+zRY0HD7Wb/pndb4FliA/buZQmjohO4vezeX0hl70rJlPJEZTyYoWgxbxFA==\",\"nonce\":\"FlJOaLyBEHMaH5fEnYjdHc6nn18+WkRj\"},\"salt\":\"CmuCcWpbqpKUUv+1aE2ZwvQl7EIQ731uFibSq++vwtY=\",\"version\":2}",
|
||||
"config": {
|
||||
"provider": {
|
||||
"type": "testnet"
|
||||
},
|
||||
"selectedAccount": "0x4dd5d356c5a016a220bcd69e82e5af680a430d00"
|
||||
},
|
||||
"showSeedWords": false,
|
||||
"isEthConfirmed": true
|
||||
}
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
QUnit.test('agree to terms', function (assert) {
|
||||
var done = assert.async()
|
||||
|
||||
// Select the mock app root
|
||||
var app = $('iframe').contents().find('#app-content .mock-app-root')
|
||||
|
||||
app.find('.markdown').prop('scrollTop', 100000000)
|
||||
|
||||
wait().then(function() {
|
||||
app.find('button').click()
|
||||
}).then(function() {
|
||||
return wait()
|
||||
}).then(function() {
|
||||
var title = app.find('h1').text()
|
||||
assert.equal(title, 'MetaMask', 'title screen')
|
||||
|
||||
var buttons = app.find('button')
|
||||
assert.equal(buttons.length, 2, 'two buttons: create and restore')
|
||||
|
||||
done()
|
||||
})
|
||||
|
||||
// Wait for view to transition:
|
||||
})
|
@ -1,5 +1,5 @@
|
||||
var ConfigManager = require('../../app/scripts/lib/config-manager')
|
||||
const STORAGE_KEY = 'metamask-persistance-key'
|
||||
const STORAGE_KEY = 'metamask-config'
|
||||
const extend = require('xtend')
|
||||
|
||||
module.exports = function() {
|
||||
@ -9,6 +9,7 @@ module.exports = function() {
|
||||
function loadData () {
|
||||
var oldData = getOldStyleData()
|
||||
var newData
|
||||
|
||||
try {
|
||||
newData = JSON.parse(window.localStorage[STORAGE_KEY])
|
||||
} catch (e) {}
|
||||
|
32
test/lib/mock-encryptor.js
Normal file
32
test/lib/mock-encryptor.js
Normal file
@ -0,0 +1,32 @@
|
||||
var mockHex = '0xabcdef0123456789'
|
||||
var mockKey = new Buffer(32)
|
||||
let cacheVal
|
||||
|
||||
module.exports = {
|
||||
|
||||
encrypt(password, dataObj) {
|
||||
cacheVal = dataObj
|
||||
return Promise.resolve(mockHex)
|
||||
},
|
||||
|
||||
decrypt(password, text) {
|
||||
return Promise.resolve(cacheVal || {})
|
||||
},
|
||||
|
||||
encryptWithKey(key, dataObj) {
|
||||
return this.encrypt(key, dataObj)
|
||||
},
|
||||
|
||||
decryptWithKey(key, text) {
|
||||
return this.decrypt(key, text)
|
||||
},
|
||||
|
||||
keyFromPassword(password) {
|
||||
return Promise.resolve(mockKey)
|
||||
},
|
||||
|
||||
generateSalt() {
|
||||
return 'WHADDASALT!'
|
||||
},
|
||||
|
||||
}
|
38
test/lib/mock-simple-keychain.js
Normal file
38
test/lib/mock-simple-keychain.js
Normal file
@ -0,0 +1,38 @@
|
||||
var fakeWallet = {
|
||||
privKey: '0x123456788890abcdef',
|
||||
address: '0xfedcba0987654321',
|
||||
}
|
||||
const type = 'Simple Key Pair'
|
||||
|
||||
module.exports = class MockSimpleKeychain {
|
||||
|
||||
static type() { return type }
|
||||
|
||||
constructor(opts) {
|
||||
this.type = type
|
||||
this.opts = opts || {}
|
||||
this.wallets = []
|
||||
}
|
||||
|
||||
serialize() {
|
||||
return [ fakeWallet.privKey ]
|
||||
}
|
||||
|
||||
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++) {
|
||||
this.wallets.push(fakeWallet)
|
||||
}
|
||||
}
|
||||
|
||||
getAccounts() {
|
||||
return this.wallets.map(w => w.address)
|
||||
}
|
||||
|
||||
}
|
@ -1,60 +0,0 @@
|
||||
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'))
|
||||
|
||||
describe('#recoverFromSeed(password, seed)', function() {
|
||||
|
||||
beforeEach(function() {
|
||||
// sinon allows stubbing methods that are easily verified
|
||||
this.sinon = sinon.sandbox.create()
|
||||
})
|
||||
|
||||
afterEach(function() {
|
||||
// sinon requires cleanup otherwise it will overwrite context
|
||||
this.sinon.restore()
|
||||
})
|
||||
|
||||
// stub out account manager
|
||||
actions._setAccountManager({
|
||||
recoverFromSeed(pw, seed, cb) {
|
||||
cb(null, {
|
||||
identities: {
|
||||
foo: 'bar'
|
||||
}
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
it('sets metamask.isUnlocked to true', function() {
|
||||
var initialState = {
|
||||
metamask: {
|
||||
isUnlocked: false,
|
||||
isInitialized: false,
|
||||
}
|
||||
}
|
||||
freeze(initialState)
|
||||
|
||||
const restorePhrase = 'invite heavy among daring outdoor dice jelly coil stable note seat vicious'
|
||||
const password = 'foo'
|
||||
const dispatchFunc = actions.recoverFromSeed(password, restorePhrase)
|
||||
|
||||
var dispatchStub = this.sinon.stub()
|
||||
dispatchStub.withArgs({ TYPE: actions.unlockMetamask() }).onCall(0)
|
||||
dispatchStub.withArgs({ TYPE: actions.showAccountsPage() }).onCall(1)
|
||||
|
||||
var action
|
||||
var resultingState = initialState
|
||||
dispatchFunc((newAction) => {
|
||||
action = newAction
|
||||
resultingState = reducers(resultingState, action)
|
||||
})
|
||||
|
||||
assert.equal(resultingState.metamask.isUnlocked, true, 'was unlocked')
|
||||
assert.equal(resultingState.metamask.isInitialized, true, 'was initialized')
|
||||
});
|
||||
});
|
@ -44,6 +44,5 @@ describe('SHOW_ACCOUNT_DETAIL', function() {
|
||||
|
||||
var resultingState = reducers(initialState, action)
|
||||
assert.equal(resultingState.metamask.selectedAccount, action.value)
|
||||
assert.equal(resultingState.metamask.selectedAddress, action.value)
|
||||
})
|
||||
})
|
||||
|
@ -46,7 +46,7 @@ describe('tx confirmation screen', function() {
|
||||
describe('cancelTx', function() {
|
||||
|
||||
before(function(done) {
|
||||
actions._setAccountManager({
|
||||
actions._setBackgroundConnection({
|
||||
approveTransaction(txId, cb) { cb('An error!') },
|
||||
cancelTransaction(txId) { /* noop */ },
|
||||
clearSeedWordCache(cb) { cb() },
|
||||
@ -75,7 +75,7 @@ describe('tx confirmation screen', function() {
|
||||
before(function(done) {
|
||||
alert = () => {/* noop */}
|
||||
|
||||
actions._setAccountManager({
|
||||
actions._setBackgroundConnection({
|
||||
approveTransaction(txId, cb) { cb({message: 'An error!'}) },
|
||||
})
|
||||
|
||||
@ -96,7 +96,7 @@ describe('tx confirmation screen', function() {
|
||||
|
||||
describe('when there is success', function() {
|
||||
it('should complete tx and go home', function() {
|
||||
actions._setAccountManager({
|
||||
actions._setBackgroundConnection({
|
||||
approveTransaction(txId, cb) { cb() },
|
||||
})
|
||||
|
||||
@ -135,7 +135,7 @@ describe('tx confirmation screen', function() {
|
||||
}
|
||||
freeze(initialState)
|
||||
|
||||
actions._setAccountManager({
|
||||
actions._setBackgroundConnection({
|
||||
approveTransaction(txId, cb) { cb() },
|
||||
})
|
||||
|
||||
|
@ -1,8 +1,10 @@
|
||||
// polyfill fetch
|
||||
global.fetch = global.fetch || require('isomorphic-fetch')
|
||||
const assert = require('assert')
|
||||
const extend = require('xtend')
|
||||
const rp = require('request-promise')
|
||||
const nock = require('nock')
|
||||
var configManagerGen = require('../lib/mock-config-manager')
|
||||
const configManagerGen = require('../lib/mock-config-manager')
|
||||
const STORAGE_KEY = 'metamask-persistance-key'
|
||||
|
||||
describe('config-manager', function() {
|
||||
@ -100,31 +102,31 @@ describe('config-manager', function() {
|
||||
|
||||
describe('confirmation', function() {
|
||||
|
||||
describe('#getConfirmed', function() {
|
||||
describe('#getConfirmedDisclaimer', function() {
|
||||
it('should return false if no previous key exists', function() {
|
||||
var result = configManager.getConfirmed()
|
||||
var result = configManager.getConfirmedDisclaimer()
|
||||
assert.ok(!result)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#setConfirmed', function() {
|
||||
it('should make getConfirmed return true once set', function() {
|
||||
assert.equal(configManager.getConfirmed(), false)
|
||||
configManager.setConfirmed(true)
|
||||
var result = configManager.getConfirmed()
|
||||
describe('#setConfirmedDisclaimer', function() {
|
||||
it('should make getConfirmedDisclaimer return true once set', function() {
|
||||
assert.equal(configManager.getConfirmedDisclaimer(), false)
|
||||
configManager.setConfirmedDisclaimer(true)
|
||||
var result = configManager.getConfirmedDisclaimer()
|
||||
assert.equal(result, true)
|
||||
})
|
||||
|
||||
it('should be able to set false', function() {
|
||||
configManager.setConfirmed(false)
|
||||
var result = configManager.getConfirmed()
|
||||
configManager.setConfirmedDisclaimer(false)
|
||||
var result = configManager.getConfirmedDisclaimer()
|
||||
assert.equal(result, false)
|
||||
})
|
||||
|
||||
it('should persist to local storage', function() {
|
||||
configManager.setConfirmed(true)
|
||||
configManager.setConfirmedDisclaimer(true)
|
||||
var data = configManager.getData()
|
||||
assert.equal(data.isConfirmed, true)
|
||||
assert.equal(data.isDisclaimerConfirmed, true)
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -153,7 +155,7 @@ describe('config-manager', function() {
|
||||
rpcTarget: 'foobar'
|
||||
},
|
||||
}
|
||||
configManager.setConfirmed(true)
|
||||
configManager.setConfirmedDisclaimer(true)
|
||||
configManager.setConfig(testConfig)
|
||||
|
||||
var testWallet = {
|
||||
@ -164,7 +166,7 @@ describe('config-manager', function() {
|
||||
var result = configManager.getData()
|
||||
assert.equal(result.wallet.name, testWallet.name, 'wallet name is set')
|
||||
assert.equal(result.config.provider.rpcTarget, testConfig.provider.rpcTarget)
|
||||
assert.equal(configManager.getConfirmed(), true)
|
||||
assert.equal(configManager.getConfirmedDisclaimer(), true)
|
||||
|
||||
testConfig.provider.type = 'something else!'
|
||||
configManager.setConfig(testConfig)
|
||||
@ -173,7 +175,7 @@ describe('config-manager', function() {
|
||||
assert.equal(result.wallet.name, testWallet.name, 'wallet name is set')
|
||||
assert.equal(result.config.provider.rpcTarget, testConfig.provider.rpcTarget)
|
||||
assert.equal(result.config.provider.type, testConfig.provider.type)
|
||||
assert.equal(configManager.getConfirmed(), true)
|
||||
assert.equal(configManager.getConfirmedDisclaimer(), true)
|
||||
})
|
||||
})
|
||||
|
||||
@ -215,7 +217,7 @@ describe('config-manager', function() {
|
||||
|
||||
describe('transactions', function() {
|
||||
beforeEach(function() {
|
||||
configManager._saveTxList([])
|
||||
configManager.setTxList([])
|
||||
})
|
||||
|
||||
describe('#getTxList', function() {
|
||||
@ -226,90 +228,13 @@ describe('config-manager', function() {
|
||||
})
|
||||
})
|
||||
|
||||
describe('#_saveTxList', function() {
|
||||
describe('#setTxList', function() {
|
||||
it('saves the submitted data to the tx list', function() {
|
||||
var target = [{ foo: 'bar' }]
|
||||
configManager._saveTxList(target)
|
||||
configManager.setTxList(target)
|
||||
var result = configManager.getTxList()
|
||||
assert.equal(result[0].foo, 'bar')
|
||||
})
|
||||
})
|
||||
|
||||
describe('#addTx', function() {
|
||||
it('adds a tx returned in getTxList', function() {
|
||||
var tx = { id: 1 }
|
||||
configManager.addTx(tx)
|
||||
var result = configManager.getTxList()
|
||||
assert.ok(Array.isArray(result))
|
||||
assert.equal(result.length, 1)
|
||||
assert.equal(result[0].id, 1)
|
||||
})
|
||||
|
||||
it('cuts off early txs beyond a limit', function() {
|
||||
const limit = configManager.txLimit
|
||||
for (let i = 0; i < limit + 1; i++) {
|
||||
let tx = { id: i }
|
||||
configManager.addTx(tx)
|
||||
}
|
||||
var result = configManager.getTxList()
|
||||
assert.equal(result.length, limit, `limit of ${limit} txs enforced`)
|
||||
assert.equal(result[0].id, 1, 'early txs truncted')
|
||||
})
|
||||
})
|
||||
|
||||
describe('#confirmTx', function() {
|
||||
it('sets the tx status to confirmed', function() {
|
||||
var tx = { id: 1, status: 'unconfirmed' }
|
||||
configManager.addTx(tx)
|
||||
configManager.confirmTx(1)
|
||||
var result = configManager.getTxList()
|
||||
assert.ok(Array.isArray(result))
|
||||
assert.equal(result.length, 1)
|
||||
assert.equal(result[0].status, 'confirmed')
|
||||
})
|
||||
})
|
||||
|
||||
describe('#rejectTx', function() {
|
||||
it('sets the tx status to rejected', function() {
|
||||
var tx = { id: 1, status: 'unconfirmed' }
|
||||
configManager.addTx(tx)
|
||||
configManager.rejectTx(1)
|
||||
var result = configManager.getTxList()
|
||||
assert.ok(Array.isArray(result))
|
||||
assert.equal(result.length, 1)
|
||||
assert.equal(result[0].status, 'rejected')
|
||||
})
|
||||
})
|
||||
|
||||
describe('#updateTx', function() {
|
||||
it('replaces the tx with the same id', function() {
|
||||
configManager.addTx({ id: '1', status: 'unconfirmed' })
|
||||
configManager.addTx({ id: '2', status: 'confirmed' })
|
||||
configManager.updateTx({ id: '1', status: 'blah', hash: 'foo' })
|
||||
var result = configManager.getTx('1')
|
||||
assert.equal(result.hash, 'foo')
|
||||
})
|
||||
})
|
||||
|
||||
describe('#unconfirmedTxs', function() {
|
||||
it('returns unconfirmed txs in a hash', function() {
|
||||
configManager.addTx({ id: '1', status: 'unconfirmed' })
|
||||
configManager.addTx({ id: '2', status: 'confirmed' })
|
||||
let result = configManager.unconfirmedTxs()
|
||||
assert.equal(typeof result, 'object')
|
||||
assert.equal(result['1'].status, 'unconfirmed')
|
||||
assert.equal(result['0'], undefined)
|
||||
assert.equal(result['2'], undefined)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getTx', function() {
|
||||
it('returns a tx with the requested id', function() {
|
||||
configManager.addTx({ id: '1', status: 'unconfirmed' })
|
||||
configManager.addTx({ id: '2', status: 'confirmed' })
|
||||
assert.equal(configManager.getTx('1').status, 'unconfirmed')
|
||||
assert.equal(configManager.getTx('2').status, 'confirmed')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
146
test/unit/idStore-migration-test.js
Normal file
146
test/unit/idStore-migration-test.js
Normal file
@ -0,0 +1,146 @@
|
||||
const async = require('async')
|
||||
const assert = require('assert')
|
||||
const ethUtil = require('ethereumjs-util')
|
||||
const BN = ethUtil.BN
|
||||
const ConfigManager = require('../../app/scripts/lib/config-manager')
|
||||
const delegateCallCode = require('../lib/example-code.json').delegateCallCode
|
||||
|
||||
// The old way:
|
||||
const IdentityStore = require('../../app/scripts/lib/idStore')
|
||||
const STORAGE_KEY = 'metamask-config'
|
||||
const extend = require('xtend')
|
||||
|
||||
// The new ways:
|
||||
var KeyringController = require('../../app/scripts/keyring-controller')
|
||||
const mockEncryptor = require('../lib/mock-encryptor')
|
||||
const MockSimpleKeychain = require('../lib/mock-simple-keychain')
|
||||
const sinon = require('sinon')
|
||||
|
||||
const mockVault = {
|
||||
seed: 'picnic injury awful upper eagle junk alert toss flower renew silly vague',
|
||||
account: '0x5d8de92c205279c10e5669f797b853ccef4f739a',
|
||||
}
|
||||
|
||||
const badVault = {
|
||||
seed: 'radar blur cabbage chef fix engine embark joy scheme fiction master release',
|
||||
}
|
||||
|
||||
describe('IdentityStore to KeyringController migration', function() {
|
||||
|
||||
// The stars of the show:
|
||||
let idStore, keyringController, seedWords, configManager
|
||||
|
||||
let password = 'password123'
|
||||
let entropy = 'entripppppyy duuude'
|
||||
let accounts = []
|
||||
let newAccounts = []
|
||||
let originalKeystore
|
||||
|
||||
// This is a lot of setup, I know!
|
||||
// We have to create an old style vault, populate it,
|
||||
// and THEN create a new one, before we can run tests on it.
|
||||
beforeEach(function(done) {
|
||||
this.sinon = sinon.sandbox.create()
|
||||
window.localStorage = {} // Hacking localStorage support into JSDom
|
||||
configManager = new ConfigManager({
|
||||
loadData,
|
||||
setData: (d) => { window.localStorage = d }
|
||||
})
|
||||
|
||||
|
||||
idStore = new IdentityStore({
|
||||
configManager: configManager,
|
||||
ethStore: {
|
||||
addAccount(acct) { accounts.push(ethUtil.addHexPrefix(acct)) },
|
||||
del(acct) { delete accounts[acct] },
|
||||
},
|
||||
})
|
||||
|
||||
idStore._createVault(password, mockVault.seed, (err) => {
|
||||
assert.ifError(err, 'createNewVault threw error')
|
||||
originalKeystore = idStore._idmgmt.keyStore
|
||||
|
||||
idStore.setLocked((err) => {
|
||||
assert.ifError(err, 'createNewVault threw error')
|
||||
keyringController = new KeyringController({
|
||||
configManager,
|
||||
ethStore: {
|
||||
addAccount(acct) { newAccounts.push(ethUtil.addHexPrefix(acct)) },
|
||||
del(acct) { delete newAccounts[acct] },
|
||||
},
|
||||
txManager: {
|
||||
getTxList: () => [],
|
||||
getUnapprovedTxList: () => []
|
||||
},
|
||||
})
|
||||
|
||||
// Stub out the browser crypto for a mock encryptor.
|
||||
// Browser crypto is tested in the integration test suite.
|
||||
keyringController.encryptor = mockEncryptor
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('entering a password', function() {
|
||||
it('should identify an old wallet as an initialized keyring', function(done) {
|
||||
keyringController.configManager.setWallet('something')
|
||||
keyringController.getState()
|
||||
.then((state) => {
|
||||
assert(state.isInitialized, 'old vault counted as initialized.')
|
||||
assert(!state.lostAccounts, 'no lost accounts')
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
function loadData () {
|
||||
var oldData = getOldStyleData()
|
||||
var newData
|
||||
try {
|
||||
newData = JSON.parse(window.localStorage[STORAGE_KEY])
|
||||
} catch (e) {}
|
||||
|
||||
var data = extend({
|
||||
meta: {
|
||||
version: 0,
|
||||
},
|
||||
data: {
|
||||
config: {
|
||||
provider: {
|
||||
type: 'testnet',
|
||||
},
|
||||
},
|
||||
},
|
||||
}, oldData || null, newData || null)
|
||||
return data
|
||||
}
|
||||
|
||||
function setData (data) {
|
||||
window.localStorage[STORAGE_KEY] = JSON.stringify(data)
|
||||
}
|
||||
|
||||
function getOldStyleData () {
|
||||
var config, wallet, seedWords
|
||||
|
||||
var result = {
|
||||
meta: { version: 0 },
|
||||
data: {},
|
||||
}
|
||||
|
||||
try {
|
||||
config = JSON.parse(window.localStorage['config'])
|
||||
result.data.config = config
|
||||
} catch (e) {}
|
||||
try {
|
||||
wallet = JSON.parse(window.localStorage['lightwallet'])
|
||||
result.data.wallet = wallet
|
||||
} catch (e) {}
|
||||
try {
|
||||
seedWords = window.localStorage['seedWords']
|
||||
result.data.seedWords = seedWords
|
||||
} catch (e) {}
|
||||
|
||||
return result
|
||||
}
|
@ -11,7 +11,6 @@ describe('IdentityStore', function() {
|
||||
describe('#createNewVault', function () {
|
||||
let idStore
|
||||
let password = 'password123'
|
||||
let entropy = 'entripppppyy duuude'
|
||||
let seedWords
|
||||
let accounts = []
|
||||
let originalKeystore
|
||||
@ -26,7 +25,7 @@ describe('IdentityStore', function() {
|
||||
},
|
||||
})
|
||||
|
||||
idStore.createNewVault(password, entropy, (err, seeds) => {
|
||||
idStore.createNewVault(password, (err, seeds) => {
|
||||
assert.ifError(err, 'createNewVault threw error')
|
||||
seedWords = seeds
|
||||
originalKeystore = idStore._idmgmt.keyStore
|
||||
@ -140,54 +139,4 @@ describe('IdentityStore', function() {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('#addGasBuffer', function() {
|
||||
it('formats the result correctly', function() {
|
||||
const idStore = new IdentityStore({
|
||||
configManager: configManagerGen(),
|
||||
ethStore: {
|
||||
addAccount(acct) { accounts.push(ethUtil.addHexPrefix(acct)) },
|
||||
},
|
||||
})
|
||||
|
||||
const gas = '0x01'
|
||||
const bnGas = new BN(gas, 16)
|
||||
const bnResult = idStore.addGasBuffer(bnGas)
|
||||
|
||||
assert.ok(bnResult.gt(gas), 'added more gas as buffer.')
|
||||
})
|
||||
|
||||
it('buffers 20%', function() {
|
||||
const idStore = new IdentityStore({
|
||||
configManager: configManagerGen(),
|
||||
ethStore: {
|
||||
addAccount(acct) { accounts.push(ethUtil.addHexPrefix(acct)) },
|
||||
},
|
||||
})
|
||||
|
||||
const gas = '0x04ee59' // Actual estimated gas example
|
||||
const bnGas = new BN(ethUtil.stripHexPrefix(gas), 16)
|
||||
const five = new BN('5', 10)
|
||||
const correctBuffer = bnGas.div(five)
|
||||
const correct = bnGas.add(correctBuffer)
|
||||
|
||||
const bnResult = idStore.addGasBuffer(bnGas)
|
||||
|
||||
assert(bnResult.gt(bnGas), 'Estimate increased in value.')
|
||||
assert.equal(bnResult.sub(bnGas).toString(10), correctBuffer.toString(10), 'added 20% gas')
|
||||
})
|
||||
})
|
||||
|
||||
describe('#checkForDelegateCall', function() {
|
||||
const idStore = new IdentityStore({
|
||||
configManager: configManagerGen(),
|
||||
ethStore: {
|
||||
addAccount(acct) { accounts.push(ethUtil.addHexPrefix(acct)) },
|
||||
},
|
||||
})
|
||||
|
||||
var result = idStore.checkForDelegateCall(delegateCallCode)
|
||||
assert.equal(result, true, 'no delegate call in provided code')
|
||||
})
|
||||
|
||||
})
|
||||
|
189
test/unit/keyring-controller-test.js
Normal file
189
test/unit/keyring-controller-test.js
Normal file
@ -0,0 +1,189 @@
|
||||
var assert = require('assert')
|
||||
var KeyringController = require('../../app/scripts/keyring-controller')
|
||||
var configManagerGen = require('../lib/mock-config-manager')
|
||||
const ethUtil = require('ethereumjs-util')
|
||||
const BN = ethUtil.BN
|
||||
const async = require('async')
|
||||
const mockEncryptor = require('../lib/mock-encryptor')
|
||||
const MockSimpleKeychain = require('../lib/mock-simple-keychain')
|
||||
const sinon = require('sinon')
|
||||
|
||||
describe('KeyringController', function() {
|
||||
|
||||
let keyringController, state
|
||||
let password = 'password123'
|
||||
let seedWords = 'puzzle seed penalty soldier say clay field arctic metal hen cage runway'
|
||||
let addresses = ['eF35cA8EbB9669A35c31b5F6f249A9941a812AC1'.toLowerCase()]
|
||||
let accounts = []
|
||||
let originalKeystore
|
||||
|
||||
beforeEach(function(done) {
|
||||
this.sinon = sinon.sandbox.create()
|
||||
window.localStorage = {} // Hacking localStorage support into JSDom
|
||||
|
||||
keyringController = new KeyringController({
|
||||
configManager: configManagerGen(),
|
||||
txManager: {
|
||||
getTxList: () => [],
|
||||
getUnapprovedTxList: () => []
|
||||
},
|
||||
ethStore: {
|
||||
addAccount(acct) { accounts.push(ethUtil.addHexPrefix(acct)) },
|
||||
},
|
||||
})
|
||||
|
||||
// Stub out the browser crypto for a mock encryptor.
|
||||
// Browser crypto is tested in the integration test suite.
|
||||
keyringController.encryptor = mockEncryptor
|
||||
|
||||
keyringController.createNewVaultAndKeychain(password)
|
||||
.then(function (newState) {
|
||||
state = newState
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(function() {
|
||||
// Cleanup mocks
|
||||
this.sinon.restore()
|
||||
})
|
||||
|
||||
describe('#createNewVaultAndKeychain', function () {
|
||||
this.timeout(10000)
|
||||
|
||||
it('should set a vault on the configManager', function(done) {
|
||||
keyringController.configManager.setVault(null)
|
||||
assert(!keyringController.configManager.getVault(), 'no previous vault')
|
||||
keyringController.createNewVaultAndKeychain(password)
|
||||
.then(() => {
|
||||
const vault = keyringController.configManager.getVault()
|
||||
assert(vault, 'vault created')
|
||||
done()
|
||||
})
|
||||
.catch((reason) => {
|
||||
assert.ifError(reason)
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('#restoreKeyring', function() {
|
||||
|
||||
it(`should pass a keyring's serialized data back to the correct type.`, function(done) {
|
||||
const mockSerialized = {
|
||||
type: 'HD Key Tree',
|
||||
data: {
|
||||
mnemonic: seedWords,
|
||||
numberOfAccounts: 1,
|
||||
}
|
||||
}
|
||||
const mock = this.sinon.mock(keyringController)
|
||||
|
||||
mock.expects('getBalanceAndNickname')
|
||||
.exactly(1)
|
||||
|
||||
keyringController.restoreKeyring(mockSerialized)
|
||||
.then((keyring) => {
|
||||
assert.equal(keyring.wallets.length, 1, 'one wallet restored')
|
||||
return keyring.getAccounts()
|
||||
})
|
||||
.then((accounts) => {
|
||||
assert.equal(accounts[0], addresses[0])
|
||||
mock.verify()
|
||||
done()
|
||||
})
|
||||
.catch((reason) => {
|
||||
assert.ifError(reason)
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('#createNickname', function() {
|
||||
it('should add the address to the identities hash', function() {
|
||||
const fakeAddress = '0x12345678'
|
||||
keyringController.createNickname(fakeAddress)
|
||||
const identities = keyringController.identities
|
||||
const identity = identities[fakeAddress]
|
||||
assert.equal(identity.address, fakeAddress)
|
||||
|
||||
const nick = keyringController.configManager.nicknameForWallet(fakeAddress)
|
||||
assert.equal(typeof nick, 'string')
|
||||
})
|
||||
})
|
||||
|
||||
describe('#saveAccountLabel', function() {
|
||||
it ('sets the nickname', function(done) {
|
||||
const account = addresses[0]
|
||||
var nick = 'Test nickname'
|
||||
keyringController.identities[ethUtil.addHexPrefix(account)] = {}
|
||||
keyringController.saveAccountLabel(account, nick)
|
||||
.then((label) => {
|
||||
assert.equal(label, nick)
|
||||
const persisted = keyringController.configManager.nicknameForWallet(account)
|
||||
assert.equal(persisted, nick)
|
||||
done()
|
||||
})
|
||||
.catch((reason) => {
|
||||
assert.ifError(reason)
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
this.timeout(10000)
|
||||
it('retrieves the persisted nickname', function(done) {
|
||||
const account = addresses[0]
|
||||
var nick = 'Test nickname'
|
||||
keyringController.configManager.setNicknameForWallet(account, nick)
|
||||
keyringController.createNewVaultAndRestore(password, seedWords)
|
||||
.then((state) => {
|
||||
|
||||
const identity = keyringController.identities['0x' + account]
|
||||
assert.equal(identity.name, nick)
|
||||
|
||||
assert(accounts)
|
||||
done()
|
||||
})
|
||||
.catch((reason) => {
|
||||
assert.ifError(reason)
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getAccounts', function() {
|
||||
it('returns the result of getAccounts for each keyring', function() {
|
||||
keyringController.keyrings = [
|
||||
{ getAccounts() { return Promise.resolve([1,2,3]) } },
|
||||
{ getAccounts() { return Promise.resolve([4,5,6]) } },
|
||||
]
|
||||
|
||||
keyringController.getAccounts()
|
||||
.then((result) => {
|
||||
assert.deepEqual(result, [1,2,3,4,5,6])
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('#addGasBuffer', function() {
|
||||
it('adds 100k gas buffer to estimates', function() {
|
||||
|
||||
const gas = '0x04ee59' // Actual estimated gas example
|
||||
const tooBigOutput = '0x80674f9' // Actual bad output
|
||||
const bnGas = new BN(ethUtil.stripHexPrefix(gas), 16)
|
||||
const correctBuffer = new BN('100000', 10)
|
||||
const correct = bnGas.add(correctBuffer)
|
||||
|
||||
const tooBig = new BN(tooBigOutput, 16)
|
||||
const result = keyringController.addGasBuffer(gas)
|
||||
const bnResult = new BN(ethUtil.stripHexPrefix(result), 16)
|
||||
|
||||
assert.equal(result.indexOf('0x'), 0, 'included hex prefix')
|
||||
assert(bnResult.gt(bnGas), 'Estimate increased in value.')
|
||||
assert.equal(bnResult.sub(bnGas).toString(10), '100000', 'added 100k gas')
|
||||
assert.equal(result, '0x' + correct.toString(16), 'Added the right amount')
|
||||
assert.notEqual(result, tooBigOutput, 'not that bad estimate')
|
||||
})
|
||||
})
|
||||
})
|
127
test/unit/keyrings/hd-test.js
Normal file
127
test/unit/keyrings/hd-test.js
Normal file
@ -0,0 +1,127 @@
|
||||
const assert = require('assert')
|
||||
const extend = require('xtend')
|
||||
const HdKeyring = require('../../../app/scripts/keyrings/hd')
|
||||
|
||||
// Sample account:
|
||||
const privKeyHex = 'b8a9c05beeedb25df85f8d641538cbffedf67216048de9c678ee26260eb91952'
|
||||
|
||||
const sampleMnemonic = 'finish oppose decorate face calm tragic certain desk hour urge dinosaur mango'
|
||||
const firstAcct = '1c96099350f13d558464ec79b9be4445aa0ef579'
|
||||
const secondAcct = '1b00aed43a693f3a957f9feb5cc08afa031e37a0'
|
||||
|
||||
describe('hd-keyring', function() {
|
||||
|
||||
let keyring
|
||||
beforeEach(function() {
|
||||
keyring = new HdKeyring()
|
||||
})
|
||||
|
||||
describe('constructor', function(done) {
|
||||
keyring = new HdKeyring({
|
||||
mnemonic: sampleMnemonic,
|
||||
numberOfAccounts: 2,
|
||||
})
|
||||
|
||||
const accounts = keyring.getAccounts()
|
||||
.then((accounts) => {
|
||||
assert.equal(accounts[0], firstAcct)
|
||||
assert.equal(accounts[1], secondAcct)
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
describe('Keyring.type', function() {
|
||||
it('is a class property that returns the type string.', function() {
|
||||
const type = HdKeyring.type
|
||||
assert.equal(typeof type, 'string')
|
||||
})
|
||||
})
|
||||
|
||||
describe('#type', function() {
|
||||
it('returns the correct value', function() {
|
||||
const type = keyring.type
|
||||
const correct = HdKeyring.type
|
||||
assert.equal(type, correct)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#serialize empty wallets.', function() {
|
||||
it('serializes a new mnemonic', function() {
|
||||
keyring.serialize()
|
||||
.then((output) => {
|
||||
assert.equal(output.numberOfAccounts, 0)
|
||||
assert.equal(output.mnemonic, null)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('#deserialize a private key', function() {
|
||||
it('serializes what it deserializes', function(done) {
|
||||
keyring.deserialize({
|
||||
mnemonic: sampleMnemonic,
|
||||
numberOfAccounts: 1
|
||||
})
|
||||
.then(() => {
|
||||
assert.equal(keyring.wallets.length, 1, 'restores two accounts')
|
||||
return keyring.addAccounts(1)
|
||||
}).then(() => {
|
||||
return keyring.getAccounts()
|
||||
}).then((accounts) => {
|
||||
assert.equal(accounts[0], firstAcct)
|
||||
assert.equal(accounts[1], secondAcct)
|
||||
assert.equal(accounts.length, 2)
|
||||
|
||||
return keyring.serialize()
|
||||
}).then((serialized) => {
|
||||
assert.equal(serialized.mnemonic, sampleMnemonic)
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('#addAccounts', function() {
|
||||
describe('with no arguments', function() {
|
||||
it('creates a single wallet', function(done) {
|
||||
keyring.addAccounts()
|
||||
.then(() => {
|
||||
assert.equal(keyring.wallets.length, 1)
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('with a numeric argument', function() {
|
||||
it('creates that number of wallets', function(done) {
|
||||
keyring.addAccounts(3)
|
||||
.then(() => {
|
||||
assert.equal(keyring.wallets.length, 3)
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getAccounts', function() {
|
||||
it('calls getAddress on each wallet', function(done) {
|
||||
|
||||
// Push a mock wallet
|
||||
const desiredOutput = 'foo'
|
||||
keyring.wallets.push({
|
||||
getAddress() {
|
||||
return {
|
||||
toString() {
|
||||
return desiredOutput
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const output = keyring.getAccounts()
|
||||
.then((output) => {
|
||||
assert.equal(output[0], desiredOutput)
|
||||
assert.equal(output.length, 1)
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
91
test/unit/keyrings/simple-test.js
Normal file
91
test/unit/keyrings/simple-test.js
Normal file
@ -0,0 +1,91 @@
|
||||
const assert = require('assert')
|
||||
const extend = require('xtend')
|
||||
const ethUtil = require('ethereumjs-util')
|
||||
const SimpleKeyring = require('../../../app/scripts/keyrings/simple')
|
||||
const TYPE_STR = 'Simple Key Pair'
|
||||
|
||||
// Sample account:
|
||||
const privKeyHex = 'b8a9c05beeedb25df85f8d641538cbffedf67216048de9c678ee26260eb91952'
|
||||
|
||||
describe('simple-keyring', function() {
|
||||
|
||||
let keyring
|
||||
beforeEach(function() {
|
||||
keyring = new SimpleKeyring()
|
||||
})
|
||||
|
||||
describe('Keyring.type', function() {
|
||||
it('is a class property that returns the type string.', function() {
|
||||
const type = SimpleKeyring.type
|
||||
assert.equal(type, TYPE_STR)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#type', function() {
|
||||
it('returns the correct value', function() {
|
||||
const type = keyring.type
|
||||
assert.equal(type, TYPE_STR)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#serialize empty wallets.', function() {
|
||||
it('serializes an empty array', function(done) {
|
||||
keyring.serialize()
|
||||
.then((output) => {
|
||||
assert.deepEqual(output, [])
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('#deserialize a private key', function() {
|
||||
it('serializes what it deserializes', function() {
|
||||
keyring.deserialize([privKeyHex])
|
||||
.then(() => {
|
||||
assert.equal(keyring.wallets.length, 1, 'has one wallet')
|
||||
const serialized = keyring.serialize()
|
||||
assert.equal(serialized[0], privKeyHex)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('#addAccounts', function() {
|
||||
describe('with no arguments', function() {
|
||||
it('creates a single wallet', function() {
|
||||
keyring.addAccounts()
|
||||
.then(() => {
|
||||
assert.equal(keyring.wallets.length, 1)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('with a numeric argument', function() {
|
||||
it('creates that number of wallets', function() {
|
||||
keyring.addAccounts(3)
|
||||
.then(() => {
|
||||
assert.equal(keyring.wallets.length, 3)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getAccounts', function() {
|
||||
it('calls getAddress on each wallet', function(done) {
|
||||
|
||||
// Push a mock wallet
|
||||
const desiredOutput = '0x18a3462427bcc9133bb46e88bcbe39cd7ef0e761'
|
||||
keyring.wallets.push({
|
||||
getAddress() {
|
||||
return ethUtil.toBuffer(desiredOutput)
|
||||
}
|
||||
})
|
||||
|
||||
keyring.getAccounts()
|
||||
.then((output) => {
|
||||
assert.equal(output[0], desiredOutput)
|
||||
assert.equal(output.length, 1)
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
@ -9,7 +9,7 @@ describe('MetaMaskController', function() {
|
||||
let controller = new MetaMaskController({
|
||||
showUnconfirmedMessage: noop,
|
||||
unlockAccountMessage: noop,
|
||||
showUnconfirmedTx: noop,
|
||||
showUnapprovedTx: noop,
|
||||
setData,
|
||||
loadData,
|
||||
})
|
||||
@ -25,24 +25,6 @@ describe('MetaMaskController', function() {
|
||||
this.sinon.restore()
|
||||
})
|
||||
|
||||
describe('#enforceTxValidations', function () {
|
||||
it('returns null for positive values', function() {
|
||||
var sample = {
|
||||
value: '0x01'
|
||||
}
|
||||
var res = controller.enforceTxValidations(sample)
|
||||
assert.equal(res, null, 'no error')
|
||||
})
|
||||
|
||||
|
||||
it('returns error for negative values', function() {
|
||||
var sample = {
|
||||
value: '-0x01'
|
||||
}
|
||||
var res = controller.enforceTxValidations(sample)
|
||||
assert.ok(res, 'error')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
|
22
test/unit/nodeify-test.js
Normal file
22
test/unit/nodeify-test.js
Normal file
@ -0,0 +1,22 @@
|
||||
const assert = require('assert')
|
||||
const nodeify = require('../../app/scripts/lib/nodeify')
|
||||
|
||||
describe('nodeify', function() {
|
||||
|
||||
var obj = {
|
||||
foo: 'bar',
|
||||
promiseFunc: function (a) {
|
||||
var solution = this.foo + a
|
||||
return Promise.resolve(solution)
|
||||
}
|
||||
}
|
||||
|
||||
it('should retain original context', function(done) {
|
||||
var nodified = nodeify(obj.promiseFunc).bind(obj)
|
||||
nodified('baz', function (err, res) {
|
||||
assert.equal(res, 'barbaz')
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
})
|
@ -5,13 +5,14 @@ const nock = require('nock')
|
||||
const configManagerGen = require('../lib/mock-config-manager')
|
||||
const NoticeController = require('../../app/scripts/notice-controller')
|
||||
const STORAGE_KEY = 'metamask-persistance-key'
|
||||
// Hacking localStorage support into JSDom
|
||||
window.localStorage = {}
|
||||
|
||||
describe('notice-controller', function() {
|
||||
var noticeController
|
||||
|
||||
beforeEach(function() {
|
||||
// simple localStorage polyfill
|
||||
window.localStorage = {}
|
||||
if (window.localStorage.clear) window.localStorage.clear()
|
||||
let configManager = configManagerGen()
|
||||
noticeController = new NoticeController({
|
||||
configManager: configManager,
|
||||
|
215
test/unit/tx-manager-test.js
Normal file
215
test/unit/tx-manager-test.js
Normal file
@ -0,0 +1,215 @@
|
||||
const assert = require('assert')
|
||||
const extend = require('xtend')
|
||||
const EventEmitter = require('events')
|
||||
const STORAGE_KEY = 'metamask-persistance-key'
|
||||
const TransactionManager = require('../../app/scripts/transaction-manager')
|
||||
|
||||
describe('Transaction Manager', function() {
|
||||
let txManager
|
||||
|
||||
const onTxDoneCb = () => true
|
||||
beforeEach(function() {
|
||||
txManager = new TransactionManager ({
|
||||
txList: [],
|
||||
setTxList: () => {},
|
||||
provider: "testnet",
|
||||
txHistoryLimit: 10,
|
||||
blockTracker: new EventEmitter(),
|
||||
getNetwork: function(){ return 'unit test' }
|
||||
})
|
||||
})
|
||||
|
||||
describe('#validateTxParams', function () {
|
||||
it('returns null for positive values', function() {
|
||||
var sample = {
|
||||
value: '0x01'
|
||||
}
|
||||
var res = txManager.txProviderUtils.validateTxParams(sample, (err) => {
|
||||
assert.equal(err, null, 'no error')
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
it('returns error for negative values', function() {
|
||||
var sample = {
|
||||
value: '-0x01'
|
||||
}
|
||||
var res = txManager.txProviderUtils.validateTxParams(sample, (err) => {
|
||||
assert.ok(err, 'error')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getTxList', function() {
|
||||
it('when new should return empty array', function() {
|
||||
var result = txManager.getTxList()
|
||||
assert.ok(Array.isArray(result))
|
||||
assert.equal(result.length, 0)
|
||||
})
|
||||
it('should also return transactions from local storage if any', function() {
|
||||
|
||||
})
|
||||
})
|
||||
|
||||
describe('#_saveTxList', function() {
|
||||
it('saves the submitted data to the tx list', function() {
|
||||
var target = [{ foo: 'bar', metamaskNetworkId: 'unit test' }]
|
||||
txManager._saveTxList(target)
|
||||
var result = txManager.getTxList()
|
||||
assert.equal(result[0].foo, 'bar')
|
||||
})
|
||||
})
|
||||
|
||||
describe('#addTx', function() {
|
||||
it('adds a tx returned in getTxList', function() {
|
||||
var tx = { id: 1, status: 'confirmed', metamaskNetworkId: 'unit test' }
|
||||
txManager.addTx(tx, onTxDoneCb)
|
||||
var result = txManager.getTxList()
|
||||
assert.ok(Array.isArray(result))
|
||||
assert.equal(result.length, 1)
|
||||
assert.equal(result[0].id, 1)
|
||||
})
|
||||
|
||||
it('cuts off early txs beyond a limit', function() {
|
||||
const limit = txManager.txHistoryLimit
|
||||
for (let i = 0; i < limit + 1; i++) {
|
||||
let tx = { id: i, time: new Date(), status: 'confirmed', metamaskNetworkId: 'unit test' }
|
||||
txManager.addTx(tx, onTxDoneCb)
|
||||
}
|
||||
var result = txManager.getTxList()
|
||||
assert.equal(result.length, limit, `limit of ${limit} txs enforced`)
|
||||
assert.equal(result[0].id, 1, 'early txs truncted')
|
||||
})
|
||||
|
||||
it('cuts off early txs beyond a limit whether or not it is confirmed or rejected', function() {
|
||||
const limit = txManager.txHistoryLimit
|
||||
for (let i = 0; i < limit + 1; i++) {
|
||||
let tx = { id: i, time: new Date(), status: 'rejected', metamaskNetworkId: 'unit test' }
|
||||
txManager.addTx(tx, onTxDoneCb)
|
||||
}
|
||||
var result = txManager.getTxList()
|
||||
assert.equal(result.length, limit, `limit of ${limit} txs enforced`)
|
||||
assert.equal(result[0].id, 1, 'early txs truncted')
|
||||
})
|
||||
|
||||
it('cuts off early txs beyond a limit but does not cut unapproved txs', function() {
|
||||
var unconfirmedTx = { id: 0, time: new Date(), status: 'unapproved', metamaskNetworkId: 'unit test' }
|
||||
txManager.addTx(unconfirmedTx, onTxDoneCb)
|
||||
const limit = txManager.txHistoryLimit
|
||||
for (let i = 1; i < limit + 1; i++) {
|
||||
let tx = { id: i, time: new Date(), status: 'confirmed', metamaskNetworkId: 'unit test' }
|
||||
txManager.addTx(tx, onTxDoneCb)
|
||||
}
|
||||
var result = txManager.getTxList()
|
||||
assert.equal(result.length, limit, `limit of ${limit} txs enforced`)
|
||||
assert.equal(result[0].id, 0, 'first tx should still be there')
|
||||
assert.equal(result[0].status, 'unapproved', 'first tx should be unapproved')
|
||||
assert.equal(result[1].id, 2, 'early txs truncted')
|
||||
})
|
||||
})
|
||||
|
||||
describe('#setTxStatusSigned', function() {
|
||||
it('sets the tx status to signed', function() {
|
||||
var tx = { id: 1, status: 'unapproved', metamaskNetworkId: 'unit test' }
|
||||
txManager.addTx(tx, onTxDoneCb)
|
||||
txManager.setTxStatusSigned(1)
|
||||
var result = txManager.getTxList()
|
||||
assert.ok(Array.isArray(result))
|
||||
assert.equal(result.length, 1)
|
||||
assert.equal(result[0].status, 'signed')
|
||||
})
|
||||
|
||||
it('should emit a signed event to signal the exciton of callback', (done) => {
|
||||
this.timeout(10000)
|
||||
var tx = { id: 1, status: 'unapproved', metamaskNetworkId: 'unit test' }
|
||||
let onTxDoneCb = function () {
|
||||
assert(true, 'event listener has been triggered and onTxDoneCb executed')
|
||||
done()
|
||||
}
|
||||
txManager.addTx(tx)
|
||||
txManager.on('1:signed', onTxDoneCb)
|
||||
txManager.setTxStatusSigned(1)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#setTxStatusRejected', function() {
|
||||
it('sets the tx status to rejected', function() {
|
||||
var tx = { id: 1, status: 'unapproved', metamaskNetworkId: 'unit test' }
|
||||
txManager.addTx(tx)
|
||||
txManager.setTxStatusRejected(1)
|
||||
var result = txManager.getTxList()
|
||||
assert.ok(Array.isArray(result))
|
||||
assert.equal(result.length, 1)
|
||||
assert.equal(result[0].status, 'rejected')
|
||||
})
|
||||
|
||||
it('should emit a rejected event to signal the exciton of callback', (done) => {
|
||||
this.timeout(10000)
|
||||
var tx = { id: 1, status: 'unapproved', metamaskNetworkId: 'unit test' }
|
||||
txManager.addTx(tx)
|
||||
let onTxDoneCb = function (err, txId) {
|
||||
assert(true, 'event listener has been triggered and onTxDoneCb executed')
|
||||
done()
|
||||
}
|
||||
txManager.on('1:rejected', onTxDoneCb)
|
||||
txManager.setTxStatusRejected(1)
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
describe('#updateTx', function() {
|
||||
it('replaces the tx with the same id', function() {
|
||||
txManager.addTx({ id: '1', status: 'unapproved', metamaskNetworkId: 'unit test' }, onTxDoneCb)
|
||||
txManager.addTx({ id: '2', status: 'confirmed', metamaskNetworkId: 'unit test' }, onTxDoneCb)
|
||||
txManager.updateTx({ id: '1', status: 'blah', hash: 'foo', metamaskNetworkId: 'unit test' })
|
||||
var result = txManager.getTx('1')
|
||||
assert.equal(result.hash, 'foo')
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getUnapprovedTxList', function() {
|
||||
it('returns unapproved txs in a hash', function() {
|
||||
txManager.addTx({ id: '1', status: 'unapproved', metamaskNetworkId: 'unit test' }, onTxDoneCb)
|
||||
txManager.addTx({ id: '2', status: 'confirmed', metamaskNetworkId: 'unit test' }, onTxDoneCb)
|
||||
let result = txManager.getUnapprovedTxList()
|
||||
assert.equal(typeof result, 'object')
|
||||
assert.equal(result['1'].status, 'unapproved')
|
||||
assert.equal(result['2'], undefined)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getTx', function() {
|
||||
it('returns a tx with the requested id', function() {
|
||||
txManager.addTx({ id: '1', status: 'unapproved', metamaskNetworkId: 'unit test' }, onTxDoneCb)
|
||||
txManager.addTx({ id: '2', status: 'confirmed', metamaskNetworkId: 'unit test' }, onTxDoneCb)
|
||||
assert.equal(txManager.getTx('1').status, 'unapproved')
|
||||
assert.equal(txManager.getTx('2').status, 'confirmed')
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getFilteredTxList', function() {
|
||||
it('returns a tx with the requested data', function() {
|
||||
var foop = 0
|
||||
var zoop = 0
|
||||
for (let i = 0; i < 10; ++i ){
|
||||
let everyOther = i % 2
|
||||
txManager.addTx({ id: i,
|
||||
status: everyOther ? 'unapproved' : 'confirmed',
|
||||
metamaskNetworkId: 'unit test',
|
||||
txParams: {
|
||||
from: everyOther ? 'foop' : 'zoop',
|
||||
to: everyOther ? 'zoop' : 'foop',
|
||||
}
|
||||
}, onTxDoneCb)
|
||||
everyOther ? ++foop : ++zoop
|
||||
}
|
||||
assert.equal(txManager.getFilteredTxList({status: 'confirmed', from: 'zoop'}).length, zoop)
|
||||
assert.equal(txManager.getFilteredTxList({status: 'confirmed', to: 'foop'}).length, zoop)
|
||||
assert.equal(txManager.getFilteredTxList({status: 'confirmed', from: 'foop'}).length, 0)
|
||||
assert.equal(txManager.getFilteredTxList({status: 'confirmed'}).length, zoop)
|
||||
assert.equal(txManager.getFilteredTxList({from: 'foop'}).length, foop)
|
||||
assert.equal(txManager.getFilteredTxList({from: 'zoop'}).length, zoop)
|
||||
})
|
||||
})
|
||||
|
||||
})
|
@ -6,4 +6,5 @@ launch_in_ci:
|
||||
- Firefox
|
||||
framework:
|
||||
- qunit
|
||||
before_tests: "npm run buildCiUnits"
|
||||
test_page: "test/integration/index.html"
|
||||
|
@ -25,7 +25,7 @@ const Selector = require('./development/selector')
|
||||
// Query String
|
||||
const qs = require('qs')
|
||||
let queryString = qs.parse(window.location.href.split('#')[1])
|
||||
let selectedView = queryString.view || 'account detail'
|
||||
let selectedView = queryString.view || 'first time'
|
||||
const firstState = states[selectedView]
|
||||
updateQueryParams(selectedView)
|
||||
|
||||
@ -41,7 +41,7 @@ function updateQueryParams(newView) {
|
||||
}
|
||||
|
||||
const actions = {
|
||||
_setAccountManager(){},
|
||||
_setBackgroundConnection(){},
|
||||
update: function(stateName) {
|
||||
selectedView = stateName
|
||||
updateQueryParams(stateName)
|
||||
|
@ -26,12 +26,10 @@ function mapStateToProps (state) {
|
||||
accounts: state.metamask.accounts,
|
||||
address: state.metamask.selectedAccount,
|
||||
accountDetail: state.appState.accountDetail,
|
||||
transactions: state.metamask.transactions,
|
||||
network: state.metamask.network,
|
||||
unconfTxs: valuesFor(state.metamask.unconfTxs),
|
||||
unconfMsgs: valuesFor(state.metamask.unconfMsgs),
|
||||
isEthWarningConfirmed: state.metamask.isEthConfirmed,
|
||||
shapeShiftTxList: state.metamask.shapeShiftTxList,
|
||||
transactions: state.metamask.selectedAccountTxList || [],
|
||||
}
|
||||
}
|
||||
|
||||
@ -249,20 +247,10 @@ AccountDetailScreen.prototype.subview = function () {
|
||||
}
|
||||
|
||||
AccountDetailScreen.prototype.transactionList = function () {
|
||||
const { transactions, unconfTxs, unconfMsgs, address, network, shapeShiftTxList } = this.props
|
||||
|
||||
var txsToRender = transactions.concat(unconfTxs)
|
||||
// only transactions that are from the current address
|
||||
.filter(tx => tx.txParams.from === address)
|
||||
// only transactions that are on the current network
|
||||
.filter(tx => tx.txParams.metamaskNetworkId === network)
|
||||
// sort by recency
|
||||
.sort((a, b) => b.time - a.time)
|
||||
|
||||
const {transactions, unconfMsgs, address, network, shapeShiftTxList } = this.props
|
||||
return h(TransactionList, {
|
||||
txsToRender,
|
||||
transactions: transactions.sort((a, b) => b.time - a.time),
|
||||
network,
|
||||
unconfTxs,
|
||||
unconfMsgs,
|
||||
address,
|
||||
shapeShiftTxList,
|
||||
|
@ -15,19 +15,21 @@ function AccountListItem () {
|
||||
}
|
||||
|
||||
AccountListItem.prototype.render = function () {
|
||||
const identity = this.props.identity
|
||||
var isSelected = this.props.selectedAddress === identity.address
|
||||
var account = this.props.accounts[identity.address]
|
||||
const { identity, selectedAccount, accounts, onShowDetail } = this.props
|
||||
|
||||
const isSelected = selectedAccount === identity.address
|
||||
const account = accounts[identity.address]
|
||||
const selectedClass = isSelected ? '.selected' : ''
|
||||
|
||||
return (
|
||||
h(`.accounts-list-option.flex-row.flex-space-between.pointer.hover-white${selectedClass}`, {
|
||||
key: `account-panel-${identity.address}`,
|
||||
onClick: (event) => this.props.onShowDetail(identity.address, event),
|
||||
onClick: (event) => onShowDetail(identity.address, event),
|
||||
}, [
|
||||
|
||||
h('.identicon-wrapper.flex-column.flex-center.select-none', [
|
||||
this.pendingOrNot(),
|
||||
this.indicateIfLoose(),
|
||||
h(Identicon, {
|
||||
address: identity.address,
|
||||
imageify: true,
|
||||
@ -48,7 +50,7 @@ AccountListItem.prototype.render = function () {
|
||||
},
|
||||
}, ethUtil.toChecksumAddress(identity.address)),
|
||||
h(EthBalance, {
|
||||
value: account.balance,
|
||||
value: account && account.balance,
|
||||
style: {
|
||||
lineHeight: '7px',
|
||||
marginTop: '10px',
|
||||
@ -70,6 +72,14 @@ AccountListItem.prototype.render = function () {
|
||||
)
|
||||
}
|
||||
|
||||
AccountListItem.prototype.indicateIfLoose = function () {
|
||||
try { // Sometimes keyrings aren't loaded yet:
|
||||
const type = this.props.keyring.type
|
||||
const isLoose = type !== 'HD Key Tree'
|
||||
return isLoose ? h('.keyring-label', 'LOOSE') : null
|
||||
} catch (e) { return }
|
||||
}
|
||||
|
||||
AccountListItem.prototype.pendingOrNot = function () {
|
||||
const pending = this.props.pending
|
||||
if (pending.length === 0) return null
|
||||
|
@ -19,9 +19,10 @@ function mapStateToProps (state) {
|
||||
accounts: state.metamask.accounts,
|
||||
identities: state.metamask.identities,
|
||||
unconfTxs: state.metamask.unconfTxs,
|
||||
selectedAddress: state.metamask.selectedAddress,
|
||||
selectedAccount: state.metamask.selectedAccount,
|
||||
scrollToBottom: state.appState.scrollToBottom,
|
||||
pending,
|
||||
keyrings: state.metamask.keyrings,
|
||||
}
|
||||
}
|
||||
|
||||
@ -31,14 +32,11 @@ function AccountsScreen () {
|
||||
}
|
||||
|
||||
AccountsScreen.prototype.render = function () {
|
||||
var state = this.props
|
||||
var identityList = valuesFor(state.identities)
|
||||
var unconfTxList = valuesFor(state.unconfTxs)
|
||||
var actions = {
|
||||
onSelect: this.onSelect.bind(this),
|
||||
onShowDetail: this.onShowDetail.bind(this),
|
||||
goHome: this.goHome.bind(this),
|
||||
}
|
||||
const props = this.props
|
||||
const { keyrings } = props
|
||||
const identityList = valuesFor(props.identities)
|
||||
const unconfTxList = valuesFor(props.unconfTxs)
|
||||
|
||||
return (
|
||||
|
||||
h('.accounts-section.flex-grow', [
|
||||
@ -46,7 +44,7 @@ AccountsScreen.prototype.render = function () {
|
||||
// subtitle and nav
|
||||
h('.section-title.flex-center', [
|
||||
h('i.fa.fa-arrow-left.fa-lg.cursor-pointer', {
|
||||
onClick: actions.goHome,
|
||||
onClick: this.goHome.bind(this),
|
||||
}),
|
||||
h('h2.page-subtitle', 'Select Account'),
|
||||
]),
|
||||
@ -73,13 +71,19 @@ AccountsScreen.prototype.render = function () {
|
||||
}
|
||||
})
|
||||
|
||||
const simpleAddress = identity.address.substring(2).toLowerCase()
|
||||
const keyring = keyrings.find((kr) => {
|
||||
return kr.accounts.includes(simpleAddress)
|
||||
})
|
||||
|
||||
return h(AccountListItem, {
|
||||
key: `acct-panel-${identity.address}`,
|
||||
identity,
|
||||
selectedAddress: this.props.selectedAddress,
|
||||
selectedAccount: this.props.selectedAccount,
|
||||
accounts: this.props.accounts,
|
||||
onShowDetail: this.onShowDetail.bind(this),
|
||||
pending,
|
||||
keyring,
|
||||
})
|
||||
}),
|
||||
|
||||
@ -87,7 +91,7 @@ AccountsScreen.prototype.render = function () {
|
||||
h('div.footer.hover-white.pointer', {
|
||||
key: 'reveal-account-bar',
|
||||
onClick: () => {
|
||||
this.onRevealAccount()
|
||||
this.addNewAccount()
|
||||
},
|
||||
style: {
|
||||
display: 'flex',
|
||||
@ -137,8 +141,8 @@ AccountsScreen.prototype.navigateToConfTx = function () {
|
||||
AccountsScreen.prototype.onSelect = function (address, event) {
|
||||
event.stopPropagation()
|
||||
// if already selected, deselect
|
||||
if (this.props.selectedAddress === address) address = null
|
||||
this.props.dispatch(actions.setSelectedAddress(address))
|
||||
if (this.props.selectedAccount === address) address = null
|
||||
this.props.dispatch(actions.setSelectedAccount(address))
|
||||
}
|
||||
|
||||
AccountsScreen.prototype.onShowDetail = function (address, event) {
|
||||
@ -146,8 +150,8 @@ AccountsScreen.prototype.onShowDetail = function (address, event) {
|
||||
this.props.dispatch(actions.showAccountDetail(address))
|
||||
}
|
||||
|
||||
AccountsScreen.prototype.onRevealAccount = function () {
|
||||
this.props.dispatch(actions.revealAccount())
|
||||
AccountsScreen.prototype.addNewAccount = function () {
|
||||
this.props.dispatch(actions.addNewAccount(0))
|
||||
}
|
||||
|
||||
AccountsScreen.prototype.goHome = function () {
|
||||
|
@ -1,9 +1,15 @@
|
||||
var actions = {
|
||||
_setBackgroundConnection: _setBackgroundConnection,
|
||||
|
||||
GO_HOME: 'GO_HOME',
|
||||
goHome: goHome,
|
||||
// menu state
|
||||
getNetworkStatus: 'getNetworkStatus',
|
||||
|
||||
// transition state
|
||||
TRANSITION_FORWARD: 'TRANSITION_FORWARD',
|
||||
TRANSITION_BACKWARD: 'TRANSITION_BACKWARD',
|
||||
transitionForward,
|
||||
transitionBackward,
|
||||
// remote state
|
||||
UPDATE_METAMASK_STATE: 'UPDATE_METAMASK_STATE',
|
||||
updateMetamaskState: updateMetamaskState,
|
||||
@ -14,26 +20,28 @@ var actions = {
|
||||
showNotice: showNotice,
|
||||
CLEAR_NOTICES: 'CLEAR_NOTICES',
|
||||
clearNotices: clearNotices,
|
||||
markAccountsFound,
|
||||
// intialize screen
|
||||
AGREE_TO_DISCLAIMER: 'AGREE_TO_DISCLAIMER',
|
||||
agreeToDisclaimer: agreeToDisclaimer,
|
||||
CREATE_NEW_VAULT_IN_PROGRESS: 'CREATE_NEW_VAULT_IN_PROGRESS',
|
||||
SHOW_CREATE_VAULT: 'SHOW_CREATE_VAULT',
|
||||
SHOW_RESTORE_VAULT: 'SHOW_RESTORE_VAULT',
|
||||
FORGOT_PASSWORD: 'FORGOT_PASSWORD',
|
||||
forgotPassword: forgotPassword,
|
||||
SHOW_INIT_MENU: 'SHOW_INIT_MENU',
|
||||
SHOW_NEW_VAULT_SEED: 'SHOW_NEW_VAULT_SEED',
|
||||
SHOW_INFO_PAGE: 'SHOW_INFO_PAGE',
|
||||
RECOVER_FROM_SEED: 'RECOVER_FROM_SEED',
|
||||
CLEAR_SEED_WORD_CACHE: 'CLEAR_SEED_WORD_CACHE',
|
||||
clearSeedWordCache: clearSeedWordCache,
|
||||
recoverFromSeed: recoverFromSeed,
|
||||
unlockMetamask: unlockMetamask,
|
||||
unlockFailed: unlockFailed,
|
||||
showCreateVault: showCreateVault,
|
||||
showRestoreVault: showRestoreVault,
|
||||
showInitializeMenu: showInitializeMenu,
|
||||
createNewVault: createNewVault,
|
||||
createNewVaultAndKeychain: createNewVaultAndKeychain,
|
||||
createNewVaultAndRestore: createNewVaultAndRestore,
|
||||
createNewVaultInProgress: createNewVaultInProgress,
|
||||
addNewKeyring,
|
||||
addNewAccount,
|
||||
showNewVaultSeed: showNewVaultSeed,
|
||||
showInfoPage: showInfoPage,
|
||||
// seed recovery actions
|
||||
@ -59,8 +67,6 @@ var actions = {
|
||||
SHOW_ACCOUNTS_PAGE: 'SHOW_ACCOUNTS_PAGE',
|
||||
SHOW_CONF_TX_PAGE: 'SHOW_CONF_TX_PAGE',
|
||||
SHOW_CONF_MSG_PAGE: 'SHOW_CONF_MSG_PAGE',
|
||||
REVEAL_ACCOUNT: 'REVEAL_ACCOUNT',
|
||||
revealAccount: revealAccount,
|
||||
SET_CURRENT_FIAT: 'SET_CURRENT_FIAT',
|
||||
setCurrentFiat: setCurrentFiat,
|
||||
// account detail screen
|
||||
@ -74,16 +80,12 @@ var actions = {
|
||||
showPrivateKey: showPrivateKey,
|
||||
SAVE_ACCOUNT_LABEL: 'SAVE_ACCOUNT_LABEL',
|
||||
saveAccountLabel: saveAccountLabel,
|
||||
AGREE_TO_ETH_WARNING: 'AGREE_TO_ETH_WARNING',
|
||||
agreeToEthWarning: agreeToEthWarning,
|
||||
SHOW_ETH_WARNING: 'SHOW_ETH_WARNING',
|
||||
showEthWarning: showEthWarning,
|
||||
// tx conf screen
|
||||
COMPLETED_TX: 'COMPLETED_TX',
|
||||
TRANSACTION_ERROR: 'TRANSACTION_ERROR',
|
||||
NEXT_TX: 'NEXT_TX',
|
||||
PREVIOUS_TX: 'PREV_TX',
|
||||
setSelectedAddress: setSelectedAddress,
|
||||
setSelectedAccount: setSelectedAccount,
|
||||
signMsg: signMsg,
|
||||
cancelMsg: cancelMsg,
|
||||
sendTx: sendTx,
|
||||
@ -96,12 +98,12 @@ var actions = {
|
||||
viewPendingTx: viewPendingTx,
|
||||
VIEW_PENDING_TX: 'VIEW_PENDING_TX',
|
||||
// app messages
|
||||
confirmSeedWords: confirmSeedWords,
|
||||
showAccountDetail: showAccountDetail,
|
||||
BACK_TO_ACCOUNT_DETAIL: 'BACK_TO_ACCOUNT_DETAIL',
|
||||
backToAccountDetail: backToAccountDetail,
|
||||
showAccountsPage: showAccountsPage,
|
||||
showConfTxPage: showConfTxPage,
|
||||
confirmSeedWords: confirmSeedWords,
|
||||
// config screen
|
||||
SHOW_CONFIG_PAGE: 'SHOW_CONFIG_PAGE',
|
||||
SET_RPC_TARGET: 'SET_RPC_TARGET',
|
||||
@ -111,8 +113,6 @@ var actions = {
|
||||
showConfigPage: showConfigPage,
|
||||
setRpcTarget: setRpcTarget,
|
||||
setProviderType: setProviderType,
|
||||
// hacky - need a way to get a reference to account manager
|
||||
_setAccountManager: _setAccountManager,
|
||||
// loading overlay
|
||||
SHOW_LOADING: 'SHOW_LOADING_INDICATION',
|
||||
HIDE_LOADING: 'HIDE_LOADING_INDICATION',
|
||||
@ -149,13 +149,18 @@ var actions = {
|
||||
RECOVERY_IN_PROGRESS: 'RECOVERY_IN_PROGRESS',
|
||||
BACK_TO_UNLOCK_VIEW: 'BACK_TO_UNLOCK_VIEW',
|
||||
backToUnlockView: backToUnlockView,
|
||||
// SHOWING KEYCHAIN
|
||||
SHOW_NEW_KEYCHAIN: 'SHOW_NEW_KEYCHAIN',
|
||||
showNewKeychain: showNewKeychain,
|
||||
|
||||
callBackgroundThenUpdate,
|
||||
}
|
||||
|
||||
module.exports = actions
|
||||
|
||||
var _accountManager = null
|
||||
function _setAccountManager (accountManager) {
|
||||
_accountManager = accountManager
|
||||
var background = null
|
||||
function _setBackgroundConnection (backgroundConnection) {
|
||||
background = backgroundConnection
|
||||
}
|
||||
|
||||
function goHome () {
|
||||
@ -170,29 +175,60 @@ function tryUnlockMetamask (password) {
|
||||
return (dispatch) => {
|
||||
dispatch(actions.showLoadingIndication())
|
||||
dispatch(actions.unlockInProgress())
|
||||
_accountManager.submitPassword(password, (err, selectedAccount) => {
|
||||
background.submitPassword(password, (err, newState) => {
|
||||
dispatch(actions.hideLoadingIndication())
|
||||
if (err) {
|
||||
dispatch(actions.unlockFailed())
|
||||
dispatch(actions.unlockFailed(err.message))
|
||||
} else {
|
||||
dispatch(actions.unlockMetamask(selectedAccount))
|
||||
dispatch(actions.transitionForward())
|
||||
dispatch(actions.updateMetamaskState(newState))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function createNewVault (password, entropy) {
|
||||
function transitionForward () {
|
||||
return {
|
||||
type: this.TRANSITION_FORWARD,
|
||||
}
|
||||
}
|
||||
|
||||
function transitionBackward () {
|
||||
return {
|
||||
type: this.TRANSITION_BACKWARD,
|
||||
}
|
||||
}
|
||||
|
||||
function confirmSeedWords () {
|
||||
return (dispatch) => {
|
||||
dispatch(actions.createNewVaultInProgress())
|
||||
_accountManager.createNewVault(password, entropy, (err, result) => {
|
||||
dispatch(actions.showLoadingIndication())
|
||||
background.clearSeedWordCache((err, account) => {
|
||||
dispatch(actions.hideLoadingIndication())
|
||||
if (err) {
|
||||
return dispatch(actions.displayWarning(err.message))
|
||||
}
|
||||
dispatch(actions.showNewVaultSeed(result))
|
||||
|
||||
console.log('Seed word cache cleared. ' + account)
|
||||
dispatch(actions.showAccountDetail(account))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function createNewVaultAndRestore (password, seed) {
|
||||
return (dispatch) => {
|
||||
dispatch(actions.showLoadingIndication())
|
||||
background.createNewVaultAndRestore(password, seed, (err) => {
|
||||
dispatch(actions.hideLoadingIndication())
|
||||
if (err) return dispatch(actions.displayWarning(err.message))
|
||||
dispatch(actions.showAccountsPage())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function createNewVaultAndKeychain (password) {
|
||||
return callBackgroundThenUpdate(background.createNewVaultAndKeychain, password)
|
||||
}
|
||||
|
||||
function revealSeedConfirmation () {
|
||||
return {
|
||||
type: this.REVEAL_SEED_CONFIRMATION,
|
||||
@ -202,29 +238,22 @@ function revealSeedConfirmation () {
|
||||
function requestRevealSeed (password) {
|
||||
return (dispatch) => {
|
||||
dispatch(actions.showLoadingIndication())
|
||||
_accountManager.tryPassword(password, (err, seed) => {
|
||||
dispatch(actions.hideLoadingIndication())
|
||||
background.submitPassword(password, (err) => {
|
||||
if (err) return dispatch(actions.displayWarning(err.message))
|
||||
_accountManager.recoverSeed((err, seed) => {
|
||||
background.placeSeedWords((err) => {
|
||||
if (err) return dispatch(actions.displayWarning(err.message))
|
||||
dispatch(actions.showNewVaultSeed(seed))
|
||||
dispatch(actions.hideLoadingIndication())
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function recoverFromSeed (password, seed) {
|
||||
return (dispatch) => {
|
||||
// dispatch(actions.createNewVaultInProgress())
|
||||
dispatch(actions.showLoadingIndication())
|
||||
_accountManager.recoverFromSeed(password, seed, (err, metamaskState) => {
|
||||
dispatch(actions.hideLoadingIndication())
|
||||
if (err) return dispatch(actions.displayWarning(err.message))
|
||||
function addNewKeyring (type, opts) {
|
||||
return callBackgroundThenUpdate(background.addNewKeyring, type, opts)
|
||||
}
|
||||
|
||||
var account = Object.keys(metamaskState.identities)[0]
|
||||
dispatch(actions.unlockMetamask(account))
|
||||
})
|
||||
}
|
||||
function addNewAccount (ringNumber = 0) {
|
||||
return callBackgroundThenUpdate(background.addNewAccount, ringNumber)
|
||||
}
|
||||
|
||||
function showInfoPage () {
|
||||
@ -233,29 +262,14 @@ function showInfoPage () {
|
||||
}
|
||||
}
|
||||
|
||||
function setSelectedAddress (address) {
|
||||
return (dispatch) => {
|
||||
_accountManager.setSelectedAddress(address)
|
||||
}
|
||||
}
|
||||
|
||||
function revealAccount () {
|
||||
return (dispatch) => {
|
||||
dispatch(actions.showLoadingIndication())
|
||||
_accountManager.revealAccount((err) => {
|
||||
dispatch(actions.hideLoadingIndication())
|
||||
if (err) return dispatch(actions.displayWarning(err.message))
|
||||
dispatch({
|
||||
type: actions.REVEAL_ACCOUNT,
|
||||
})
|
||||
})
|
||||
}
|
||||
function setSelectedAccount (address) {
|
||||
return callBackgroundThenUpdate(background.setSelectedAccount, address)
|
||||
}
|
||||
|
||||
function setCurrentFiat (fiat) {
|
||||
return (dispatch) => {
|
||||
dispatch(this.showLoadingIndication())
|
||||
_accountManager.setCurrentFiat(fiat, (data, err) => {
|
||||
background.setCurrentFiat(fiat, (data, err) => {
|
||||
dispatch(this.hideLoadingIndication())
|
||||
dispatch({
|
||||
type: this.SET_CURRENT_FIAT,
|
||||
@ -273,7 +287,7 @@ function signMsg (msgData) {
|
||||
return (dispatch) => {
|
||||
dispatch(actions.showLoadingIndication())
|
||||
|
||||
_accountManager.signMessage(msgData, (err) => {
|
||||
background.signMessage(msgData, (err) => {
|
||||
dispatch(actions.hideLoadingIndication())
|
||||
|
||||
if (err) return dispatch(actions.displayWarning(err.message))
|
||||
@ -284,7 +298,7 @@ function signMsg (msgData) {
|
||||
|
||||
function signTx (txData) {
|
||||
return (dispatch) => {
|
||||
_accountManager.setGasMultiplier(txData.gasMultiplier, (err) => {
|
||||
background.setGasMultiplier(txData.gasMultiplier, (err) => {
|
||||
if (err) return dispatch(actions.displayWarning(err.message))
|
||||
web3.eth.sendTransaction(txData, (err, data) => {
|
||||
dispatch(actions.hideLoadingIndication())
|
||||
@ -299,7 +313,7 @@ function signTx (txData) {
|
||||
|
||||
function sendTx (txData) {
|
||||
return (dispatch) => {
|
||||
_accountManager.approveTransaction(txData.id, (err) => {
|
||||
background.approveTransaction(txData.id, (err) => {
|
||||
if (err) {
|
||||
alert(err.message)
|
||||
dispatch(actions.txError(err))
|
||||
@ -325,12 +339,12 @@ function txError (err) {
|
||||
}
|
||||
|
||||
function cancelMsg (msgData) {
|
||||
_accountManager.cancelMessage(msgData.id)
|
||||
background.cancelMessage(msgData.id)
|
||||
return actions.completedTx(msgData.id)
|
||||
}
|
||||
|
||||
function cancelTx (txData) {
|
||||
_accountManager.cancelTransaction(txData.id)
|
||||
background.cancelTransaction(txData.id)
|
||||
return actions.completedTx(txData.id)
|
||||
}
|
||||
|
||||
@ -350,6 +364,12 @@ function showRestoreVault () {
|
||||
}
|
||||
}
|
||||
|
||||
function forgotPassword () {
|
||||
return {
|
||||
type: actions.FORGOT_PASSWORD,
|
||||
}
|
||||
}
|
||||
|
||||
function showInitializeMenu () {
|
||||
return {
|
||||
type: actions.SHOW_INIT_MENU,
|
||||
@ -359,7 +379,7 @@ function showInitializeMenu () {
|
||||
function agreeToDisclaimer () {
|
||||
return (dispatch) => {
|
||||
dispatch(this.showLoadingIndication())
|
||||
_accountManager.agreeToDisclaimer((err) => {
|
||||
background.agreeToDisclaimer((err) => {
|
||||
if (err) {
|
||||
return dispatch(actions.displayWarning(err.message))
|
||||
}
|
||||
@ -391,6 +411,12 @@ function backToUnlockView () {
|
||||
}
|
||||
}
|
||||
|
||||
function showNewKeychain () {
|
||||
return {
|
||||
type: actions.SHOW_NEW_KEYCHAIN,
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// unlock screen
|
||||
//
|
||||
@ -401,9 +427,10 @@ function unlockInProgress () {
|
||||
}
|
||||
}
|
||||
|
||||
function unlockFailed () {
|
||||
function unlockFailed (message) {
|
||||
return {
|
||||
type: actions.UNLOCK_FAILED,
|
||||
value: message,
|
||||
}
|
||||
}
|
||||
|
||||
@ -422,32 +449,22 @@ function updateMetamaskState (newState) {
|
||||
}
|
||||
|
||||
function lockMetamask () {
|
||||
return (dispatch) => {
|
||||
_accountManager.setLocked((err) => {
|
||||
dispatch(actions.hideLoadingIndication())
|
||||
if (err) {
|
||||
return dispatch(actions.displayWarning(err.message))
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: actions.LOCK_METAMASK,
|
||||
})
|
||||
})
|
||||
}
|
||||
return callBackgroundThenUpdate(background.setLocked)
|
||||
}
|
||||
|
||||
function showAccountDetail (address) {
|
||||
return (dispatch) => {
|
||||
dispatch(actions.showLoadingIndication())
|
||||
_accountManager.setSelectedAddress(address, (err, address) => {
|
||||
background.setSelectedAccount(address, (err, newState) => {
|
||||
dispatch(actions.hideLoadingIndication())
|
||||
if (err) {
|
||||
return dispatch(actions.displayWarning(err.message))
|
||||
}
|
||||
|
||||
dispatch(actions.updateMetamaskState(newState))
|
||||
dispatch({
|
||||
type: actions.SHOW_ACCOUNT_DETAIL,
|
||||
value: address,
|
||||
value: newState.selectedAccount,
|
||||
})
|
||||
})
|
||||
}
|
||||
@ -459,27 +476,6 @@ function backToAccountDetail (address) {
|
||||
value: address,
|
||||
}
|
||||
}
|
||||
function clearSeedWordCache (account) {
|
||||
return {
|
||||
type: actions.CLEAR_SEED_WORD_CACHE,
|
||||
value: account,
|
||||
}
|
||||
}
|
||||
|
||||
function confirmSeedWords () {
|
||||
return (dispatch) => {
|
||||
dispatch(actions.showLoadingIndication())
|
||||
_accountManager.clearSeedWordCache((err, account) => {
|
||||
dispatch(actions.hideLoadingIndication())
|
||||
if (err) {
|
||||
return dispatch(actions.displayWarning(err.message))
|
||||
}
|
||||
|
||||
console.log('Seed word cache cleared. ' + account)
|
||||
dispatch(actions.showAccountDetail(account))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function showAccountsPage () {
|
||||
return {
|
||||
@ -533,10 +529,10 @@ function goBackToInitView () {
|
||||
function markNoticeRead (notice) {
|
||||
return (dispatch) => {
|
||||
dispatch(this.showLoadingIndication())
|
||||
_accountManager.markNoticeRead(notice, (err, notice) => {
|
||||
background.markNoticeRead(notice, (err, notice) => {
|
||||
dispatch(this.hideLoadingIndication())
|
||||
if (err) {
|
||||
return dispatch(actions.showWarning(err))
|
||||
return dispatch(actions.displayWarning(err))
|
||||
}
|
||||
if (notice) {
|
||||
return dispatch(actions.showNotice(notice))
|
||||
@ -563,12 +559,16 @@ function clearNotices () {
|
||||
}
|
||||
}
|
||||
|
||||
function markAccountsFound() {
|
||||
return callBackgroundThenUpdate(background.markAccountsFound)
|
||||
}
|
||||
|
||||
//
|
||||
// config
|
||||
//
|
||||
|
||||
function setRpcTarget (newRpc) {
|
||||
_accountManager.setRpcTarget(newRpc)
|
||||
background.setRpcTarget(newRpc)
|
||||
return {
|
||||
type: actions.SET_RPC_TARGET,
|
||||
value: newRpc,
|
||||
@ -576,7 +576,7 @@ function setRpcTarget (newRpc) {
|
||||
}
|
||||
|
||||
function setProviderType (type) {
|
||||
_accountManager.setProviderType(type)
|
||||
background.setProviderType(type)
|
||||
return {
|
||||
type: actions.SET_PROVIDER_TYPE,
|
||||
value: type,
|
||||
@ -584,7 +584,7 @@ function setProviderType (type) {
|
||||
}
|
||||
|
||||
function useEtherscanProvider () {
|
||||
_accountManager.useEtherscanProvider()
|
||||
background.useEtherscanProvider()
|
||||
return {
|
||||
type: actions.USE_ETHERSCAN_PROVIDER,
|
||||
}
|
||||
@ -639,7 +639,7 @@ function exportAccount (address) {
|
||||
return function (dispatch) {
|
||||
dispatch(self.showLoadingIndication())
|
||||
|
||||
_accountManager.exportAccount(address, function (err, result) {
|
||||
background.exportAccount(address, function (err, result) {
|
||||
dispatch(self.hideLoadingIndication())
|
||||
|
||||
if (err) {
|
||||
@ -662,7 +662,7 @@ function showPrivateKey (key) {
|
||||
function saveAccountLabel (account, label) {
|
||||
return (dispatch) => {
|
||||
dispatch(actions.showLoadingIndication())
|
||||
_accountManager.saveAccountLabel(account, label, (err) => {
|
||||
background.saveAccountLabel(account, label, (err) => {
|
||||
dispatch(actions.hideLoadingIndication())
|
||||
if (err) {
|
||||
return dispatch(actions.displayWarning(err.message))
|
||||
@ -681,28 +681,9 @@ function showSendPage () {
|
||||
}
|
||||
}
|
||||
|
||||
function agreeToEthWarning () {
|
||||
return (dispatch) => {
|
||||
_accountManager.agreeToEthWarning((err) => {
|
||||
if (err) {
|
||||
return dispatch(actions.showEthWarning(err.message))
|
||||
}
|
||||
dispatch({
|
||||
type: actions.AGREE_TO_ETH_WARNING,
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function showEthWarning () {
|
||||
return {
|
||||
type: actions.SHOW_ETH_WARNING,
|
||||
}
|
||||
}
|
||||
|
||||
function buyEth (address, amount) {
|
||||
return (dispatch) => {
|
||||
_accountManager.buyEth(address, amount)
|
||||
background.buyEth(address, amount)
|
||||
dispatch({
|
||||
type: actions.BUY_ETH,
|
||||
})
|
||||
@ -780,7 +761,7 @@ function coinShiftRquest (data, marketData) {
|
||||
if (response.error) return dispatch(actions.displayWarning(response.error))
|
||||
var message = `
|
||||
Deposit your ${response.depositType} to the address bellow:`
|
||||
_accountManager.createShapeShiftTx(response.deposit, response.depositType)
|
||||
background.createShapeShiftTx(response.deposit, response.depositType)
|
||||
dispatch(actions.showQrView(response.deposit, [message].concat(marketData)))
|
||||
})
|
||||
}
|
||||
@ -836,3 +817,24 @@ function shapeShiftRequest (query, options, cb) {
|
||||
return shapShiftReq.send()
|
||||
}
|
||||
}
|
||||
|
||||
// Call Background Then Update
|
||||
//
|
||||
// A function generator for a common pattern wherein:
|
||||
// We show loading indication.
|
||||
// We call a background method.
|
||||
// We hide loading indication.
|
||||
// If it errored, we show a warning.
|
||||
// If it didn't, we update the state.
|
||||
function callBackgroundThenUpdate (method, ...args) {
|
||||
return (dispatch) => {
|
||||
dispatch(actions.showLoadingIndication())
|
||||
method.call(background, ...args, (err, newState) => {
|
||||
dispatch(actions.hideLoadingIndication())
|
||||
if (err) {
|
||||
return dispatch(actions.displayWarning(err.message))
|
||||
}
|
||||
dispatch(actions.updateMetamaskState(newState))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
183
ui/app/app.js
183
ui/app/app.js
@ -7,9 +7,7 @@ const ReactCSSTransitionGroup = require('react-addons-css-transition-group')
|
||||
// init
|
||||
const DisclaimerScreen = require('./first-time/disclaimer')
|
||||
const InitializeMenuScreen = require('./first-time/init-menu')
|
||||
const CreateVaultScreen = require('./first-time/create-vault')
|
||||
const CreateVaultCompleteScreen = require('./first-time/create-vault-complete')
|
||||
const RestoreVaultScreen = require('./first-time/restore-vault')
|
||||
const NewKeyChainScreen = require('./new-keychain')
|
||||
// unlock
|
||||
const UnlockScreen = require('./unlock')
|
||||
// accounts
|
||||
@ -18,10 +16,10 @@ const AccountDetailScreen = require('./account-detail')
|
||||
const SendTransactionScreen = require('./send')
|
||||
const ConfirmTxScreen = require('./conf-tx')
|
||||
// notice
|
||||
const NoticeScreen = require('./notice')
|
||||
const NoticeScreen = require('./components/notice')
|
||||
const generateLostAccountsNotice = require('../lib/lost-accounts-notice')
|
||||
// other views
|
||||
const ConfigScreen = require('./config')
|
||||
const RevealSeedConfirmation = require('./recover-seed/confirmation')
|
||||
const InfoScreen = require('./info')
|
||||
const LoadingIndicator = require('./components/loading')
|
||||
const SandwichExpando = require('sandwich-expando')
|
||||
@ -29,9 +27,12 @@ const MenuDroppo = require('menu-droppo')
|
||||
const DropMenuItem = require('./components/drop-menu-item')
|
||||
const NetworkIndicator = require('./components/network')
|
||||
const Tooltip = require('./components/tooltip')
|
||||
const EthStoreWarning = require('./eth-store-warning')
|
||||
const BuyView = require('./components/buy-button-subview')
|
||||
const QrView = require('./components/qr-code')
|
||||
const HDCreateVaultComplete = require('./keychains/hd/create-vault-complete')
|
||||
const HDRestoreVaultScreen = require('./keychains/hd/restore-vault')
|
||||
const RevealSeedConfirmation = require('./keychains/hd/recover-seed/confirmation')
|
||||
|
||||
module.exports = connect(mapStateToProps)(App)
|
||||
|
||||
inherits(App, Component)
|
||||
@ -41,8 +42,7 @@ function mapStateToProps (state) {
|
||||
return {
|
||||
// state from plugin
|
||||
isLoading: state.appState.isLoading,
|
||||
isConfirmed: state.metamask.isConfirmed,
|
||||
isEthConfirmed: state.metamask.isEthConfirmed,
|
||||
isDisclaimerConfirmed: state.metamask.isDisclaimerConfirmed,
|
||||
noActiveNotices: state.metamask.noActiveNotices,
|
||||
isInitialized: state.metamask.isInitialized,
|
||||
isUnlocked: state.metamask.isUnlocked,
|
||||
@ -56,6 +56,8 @@ function mapStateToProps (state) {
|
||||
network: state.metamask.network,
|
||||
provider: state.metamask.provider,
|
||||
forgottenPassword: state.appState.forgottenPassword,
|
||||
lastUnreadNotice: state.metamask.lastUnreadNotice,
|
||||
lostAccounts: state.metamask.lostAccounts,
|
||||
}
|
||||
}
|
||||
|
||||
@ -94,7 +96,6 @@ App.prototype.render = function () {
|
||||
transitionLeaveTimeout: 300,
|
||||
}, [
|
||||
this.renderPrimary(),
|
||||
this.renderBackToInitButton(),
|
||||
]),
|
||||
]),
|
||||
])
|
||||
@ -102,7 +103,6 @@ App.prototype.render = function () {
|
||||
}
|
||||
|
||||
App.prototype.renderAppBar = function () {
|
||||
|
||||
if (window.METAMASK_UI_TYPE === 'notification') {
|
||||
return null
|
||||
}
|
||||
@ -141,15 +141,21 @@ App.prototype.renderAppBar = function () {
|
||||
src: '/images/icon-128.png',
|
||||
}),
|
||||
|
||||
h(NetworkIndicator, {
|
||||
network: this.props.network,
|
||||
provider: this.props.provider,
|
||||
onClick: (event) => {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
this.setState({ isNetworkMenuOpen: !isNetworkMenuOpen })
|
||||
h('#network-spacer.flex-center', {
|
||||
style: {
|
||||
marginRight: '-72px',
|
||||
},
|
||||
}),
|
||||
}, [
|
||||
h(NetworkIndicator, {
|
||||
network: this.props.network,
|
||||
provider: this.props.provider,
|
||||
onClick: (event) => {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
this.setState({ isNetworkMenuOpen: !isNetworkMenuOpen })
|
||||
},
|
||||
}),
|
||||
]),
|
||||
]),
|
||||
|
||||
// metamask name
|
||||
@ -254,7 +260,15 @@ App.prototype.renderNetworkDropdown = function () {
|
||||
activeNetworkRender: props.provider.rpcTarget,
|
||||
}),
|
||||
|
||||
this.renderCustomOption(props.provider.rpcTarget),
|
||||
this.renderCustomOption(props.provider),
|
||||
|
||||
h(DropMenuItem, {
|
||||
label: 'Custom RPC',
|
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: false }),
|
||||
action: () => this.props.dispatch(actions.showConfigPage()),
|
||||
icon: h('i.fa.fa-question-circle.fa-lg'),
|
||||
}),
|
||||
|
||||
])
|
||||
}
|
||||
|
||||
@ -305,6 +319,7 @@ App.prototype.renderDropdown = function () {
|
||||
}),
|
||||
])
|
||||
}
|
||||
|
||||
App.prototype.renderBackButton = function (style, justArrow = false) {
|
||||
var props = this.props
|
||||
return (
|
||||
@ -322,85 +337,17 @@ App.prototype.renderBackButton = function (style, justArrow = false) {
|
||||
}, 'BACK'),
|
||||
])
|
||||
)
|
||||
|
||||
}
|
||||
App.prototype.renderBackToInitButton = function () {
|
||||
var props = this.props
|
||||
var button = null
|
||||
if (!props.isConfirmed) return button
|
||||
if (!props.isUnlocked) {
|
||||
if (props.currentView.name === 'InitMenu') {
|
||||
button = props.forgottenPassword ? h('.flex-row', {
|
||||
key: 'rightArrow',
|
||||
style: {
|
||||
position: 'absolute',
|
||||
bottom: '10px',
|
||||
right: '15px',
|
||||
fontSize: '21px',
|
||||
fontFamily: 'Montserrat Light',
|
||||
color: '#7F8082',
|
||||
width: '77.578px',
|
||||
alignItems: 'flex-end',
|
||||
},
|
||||
}, [
|
||||
h('div.cursor-pointer', {
|
||||
style: {
|
||||
marginRight: '3px',
|
||||
},
|
||||
onClick: () => props.dispatch(actions.backToUnlockView()),
|
||||
}, 'LOGIN'),
|
||||
h('i.fa.fa-arrow-right.cursor-pointer'),
|
||||
]) : null
|
||||
} else if (props.isInitialized) {
|
||||
var style
|
||||
switch (props.currentView.name) {
|
||||
case 'createVault':
|
||||
style = {
|
||||
position: 'absolute',
|
||||
top: '41px',
|
||||
left: '80px',
|
||||
fontSize: '21px',
|
||||
fontFamily: 'Montserrat Bold',
|
||||
color: 'rgb(174, 174, 174)',
|
||||
}
|
||||
return this.renderBackButton(style, true)
|
||||
case 'restoreVault':
|
||||
style = {
|
||||
position: 'absolute',
|
||||
top: '41px',
|
||||
left: '70px',
|
||||
fontSize: '21px',
|
||||
fontFamily: 'Montserrat Bold',
|
||||
color: 'rgb(174, 174, 174)',
|
||||
}
|
||||
return this.renderBackButton(style, true)
|
||||
default:
|
||||
style = {
|
||||
position: 'absolute',
|
||||
bottom: '10px',
|
||||
left: '15px',
|
||||
fontSize: '21px',
|
||||
fontFamily: 'Montserrat Light',
|
||||
color: '#7F8082',
|
||||
width: '71.969px',
|
||||
alignItems: 'flex-end',
|
||||
}
|
||||
return this.renderBackButton(style)
|
||||
}
|
||||
}
|
||||
}
|
||||
return button
|
||||
}
|
||||
|
||||
App.prototype.renderPrimary = function () {
|
||||
var props = this.props
|
||||
|
||||
if (!props.isConfirmed) {
|
||||
if (!props.isDisclaimerConfirmed) {
|
||||
return h(DisclaimerScreen, {key: 'disclaimerScreen'})
|
||||
}
|
||||
|
||||
if (props.seedWords) {
|
||||
return h(CreateVaultCompleteScreen, {key: 'createVaultComplete'})
|
||||
return h(HDCreateVaultComplete, {key: 'HDCreateVaultComplete'})
|
||||
}
|
||||
|
||||
// show initialize screen
|
||||
@ -408,34 +355,43 @@ App.prototype.renderPrimary = function () {
|
||||
// show current view
|
||||
switch (props.currentView.name) {
|
||||
|
||||
case 'createVault':
|
||||
return h(CreateVaultScreen, {key: 'createVault'})
|
||||
|
||||
case 'restoreVault':
|
||||
return h(RestoreVaultScreen, {key: 'restoreVault'})
|
||||
|
||||
case 'createVaultComplete':
|
||||
return h(CreateVaultCompleteScreen, {key: 'createVaultComplete'})
|
||||
return h(HDRestoreVaultScreen, {key: 'HDRestoreVaultScreen'})
|
||||
|
||||
default:
|
||||
return h(InitializeMenuScreen, {key: 'menuScreenInit'})
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// show unlock screen
|
||||
if (!props.isUnlocked) {
|
||||
return h(UnlockScreen, {key: 'locked'})
|
||||
switch (props.currentView.name) {
|
||||
|
||||
case 'restoreVault':
|
||||
return h(HDRestoreVaultScreen, {key: 'HDRestoreVaultScreen'})
|
||||
|
||||
default:
|
||||
return h(UnlockScreen, {key: 'locked'})
|
||||
}
|
||||
}
|
||||
|
||||
// notices
|
||||
if (!props.noActiveNotices) {
|
||||
return h(NoticeScreen, {key: 'NoticeScreen'})
|
||||
return h(NoticeScreen, {
|
||||
notice: props.lastUnreadNotice,
|
||||
key: 'NoticeScreen',
|
||||
onConfirm: () => props.dispatch(actions.markNoticeRead(props.lastUnreadNotice)),
|
||||
})
|
||||
} else if (props.lostAccounts && props.lostAccounts.length > 0) {
|
||||
return h(NoticeScreen, {
|
||||
notice: generateLostAccountsNotice(props.lostAccounts),
|
||||
key: 'LostAccountsNotice',
|
||||
onConfirm: () => props.dispatch(actions.markAccountsFound()),
|
||||
})
|
||||
}
|
||||
|
||||
// show current view
|
||||
switch (props.currentView.name) {
|
||||
case 'EthStoreWarning':
|
||||
return h(EthStoreWarning, {key: 'ethWarning'})
|
||||
|
||||
case 'accounts':
|
||||
return h(AccountsScreen, {key: 'accounts'})
|
||||
@ -446,6 +402,9 @@ App.prototype.renderPrimary = function () {
|
||||
case 'sendTransaction':
|
||||
return h(SendTransactionScreen, {key: 'send-transaction'})
|
||||
|
||||
case 'newKeychain':
|
||||
return h(NewKeyChainScreen, {key: 'new-keychain'})
|
||||
|
||||
case 'confTx':
|
||||
return h(ConfirmTxScreen, {key: 'confirm-tx'})
|
||||
|
||||
@ -458,10 +417,9 @@ App.prototype.renderPrimary = function () {
|
||||
case 'info':
|
||||
return h(InfoScreen, {key: 'info'})
|
||||
|
||||
case 'createVault':
|
||||
return h(CreateVaultScreen, {key: 'createVault'})
|
||||
case 'buyEth':
|
||||
return h(BuyView, {key: 'buyEthView'})
|
||||
|
||||
case 'qr':
|
||||
return h('div', {
|
||||
style: {
|
||||
@ -506,23 +464,14 @@ App.prototype.toggleMetamaskActive = function () {
|
||||
}
|
||||
}
|
||||
|
||||
App.prototype.renderCustomOption = function (rpcTarget) {
|
||||
App.prototype.renderCustomOption = function (provider) {
|
||||
const { rpcTarget, type } = provider
|
||||
if (type !== 'rpc') return null
|
||||
|
||||
switch (rpcTarget) {
|
||||
case undefined:
|
||||
return h(DropMenuItem, {
|
||||
label: 'Custom RPC',
|
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: false }),
|
||||
action: () => this.props.dispatch(actions.showConfigPage()),
|
||||
icon: h('i.fa.fa-question-circle.fa-lg'),
|
||||
})
|
||||
|
||||
case 'http://localhost:8545':
|
||||
return h(DropMenuItem, {
|
||||
label: 'Custom RPC',
|
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: false }),
|
||||
action: () => this.props.dispatch(actions.showConfigPage()),
|
||||
icon: h('i.fa.fa-question-circle.fa-lg'),
|
||||
})
|
||||
return null
|
||||
|
||||
default:
|
||||
return h(DropMenuItem, {
|
||||
|
@ -7,7 +7,7 @@ const actions = require('../actions')
|
||||
const isValidAddress = require('../util').isValidAddress
|
||||
module.exports = connect(mapStateToProps)(CoinbaseForm)
|
||||
|
||||
function mapStateToProps(state) {
|
||||
function mapStateToProps (state) {
|
||||
return {
|
||||
selectedAccount: state.selectedAccount,
|
||||
warning: state.appState.warning,
|
||||
@ -16,7 +16,7 @@ function mapStateToProps(state) {
|
||||
|
||||
inherits(CoinbaseForm, Component)
|
||||
|
||||
function CoinbaseForm() {
|
||||
function CoinbaseForm () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
@ -124,7 +124,6 @@ CoinbaseForm.prototype.toCoinbase = function () {
|
||||
}
|
||||
|
||||
CoinbaseForm.prototype.renderLoading = function () {
|
||||
|
||||
return h('img', {
|
||||
style: {
|
||||
width: '27px',
|
||||
@ -134,9 +133,8 @@ CoinbaseForm.prototype.renderLoading = function () {
|
||||
})
|
||||
}
|
||||
|
||||
function isValidAmountforCoinBase(amount) {
|
||||
function isValidAmountforCoinBase (amount) {
|
||||
amount = parseFloat(amount)
|
||||
|
||||
if (amount) {
|
||||
if (amount <= 15 && amount > 0) {
|
||||
return {
|
||||
|
@ -50,12 +50,10 @@ CopyButton.prototype.render = function () {
|
||||
])
|
||||
}
|
||||
|
||||
CopyButton.prototype.debounceRestore = function() {
|
||||
|
||||
CopyButton.prototype.debounceRestore = function () {
|
||||
this.setState({ copied: true })
|
||||
clearTimeout(this.timeout)
|
||||
this.timeout = setTimeout(() => {
|
||||
this.setState({ copied: false })
|
||||
}, 850)
|
||||
|
||||
}
|
||||
|
@ -32,9 +32,9 @@ DropMenuItem.prototype.render = function () {
|
||||
}
|
||||
|
||||
DropMenuItem.prototype.activeNetworkRender = function () {
|
||||
let activeNetwork = this.props.activeNetworkRender
|
||||
let { provider } = this.props
|
||||
let providerType = provider ? provider.type : null
|
||||
const activeNetwork = this.props.activeNetworkRender
|
||||
const { provider } = this.props
|
||||
const providerType = provider ? provider.type : null
|
||||
if (activeNetwork === undefined) return
|
||||
|
||||
switch (this.props.label) {
|
||||
|
@ -15,9 +15,10 @@ function EthBalanceComponent () {
|
||||
|
||||
EthBalanceComponent.prototype.render = function () {
|
||||
var props = this.props
|
||||
let { value } = props
|
||||
var style = props.style
|
||||
var needsParse = this.props.needsParse !== undefined ? this.props.needsParse : true
|
||||
const value = formatBalance(props.value, 6, needsParse)
|
||||
value = value ? formatBalance(value, 6, needsParse) : '...'
|
||||
var width = props.width
|
||||
|
||||
return (
|
||||
@ -38,6 +39,7 @@ EthBalanceComponent.prototype.render = function () {
|
||||
EthBalanceComponent.prototype.renderBalance = function (value) {
|
||||
var props = this.props
|
||||
if (value === 'None') return value
|
||||
if (value === '...') return value
|
||||
var balanceObj = generateBalanceObject(value, props.shorten ? 1 : 3)
|
||||
var balance
|
||||
var splitBalance = value.split(' ')
|
||||
|
@ -16,8 +16,8 @@ function IdenticonComponent () {
|
||||
}
|
||||
|
||||
IdenticonComponent.prototype.render = function () {
|
||||
var state = this.props
|
||||
var diameter = state.diameter || this.defaultDiameter
|
||||
var props = this.props
|
||||
var diameter = props.diameter || this.defaultDiameter
|
||||
return (
|
||||
h('div', {
|
||||
key: 'identicon-' + this.props.address,
|
||||
@ -33,15 +33,31 @@ IdenticonComponent.prototype.render = function () {
|
||||
}
|
||||
|
||||
IdenticonComponent.prototype.componentDidMount = function () {
|
||||
var state = this.props
|
||||
var address = state.address
|
||||
var props = this.props
|
||||
var address = props.address
|
||||
|
||||
if (!address) return
|
||||
|
||||
var container = findDOMNode(this)
|
||||
var diameter = state.diameter || this.defaultDiameter
|
||||
var imageify = state.imageify === undefined ? true : state.imageify
|
||||
var img = iconFactory.iconForAddress(address, diameter, imageify)
|
||||
var diameter = props.diameter || this.defaultDiameter
|
||||
var img = iconFactory.iconForAddress(address, diameter, false)
|
||||
container.appendChild(img)
|
||||
}
|
||||
|
||||
IdenticonComponent.prototype.componentDidUpdate = function () {
|
||||
var props = this.props
|
||||
var address = props.address
|
||||
|
||||
if (!address) return
|
||||
|
||||
var container = findDOMNode(this)
|
||||
|
||||
var children = container.children
|
||||
for (var i = 0; i < children.length; i++) {
|
||||
container.removeChild(children[i])
|
||||
}
|
||||
|
||||
var diameter = props.diameter || this.defaultDiameter
|
||||
var img = iconFactory.iconForAddress(address, diameter, false)
|
||||
container.appendChild(img)
|
||||
}
|
||||
|
@ -22,7 +22,6 @@ Network.prototype.render = function () {
|
||||
let iconName, hoverText
|
||||
|
||||
if (networkNumber === 'loading') {
|
||||
|
||||
return h('img.network-indicator', {
|
||||
title: 'Attempting to connect to blockchain.',
|
||||
onClick: (event) => this.props.onClick(event),
|
||||
@ -32,7 +31,6 @@ Network.prototype.render = function () {
|
||||
},
|
||||
src: 'images/loading.svg',
|
||||
})
|
||||
|
||||
} else if (providerName === 'mainnet') {
|
||||
hoverText = 'Main Ethereum Network'
|
||||
iconName = 'ethereum-network'
|
||||
@ -48,11 +46,7 @@ Network.prototype.render = function () {
|
||||
}
|
||||
|
||||
return (
|
||||
h('#network_component.flex-center.pointer', {
|
||||
style: {
|
||||
marginRight: '-27px',
|
||||
marginLeft: '-3px',
|
||||
},
|
||||
h('#network_component.pointer', {
|
||||
title: hoverText,
|
||||
onClick: (event) => this.props.onClick(event),
|
||||
}, [
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user