Merge pull request #816 from MetaMask/i328-MultiVault

Multi vault to Dev Branch
feature/default_network_editable
kumavis 8 years ago committed by GitHub
commit 6400eb8453
  1. 4
      .eslintrc
  2. 3
      .gitignore
  3. 1
      CHANGELOG.md
  4. 4
      README.md
  5. 11
      app/scripts/background.js
  6. 14
      app/scripts/contentscript.js
  7. 2
      app/scripts/inpage.js
  8. 567
      app/scripts/keyring-controller.js
  9. 101
      app/scripts/keyrings/hd.js
  10. 67
      app/scripts/keyrings/simple.js
  11. 5
      app/scripts/lib/auto-faucet.js
  12. 5
      app/scripts/lib/auto-reload.js
  13. 77
      app/scripts/lib/config-manager.js
  14. 149
      app/scripts/lib/encryptor.js
  15. 52
      app/scripts/lib/idStore-migrator.js
  16. 22
      app/scripts/lib/idStore.js
  17. 15
      app/scripts/lib/inpage-provider.js
  18. 2
      app/scripts/lib/is-popup-or-notification.js
  19. 12
      app/scripts/lib/notifications.js
  20. 9
      app/scripts/lib/random-id.js
  21. 28
      app/scripts/lib/sig-util.js
  22. 155
      app/scripts/metamask-controller.js
  23. 4
      app/scripts/popup-core.js
  24. 2
      app/scripts/popup.js
  25. 2
      development/states.js
  26. 2
      development/states.json
  27. 2
      development/states/account-detail-with-shapeshift-tx.json
  28. 2
      development/states/account-detail-with-transaction-history.json
  29. 2
      development/states/account-detail.json
  30. 5
      development/states/accounts.json
  31. 2
      development/states/config.json
  32. 2
      development/states/create-vault-password.json
  33. 3
      development/states/custom-rpc.json
  34. 2
      development/states/empty-account-detail.json
  35. 20
      development/states/first-time.json
  36. 2
      development/states/help.json
  37. 3
      development/states/locked.json
  38. 2
      development/states/new-vault.json
  39. 2
      development/states/pending-crash.json
  40. 5
      development/states/pending-signature.json
  41. 2
      development/states/pending-tx-contract.json
  42. 2
      development/states/pending-tx-send-coin.json
  43. 2
      development/states/pending-tx-value.json
  44. 5
      development/states/restore-vault.json
  45. 5
      development/states/send.json
  46. 5
      development/states/shapeshift.json
  47. 2
      development/states/show-seed-words.json
  48. 188
      docs/multi_vault_planning.md
  49. 4
      mock-dev.js
  50. 13
      package.json
  51. 2
      test/integration/index.html
  52. 21
      test/integration/index.js
  53. 67
      test/integration/lib/encryptor-test.js
  54. 15
      test/integration/lib/first-time.js
  55. 24
      test/integration/tests.js
  56. 2
      test/lib/mock-config-manager.js
  57. 32
      test/lib/mock-encryptor.js
  58. 38
      test/lib/mock-simple-keychain.js
  59. 60
      test/unit/actions/restore_vault_test.js
  60. 8
      test/unit/actions/tx_test.js
  61. 28
      test/unit/config-manager-test.js
  62. 160
      test/unit/idStore-migration-test.js
  63. 172
      test/unit/keyring-controller-test.js
  64. 108
      test/unit/keyrings/hd-test.js
  65. 83
      test/unit/keyrings/simple-test.js
  66. 1
      testem.yml
  67. 4
      ui-dev.js
  68. 1
      ui/app/account-detail.js
  69. 14
      ui/app/accounts/index.js
  70. 203
      ui/app/actions.js
  71. 93
      ui/app/app.js
  72. 8
      ui/app/components/coinbase-form.js
  73. 4
      ui/app/components/copyButton.js
  74. 6
      ui/app/components/drop-menu-item.js
  75. 30
      ui/app/components/identicon.js
  76. 5
      ui/app/components/network.js
  77. 4
      ui/app/components/shapeshift-form.js
  78. 1
      ui/app/components/shift-list-item.js
  79. 2
      ui/app/components/tooltip.js
  80. 2
      ui/app/components/transaction-list-item.js
  81. 89
      ui/app/eth-store-warning.js
  82. 129
      ui/app/first-time/create-vault.js
  83. 131
      ui/app/first-time/init-menu.js
  84. 3
      ui/app/keychains/hd/create-vault-complete.js
  85. 22
      ui/app/keychains/hd/recover-seed/confirmation.js
  86. 16
      ui/app/keychains/hd/restore-vault.js
  87. 29
      ui/app/new-keychain.js
  88. 2
      ui/app/reducers.js
  89. 18
      ui/app/reducers/app.js
  90. 8
      ui/app/reducers/metamask.js
  91. 16
      ui/app/unlock.js
  92. 2
      ui/index.js
  93. 2
      ui/lib/account-link.js
  94. 9
      ui/lib/contract-namer.js
  95. 4
      ui/lib/icon-factory.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

@ -1,5 +1,4 @@
dist
node_modules
temp
.tmp
@ -7,7 +6,6 @@ temp
app/bower_components
test/bower_components
package
.DS_Store
builds/
notes.txt
@ -15,3 +13,4 @@ app/.DS_Store
development/bundle.js
builds.zip
test/integration/bundle.js
npm-debug.log

@ -3,6 +3,7 @@
## Current Master
- Show a warning when a transaction fails during simulation.
- Fix bug where 20% of gas estimate was not being added properly.
- Render error messages in our confirmation screen more gracefully.
## 2.13.7 2016-11-8

@ -90,6 +90,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.

@ -10,6 +10,7 @@ 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({
@ -21,7 +22,7 @@ const controller = new MetamaskController({
setData,
loadData,
})
const idStore = controller.idStore
const keyringController = controller.keyringController
function triggerUi () {
if (!popupIsOpen) notification.show()
@ -29,7 +30,7 @@ function triggerUi () {
// 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'})
}
})
@ -82,7 +83,7 @@ function setupControllerConnection (stream) {
// push updates to popup
controller.ethStore.on('update', controller.sendUpdate.bind(controller))
controller.listeners.push(remote)
idStore.on('update', controller.sendUpdate.bind(controller))
keyringController.on('update', controller.sendUpdate.bind(controller))
// teardown on disconnect
eos(stream, () => {
@ -96,9 +97,9 @@ function setupControllerConnection (stream) {
// plugin badge text
//
idStore.on('update', updateBadge)
keyringController.on('update', updateBadge)
function updateBadge (state) {
function updateBadge () {
var label = ''
var unconfTxs = controller.configManager.unconfirmedTxs()
var unconfTxLen = Object.keys(unconfTxs).length

@ -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,7 +43,7 @@ reloadStream.once('data', triggerReload)
var pingChannel = inpageProvider.multiStream.createStream('pingpong')
var pingStream = new PingStream({ objectMode: true })
// wait for first successful reponse
metamaskStream.once('_data', function(){
metamaskStream.once('_data', function () {
pingStream.pipe(pingChannel).pipe(pingStream)
})
endOfStream(pingStream, triggerReload)

@ -0,0 +1,567 @@
const async = require('async')
const ethUtil = require('ethereumjs-util')
const ethBinToOps = require('eth-bin-to-ops')
const EthQuery = require('eth-query')
const bip39 = require('bip39')
const Transaction = require('ethereumjs-tx')
const EventEmitter = require('events').EventEmitter
const normalize = require('./lib/sig-util').normalize
const encryptor = require('./lib/encryptor')
const messageManager = require('./lib/message-manager')
const autoFaucet = require('./lib/auto-faucet')
const IdStoreMigrator = require('./lib/idStore-migrator')
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 {
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._unconfTxCbs = {}
this._unconfMsgCbs = {}
this.getNetwork = opts.getNetwork
// TEMPORARY UNTIL FULL DEPRECATION:
this.idStoreMigrator = new IdStoreMigrator({
configManager: this.configManager,
})
}
getState () {
const configManager = this.configManager
const address = configManager.getSelectedAccount()
const wallet = configManager.getWallet() // old style vault
const vault = configManager.getVault() // new style vault
return {
seedWords: this.configManager.getSeedWords(),
isInitialized: (!!wallet || !!vault),
isUnlocked: !!this.key,
isDisclaimerConfirmed: this.configManager.getConfirmedDisclaimer(), // AUDIT this.configManager.getConfirmedDisclaimer(),
unconfTxs: this.configManager.unconfirmedTxs(),
transactions: this.configManager.getTxList(),
unconfMsgs: messageManager.unconfirmedMsgs(),
messages: messageManager.getMsgList(),
selectedAddress: address,
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,
}
}
setStore (ethStore) {
this.ethStore = ethStore
}
createNewVaultAndKeychain (password, entropy, cb) {
this.createNewVault(password, entropy, (err) => {
if (err) return cb(err)
this.createFirstKeyTree(password, cb)
})
}
createNewVaultAndRestore (password, seed, cb) {
if (typeof password !== 'string') {
return cb('Password must be text.')
}
if (!bip39.validateMnemonic(seed)) {
return cb('Seed phrase is invalid.')
}
this.clearKeyrings()
this.createNewVault(password, '', (err) => {
if (err) return cb(err)
this.addNewKeyring('HD Key Tree', {
mnemonic: seed,
numberOfAccounts: 1,
}, (err) => {
if (err) return cb(err)
const firstKeyring = this.keyrings[0]
const accounts = firstKeyring.getAccounts()
const firstAccount = accounts[0]
const hexAccount = normalize(firstAccount)
this.configManager.setSelectedAccount(hexAccount)
this.setupAccounts(accounts)
this.emit('update')
cb()
})
})
}
migrateAndGetKey (password) {
let key
const shouldMigrate = !!this.configManager.getWallet() && !this.configManager.getVault()
return this.loadKey(password)
.then((derivedKey) => {
key = derivedKey
this.key = key
return this.idStoreMigrator.oldSeedForPassword(password)
})
.then((serialized) => {
if (serialized && shouldMigrate) {
const keyring = this.restoreKeyring(serialized)
this.keyrings.push(keyring)
this.configManager.setSelectedAccount(keyring.getAccounts()[0])
}
return key
})
}
createNewVault (password, entropy, cb) {
const configManager = this.configManager
const salt = this.encryptor.generateSalt()
configManager.setSalt(salt)
return this.migrateAndGetKey(password)
.then(() => {
return this.persistAllKeyrings()
})
.then(() => {
cb()
})
.catch((err) => {
cb(err)
})
}
createFirstKeyTree (password, cb) {
this.clearKeyrings()
this.addNewKeyring('HD Key Tree', {numberOfAccounts: 1}, (err) => {
const accounts = this.keyrings[0].getAccounts()
const firstAccount = accounts[0]
const hexAccount = normalize(firstAccount)
this.configManager.setSelectedAccount(firstAccount)
this.placeSeedWords()
autoFaucet(hexAccount)
this.setupAccounts(accounts)
this.persistAllKeyrings()
.then(() => {
cb(err)
})
.catch((reason) => {
cb(reason)
})
})
}
placeSeedWords () {
const firstKeyring = this.keyrings[0]
const seedWords = firstKeyring.serialize().mnemonic
this.configManager.setSeedWords(seedWords)
}
submitPassword (password, cb) {
this.migrateAndGetKey(password)
.then((key) => {
return this.unlockKeyrings(key)
})
.then((keyrings) => {
this.keyrings = keyrings
this.setupAccounts()
this.emit('update')
cb(null, this.getState())
})
.catch((err) => {
console.error(err)
cb(err)
})
}
loadKey (password) {
const salt = this.configManager.getSalt() || this.encryptor.generateSalt()
return this.encryptor.keyFromPassword(password + salt)
.then((key) => {
this.key = key
this.configManager.setSalt(salt)
return key
})
}
addNewKeyring (type, opts, cb) {
const Keyring = this.getKeyringClassForType(type)
const keyring = new Keyring(opts)
const accounts = keyring.getAccounts()
this.keyrings.push(keyring)
this.setupAccounts(accounts)
this.persistAllKeyrings()
.then(() => {
cb()
})
.catch((reason) => {
cb(reason)
})
}
addNewAccount (keyRingNum = 0, cb) {
const ring = this.keyrings[keyRingNum]
const accounts = ring.addAccounts(1)
this.setupAccounts(accounts)
this.persistAllKeyrings()
.then(() => {
cb()
})
.catch((reason) => {
cb(reason)
})
}
setupAccounts (accounts) {
var arr = accounts || this.getAccounts()
arr.forEach((account) => {
this.getBalanceAndNickname(account)
})
}
// Takes an account address and an iterator representing
// the current number of named accounts.
getBalanceAndNickname (account) {
const address = normalize(account)
this.ethStore.addAccount(address)
this.createNickname(address)
}
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)
}
saveAccountLabel (account, label, cb) {
const address = normalize(account)
const configManager = this.configManager
configManager.setNicknameForWallet(address, label)
this.identities[address].name = label
if (cb) {
cb(null, label)
} else {
return label
}
}
persistAllKeyrings () {
const serialized = this.keyrings.map((k) => {
return {
type: k.type,
data: k.serialize(),
}
})
return this.encryptor.encryptWithKey(this.key, serialized)
.then((encryptedString) => {
this.configManager.setVault(encryptedString)
return true
})
}
unlockKeyrings (key) {
const encryptedVault = this.configManager.getVault()
return this.encryptor.decryptWithKey(key, encryptedVault)
.then((vault) => {
vault.forEach(this.restoreKeyring.bind(this))
return this.keyrings
})
}
restoreKeyring (serialized) {
const { type, data } = serialized
const Keyring = this.getKeyringClassForType(type)
const keyring = new Keyring()
keyring.deserialize(data)
const accounts = keyring.getAccounts()
this.setupAccounts(accounts)
this.keyrings.push(keyring)
return keyring
}
getKeyringClassForType (type) {
const Keyring = this.keyringTypes.reduce((res, kr) => {
if (kr.type() === type) {
return kr
} else {
return res
}
})
return Keyring
}
getAccounts () {
const keyrings = this.keyrings || []
return keyrings.map(kr => kr.getAccounts())
.reduce((res, arr) => {
return res.concat(arr)
}, [])
}
setSelectedAddress (address, cb) {
var addr = normalize(address)
this.configManager.setSelectedAccount(addr)
cb(null, addr)
}
addUnconfirmedTransaction (txParams, onTxDoneCb, cb) {
var self = this
const configManager = this.configManager
// create txData obj with parameters and meta data
var time = (new Date()).getTime()
var txId = createId()
txParams.metamaskId = txId
txParams.metamaskNetworkId = this.getNetwork()
var txData = {
id: txId,
txParams: txParams,
time: time,
status: 'unconfirmed',
gasMultiplier: configManager.getGasMultiplier() || 1,
metamaskNetworkId: this.getNetwork(),
}
// keep the onTxDoneCb around for after approval/denial (requires user interaction)
// This onTxDoneCb fires completion to the Dapp's write operation.
this._unconfTxCbs[txId] = onTxDoneCb
var provider = this.ethStore._query.currentProvider
var query = new EthQuery(provider)
// calculate metadata for tx
async.parallel([
analyzeForDelegateCall,
estimateGas,
], didComplete)
// perform static analyis on the target contract code
function analyzeForDelegateCall (cb) {
if (txParams.to) {
query.getCode(txParams.to, function (err, result) {
if (err) return cb(err)
var code = ethUtil.toBuffer(result)
if (code !== '0x') {
var ops = ethBinToOps(code)
var containsDelegateCall = ops.some((op) => op.name === 'DELEGATECALL')
txData.containsDelegateCall = containsDelegateCall
cb()
} else {
cb()
}
})
} else {
cb()
}
}
function estimateGas (cb) {
query.estimateGas(txParams, function (err, result) {
if (err) return cb(err)
txData.estimatedGas = self.addGasBuffer(result)
cb()
})
}
function didComplete (err) {
if (err) return cb(err)
configManager.addTx(txData)
// signal update
self.emit('update')
// signal completion of add tx
cb(null, txData)
}
}
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
}
approveTransaction (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.emit('update')
}
cancelTransaction (txId, cb) {
const configManager = this.configManager
var approvalCb = this._unconfTxCbs[txId] || noop
// reject tx
approvalCb(null, false)
// clean up
configManager.rejectTx(txId)
delete this._unconfTxCbs[txId]
if (cb && typeof cb === 'function') {
cb()
}
}
signTransaction (txParams, cb) {
try {
const address = normalize(txParams.from)
const keyring = this.getKeyringForAccount(address)
// Handle gas pricing
var gasMultiplier = this.configManager.getGasMultiplier() || 1
var gasPrice = new BN(ethUtil.stripHexPrefix(txParams.gasPrice), 16)
gasPrice = gasPrice.mul(new BN(gasMultiplier * 100, 10)).div(new BN(100, 10))
txParams.gasPrice = ethUtil.intToHex(gasPrice.toNumber())
// normalize values
txParams.to = 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)
let tx = new Transaction(txParams)
tx = keyring.signTransaction(address, tx)
// Add the tx hash to the persisted meta-tx object
var txHash = ethUtil.bufferToHex(tx.hash())
var metaTx = this.configManager.getTx(txParams.metamaskId)
metaTx.hash = txHash
this.configManager.updateTx(metaTx)
// return raw serialized tx
var rawTx = ethUtil.bufferToHex(tx.serialize())
cb(null, rawTx)
} catch (e) {
cb(e)
}
}
signMessage (msgParams, cb) {
try {
const keyring = this.getKeyringForAccount(msgParams.from)
const address = normalize(msgParams.from)
const rawSig = keyring.signMessage(address, msgParams.data)
cb(null, rawSig)
} catch (e) {
cb(e)
}
}
getKeyringForAccount (address) {
const hexed = normalize(address)
return this.keyrings.find((ring) => {
return ring.getAccounts()
.map(normalize)
.includes(hexed)
})
}
cancelMessage (msgId, cb) {
if (cb && typeof cb === 'function') {
cb()
}
}
setLocked (cb) {
this.key = null
this.keyrings = []
this.emit('update')
cb()
}
exportAccount (address, cb) {
try {
const keyring = this.getKeyringForAccount(address)
const privateKey = keyring.exportAccount(normalize(address))
cb(null, privateKey)
} catch (e) {
cb(e)
}
}
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))
}
clearSeedWordCache (cb) {
this.configManager.setSeedWords(null)
cb(null, this.configManager.getSelectedAccount())
}
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 () {}

@ -0,0 +1,101 @@
const EventEmitter = require('events').EventEmitter
const hdkey = require('ethereumjs-wallet/hdkey')
const bip39 = require('bip39')
const ethUtil = require('ethereumjs-util')
const sigUtil = require('../lib/sig-util')
const type = 'HD Key Tree'
const hdPathString = `m/44'/60'/0'/0`
module.exports = class HdKeyring extends EventEmitter {
static type () {
return type
}
constructor (opts = {}) {
super()
this.type = type
this.deserialize(opts)
}
deserialize (opts = {}) {
this.opts = opts || {}
this.wallets = []
this.mnemonic = null
this.root = null
if ('mnemonic' in opts) {
this.initFromMnemonic(opts.mnemonic)
}
if ('numberOfAccounts' in opts) {
this.addAccounts(opts.numberOfAccounts)
}
}
initFromMnemonic (mnemonic) {
this.mnemonic = mnemonic
const seed = bip39.mnemonicToSeed(mnemonic)
this.hdWallet = hdkey.fromMasterSeed(seed)
this.root = this.hdWallet.derivePath(hdPathString)
}
serialize () {
return {
mnemonic: this.mnemonic,
numberOfAccounts: this.wallets.length,
}
}
exportAccount (address) {
const wallet = this.getWalletForAccount(address)
return wallet.getPrivateKey().toString('hex')
}
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)
}
return newWallets.map(w => w.getAddress().toString('hex'))
}
getAccounts () {
return 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 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 rawMsgSig
}
getWalletForAccount (account) {
return this.wallets.find((w) => {
const address = w.getAddress().toString('hex')
return ((address === account) || (sigUtil.normalize(address) === account))
})
}
}

@ -0,0 +1,67 @@
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')
module.exports = class SimpleKeyring extends EventEmitter {
static type () {
return type
}
constructor (opts) {
super()
this.type = type
this.opts = opts || {}
this.wallets = []
}
serialize () {
return this.wallets.map(w => w.getPrivateKey().toString('hex'))
}
deserialize (wallets = []) {
this.wallets = wallets.map((w) => {
var b = new Buffer(w, 'hex')
const wallet = Wallet.fromPrivateKey(b)
return wallet
})
}
addAccounts (n = 1) {
var newWallets = []
for (var i = 0; i < n; i++) {
newWallets.push(Wallet.generate())
}
this.wallets = this.wallets.concat(newWallets)
return newWallets.map(w => w.getAddress().toString('hex'))
}
getAccounts () {
return 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 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 rawMsgSig
}
getWalletForAccount (account) {
return this.wallets.find(w => w.getAddress().toString('hex') === account)
}
}

@ -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()
}
}

@ -2,6 +2,7 @@ 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 TESTNET_RPC = MetamaskConfig.network.testnet
const MAINNET_RPC = MetamaskConfig.network.mainnet
@ -110,6 +111,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 ('vault' in data) && 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
@ -117,7 +139,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)
}
@ -132,11 +154,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
@ -235,13 +269,15 @@ ConfigManager.prototype.getWalletNicknames = function () {
}
ConfigManager.prototype.nicknameForWallet = function (account) {
const address = ethUtil.addHexPrefix(account.toLowerCase())
const nicknames = this.getWalletNicknames()
return nicknames[account]
return nicknames[address]
}
ConfigManager.prototype.setNicknameForWallet = function (account, nickname) {
const address = ethUtil.addHexPrefix(account.toLowerCase())
const nicknames = this.getWalletNicknames()
nicknames[account] = nickname
nicknames[address] = nickname
var data = this.getData()
data.walletNicknames = nicknames
this.setData(data)
@ -249,6 +285,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)
@ -266,15 +313,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) {
@ -311,7 +358,6 @@ ConfigManager.prototype.updateConversionRate = function () {
this.setConversionPrice(0)
this.setConversionDate('N/A')
})
}
ConfigManager.prototype.setConversionPrice = function (price) {
@ -336,21 +382,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 : []

@ -0,0 +1,149 @@
var ethUtil = require('ethereumjs-util')
module.exports = {
// Simple encryption methods:
encrypt,
decrypt,
// More advanced encryption methods:
keyFromPassword,
encryptWithKey,
decryptWithKey,
// Buffer <-> String methods
convertArrayBufferViewtoString,
convertStringToArrayBufferView,
// Buffer <-> Hex string methods
serializeBufferForStorage,
serializeBufferFromStorage,
// Buffer <-> base64 string methods
encodeBufferToBase64,
decodeBase64ToBuffer,
generateSalt,
}
// Takes a Pojo, returns cypher text.
function encrypt (password, dataObj) {
return keyFromPassword(password)
.then(function (passwordDerivedKey) {
return encryptWithKey(passwordDerivedKey, dataObj)
})
}
function encryptWithKey (key, dataObj) {
var data = JSON.stringify(dataObj)
var dataBuffer = convertStringToArrayBufferView(data)
var vector = global.crypto.getRandomValues(new Uint8Array(16))
return global.crypto.subtle.encrypt({
name: 'AES-GCM',
iv: vector,
}, key, dataBuffer).then(function (buf) {
var buffer = new Uint8Array(buf)
var vectorStr = encodeBufferToBase64(vector)
var vaultStr = encodeBufferToBase64(buffer)
return `${vaultStr}\\${vectorStr}`
})
}
// Takes encrypted text, returns the restored Pojo.
function decrypt (password, text) {
return keyFromPassword(password)
.then(function (key) {
return decryptWithKey(key, text)
})
}
function decryptWithKey (key, text) {
const parts = text.split('\\')
const encryptedData = decodeBase64ToBuffer(parts[0])
const vector = decodeBase64ToBuffer(parts[1])
return crypto.subtle.decrypt({name: 'AES-GCM', iv: vector}, key, encryptedData)
.then(function (result) {
const decryptedData = new Uint8Array(result)
const decryptedStr = convertArrayBufferViewtoString(decryptedData)
const decryptedObj = JSON.parse(decryptedStr)
return decryptedObj
})
.catch(function (reason) {
throw new Error('Incorrect password')
})
}
function convertStringToArrayBufferView (str) {
var bytes = new Uint8Array(str.length)
for (var i = 0; i < str.length; i++) {
bytes[i] = str.charCodeAt(i)
}
return bytes
}
function convertArrayBufferViewtoString (buffer) {
var str = ''
for (var i = 0; i < buffer.byteLength; i++) {
str += String.fromCharCode(buffer[i])
}
return str
}
function keyFromPassword (password) {
var passBuffer = convertStringToArrayBufferView(password)
return global.crypto.subtle.digest('SHA-256', passBuffer)
.then(function (passHash) {
return global.crypto.subtle.importKey('raw', passHash, {name: 'AES-GCM'}, false, ['encrypt', 'decrypt'])
})
}
function serializeBufferFromStorage (str) {
str = ethUtil.stripHexPrefix(str)
var buf = new Uint8Array(str.length / 2)
for (var i = 0; i < str.length; i += 2) {
var seg = str.substr(i, 2)
buf[i / 2] = parseInt(seg, 16)
}
return buf
}
// Should return a string, ready for storage, in hex format.
function serializeBufferForStorage (buffer) {
var result = '0x'
var len = buffer.length || buffer.byteLength
for (var i = 0; i < len; i++) {
result += unprefixedHex(buffer[i])
}
return result
}
function unprefixedHex (num) {
var hex = num.toString(16)
while (hex.length < 2) {
hex = '0' + hex
}
return hex
}
function encodeBufferToBase64 (buf) {
var b64encoded = btoa(String.fromCharCode.apply(null, buf))
return b64encoded
}
function decodeBase64ToBuffer (base64) {
var buf = new Uint8Array(atob(base64).split('')
.map(function (c) {
return c.charCodeAt(0)
}))
return buf
}
function generateSalt (byteCount = 32) {
var view = new Uint8Array(byteCount)
global.crypto.getRandomValues(view)
var b64encoded = btoa(String.fromCharCode.apply(null, view))
return b64encoded
}

@ -0,0 +1,52 @@
const IdentityStore = require('./idStore')
module.exports = class IdentityStoreMigrator {
constructor ({ configManager }) {
this.configManager = configManager
const hasOldVault = this.hasOldVault()
if (!hasOldVault) {
this.idStore = new IdentityStore({ configManager })
}
}
oldSeedForPassword (password) {
const hasOldVault = this.hasOldVault()
const configManager = this.configManager
if (!this.idStore) {
this.idStore = new IdentityStore({ configManager })
}
if (!hasOldVault) {
return Promise.resolve(null)
}
return new Promise((resolve, reject) => {
this.idStore.submitPassword(password, (err) => {
if (err) return reject(err)
try {
resolve(this.serializeVault())
} catch (e) {
reject(e)
}
})
})
}
serializeVault () {
const mnemonic = this.idStore._idmgmt.getSeed()
const n = this.idStore._getAddresses().length
return {
type: 'HD Key Tree',
data: { mnemonic, n },
}
}
hasOldVault () {
const wallet = this.configManager.getWallet()
return wallet
}
}

@ -102,8 +102,7 @@ IdentityStore.prototype.getState = function () {
isInitialized: !!configManager.getWallet() && !seedWords,
isUnlocked: this._isUnlocked(),
seedWords: seedWords,
isConfirmed: configManager.getConfirmed(),
isEthConfirmed: configManager.getShouldntShowWarning(),
isDisclaimerConfirmed: configManager.getConfirmedDisclaimer(),
unconfTxs: configManager.unconfirmedTxs(),
transactions: configManager.getTxList(),
unconfMsgs: messageManager.unconfirmedMsgs(),
@ -114,7 +113,6 @@ IdentityStore.prototype.getState = function () {
conversionRate: configManager.getConversionRate(),
conversionDate: configManager.getConversionDate(),
gasMultiplier: configManager.getGasMultiplier(),
}))
}
@ -245,10 +243,10 @@ IdentityStore.prototype.addUnconfirmedTransaction = function (txParams, onTxDone
], didComplete)
// perform static analyis on the target contract code
function analyzeForDelegateCall(cb){
function analyzeForDelegateCall (cb) {
if (txParams.to) {
query.getCode(txParams.to, (err, result) => {
if (err) return cb(err)
if (err) return cb(err.message || err)
var containsDelegateCall = self.checkForDelegateCall(result)
txData.containsDelegateCall = containsDelegateCall
cb()
@ -258,16 +256,16 @@ IdentityStore.prototype.addUnconfirmedTransaction = function (txParams, onTxDone
}
}
function estimateGas(cb){
function estimateGas (cb) {
var estimationParams = extend(txParams)
// 1 billion gas for estimation
var gasLimit = '0x3b9aca00'
estimationParams.gas = gasLimit
query.estimateGas(estimationParams, function(err, result){
if (err) return cb(err)
query.estimateGas(estimationParams, function (err, result) {
if (err) return cb(err.message || err)
if (result === estimationParams.gas) {
txData.simulationFails = true
query.getBlockByNumber('latest', true, function(err, block){
query.getBlockByNumber('latest', true, function (err, block) {
if (err) return cb(err)
txData.estimatedGas = block.gasLimit
txData.txParams.gas = block.gasLimit
@ -282,7 +280,7 @@ IdentityStore.prototype.addUnconfirmedTransaction = function (txParams, onTxDone
}
function didComplete (err) {
if (err) return cb(err)
if (err) return cb(err.message || err)
configManager.addTx(txData)
// signal update
self._didUpdate()
@ -440,7 +438,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)

@ -2,6 +2,7 @@ const Streams = require('mississippi')
const StreamProvider = require('web3-stream-provider')
const ObjectMultiplex = require('./obj-multiplex')
const RemoteStore = require('./remote-store.js').RemoteStore
const createRandomId = require('./random-id')
module.exports = MetamaskInpageProvider
@ -39,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()
@ -48,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) => {
@ -119,16 +120,6 @@ function remoteStoreWithLocalStorageCache (storageKey) {
return store
}
function createRandomId(){
const extraDigits = 3
// 13 time digits
const datePart = new Date().getTime() * Math.pow(10, extraDigits)
// 3 random digits
const extraPart = Math.floor(Math.random() * Math.pow(10, extraDigits))
// 16 digits
return datePart + extraPart
}
function eachJsonMessage(payload, transformFn){
if (Array.isArray(payload)) {
return payload.map(transformFn)

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

@ -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

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

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

@ -1,7 +1,7 @@
const extend = require('xtend')
const EthStore = require('eth-store')
const MetaMaskProvider = require('web3-provider-engine/zero.js')
const IdentityStore = require('./lib/idStore')
const KeyringController = require('./keyring-controller')
const messageManager = require('./lib/message-manager')
const HostStore = require('./lib/remote-store.js').HostStore
const Web3 = require('web3')
@ -11,15 +11,18 @@ const extension = require('./lib/extension')
module.exports = class MetamaskController {
constructor (opts) {
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),
})
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.publicConfigStore = this.initPublicConfigStore()
@ -30,19 +33,19 @@ module.exports = class MetamaskController {
this.checkTOSChange()
this.scheduleConversionInterval()
}
getState () {
return extend(
this.state,
this.ethStore.getState(),
this.idStore.getState(),
this.configManager.getConfig()
this.configManager.getConfig(),
this.keyringController.getState()
)
}
getApi () {
const idStore = this.idStore
const keyringController = this.keyringController
return {
getState: (cb) => { cb(null, this.getState()) },
@ -52,27 +55,26 @@ module.exports = class MetamaskController {
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),
// 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),
// forward directly to keyringController
placeSeedWords: keyringController.placeSeedWords.bind(keyringController),
createNewVaultAndKeychain: keyringController.createNewVaultAndKeychain.bind(keyringController),
createNewVaultAndRestore: keyringController.createNewVaultAndRestore.bind(keyringController),
clearSeedWordCache: keyringController.clearSeedWordCache.bind(keyringController),
addNewKeyring: keyringController.addNewKeyring.bind(keyringController),
addNewAccount: keyringController.addNewAccount.bind(keyringController),
submitPassword: keyringController.submitPassword.bind(keyringController),
setSelectedAddress: keyringController.setSelectedAddress.bind(keyringController),
approveTransaction: keyringController.approveTransaction.bind(keyringController),
cancelTransaction: keyringController.cancelTransaction.bind(keyringController),
signMessage: keyringController.signMessage.bind(keyringController),
cancelMessage: keyringController.cancelMessage.bind(keyringController),
setLocked: keyringController.setLocked.bind(keyringController),
exportAccount: keyringController.exportAccount.bind(keyringController),
saveAccountLabel: keyringController.saveAccountLabel.bind(keyringController),
// coinbase
buyEth: this.buyEth.bind(this),
// shapeshift
@ -85,23 +87,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)
@ -134,38 +119,38 @@ module.exports = class MetamaskController {
}
initializeProvider (opts) {
const idStore = this.idStore
const keyringController = this.keyringController
var providerOpts = {
rpcUrl: this.configManager.getCurrentRpcAddress(),
// account mgmt
getAccounts: (cb) => {
var selectedAddress = idStore.getSelectedAddress()
var selectedAddress = this.configManager.getSelectedAccount()
var result = selectedAddress ? [selectedAddress] : []
cb(null, result)
},
// tx signing
approveTransaction: this.newUnsignedTransaction.bind(this),
signTransaction: (...args) => {
idStore.signTransaction(...args)
keyringController.signTransaction(...args)
this.sendUpdate()
},
// 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
}
@ -173,7 +158,7 @@ module.exports = class MetamaskController {
initPublicConfigStore () {
// get init state
var initPublicState = extend(
idStoreToPublic(this.idStore.getState()),
keyringControllerToPublic(this.keyringController.getState()),
configToPublic(this.configManager.getConfig())
)
@ -183,12 +168,14 @@ module.exports = class MetamaskController {
this.configManager.subscribe(function (state) {
storeSetFromObj(publicConfigStore, configToPublic(state))
})
this.idStore.on('update', function (state) {
storeSetFromObj(publicConfigStore, idStoreToPublic(state))
this.keyringController.on('update', () => {
const state = this.keyringController.getState()
storeSetFromObj(publicConfigStore, keyringControllerToPublic(state))
this.sendUpdate()
})
// idStore substate
function idStoreToPublic (state) {
// keyringController substate
function keyringControllerToPublic (state) {
return {
selectedAddress: state.selectedAddress,
}
@ -211,11 +198,10 @@ module.exports = class MetamaskController {
}
newUnsignedTransaction (txParams, onTxDoneCb) {
const idStore = this.idStore
let err = this.enforceTxValidations(txParams)
const keyringController = this.keyringController
const err = this.enforceTxValidations(txParams)
if (err) return onTxDoneCb(err)
idStore.addUnconfirmedTransaction(txParams, onTxDoneCb, (err, txData) => {
keyringController.addUnconfirmedTransaction(txParams, onTxDoneCb, (err, txData) => {
if (err) return onTxDoneCb(err)
this.sendUpdate()
this.opts.showUnconfirmedTx(txParams, txData, onTxDoneCb)
@ -230,9 +216,9 @@ module.exports = class MetamaskController {
}
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)
@ -241,8 +227,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)
}
@ -261,8 +247,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()
}
}
@ -287,12 +273,11 @@ module.exports = class MetamaskController {
} catch (e) {
console.error('Error in checking TOS change.')
}
}
agreeToDisclaimer (cb) {
try {
this.configManager.setConfirmed(true)
this.configManager.setConfirmedDisclaimer(true)
cb()
} catch (e) {
cb(e)
@ -301,7 +286,7 @@ module.exports = class MetamaskController {
resetDisclaimer () {
try {
this.configManager.setConfirmed(false)
this.configManager.setConfirmedDisclaimer(false)
} catch (e) {
console.error(e)
}
@ -332,26 +317,17 @@ module.exports = class MetamaskController {
}, 300000)
}
agreeToEthWarning (cb) {
try {
this.configManager.setShouldntShowWarning()
cb()
} catch (e) {
cb(e)
}
}
// called from popup
setRpcTarget (rpcTarget) {
this.configManager.setRpcTarget(rpcTarget)
extension.runtime.reload()
this.idStore.getNetwork()
this.getNetwork()
}
setProviderType (type) {
this.configManager.setProviderType(type)
extension.runtime.reload()
this.idStore.getNetwork()
this.getNetwork()
}
useEtherscanProvider () {
@ -362,7 +338,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 === '2') {
@ -378,6 +354,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)
@ -386,4 +381,8 @@ module.exports = class MetamaskController {
cb(e)
}
}
getStateNetwork () {
return this.state.network
}
}

@ -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()
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -136,7 +136,7 @@
"selectedAddress": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc",
"network": "1",
"seedWords": null,
"isConfirmed": true,
"isDisclaimerConfirmed": true,
"unconfMsgs": {},
"messages": [],
"provider": {

@ -102,7 +102,7 @@
"selectedAddress": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc",
"network": "2",
"seedWords": null,
"isConfirmed": true,
"isDisclaimerConfirmed": true,
"unconfMsgs": {},
"messages": [],
"provider": {

@ -60,7 +60,7 @@
"selectedAddress": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc",
"network": "2",
"seedWords": null,
"isConfirmed": true,
"isDisclaimerConfirmed": true,
"unconfMsgs": {},
"messages": [],
"provider": {

@ -2,7 +2,6 @@
"metamask": {
"isInitialized": true,
"isUnlocked": true,
"isEthConfirmed": true,
"rpcTarget": "https://rawtestrpc.metamask.io/",
"identities": {
"0x0abdd95cafcabec9b3e99dcd09fc4b441037cb80": {
@ -92,7 +91,7 @@
"transactions": [],
"selectedAddress": "0x0abdd95cafcabec9b3e99dcd09fc4b441037cb80",
"network": "2",
"isConfirmed": true,
"isDisclaimerConfirmed": true,
"unconfMsgs": {},
"messages": [],
"shapeShiftTxList": [],
@ -116,4 +115,4 @@
"scrollToBottom": true
},
"identities": {}
}
}

@ -59,7 +59,7 @@
"transactions": [],
"selectedAddress": "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": {

@ -160,8 +160,7 @@
"selectedAddress": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
"network": "166",
"seedWords": null,
"isConfirmed": true,
"isEthConfirmed": true,
"isDisclaimerConfirmed": true,
"unconfMsgs": {},
"messages": [],
"provider": {

@ -57,7 +57,7 @@
"selectedAddress": "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,7 +55,7 @@
},
"transactions": [],
"network": "2",
"isConfirmed": true,
"isDisclaimerConfirmed": true,
"unconfMsgs": {},
"messages": [],
"provider": {

@ -2,7 +2,6 @@
"metamask": {
"isInitialized": true,
"isUnlocked": false,
"isEthConfirmed": true,
"currentDomain": "example.com",
"rpcTarget": "https://rawtestrpc.metamask.io/",
"identities": {},
@ -15,7 +14,7 @@
"selectedAddress": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
"network": "1473186153102",
"seedWords": null,
"isConfirmed": true,
"isDisclaimerConfirmed": true,
"unconfMsgs": {},
"messages": [],
"shapeShiftTxList": [],

@ -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": {
@ -355,7 +354,7 @@
"selectedAddress": "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":[],"selectedAddress":"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

@ -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": {
@ -50,7 +49,7 @@
"selectedAddress": "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": {

@ -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.

@ -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)
@ -107,7 +107,7 @@ function getOldStyleData () {
return result
}
actions._setAccountManager(controller.getApi())
actions._setBackgroundConnection(controller.getApi())
actions.update = function(stateName) {
selectedView = stateName
updateQueryParams(stateName)

@ -4,18 +4,19 @@
"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",
"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\"",
"ui": "node development/genStates.js && 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": "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"
},
"browserify": {
@ -33,6 +34,7 @@
},
"dependencies": {
"async": "^1.5.2",
"bip39": "^2.2.0",
"browserify-derequire": "^0.9.4",
"clone": "^1.0.2",
"copy-to-clipboard": "^2.0.0",
@ -46,6 +48,7 @@
"eth-store": "^1.1.0",
"ethereumjs-tx": "^1.0.0",
"ethereumjs-util": "^4.4.0",
"ethereumjs-wallet": "^0.6.0",
"express": "^4.14.0",
"gulp-eslint": "^2.0.0",
"hat": "0.0.3",
@ -53,7 +56,7 @@
"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",

@ -12,7 +12,7 @@
<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">

@ -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);

@ -0,0 +1,67 @@
var encryptor = require('../../../app/scripts/lib/encryptor')
QUnit.test('encryptor:serializeBufferForStorage', function (assert) {
assert.expect(1)
var buf = new Buffer(2)
buf[0] = 16
buf[1] = 1
var output = encryptor.serializeBufferForStorage(buf)
var expect = '0x1001'
assert.equal(expect, output)
})
QUnit.test('encryptor:serializeBufferFromStorage', function (assert) {
assert.expect(2)
var input = '0x1001'
var output = encryptor.serializeBufferFromStorage(input)
assert.equal(output[0], 16)
assert.equal(output[1], 1)
})
QUnit.test('encryptor:encrypt & decrypt', function(assert) {
var done = assert.async();
var password, data, encrypted
password = 'a sample passw0rd'
data = { foo: 'data to encrypt' }
encryptor.encrypt(password, data)
.then(function(encryptedStr) {
assert.equal(typeof encryptedStr, 'string', 'returns a string')
return encryptor.decrypt(password, encryptedStr)
})
.then(function (decryptedObj) {
assert.deepEqual(decryptedObj, data, 'decrypted what was encrypted')
done()
})
.catch(function(reason) {
assert.ifError(reason, 'threw an error')
done(reason)
})
})
QUnit.test('encryptor:encrypt & decrypt with wrong password', function(assert) {
var done = assert.async();
var password, data, encrypted, wrongPassword
password = 'a sample passw0rd'
wrongPassword = 'a wrong password'
data = { foo: 'data to encrypt' }
encryptor.encrypt(password, data)
.then(function(encryptedStr) {
assert.equal(typeof encryptedStr, 'string', 'returns a string')
return encryptor.decrypt(wrongPassword, encryptedStr)
})
.then(function (decryptedObj) {
assert.equal(!decryptedObj, true, 'Wrong password should not decrypt')
done()
})
.catch(function(reason) {
done()
})
})

@ -0,0 +1,15 @@
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')
done()
})
})

@ -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() {

@ -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!'
},
}

@ -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')
});
});

@ -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() },
})

@ -100,31 +100,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 +153,7 @@ describe('config-manager', function() {
rpcTarget: 'foobar'
},
}
configManager.setConfirmed(true)
configManager.setConfirmedDisclaimer(true)
configManager.setConfig(testConfig)
var testWallet = {
@ -164,7 +164,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 +173,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)
})
})

@ -0,0 +1,160 @@
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',
}
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, null, (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] },
},
})
// 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() {
keyringController.configManager.setWallet('something')
const state = keyringController.getState()
assert(state.isInitialized, 'old vault counted as initialized.')
})
/*
it('should use the password to migrate the old vault', function(done) {
this.timeout(5000)
console.log('calling submitPassword')
console.dir(keyringController)
keyringController.submitPassword(password, function (err, state) {
assert.ifError(err, 'submitPassword threw error')
function log(str, dat) { console.log(str + ': ' + JSON.stringify(dat)) }
let newAccounts = keyringController.getAccounts()
log('new accounts: ', newAccounts)
let newAccount = ethUtil.addHexPrefix(newAccounts[0])
assert.equal(ethUtil.addHexPrefix(newAccount), mockVault.account, 'restored the correct account')
const newSeed = keyringController.keyrings[0].mnemonic
log('keyringController keyrings', keyringController.keyrings)
assert.equal(newSeed, mockVault.seed, 'seed phrase transferred.')
assert(configManager.getVault(), 'new type of vault is persisted')
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
}

@ -0,0 +1,172 @@
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 entropy = 'entripppppyy duuude'
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(),
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, null, function (err, newState) {
assert.ifError(err)
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, null, (err, state) => {
assert.ifError(err)
const vault = keyringController.configManager.getVault()
assert(vault, 'vault created')
done()
})
})
})
describe('#restoreKeyring', function() {
it(`should pass a keyring's serialized data back to the correct type.`, function() {
const mockSerialized = {
type: 'HD Key Tree',
data: {
mnemonic: seedWords,
numberOfAccounts: 1,
}
}
const mock = this.sinon.mock(keyringController)
mock.expects('getBalanceAndNickname')
.exactly(1)
var keyring = keyringController.restoreKeyring(mockSerialized)
assert.equal(keyring.wallets.length, 1, 'one wallet restored')
assert.equal(keyring.getAccounts()[0], addresses[0])
mock.verify()
})
})
describe('#migrateAndGetKey', function() {
it('should return the key for that password', function(done) {
keyringController.migrateAndGetKey(password)
.then((key) => {
assert(key, 'a key is returned')
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() {
const account = addresses[0]
var nick = 'Test nickname'
keyringController.identities[ethUtil.addHexPrefix(account)] = {}
const label = keyringController.saveAccountLabel(account, nick)
assert.equal(label, nick)
const persisted = keyringController.configManager.nicknameForWallet(account)
assert.equal(persisted, nick)
})
this.timeout(10000)
it('retrieves the persisted nickname', function(done) {
const account = addresses[0]
var nick = 'Test nickname'
keyringController.configManager.setNicknameForWallet(account, nick)
console.log('calling to restore')
keyringController.createNewVaultAndRestore(password, seedWords, (err, state) => {
console.dir({err})
assert.ifError(err)
const identity = keyringController.identities['0x' + account]
assert.equal(identity.name, nick)
assert(accounts)
done()
})
})
})
describe('#getAccounts', function() {
it('returns the result of getAccounts for each keyring', function() {
keyringController.keyrings = [
{ getAccounts() { return [1,2,3] } },
{ getAccounts() { return [4,5,6] } },
]
const result = keyringController.getAccounts()
assert.deepEqual(result, [1,2,3,4,5,6])
})
})
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')
})
})
})

@ -0,0 +1,108 @@
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() {
keyring = new HdKeyring({
mnemonic: sampleMnemonic,
numberOfAccounts: 2,
})
const accounts = keyring.getAccounts()
assert.equal(accounts[0], firstAcct)
assert.equal(accounts[1], secondAcct)
})
describe('Keyring.type()', function() {
it('is a class method 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() {
const output = keyring.serialize()
assert.equal(output.numberOfAccounts, 0)
assert.equal(output.mnemonic, null)
})
})
describe('#deserialize a private key', function() {
it('serializes what it deserializes', function() {
keyring.deserialize({
mnemonic: sampleMnemonic,
numberOfAccounts: 1
})
assert.equal(keyring.wallets.length, 1, 'restores two accounts')
keyring.addAccounts(1)
const accounts = keyring.getAccounts()
assert.equal(accounts[0], firstAcct)
assert.equal(accounts[1], secondAcct)
assert.equal(accounts.length, 2)
const serialized = keyring.serialize()
assert.equal(serialized.mnemonic, sampleMnemonic)
})
})
describe('#addAccounts', function() {
describe('with no arguments', function() {
it('creates a single wallet', function() {
keyring.addAccounts()
assert.equal(keyring.wallets.length, 1)
})
})
describe('with a numeric argument', function() {
it('creates that number of wallets', function() {
keyring.addAccounts(3)
assert.equal(keyring.wallets.length, 3)
})
})
})
describe('#getAccounts', function() {
it('calls getAddress on each wallet', function() {
// Push a mock wallet
const desiredOutput = 'foo'
keyring.wallets.push({
getAddress() {
return {
toString() {
return desiredOutput
}
}
}
})
const output = keyring.getAccounts()
assert.equal(output[0], desiredOutput)
assert.equal(output.length, 1)
})
})
})

@ -0,0 +1,83 @@
const assert = require('assert')
const extend = require('xtend')
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 method 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() {
const output = keyring.serialize()
assert.deepEqual(output, [])
})
})
describe('#deserialize a private key', function() {
it('serializes what it deserializes', function() {
keyring.deserialize([privKeyHex])
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()
assert.equal(keyring.wallets.length, 1)
})
})
describe('with a numeric argument', function() {
it('creates that number of wallets', function() {
keyring.addAccounts(3)
assert.equal(keyring.wallets.length, 3)
})
})
})
describe('#getAccounts', function() {
it('calls getAddress on each wallet', function() {
// Push a mock wallet
const desiredOutput = 'foo'
keyring.wallets.push({
getAddress() {
return {
toString() {
return desiredOutput
}
}
}
})
const output = keyring.getAccounts()
assert.equal(output[0], desiredOutput)
assert.equal(output.length, 1)
})
})
})

@ -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)

@ -30,7 +30,6 @@ function mapStateToProps (state) {
network: state.metamask.network,
unconfTxs: valuesFor(state.metamask.unconfTxs),
unconfMsgs: valuesFor(state.metamask.unconfMsgs),
isEthWarningConfirmed: state.metamask.isEthConfirmed,
shapeShiftTxList: state.metamask.shapeShiftTxList,
}
}

@ -34,11 +34,7 @@ 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),
}
return (
h('.accounts-section.flex-grow', [
@ -46,7 +42,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'),
]),
@ -87,7 +83,7 @@ AccountsScreen.prototype.render = function () {
h('div.footer.hover-white.pointer', {
key: 'reveal-account-bar',
onClick: () => {
this.onRevealAccount()
this.addNewAccount()
},
style: {
display: 'flex',
@ -146,8 +142,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,4 +1,6 @@
var actions = {
_setBackgroundConnection: _setBackgroundConnection,
GO_HOME: 'GO_HOME',
goHome: goHome,
// menu state
@ -16,17 +18,16 @@ var actions = {
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
@ -52,8 +53,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
@ -67,10 +66,6 @@ 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',
@ -89,12 +84,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',
@ -104,8 +99,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',
@ -142,13 +135,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,
}
module.exports = actions
var _accountManager = null
function _setAccountManager (accountManager) {
_accountManager = accountManager
var background = null
function _setBackgroundConnection (backgroundConnection) {
background = backgroundConnection
}
function goHome () {
@ -163,25 +161,52 @@ 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 {
let selectedAccount
try {
selectedAccount = newState.metamask.selectedAccount
} catch (e) {}
dispatch(actions.unlockMetamask(selectedAccount))
}
})
}
}
function createNewVault (password, entropy) {
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))
})
}
}
function createNewVaultAndKeychain (password, entropy) {
return (dispatch) => {
background.createNewVaultAndKeychain(password, entropy, (err) => {
if (err) {
return dispatch(actions.showWarning(err.message))
}
})
}
}
@ -195,27 +220,35 @@ function revealSeedConfirmation () {
function requestRevealSeed (password) {
return (dispatch) => {
dispatch(actions.showLoadingIndication())
_accountManager.tryPassword(password, (err, seed) => {
background.submitPassword(password, (err) => {
dispatch(actions.hideLoadingIndication())
if (err) return dispatch(actions.displayWarning(err.message))
_accountManager.recoverSeed((err, seed) => {
if (err) return dispatch(actions.displayWarning(err.message))
dispatch(actions.showNewVaultSeed(seed))
})
background.placeSeedWords()
})
}
}
function recoverFromSeed (password, seed) {
function addNewKeyring (type, opts) {
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))
background.addNewKeyring(type, opts, (err) => {
dispatch(this.hideLoadingIndication())
if (err) {
return dispatch(actions.showWarning(err))
}
})
}
}
var account = Object.keys(metamaskState.identities)[0]
dispatch(actions.unlockMetamask(account))
function addNewAccount (ringNumber = 0) {
return (dispatch) => {
dispatch(actions.showLoadingIndication())
background.addNewAccount(ringNumber, (err) => {
dispatch(this.hideLoadingIndication())
if (err) {
return dispatch(actions.showWarning(err))
}
})
}
}
@ -228,27 +261,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,
})
})
background.setSelectedAddress(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,
@ -266,7 +286,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))
@ -277,7 +297,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())
@ -292,7 +312,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))
@ -318,12 +338,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)
}
@ -352,7 +372,7 @@ function showInitializeMenu () {
function agreeToDisclaimer () {
return (dispatch) => {
dispatch(this.showLoadingIndication())
_accountManager.agreeToDisclaimer((err) => {
background.agreeToDisclaimer((err) => {
if (err) {
return dispatch(actions.displayWarning(err.message))
}
@ -384,6 +404,12 @@ function backToUnlockView () {
}
}
function showNewKeychain () {
return {
type: actions.SHOW_NEW_KEYCHAIN,
}
}
//
// unlock screen
//
@ -394,9 +420,10 @@ function unlockInProgress () {
}
}
function unlockFailed () {
function unlockFailed (message) {
return {
type: actions.UNLOCK_FAILED,
value: message,
}
}
@ -416,15 +443,11 @@ function updateMetamaskState (newState) {
function lockMetamask () {
return (dispatch) => {
_accountManager.setLocked((err) => {
background.setLocked((err) => {
dispatch(actions.hideLoadingIndication())
if (err) {
return dispatch(actions.displayWarning(err.message))
}
dispatch({
type: actions.LOCK_METAMASK,
})
})
}
}
@ -432,7 +455,7 @@ function lockMetamask () {
function showAccountDetail (address) {
return (dispatch) => {
dispatch(actions.showLoadingIndication())
_accountManager.setSelectedAddress(address, (err, address) => {
background.setSelectedAddress(address, (err, address) => {
dispatch(actions.hideLoadingIndication())
if (err) {
return dispatch(actions.displayWarning(err.message))
@ -452,27 +475,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 {
@ -524,7 +526,7 @@ function goBackToInitView () {
//
function setRpcTarget (newRpc) {
_accountManager.setRpcTarget(newRpc)
background.setRpcTarget(newRpc)
return {
type: actions.SET_RPC_TARGET,
value: newRpc,
@ -532,7 +534,7 @@ function setRpcTarget (newRpc) {
}
function setProviderType (type) {
_accountManager.setProviderType(type)
background.setProviderType(type)
return {
type: actions.SET_PROVIDER_TYPE,
value: type,
@ -540,7 +542,7 @@ function setProviderType (type) {
}
function useEtherscanProvider () {
_accountManager.useEtherscanProvider()
background.useEtherscanProvider()
return {
type: actions.USE_ETHERSCAN_PROVIDER,
}
@ -595,7 +597,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) {
@ -618,7 +620,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))
@ -637,28 +639,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,
})
@ -736,7 +719,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)))
})
}

@ -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
@ -19,7 +17,6 @@ const SendTransactionScreen = require('./send')
const ConfirmTxScreen = require('./conf-tx')
// 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')
@ -27,9 +24,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)
@ -39,8 +39,7 @@ function mapStateToProps (state) {
return {
// state from plugin
isLoading: state.appState.isLoading,
isConfirmed: state.metamask.isConfirmed,
isEthConfirmed: state.metamask.isEthConfirmed,
isDisclaimerConfirmed: state.metamask.isDisclaimerConfirmed,
isInitialized: state.metamask.isInitialized,
isUnlocked: state.metamask.isUnlocked,
currentView: state.appState.currentView,
@ -99,7 +98,6 @@ App.prototype.render = function () {
}
App.prototype.renderAppBar = function () {
if (window.METAMASK_UI_TYPE === 'notification') {
return null
}
@ -302,6 +300,7 @@ App.prototype.renderDropdown = function () {
}),
])
}
App.prototype.renderBackButton = function (style, justArrow = false) {
var props = this.props
return (
@ -319,12 +318,13 @@ 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.isDisclaimerConfirmed) return button
if (!props.isUnlocked) {
if (props.currentView.name === 'InitMenu') {
button = props.forgottenPassword ? h('.flex-row', {
@ -348,42 +348,6 @@ App.prototype.renderBackToInitButton = function () {
}, '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
@ -392,12 +356,12 @@ App.prototype.renderBackToInitButton = function () {
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
@ -405,30 +369,28 @@ 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'})
}
}
// show current view
switch (props.currentView.name) {
case 'EthStoreWarning':
return h(EthStoreWarning, {key: 'ethWarning'})
case 'accounts':
return h(AccountsScreen, {key: 'accounts'})
@ -439,6 +401,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'})
@ -451,10 +416,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: {
@ -510,12 +474,7 @@ App.prototype.renderCustomOption = function (rpcTarget) {
})
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 <= 5 && 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) {

@ -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,17 +31,17 @@ Network.prototype.render = function () {
},
src: 'images/loading.svg',
})
} else if (providerName === 'mainnet') {
hoverText = 'Main Ethereum Network'
iconName = 'ethereum-network'
} else if (parseInt(networkNumber) === 2) {
} else if (providerName === 'testnet') {
hoverText = 'Morden Test Network'
iconName = 'morden-test-network'
} else {
hoverText = 'Unknown Private Network'
iconName = 'unknown-private-network'
}
return (
h('#network_component.flex-center.pointer', {
style: {

@ -8,7 +8,7 @@ const Qr = require('./qr-code')
const isValidAddress = require('../util').isValidAddress
module.exports = connect(mapStateToProps)(ShapeshiftForm)
function mapStateToProps(state) {
function mapStateToProps (state) {
return {
selectedAccount: state.selectedAccount,
warning: state.appState.warning,
@ -25,7 +25,6 @@ function ShapeshiftForm () {
}
ShapeshiftForm.prototype.render = function () {
return h(ReactCSSTransitionGroup, {
className: 'css-transition-group',
transitionName: 'main',
@ -34,7 +33,6 @@ ShapeshiftForm.prototype.render = function () {
}, [
this.props.qrRequested ? h(Qr, {key: 'qr'}) : this.renderMain(),
])
}
ShapeshiftForm.prototype.renderMain = function () {

@ -26,7 +26,6 @@ function ShiftListItem () {
}
ShiftListItem.prototype.render = function () {
return (
h('.transaction-list-item.flex-row', {
style: {

@ -11,7 +11,6 @@ function Tooltip () {
}
Tooltip.prototype.render = function () {
const props = this.props
const { position, title, children } = props
@ -20,5 +19,4 @@ Tooltip.prototype.render = function () {
title,
fixed: false,
}, children)
}

@ -63,7 +63,7 @@ TransactionListItem.prototype.render = function () {
style: {
fontSize: '27px',
},
}) : h( '.pop-hover', {
}) : h('.pop-hover', {
onClick: (event) => {
event.stopPropagation()
if (!isTx || isPending) return

@ -1,89 +0,0 @@
const connect = require('react-redux').connect
const Component = require('react').Component
const h = require('react-hyperscript')
const inherits = require('util').inherits
const actions = require('./actions')
module.exports = connect(mapStateToProps)(EthStoreWarning)
inherits(EthStoreWarning, Component)
function EthStoreWarning () {
Component.call(this)
}
function mapStateToProps (state) {
return {
selectedAccount: state.metamask.selectedAccount,
}
}
EthStoreWarning.prototype.render = function () {
return (
h('.flex-column', {
key: 'ethWarning',
style: {
paddingTop: '25px',
marginRight: '30px',
marginLeft: '30px',
alignItems: 'center',
},
}, [
h('.warning', {
style: {
margin: '10px 10px 10px 10px',
},
},
`MetaMask is currently in beta; use
caution in storing large
amounts of ether.
`),
h('i.fa.fa-exclamation-triangle.fa-4', {
style: {
fontSize: '152px',
color: '#AEAEAE',
textAlign: 'center',
},
}),
h('.flex-row', {
style: {
marginTop: '25px',
marginBottom: '10px',
},
}, [
h('input', {
type: 'checkbox',
onChange: this.toggleShowWarning.bind(this),
}),
h('.warning', {
style: {
fontSize: '11px',
},
}, 'Don\'t show me this message again'),
]),
h('.flex-row', {
style: {
width: '100%',
justifyContent: 'space-around',
},
}, [
h('button', {
onClick: this.toAccounts.bind(this),
},
'Continue to MetaMask'),
]),
])
)
}
EthStoreWarning.prototype.toggleShowWarning = function () {
this.props.dispatch(actions.agreeToEthWarning())
}
EthStoreWarning.prototype.toAccounts = function () {
this.props.dispatch(actions.showAccountDetail(this.props.account))
}

@ -1,129 +0,0 @@
const inherits = require('util').inherits
const Component = require('react').Component
const connect = require('react-redux').connect
const h = require('react-hyperscript')
const actions = require('../actions')
module.exports = connect(mapStateToProps)(CreateVaultScreen)
inherits(CreateVaultScreen, Component)
function CreateVaultScreen () {
Component.call(this)
}
function mapStateToProps (state) {
return {
warning: state.appState.warning,
}
}
CreateVaultScreen.prototype.render = function () {
var state = this.props
return (
h('.initialize-screen.flex-column.flex-center.flex-grow', [
h('h3.flex-center.text-transform-uppercase', {
style: {
background: '#EBEBEB',
color: '#AEAEAE',
marginBottom: 24,
width: '100%',
fontSize: '20px',
padding: 6,
},
}, [
'Create Vault',
]),
// password
h('input.large-input.letter-spacey', {
type: 'password',
id: 'password-box',
placeholder: 'New Password (min 8 chars)',
style: {
width: 260,
marginTop: 12,
},
}),
// confirm password
h('input.large-input.letter-spacey', {
type: 'password',
id: 'password-box-confirm',
placeholder: 'Confirm Password',
onKeyPress: this.createVaultOnEnter.bind(this),
style: {
width: 260,
marginTop: 16,
},
}),
h('.flex-row.flex-space-between', {
style: {
marginTop: 30,
width: '50%',
},
}, [
// cancel
h('button.primary', {
onClick: this.showInitializeMenu.bind(this),
}, 'CANCEL'),
// submit
h('button.primary', {
onClick: this.createNewVault.bind(this),
}, 'OK'),
]),
(!state.inProgress && state.warning) && (
h('span.in-progress-notification', state.warning)
),
state.inProgress && (
h('span.in-progress-notification', 'Generating Seed...')
),
])
)
}
CreateVaultScreen.prototype.componentDidMount = function () {
document.getElementById('password-box').focus()
}
CreateVaultScreen.prototype.showInitializeMenu = function () {
this.props.dispatch(actions.showInitializeMenu())
}
// create vault
CreateVaultScreen.prototype.createVaultOnEnter = function (event) {
if (event.key === 'Enter') {
event.preventDefault()
this.createNewVault()
}
}
CreateVaultScreen.prototype.createNewVault = function () {
var passwordBox = document.getElementById('password-box')
var password = passwordBox.value
var passwordConfirmBox = document.getElementById('password-box-confirm')
var passwordConfirm = passwordConfirmBox.value
// var entropy = document.getElementById('entropy-text-entry').value
if (password.length < 8) {
this.warning = 'password not long enough'
this.props.dispatch(actions.displayWarning(this.warning))
return
}
if (password !== passwordConfirm) {
this.warning = 'passwords don\'t match'
this.props.dispatch(actions.displayWarning(this.warning))
return
}
this.props.dispatch(actions.createNewVault(password, ''/* entropy*/))
}

@ -5,6 +5,8 @@ const connect = require('react-redux').connect
const h = require('react-hyperscript')
const Mascot = require('../components/mascot')
const actions = require('../actions')
const Tooltip = require('../components/tooltip')
const getCaretCoordinates = require('textarea-caret')
module.exports = connect(mapStateToProps)(InitializeMenuScreen)
@ -18,6 +20,7 @@ function mapStateToProps (state) {
return {
// state from plugin
currentView: state.appState.currentView,
warning: state.appState.warning,
}
}
@ -27,7 +30,7 @@ InitializeMenuScreen.prototype.render = function () {
switch (state.currentView.name) {
default:
return this.renderMenu()
return this.renderMenu(state)
}
}
@ -36,7 +39,7 @@ InitializeMenuScreen.prototype.render = function () {
// document.getElementById('password-box').focus()
// }
InitializeMenuScreen.prototype.renderMenu = function () {
InitializeMenuScreen.prototype.renderMenu = function (state) {
return (
h('.initialize-screen.flex-column.flex-center.flex-grow', [
@ -47,49 +50,131 @@ InitializeMenuScreen.prototype.renderMenu = function () {
h('h1', {
style: {
fontSize: '1.4em',
fontSize: '1.3em',
textTransform: 'uppercase',
color: '#7F8082',
marginBottom: 20,
marginBottom: 10,
},
}, 'MetaMask'),
h('button.primary', {
onClick: this.showCreateVault.bind(this),
h('div', [
h('h3', {
style: {
fontSize: '0.8em',
color: '#7F8082',
display: 'inline',
},
}, 'Encrypt your new DEN'),
h(Tooltip, {
title: 'Your DEN is your password-encrypted storage within MetaMask.',
}, [
h('i.fa.fa-question-circle.pointer', {
style: {
fontSize: '18px',
position: 'relative',
color: 'rgb(247, 134, 28)',
top: '2px',
marginLeft: '4px',
},
}),
]),
]),
h('span.in-progress-notification', state.warning),
// password
h('input.large-input.letter-spacey', {
type: 'password',
id: 'password-box',
placeholder: 'New Password (min 8 chars)',
onInput: this.inputChanged.bind(this),
style: {
margin: 12,
width: 260,
marginTop: 12,
},
}, 'Create New Vault'),
}),
// confirm password
h('input.large-input.letter-spacey', {
type: 'password',
id: 'password-box-confirm',
placeholder: 'Confirm Password',
onKeyPress: this.createVaultOnEnter.bind(this),
onInput: this.inputChanged.bind(this),
style: {
width: 260,
marginTop: 16,
},
}),
h('.flex-row.flex-center.flex-grow', [
h('hr'),
h('div', 'OR'),
h('hr'),
]),
h('button.primary', {
onClick: this.showRestoreVault.bind(this),
onClick: this.createNewVaultAndKeychain.bind(this),
style: {
margin: 12,
},
}, 'Restore Existing Vault'),
}, 'Create'),
h('.flex-row.flex-center.flex-grow', [
h('p.pointer', {
onClick: this.showRestoreVault.bind(this),
style: {
fontSize: '0.8em',
color: 'rgb(247, 134, 28)',
textDecoration: 'underline',
},
}, 'I already have a DEN that I would like to import'),
]),
])
)
}
// InitializeMenuScreen.prototype.splitWor = function() {
// this.props.dispatch(actions.showInitializeMenu())
// }
InitializeMenuScreen.prototype.showInitializeMenu = function () {
this.props.dispatch(actions.showInitializeMenu())
InitializeMenuScreen.prototype.createVaultOnEnter = function (event) {
if (event.key === 'Enter') {
event.preventDefault()
this.createNewVaultAndKeychain()
}
}
InitializeMenuScreen.prototype.showCreateVault = function () {
this.props.dispatch(actions.showCreateVault())
InitializeMenuScreen.prototype.componentDidMount = function () {
document.getElementById('password-box').focus()
}
InitializeMenuScreen.prototype.showRestoreVault = function () {
this.props.dispatch(actions.showRestoreVault())
}
InitializeMenuScreen.prototype.createNewVaultAndKeychain = function () {
var passwordBox = document.getElementById('password-box')
var password = passwordBox.value
var passwordConfirmBox = document.getElementById('password-box-confirm')
var passwordConfirm = passwordConfirmBox.value
// var entropy = document.getElementById('entropy-text-entry').value
if (password.length < 8) {
this.warning = 'password not long enough'
this.props.dispatch(actions.displayWarning(this.warning))
return
}
if (password !== passwordConfirm) {
this.warning = 'passwords don\'t match'
this.props.dispatch(actions.displayWarning(this.warning))
return
}
this.props.dispatch(actions.createNewVaultAndKeychain(password, ''/* entropy*/))
}
InitializeMenuScreen.prototype.inputChanged = function (event) {
// tell mascot to look at page action
var element = event.target
var boundingRect = element.getBoundingClientRect()
var coordinates = getCaretCoordinates(element, element.selectionEnd)
this.animationEventEmitter.emit('point', {
x: boundingRect.left + coordinates.left - element.scrollLeft,
y: boundingRect.top + coordinates.top - element.scrollTop,
})
}

@ -2,7 +2,7 @@ const inherits = require('util').inherits
const Component = require('react').Component
const connect = require('react-redux').connect
const h = require('react-hyperscript')
const actions = require('../actions')
const actions = require('../../actions')
module.exports = connect(mapStateToProps)(CreateVaultCompleteScreen)
@ -71,4 +71,3 @@ CreateVaultCompleteScreen.prototype.render = function () {
CreateVaultCompleteScreen.prototype.confirmSeedWords = function () {
this.props.dispatch(actions.confirmSeedWords())
}

@ -3,12 +3,12 @@ const inherits = require('util').inherits
const Component = require('react').Component
const connect = require('react-redux').connect
const h = require('react-hyperscript')
const actions = require('../actions')
const actions = require('../../../actions')
module.exports = connect(mapStateToProps)(RevealSeedConfirmatoin)
module.exports = connect(mapStateToProps)(RevealSeedConfirmation)
inherits(RevealSeedConfirmatoin, Component)
function RevealSeedConfirmatoin () {
inherits(RevealSeedConfirmation, Component)
function RevealSeedConfirmation () {
Component.call(this)
}
@ -18,9 +18,9 @@ function mapStateToProps (state) {
}
}
RevealSeedConfirmatoin.prototype.confirmationPhrase = 'I understand'
RevealSeedConfirmation.prototype.confirmationPhrase = 'I understand'
RevealSeedConfirmatoin.prototype.render = function () {
RevealSeedConfirmation.prototype.render = function () {
const props = this.props
const state = this.state
@ -68,7 +68,7 @@ RevealSeedConfirmatoin.prototype.render = function () {
style: {
marginTop: '12px',
},
}, 'Enter the phrase "I understand" to proceed.'),
}, `Enter the phrase "${this.confirmationPhrase}" to proceed.`),
// confirm confirmation
h('input.large-input.letter-spacey', {
@ -116,24 +116,24 @@ RevealSeedConfirmatoin.prototype.render = function () {
)
}
RevealSeedConfirmatoin.prototype.componentDidMount = function () {
RevealSeedConfirmation.prototype.componentDidMount = function () {
document.getElementById('password-box').focus()
}
RevealSeedConfirmatoin.prototype.goHome = function () {
RevealSeedConfirmation.prototype.goHome = function () {
this.props.dispatch(actions.showConfigPage(false))
}
// create vault
RevealSeedConfirmatoin.prototype.checkConfirmation = function (event) {
RevealSeedConfirmation.prototype.checkConfirmation = function (event) {
if (event.key === 'Enter') {
event.preventDefault()
this.revealSeedWords()
}
}
RevealSeedConfirmatoin.prototype.revealSeedWords = function () {
RevealSeedConfirmation.prototype.revealSeedWords = function () {
this.setState({ confirmationWrong: false })
const confirmBox = document.getElementById('confirm-box')

@ -1,8 +1,8 @@
const inherits = require('util').inherits
const PersistentForm = require('../../lib/persistent-form')
const PersistentForm = require('../../../lib/persistent-form')
const connect = require('react-redux').connect
const h = require('react-hyperscript')
const actions = require('../actions')
const actions = require('../../actions')
module.exports = connect(mapStateToProps)(RestoreVaultScreen)
@ -66,7 +66,7 @@ RestoreVaultScreen.prototype.render = function () {
type: 'password',
id: 'password-box-confirm',
placeholder: 'Confirm Password',
onKeyPress: this.onMaybeCreate.bind(this),
onKeyPress: this.createOnEnter.bind(this),
dataset: {
persistentFormId: 'password-confirmation',
},
@ -96,7 +96,7 @@ RestoreVaultScreen.prototype.render = function () {
// submit
h('button.primary', {
onClick: this.restoreVault.bind(this),
onClick: this.createNewVaultAndRestore.bind(this),
}, 'OK'),
]),
@ -110,13 +110,13 @@ RestoreVaultScreen.prototype.showInitializeMenu = function () {
this.props.dispatch(actions.showInitializeMenu())
}
RestoreVaultScreen.prototype.onMaybeCreate = function (event) {
RestoreVaultScreen.prototype.createOnEnter = function (event) {
if (event.key === 'Enter') {
this.restoreVault()
this.createNewVaultAndRestore()
}
}
RestoreVaultScreen.prototype.restoreVault = function () {
RestoreVaultScreen.prototype.createNewVaultAndRestore = function () {
// check password
var passwordBox = document.getElementById('password-box')
var password = passwordBox.value
@ -144,5 +144,5 @@ RestoreVaultScreen.prototype.restoreVault = function () {
// submit
this.warning = null
this.props.dispatch(actions.displayWarning(this.warning))
this.props.dispatch(actions.recoverFromSeed(password, seed))
this.props.dispatch(actions.createNewVaultAndRestore(password, seed))
}

@ -0,0 +1,29 @@
const inherits = require('util').inherits
const Component = require('react').Component
const h = require('react-hyperscript')
const connect = require('react-redux').connect
module.exports = connect(mapStateToProps)(NewKeychain)
function mapStateToProps (state) {
return {}
}
inherits(NewKeychain, Component)
function NewKeychain () {
Component.call(this)
}
NewKeychain.prototype.render = function () {
// const props = this.props
return (
h('div', {
style: {
background: 'blue',
},
}, [
h('h1', `Here's a list!!!!`),
])
)
}

@ -41,7 +41,7 @@ function rootReducer (state, action) {
return state
}
window.logState = function() {
window.logState = function () {
var stateString = JSON.stringify(window.METAMASK_CACHED_LOG_STATE, null, 2)
console.log(stateString)
}

@ -29,13 +29,10 @@ function reduceApp (state, action) {
name: 'createVaultComplete',
seedWords,
}
var ethStoreWarning = {
name: 'EthStoreWarning',
}
var appState = extend({
menuOpen: false,
currentView: seedWords ? seedConfView : !state.metamask.isEthConfirmed ? ethStoreWarning : defaultView,
currentView: seedWords ? seedConfView : defaultView,
accountDetail: {
subview: 'transactions',
},
@ -119,6 +116,15 @@ function reduceApp (state, action) {
warning: null,
})
case actions.SHOW_NEW_KEYCHAIN:
return extend(appState, {
currentView: {
name: 'newKeychain',
context: appState.currentView.context,
},
transForward: true,
})
// unlock
case actions.UNLOCK_METAMASK:
@ -272,7 +278,6 @@ function reduceApp (state, action) {
warning: null,
})
} else {
notification.closePopup()
return extend(appState, {
@ -329,7 +334,7 @@ function reduceApp (state, action) {
case actions.UNLOCK_FAILED:
return extend(appState, {
warning: 'Incorrect password. Try again.',
warning: action.value || 'Incorrect password. Try again.',
})
case actions.SHOW_LOADING:
@ -540,4 +545,3 @@ function indexForPending (state, txId) {
})
return idx
}

@ -10,7 +10,6 @@ function reduceMetamask (state, action) {
var metamaskState = extend({
isInitialized: false,
isUnlocked: false,
isEthConfirmed: false,
rpcTarget: 'https://rawtestrpc.metamask.io/',
identities: {},
unconfTxs: {},
@ -31,12 +30,7 @@ function reduceMetamask (state, action) {
case actions.AGREE_TO_DISCLAIMER:
return extend(metamaskState, {
isConfirmed: true,
})
case actions.AGREE_TO_ETH_WARNING:
return extend(metamaskState, {
isEthConfirmed: !metamaskState.isEthConfirmed,
isDisclaimerConfirmed: true,
})
case actions.UNLOCK_METAMASK:

@ -55,6 +55,8 @@ UnlockScreen.prototype.render = function () {
h('.error', {
style: {
display: warning ? 'block' : 'none',
padding: '0 20px',
textAlign: 'center',
},
}, warning),
@ -65,6 +67,17 @@ UnlockScreen.prototype.render = function () {
},
}, 'Unlock'),
]),
h('.flex-row.flex-center.flex-grow', [
h('p.pointer', {
onClick: () => this.props.dispatch(actions.showRestoreVault()),
style: {
fontSize: '0.8em',
color: 'rgb(247, 134, 28)',
textDecoration: 'underline',
},
}, 'I forgot my password.'),
]),
])
)
}
@ -104,6 +117,3 @@ UnlockScreen.prototype.inputChanged = function (event) {
})
}
UnlockScreen.prototype.emitAnim = function (name, a, b, c) {
this.animationEventEmitter.emit(name, a, b, c)
}

@ -8,7 +8,7 @@ module.exports = launchApp
function launchApp (opts) {
var accountManager = opts.accountManager
actions._setAccountManager(accountManager)
actions._setBackgroundConnection(accountManager)
// check if we are unlocked first
accountManager.getState(function (err, metamaskState) {

@ -1,4 +1,4 @@
module.exports = function(address, network) {
module.exports = function (address, network) {
const net = parseInt(network)
let link

@ -8,23 +8,22 @@
// Nickname keys must be stored in lower case.
const nicknames = {}
module.exports = function(addr, identities = {}) {
module.exports = function (addr, identities = {}) {
const address = addr.toLowerCase()
const ids = hashFromIdentities(identities)
return addrFromHash(address, ids) || addrFromHash(address, nicknames)
}
function hashFromIdentities(identities) {
function hashFromIdentities (identities) {
const result = {}
for (let key in identities) {
for (const key in identities) {
result[key] = identities[key].name
}
return result
}
function addrFromHash(addr, hash) {
function addrFromHash (addr, hash) {
const address = addr.toLowerCase()
return hash[address] || null
}

@ -55,6 +55,6 @@ function jsNumberForAddress (address) {
return seed
}
function toDataUri(identiconSrc){
function toDataUri (identiconSrc) {
return 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(identiconSrc)
}
}

Loading…
Cancel
Save