Merge branch 'dev' into obs-store2

feature/default_network_editable
kumavis 8 years ago committed by GitHub
commit 74dc20bdf1
  1. 5
      .eslintrc
  2. 22
      CHANGELOG.md
  3. 2
      app/manifest.json
  4. 45
      app/scripts/account-import-strategies/index.js
  5. 2
      app/scripts/background.js
  6. 21
      app/scripts/keyring-controller.js
  7. 2
      app/scripts/keyrings/hd.js
  8. 28
      app/scripts/keyrings/simple.js
  9. 2
      app/scripts/lib/config-manager.js
  10. 4
      app/scripts/lib/eth-store.js
  11. 58
      app/scripts/lib/tx-utils.js
  12. 74
      app/scripts/metamask-controller.js
  13. 227
      app/scripts/transaction-manager.js
  14. 84
      development/states/account-list-with-imported.json
  15. 124
      development/states/compilation-bug.json
  16. 92
      development/states/import-private-key-warning.json
  17. 64
      development/states/import-private-key.json
  18. 66
      development/states/new-account.json
  19. 12
      notices/notice_0.md
  20. 5
      package.json
  21. 3
      test/integration/lib/first-time.js
  22. 2
      test/unit/explorer-link-test.js
  23. 27
      test/unit/keyrings/simple-test.js
  24. 18
      test/unit/metamask-controller-test.js
  25. 5
      test/unit/notice-controller-test.js
  26. 69
      test/unit/tx-manager-test.js
  27. 17
      ui/app/account-detail.js
  28. 91
      ui/app/accounts/import/index.js
  29. 98
      ui/app/accounts/import/json.js
  30. 68
      ui/app/accounts/import/private-key.js
  31. 30
      ui/app/accounts/import/seed.js
  32. 10
      ui/app/accounts/index.js
  33. 45
      ui/app/actions.js
  34. 16
      ui/app/app.js
  35. 82
      ui/app/components/buy-button-subview.js
  36. 8
      ui/app/components/coinbase-form.js
  37. 8
      ui/app/components/loading.js
  38. 12
      ui/app/components/pending-tx-details.js
  39. 35
      ui/app/components/tab-bar.js
  40. 39
      ui/app/components/transaction-list-item-icon.js
  41. 21
      ui/app/components/transaction-list-item.js
  42. 9
      ui/app/components/transaction-list.js
  43. 27
      ui/app/conf-tx.js
  44. 8
      ui/app/css/lib.css
  45. 2
      ui/app/info.js
  46. 20
      ui/app/reducers/app.js
  47. 2
      ui/app/unlock.js
  48. 1
      ui/css.js
  49. 2
      ui/lib/explorer-link.js

@ -1,5 +1,6 @@
{ {
"parserOptions": { "parserOptions": {
"sourceType": "module",
"ecmaVersion": 6, "ecmaVersion": 6,
"ecmaFeatures": { "ecmaFeatures": {
"experimentalObjectRestSpread": true, "experimentalObjectRestSpread": true,
@ -44,7 +45,7 @@
"eol-last": 1, "eol-last": 1,
"eqeqeq": [2, "allow-null"], "eqeqeq": [2, "allow-null"],
"generator-star-spacing": [2, { "before": true, "after": true }], "generator-star-spacing": [2, { "before": true, "after": true }],
"handle-callback-err": [2, "^(err|error)$" ], "handle-callback-err": [1, "^(err|error)$" ],
"indent": [2, 2, { "SwitchCase": 1 }], "indent": [2, 2, { "SwitchCase": 1 }],
"jsx-quotes": [2, "prefer-single"], "jsx-quotes": [2, "prefer-single"],
"key-spacing": [2, { "beforeColon": false, "afterColon": true }], "key-spacing": [2, { "beforeColon": false, "afterColon": true }],
@ -145,6 +146,6 @@
"wrap-iife": [2, "any"], "wrap-iife": [2, "any"],
"yield-star-spacing": [2, "both"], "yield-star-spacing": [2, "both"],
"yoda": [2, "never"], "yoda": [2, "never"],
"prefer-const": 1 "prefer-const": 1,
} }
} }

@ -2,6 +2,26 @@
## Current Master ## Current Master
- Add ability to import accounts in JSON file format (used by Mist, Geth, MyEtherWallet, and more!)
## 3.1.1 2017-1-20
- Fix HD wallet seed export
## 3.1.0 2017-1-18
- Add ability to import accounts by private key.
- Fixed bug that returned the wrong transaction hashes on private networks that had not implemented EIP 155 replay protection (like TestRPC).
## 3.0.1 2017-1-17
- Fixed bug that prevented eth.sign from working.
- Fix the displaying of transactions that have been submitted to the network in Transaction History
## 3.0.0 2017-1-16
- Fix seed word account generation (https://medium.com/metamask/metamask-3-migration-guide-914b79533cdd#.t4i1qmmsz).
- Fix Bug where you see a empty transaction flash by on the confirm transaction view.
- Create visible difference in transaction history between a approved but not yet included in a block transaction and a transaction who has been confirmed.
- Fix memory leak in RPC Cache - Fix memory leak in RPC Cache
- Override RPC commands eth_syncing and web3_clientVersion - Override RPC commands eth_syncing and web3_clientVersion
- Remove certain non-essential permissions from certain builds. - Remove certain non-essential permissions from certain builds.
@ -14,6 +34,8 @@
## 2.14.1 2016-12-20 ## 2.14.1 2016-12-20
- Update Coinbase info. and increase the buy amount to $15
- Fixed ropsten transaction links
- Temporarily disable extension reload detection causing infinite reload bug. - Temporarily disable extension reload detection causing infinite reload bug.
- Implemented basic checking for valid RPC URIs. - Implemented basic checking for valid RPC URIs.

@ -1,7 +1,7 @@
{ {
"name": "MetaMask", "name": "MetaMask",
"short_name": "Metamask", "short_name": "Metamask",
"version": "2.14.1", "version": "3.1.1",
"manifest_version": 2, "manifest_version": 2,
"author": "https://metamask.io", "author": "https://metamask.io",
"description": "Ethereum Browser Extension", "description": "Ethereum Browser Extension",

@ -0,0 +1,45 @@
const Wallet = require('ethereumjs-wallet')
const importers = require('ethereumjs-wallet/thirdparty')
const ethUtil = require('ethereumjs-util')
const accountImporter = {
importAccount(strategy, args) {
try {
const importer = this.strategies[strategy]
const privateKeyHex = importer.apply(null, args)
return Promise.resolve(privateKeyHex)
} catch (e) {
return Promise.reject(e)
}
},
strategies: {
'Private Key': (privateKey) => {
const stripped = ethUtil.stripHexPrefix(privateKey)
return stripped
},
'JSON File': (input, password) => {
let wallet
try {
wallet = importers.fromEtherWallet(input, password)
} catch (e) {
console.log('Attempt to import as EtherWallet format failed, trying V3...')
}
if (!wallet) {
wallet = Wallet.fromV3(input, password, true)
}
return walletToPrivateKey(wallet)
},
},
}
function walletToPrivateKey (wallet) {
const privateKeyBuffer = wallet.getPrivateKey()
return ethUtil.bufferToHex(privateKeyBuffer)
}
module.exports = accountImporter

@ -16,6 +16,7 @@ const firstTimeState = require('./first-time-state')
const STORAGE_KEY = 'metamask-config' const STORAGE_KEY = 'metamask-config'
const METAMASK_DEBUG = 'GULP_METAMASK_DEBUG' const METAMASK_DEBUG = 'GULP_METAMASK_DEBUG'
let popupIsOpen = false let popupIsOpen = false
// state persistence // state persistence
@ -135,6 +136,7 @@ function setupController (initState) {
// User Interface setup // User Interface setup
// //
updateBadge()
controller.txManager.on('updateBadge', updateBadge) controller.txManager.on('updateBadge', updateBadge)
// plugin badge text // plugin badge text

@ -95,7 +95,6 @@ module.exports = class KeyringController extends EventEmitter {
isInitialized: (!!wallet || !!vault), isInitialized: (!!wallet || !!vault),
isUnlocked: Boolean(this.password), isUnlocked: Boolean(this.password),
isDisclaimerConfirmed: this.configManager.getConfirmedDisclaimer(), isDisclaimerConfirmed: this.configManager.getConfirmedDisclaimer(),
transactions: this.configManager.getTxList(),
unconfMsgs: messageManager.unconfirmedMsgs(), unconfMsgs: messageManager.unconfirmedMsgs(),
messages: messageManager.getMsgList(), messages: messageManager.getMsgList(),
selectedAccount: address, selectedAccount: address,
@ -173,7 +172,9 @@ module.exports = class KeyringController extends EventEmitter {
// Used when creating a first vault, to allow confirmation. // Used when creating a first vault, to allow confirmation.
// Also used when revealing the seed words in the confirmation view. // Also used when revealing the seed words in the confirmation view.
placeSeedWords () { placeSeedWords () {
const firstKeyring = this.keyrings[0] const hdKeyrings = this.keyrings.filter((keyring) => keyring.type === 'HD Key Tree')
const firstKeyring = hdKeyrings[0]
if (!firstKeyring) throw new Error('KeyringController - No HD Key Tree found')
return firstKeyring.serialize() return firstKeyring.serialize()
.then((serialized) => { .then((serialized) => {
const seedWords = serialized.mnemonic const seedWords = serialized.mnemonic
@ -235,7 +236,10 @@ module.exports = class KeyringController extends EventEmitter {
addNewKeyring (type, opts) { addNewKeyring (type, opts) {
const Keyring = this.getKeyringClassForType(type) const Keyring = this.getKeyringClassForType(type)
const keyring = new Keyring(opts) const keyring = new Keyring(opts)
return keyring.getAccounts() return keyring.deserialize(opts)
.then(() => {
return keyring.getAccounts()
})
.then((accounts) => { .then((accounts) => {
this.keyrings.push(keyring) this.keyrings.push(keyring)
return this.setupAccounts(accounts) return this.setupAccounts(accounts)
@ -317,13 +321,11 @@ module.exports = class KeyringController extends EventEmitter {
// This method signs tx and returns a promise for // This method signs tx and returns a promise for
// TX Manager to update the state after signing // TX Manager to update the state after signing
signTransaction (ethTx, selectedAddress, txId) { signTransaction (ethTx, _fromAddress) {
const address = normalize(selectedAddress) const fromAddress = normalize(_fromAddress)
return this.getKeyringForAccount(address) return this.getKeyringForAccount(fromAddress)
.then((keyring) => { .then((keyring) => {
return keyring.signTransaction(address, ethTx) return keyring.signTransaction(fromAddress, ethTx)
}).then((tx) => {
return {tx, txId}
}) })
} }
// Add Unconfirmed Message // Add Unconfirmed Message
@ -400,6 +402,7 @@ module.exports = class KeyringController extends EventEmitter {
}).then((rawSig) => { }).then((rawSig) => {
cb(null, rawSig) cb(null, rawSig)
approvalCb(null, true) approvalCb(null, true)
messageManager.confirmMsg(msgId)
return rawSig return rawSig
}) })
} catch (e) { } catch (e) {

@ -76,7 +76,7 @@ class HdKeyring extends EventEmitter {
// For eth_sign, we need to sign transactions: // For eth_sign, we need to sign transactions:
signMessage (withAccount, data) { signMessage (withAccount, data) {
const wallet = this._getWalletForAccount(withAccount) const wallet = this._getWalletForAccount(withAccount)
const message = ethUtil.removeHexPrefix(data) const message = ethUtil.stripHexPrefix(data)
var privKey = wallet.getPrivateKey() var privKey = wallet.getPrivateKey()
var msgSig = ethUtil.ecsign(new Buffer(message, 'hex'), privKey) var msgSig = ethUtil.ecsign(new Buffer(message, 'hex'), privKey)
var rawMsgSig = ethUtil.bufferToHex(sigUtil.concatSig(msgSig.v, msgSig.r, msgSig.s)) var rawMsgSig = ethUtil.bufferToHex(sigUtil.concatSig(msgSig.v, msgSig.r, msgSig.s))

@ -20,13 +20,19 @@ class SimpleKeyring extends EventEmitter {
} }
deserialize (privateKeys = []) { deserialize (privateKeys = []) {
this.wallets = privateKeys.map((privateKey) => { return new Promise((resolve, reject) => {
const stripped = ethUtil.stripHexPrefix(privateKey) try {
const buffer = new Buffer(stripped, 'hex') this.wallets = privateKeys.map((privateKey) => {
const wallet = Wallet.fromPrivateKey(buffer) const stripped = ethUtil.stripHexPrefix(privateKey)
return wallet const buffer = new Buffer(stripped, 'hex')
const wallet = Wallet.fromPrivateKey(buffer)
return wallet
})
} catch (e) {
reject(e)
}
resolve()
}) })
return Promise.resolve()
} }
addAccounts (n = 1) { addAccounts (n = 1) {
@ -35,12 +41,12 @@ class SimpleKeyring extends EventEmitter {
newWallets.push(Wallet.generate()) newWallets.push(Wallet.generate())
} }
this.wallets = this.wallets.concat(newWallets) this.wallets = this.wallets.concat(newWallets)
const hexWallets = newWallets.map(w => w.getAddress().toString('hex')) const hexWallets = newWallets.map(w => ethUtil.bufferToHex(w.getAddress()))
return Promise.resolve(hexWallets) return Promise.resolve(hexWallets)
} }
getAccounts () { getAccounts () {
return Promise.resolve(this.wallets.map(w => w.getAddress().toString('hex'))) return Promise.resolve(this.wallets.map(w => ethUtil.bufferToHex(w.getAddress())))
} }
// tx is an instance of the ethereumjs-transaction class. // tx is an instance of the ethereumjs-transaction class.
@ -54,7 +60,7 @@ class SimpleKeyring extends EventEmitter {
// For eth_sign, we need to sign transactions: // For eth_sign, we need to sign transactions:
signMessage (withAccount, data) { signMessage (withAccount, data) {
const wallet = this._getWalletForAccount(withAccount) const wallet = this._getWalletForAccount(withAccount)
const message = ethUtil.removeHexPrefix(data) const message = ethUtil.stripHexPrefix(data)
var privKey = wallet.getPrivateKey() var privKey = wallet.getPrivateKey()
var msgSig = ethUtil.ecsign(new Buffer(message, 'hex'), privKey) var msgSig = ethUtil.ecsign(new Buffer(message, 'hex'), privKey)
var rawMsgSig = ethUtil.bufferToHex(sigUtil.concatSig(msgSig.v, msgSig.r, msgSig.s)) var rawMsgSig = ethUtil.bufferToHex(sigUtil.concatSig(msgSig.v, msgSig.r, msgSig.s))
@ -70,7 +76,9 @@ class SimpleKeyring extends EventEmitter {
/* PRIVATE METHODS */ /* PRIVATE METHODS */
_getWalletForAccount (account) { _getWalletForAccount (account) {
return this.wallets.find(w => w.getAddress().toString('hex') === account) let wallet = this.wallets.find(w => ethUtil.bufferToHex(w.getAddress()) === account)
if (!wallet) throw new Error('Simple Keyring - Unable to find matching address.')
return wallet
} }
} }

@ -281,7 +281,7 @@ ConfigManager.prototype.updateConversionRate = function () {
this.setConversionPrice(parsedResponse.ticker.price) this.setConversionPrice(parsedResponse.ticker.price)
this.setConversionDate(parsedResponse.timestamp) this.setConversionDate(parsedResponse.timestamp)
}).catch((err) => { }).catch((err) => {
console.error('Error in conversion.', err) console.warn('MetaMask - Failed to query currency conversion.')
this.setConversionPrice(0) this.setConversionPrice(0)
this.setConversionDate('N/A') this.setConversionDate('N/A')
}) })

@ -43,7 +43,9 @@ EthereumStore.prototype.addAccount = function (address) {
self._currentState.accounts[address] = {} self._currentState.accounts[address] = {}
self._didUpdate() self._didUpdate()
if (!self.currentBlockNumber) return if (!self.currentBlockNumber) return
self._updateAccount(address, noop) self._updateAccount(address, () => {
self._didUpdate()
})
} }
EthereumStore.prototype.removeAccount = function (address) { EthereumStore.prototype.removeAccount = function (address) {

@ -1,6 +1,8 @@
const async = require('async') const async = require('async')
const EthQuery = require('eth-query') const EthQuery = require('eth-query')
const ethUtil = require('ethereumjs-util') const ethUtil = require('ethereumjs-util')
const Transaction = require('ethereumjs-tx')
const normalize = require('./sig-util').normalize
const BN = ethUtil.BN const BN = ethUtil.BN
/* /*
@ -14,6 +16,7 @@ module.exports = class txProviderUtils {
this.provider = provider this.provider = provider
this.query = new EthQuery(provider) this.query = new EthQuery(provider)
} }
analyzeGasUsage (txData, cb) { analyzeGasUsage (txData, cb) {
var self = this var self = this
this.query.getBlockByNumber('latest', true, (err, block) => { this.query.getBlockByNumber('latest', true, (err, block) => {
@ -71,4 +74,59 @@ module.exports = class txProviderUtils {
const correct = bnGas.add(gasBuffer) const correct = bnGas.add(gasBuffer)
return ethUtil.addHexPrefix(correct.toString(16)) return ethUtil.addHexPrefix(correct.toString(16))
} }
fillInTxParams (txParams, cb) {
let fromAddress = txParams.from
let reqs = {}
if (isUndef(txParams.gas)) reqs.gas = (cb) => this.query.estimateGas(txParams, cb)
if (isUndef(txParams.gasPrice)) reqs.gasPrice = (cb) => this.query.gasPrice(cb)
if (isUndef(txParams.nonce)) reqs.nonce = (cb) => this.query.getTransactionCount(fromAddress, 'pending', cb)
async.parallel(reqs, function(err, result) {
if (err) return cb(err)
// write results to txParams obj
Object.assign(txParams, result)
cb()
})
}
// builds ethTx from txParams object
buildEthTxFromParams (txParams, gasMultiplier = 1) {
// apply gas multiplyer
let gasPrice = new BN(ethUtil.stripHexPrefix(txParams.gasPrice), 16)
// multiply and divide by 100 so as to add percision to integer mul
gasPrice = gasPrice.mul(new BN(gasMultiplier * 100, 10)).div(new BN(100, 10))
txParams.gasPrice = ethUtil.intToHex(gasPrice.toNumber())
// normalize values
txParams.to = normalize(txParams.to)
txParams.from = normalize(txParams.from)
txParams.value = normalize(txParams.value)
txParams.data = normalize(txParams.data)
txParams.gasLimit = normalize(txParams.gasLimit || txParams.gas)
txParams.nonce = normalize(txParams.nonce)
// build ethTx
const ethTx = new Transaction(txParams)
return ethTx
}
publishTransaction (rawTx, cb) {
this.query.sendRawTransaction(rawTx, cb)
}
validateTxParams (txParams, cb) {
if (('value' in txParams) && txParams.value.indexOf('-') === 0) {
cb(new Error(`Invalid transaction value of ${txParams.value} not a positive number.`))
} else {
cb()
}
}
}
// util
function isUndef(value) {
return value === undefined
} }

@ -15,6 +15,8 @@ const IdStoreMigrator = require('./lib/idStore-migrator')
const ObservableStore = require('./lib/observable/') const ObservableStore = require('./lib/observable/')
const HostStore = require('./lib/observable/host') const HostStore = require('./lib/observable/host')
const synchronizeStore = require('./lib/observable/util/sync') const synchronizeStore = require('./lib/observable/util/sync')
const accountImporter = require('./account-import-strategies')
const version = require('../manifest.json').version const version = require('../manifest.json').version
module.exports = class MetamaskController extends EventEmitter { module.exports = class MetamaskController extends EventEmitter {
@ -57,6 +59,7 @@ module.exports = class MetamaskController extends EventEmitter {
getSelectedAccount: this.configManager.getSelectedAccount.bind(this.configManager), getSelectedAccount: this.configManager.getSelectedAccount.bind(this.configManager),
getGasMultiplier: this.configManager.getGasMultiplier.bind(this.configManager), getGasMultiplier: this.configManager.getGasMultiplier.bind(this.configManager),
getNetwork: this.getStateNetwork.bind(this), getNetwork: this.getStateNetwork.bind(this),
signTransaction: this.keyringController.signTransaction.bind(this.keyringController),
provider: this.provider, provider: this.provider,
blockTracker: this.provider, blockTracker: this.provider,
}) })
@ -77,6 +80,7 @@ module.exports = class MetamaskController extends EventEmitter {
this.ethStore.on('update', this.sendUpdate.bind(this)) this.ethStore.on('update', this.sendUpdate.bind(this))
this.keyringController.on('update', this.sendUpdate.bind(this)) this.keyringController.on('update', this.sendUpdate.bind(this))
this.txManager.on('update', this.sendUpdate.bind(this))
} }
getState () { getState () {
@ -125,7 +129,22 @@ module.exports = class MetamaskController extends EventEmitter {
.then((newState) => { cb(null, newState) }) .then((newState) => { cb(null, newState) })
.catch((reason) => { cb(reason) }) .catch((reason) => { cb(reason) })
}, },
addNewKeyring: nodeify(keyringController.addNewKeyring).bind(keyringController), addNewKeyring: (type, opts, cb) => {
keyringController.addNewKeyring(type, opts)
.then(() => keyringController.fullUpdate())
.then((newState) => { cb(null, newState) })
.catch((reason) => { cb(reason) })
},
importAccountWithStrategy: (strategy, args, cb) => {
accountImporter.importAccount(strategy, args)
.then((privateKey) => {
return keyringController.addNewKeyring('Simple Key Pair', [ privateKey ])
})
.then(keyring => keyring.getAccounts())
.then((accounts) => keyringController.setSelectedAccount(accounts[0]))
.then(() => { cb(null, keyringController.fullUpdate()) })
.catch((reason) => { cb(reason) })
},
addNewAccount: nodeify(keyringController.addNewAccount).bind(keyringController), addNewAccount: nodeify(keyringController.addNewAccount).bind(keyringController),
setSelectedAccount: nodeify(keyringController.setSelectedAccount).bind(keyringController), setSelectedAccount: nodeify(keyringController.setSelectedAccount).bind(keyringController),
saveAccountLabel: nodeify(keyringController.saveAccountLabel).bind(keyringController), saveAccountLabel: nodeify(keyringController.saveAccountLabel).bind(keyringController),
@ -200,26 +219,7 @@ module.exports = class MetamaskController extends EventEmitter {
cb(null, result) cb(null, result)
}, },
// tx signing // tx signing
approveTransaction: this.newUnsignedTransaction.bind(this), processTransaction: (txParams, cb) => this.newUnapprovedTransaction(txParams, cb),
signTransaction: (txParams, cb) => {
this.txManager.formatTxForSigining(txParams)
.then(({ethTx, address, txId}) => {
return this.keyringController.signTransaction(ethTx, address, txId)
})
.then(({tx, txId}) => {
return this.txManager.resolveSignedTransaction({tx, txId})
})
.then((rawTx) => {
cb(null, rawTx)
this.sendUpdate()
this.txManager.emit(`${txParams.metamaskId}:signingComplete`)
})
.catch((err) => {
console.error(err)
cb(err)
})
},
// msg signing // msg signing
approveMessage: this.newUnsignedMessage.bind(this), approveMessage: this.newUnsignedMessage.bind(this),
signMessage: (...args) => { signMessage: (...args) => {
@ -259,24 +259,26 @@ module.exports = class MetamaskController extends EventEmitter {
return publicConfigStore return publicConfigStore
} }
newUnsignedTransaction (txParams, onTxDoneCb) { newUnapprovedTransaction (txParams, cb) {
const txManager = this.txManager const self = this
const err = this.enforceTxValidations(txParams) self.txManager.addUnapprovedTransaction(txParams, (err, txMeta) => {
if (err) return onTxDoneCb(err) if (err) return cb(err)
txManager.addUnapprovedTransaction(txParams, onTxDoneCb, (err, txData) => { self.sendUpdate()
if (err) return onTxDoneCb(err) self.opts.showUnapprovedTx(txMeta)
this.sendUpdate() // listen for tx completion (success, fail)
this.opts.showUnapprovedTx(txParams, txData, onTxDoneCb) self.txManager.once(`${txMeta.id}:finished`, (status) => {
switch (status) {
case 'submitted':
return cb(null, txMeta.hash)
case 'rejected':
return cb(new Error('MetaMask Tx Signature: User denied transaction signature.'))
default:
return cb(new Error(`MetaMask Tx Signature: Unknown problem: ${JSON.stringify(txMeta.txParams)}`))
}
})
}) })
} }
enforceTxValidations (txParams) {
if (('value' in txParams) && txParams.value.indexOf('-') === 0) {
const msg = `Invalid transaction value of ${txParams.value} not a positive number.`
return new Error(msg)
}
}
newUnsignedMessage (msgParams, cb) { newUnsignedMessage (msgParams, cb) {
var state = this.keyringController.getState() var state = this.keyringController.getState()
if (!state.isUnlocked) { if (!state.isUnlocked) {

@ -1,11 +1,11 @@
const EventEmitter = require('events') const EventEmitter = require('events')
const async = require('async')
const extend = require('xtend') const extend = require('xtend')
const Semaphore = require('semaphore')
const ethUtil = require('ethereumjs-util') const ethUtil = require('ethereumjs-util')
const Transaction = require('ethereumjs-tx') const BN = require('ethereumjs-util').BN
const BN = ethUtil.BN
const TxProviderUtil = require('./lib/tx-utils') const TxProviderUtil = require('./lib/tx-utils')
const createId = require('./lib/random-id') const createId = require('./lib/random-id')
const normalize = require('./lib/sig-util').normalize
module.exports = class TransactionManager extends EventEmitter { module.exports = class TransactionManager extends EventEmitter {
constructor (opts) { constructor (opts) {
@ -20,6 +20,8 @@ module.exports = class TransactionManager extends EventEmitter {
this.blockTracker.on('block', this.checkForTxInBlock.bind(this)) this.blockTracker.on('block', this.checkForTxInBlock.bind(this))
this.getGasMultiplier = opts.getGasMultiplier this.getGasMultiplier = opts.getGasMultiplier
this.getNetwork = opts.getNetwork this.getNetwork = opts.getNetwork
this.signEthTx = opts.signTransaction
this.nonceLock = Semaphore(1)
} }
getState () { getState () {
@ -33,11 +35,12 @@ module.exports = class TransactionManager extends EventEmitter {
// Returns the tx list // Returns the tx list
getTxList () { getTxList () {
return this.txList let network = this.getNetwork()
return this.txList.filter(txMeta => txMeta.metamaskNetworkId === network)
} }
// Adds a tx to the txlist // Adds a tx to the txlist
addTx (txMeta, onTxDoneCb = warn) { addTx (txMeta) {
var txList = this.getTxList() var txList = this.getTxList()
var txHistoryLimit = this.txHistoryLimit var txHistoryLimit = this.txHistoryLimit
@ -53,16 +56,11 @@ module.exports = class TransactionManager extends EventEmitter {
txList.push(txMeta) txList.push(txMeta)
this._saveTxList(txList) this._saveTxList(txList)
// keep the onTxDoneCb around in a listener
// for after approval/denial (requires user interaction)
// This onTxDoneCb fires completion to the Dapp's write operation.
this.once(`${txMeta.id}:signed`, function (txId) { this.once(`${txMeta.id}:signed`, function (txId) {
this.removeAllListeners(`${txMeta.id}:rejected`) this.removeAllListeners(`${txMeta.id}:rejected`)
onTxDoneCb(null, true)
}) })
this.once(`${txMeta.id}:rejected`, function (txId) { this.once(`${txMeta.id}:rejected`, function (txId) {
this.removeAllListeners(`${txMeta.id}:signed`) this.removeAllListeners(`${txMeta.id}:signed`)
onTxDoneCb(null, false)
}) })
this.emit('updateBadge') this.emit('updateBadge')
@ -83,6 +81,7 @@ module.exports = class TransactionManager extends EventEmitter {
var index = txList.findIndex(txData => txData.id === txId) var index = txList.findIndex(txData => txData.id === txId)
txList[index] = txMeta txList[index] = txMeta
this._saveTxList(txList) this._saveTxList(txList)
this.emit('update')
} }
get unapprovedTxCount () { get unapprovedTxCount () {
@ -93,28 +92,51 @@ module.exports = class TransactionManager extends EventEmitter {
return this.getTxsByMetaData('status', 'signed').length return this.getTxsByMetaData('status', 'signed').length
} }
addUnapprovedTransaction (txParams, onTxDoneCb, cb) { addUnapprovedTransaction (txParams, done) {
// create txData obj with parameters and meta data let txMeta
var time = (new Date()).getTime() async.waterfall([
var txId = createId() // validate
txParams.metamaskId = txId (cb) => this.txProviderUtils.validateTxParams(txParams, cb),
txParams.metamaskNetworkId = this.getNetwork() // prepare txMeta
var txData = { (cb) => {
id: txId, // create txMeta obj with parameters and meta data
txParams: txParams, let time = (new Date()).getTime()
time: time, let txId = createId()
status: 'unapproved', txParams.metamaskId = txId
gasMultiplier: this.getGasMultiplier() || 1, txParams.metamaskNetworkId = this.getNetwork()
metamaskNetworkId: this.getNetwork(), txMeta = {
} id: txId,
this.txProviderUtils.analyzeGasUsage(txData, this.txDidComplete.bind(this, txData, onTxDoneCb, cb)) time: time,
// calculate metadata for tx status: 'unapproved',
gasMultiplier: this.getGasMultiplier() || 1,
metamaskNetworkId: this.getNetwork(),
txParams: txParams,
}
// calculate metadata for tx
this.txProviderUtils.analyzeGasUsage(txMeta, cb)
},
// save txMeta
(cb) => {
this.addTx(txMeta)
this.setMaxTxCostAndFee(txMeta)
cb(null, txMeta)
},
], done)
} }
txDidComplete (txMeta, onTxDoneCb, cb, err) { setMaxTxCostAndFee (txMeta) {
if (err) return cb(err) var txParams = txMeta.txParams
this.addTx(txMeta, onTxDoneCb) var gasMultiplier = txMeta.gasMultiplier
cb(null, txMeta) var gasCost = new BN(ethUtil.stripHexPrefix(txParams.gas || txMeta.estimatedGas), 16)
var gasPrice = new BN(ethUtil.stripHexPrefix(txParams.gasPrice || '0x4a817c800'), 16)
gasPrice = gasPrice.mul(new BN(gasMultiplier * 100), 10).div(new BN(100, 10))
var txFee = gasCost.mul(gasPrice)
var txValue = new BN(ethUtil.stripHexPrefix(txParams.value || '0x0'), 16)
var maxCost = txValue.add(txFee)
txMeta.txFee = txFee
txMeta.txValue = txValue
txMeta.maxCost = maxCost
this.updateTx(txMeta)
} }
getUnapprovedTxList () { getUnapprovedTxList () {
@ -127,8 +149,25 @@ module.exports = class TransactionManager extends EventEmitter {
} }
approveTransaction (txId, cb = warn) { approveTransaction (txId, cb = warn) {
this.setTxStatusSigned(txId) const self = this
this.once(`${txId}:signingComplete`, cb) // approve
self.setTxStatusApproved(txId)
// only allow one tx at a time for atomic nonce usage
self.nonceLock.take(() => {
// begin signature process
async.waterfall([
(cb) => self.fillInTxParams(txId, cb),
(cb) => self.signTransaction(txId, cb),
(rawTx, cb) => self.publishTransaction(txId, rawTx, cb),
], (err) => {
self.nonceLock.leave()
if (err) {
this.setTxStatusFailed(txId)
return cb(err)
}
cb()
})
})
} }
cancelTransaction (txId, cb = warn) { cancelTransaction (txId, cb = warn) {
@ -136,38 +175,43 @@ module.exports = class TransactionManager extends EventEmitter {
cb() cb()
} }
// formats txParams so the keyringController can sign it fillInTxParams (txId, cb) {
formatTxForSigining (txParams) { let txMeta = this.getTx(txId)
var address = txParams.from this.txProviderUtils.fillInTxParams(txMeta.txParams, (err) => {
var metaTx = this.getTx(txParams.metamaskId) if (err) return cb(err)
var gasMultiplier = metaTx.gasMultiplier this.updateTx(txMeta)
var gasPrice = new BN(ethUtil.stripHexPrefix(txParams.gasPrice), 16) cb()
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)
const ethTx = new Transaction(txParams)
var txId = txParams.metamaskId
return Promise.resolve({ethTx, address, txId})
} }
// receives a signed tx object and updates the tx hash signTransaction (txId, cb) {
// and pass it to the cb to be sent off let txMeta = this.getTx(txId)
resolveSignedTransaction ({tx, txId, cb = warn}) { let txParams = txMeta.txParams
// Add the tx hash to the persisted meta-tx object let fromAddress = txParams.from
var txHash = ethUtil.bufferToHex(tx.hash()) let ethTx = this.txProviderUtils.buildEthTxFromParams(txParams, txMeta.gasMultiplier)
var metaTx = this.getTx(txId) this.signEthTx(ethTx, fromAddress).then(() => {
metaTx.hash = txHash this.setTxStatusSigned(txMeta.id)
this.updateTx(metaTx) cb(null, ethUtil.bufferToHex(ethTx.serialize()))
var rawTx = ethUtil.bufferToHex(tx.serialize()) }).catch((err) => {
return Promise.resolve(rawTx) cb(err)
})
}
publishTransaction (txId, rawTx, cb) {
this.txProviderUtils.publishTransaction(rawTx, (err, txHash) => {
if (err) return cb(err)
this.setTxHash(txId, txHash)
this.setTxStatusSubmitted(txId)
cb()
})
}
// receives a txHash records the tx as signed
setTxHash (txId, txHash) {
// Add the tx hash to the persisted meta-tx object
let txMeta = this.getTx(txId)
txMeta.hash = txHash
this.updateTx(txMeta)
} }
/* /*
@ -212,23 +256,35 @@ module.exports = class TransactionManager extends EventEmitter {
return txMeta.status return txMeta.status
} }
// should update the status of the tx to 'rejected'.
setTxStatusRejected (txId) {
this._setTxStatus(txId, 'rejected')
}
// should update the status of the tx to 'approved'.
setTxStatusApproved (txId) {
this._setTxStatus(txId, 'approved')
}
// should update the status of the tx to 'signed'. // should update the status of the tx to 'signed'.
setTxStatusSigned (txId) { setTxStatusSigned (txId) {
this._setTxStatus(txId, 'signed') this._setTxStatus(txId, 'signed')
this.emit('updateBadge')
} }
// should update the status of the tx to 'rejected'. // should update the status of the tx to 'submitted'.
setTxStatusRejected (txId) { setTxStatusSubmitted (txId) {
this._setTxStatus(txId, 'rejected') this._setTxStatus(txId, 'submitted')
this.emit('updateBadge')
} }
// should update the status of the tx to 'confirmed'.
setTxStatusConfirmed (txId) { setTxStatusConfirmed (txId) {
this._setTxStatus(txId, 'confirmed') this._setTxStatus(txId, 'confirmed')
} }
setTxStatusFailed (txId) {
this._setTxStatus(txId, 'failed')
}
// merges txParams obj onto txData.txParams // merges txParams obj onto txData.txParams
// use extend to ensure that all fields are filled // use extend to ensure that all fields are filled
updateTxParams (txId, txParams) { updateTxParams (txId, txParams) {
@ -240,19 +296,31 @@ module.exports = class TransactionManager extends EventEmitter {
// checks if a signed tx is in a block and // checks if a signed tx is in a block and
// if included sets the tx status as 'confirmed' // if included sets the tx status as 'confirmed'
checkForTxInBlock () { checkForTxInBlock () {
var signedTxList = this.getFilteredTxList({status: 'signed', err: undefined}) var signedTxList = this.getFilteredTxList({status: 'submitted'})
if (!signedTxList.length) return if (!signedTxList.length) return
signedTxList.forEach((tx) => { signedTxList.forEach((txMeta) => {
var txHash = tx.hash var txHash = txMeta.hash
var txId = tx.id var txId = txMeta.id
if (!txHash) return if (!txHash) {
this.txProviderUtils.query.getTransactionByHash(txHash, (err, txMeta) => { txMeta.err = {
if (err || !txMeta) { errCode: 'No hash was provided',
tx.err = err || 'Tx could possibly have not been submitted' message: 'We had an error while submitting this transaction, please try again.',
this.updateTx(tx)
return txMeta ? console.error(err) : console.debug(`txMeta is ${txMeta} for:`, tx)
} }
if (txMeta.blockNumber) { this.updateTx(txMeta)
return this.setTxStatusFailed(txId)
}
this.txProviderUtils.query.getTransactionByHash(txHash, (err, txParams) => {
if (err || !txParams) {
if (!txParams) return
txMeta.err = {
isWarning: true,
errorCode: err,
message: 'There was a problem loading this transaction.',
}
this.updateTx(txMeta)
return console.error(err)
}
if (txParams.blockNumber) {
this.setTxStatusConfirmed(txId) this.setTxStatusConfirmed(txId)
} }
}) })
@ -266,6 +334,7 @@ module.exports = class TransactionManager extends EventEmitter {
// should set the status in txData // should set the status in txData
// - `'unapproved'` the user has not responded // - `'unapproved'` the user has not responded
// - `'rejected'` the user has responded no! // - `'rejected'` the user has responded no!
// - `'approved'` the user has approved the tx
// - `'signed'` the tx is signed // - `'signed'` the tx is signed
// - `'submitted'` the tx is sent to a server // - `'submitted'` the tx is sent to a server
// - `'confirmed'` the tx has been included in a block. // - `'confirmed'` the tx has been included in a block.
@ -273,7 +342,11 @@ module.exports = class TransactionManager extends EventEmitter {
var txMeta = this.getTx(txId) var txMeta = this.getTx(txId)
txMeta.status = status txMeta.status = status
this.emit(`${txMeta.id}:${status}`, txId) this.emit(`${txMeta.id}:${status}`, txId)
if (status === 'submitted' || status === 'rejected') {
this.emit(`${txMeta.id}:finished`, status)
}
this.updateTx(txMeta) this.updateTx(txMeta)
this.emit('updateBadge')
} }
// Saves the new/updated txList. // Saves the new/updated txList.

@ -0,0 +1,84 @@
{
"metamask": {
"isInitialized": true,
"isUnlocked": true,
"rpcTarget": "https://rawtestrpc.metamask.io/",
"identities": {
"0x58bda1f9d87dc7d2bcc6f7c2513efc9d03fca683": {
"address": "0x58bda1f9d87dc7d2bcc6f7c2513efc9d03fca683",
"name": "Account 1"
},
"0x9858e7d8b79fc3e6d989636721584498926da38a": {
"address": "0x9858e7d8b79fc3e6d989636721584498926da38a",
"name": "Imported Account"
}
},
"unconfTxs": {},
"currentFiat": "USD",
"conversionRate": 10.19458075,
"conversionDate": 1484696373,
"noActiveNotices": true,
"network": "3",
"accounts": {
"0x58bda1f9d87dc7d2bcc6f7c2513efc9d03fca683": {
"code": "0x",
"balance": "0x0",
"nonce": "0x0",
"address": "0x58bda1f9d87dc7d2bcc6f7c2513efc9d03fca683"
},
"0x9858e7d8b79fc3e6d989636721584498926da38a": {
"code": "0x",
"balance": "0x0",
"nonce": "0x0",
"address": "0x9858e7d8b79fc3e6d989636721584498926da38a"
}
},
"transactions": [],
"provider": {
"type": "testnet"
},
"selectedAccount": "0x9858e7d8b79fc3e6d989636721584498926da38a",
"selectedAccountTxList": [],
"isDisclaimerConfirmed": true,
"unconfMsgs": {},
"messages": [],
"shapeShiftTxList": [],
"keyringTypes": [
"Simple Key Pair",
"HD Key Tree"
],
"keyrings": [
{
"type": "HD Key Tree",
"accounts": [
"58bda1f9d87dc7d2bcc6f7c2513efc9d03fca683"
]
},
{
"type": "Simple Key Pair",
"accounts": [
"0x9858e7d8b79fc3e6d989636721584498926da38a"
]
}
],
"lostAccounts": [],
"seedWords": null
},
"appState": {
"menuOpen": false,
"currentView": {
"name": "accounts"
},
"accountDetail": {
"subview": "transactions",
"accountExport": "none",
"privateKey": ""
},
"transForward": true,
"isLoading": false,
"warning": null,
"scrollToBottom": false,
"forgottenPassword": false
},
"identities": {}
}

@ -0,0 +1,124 @@
{
"metamask": {
"isInitialized": true,
"isUnlocked": true,
"rpcTarget": "https://rawtestrpc.metamask.io/",
"identities": {
"0xac39b311dceb2a4b2f5d8461c1cdaf756f4f7ae9": {
"address": "0xac39b311dceb2a4b2f5d8461c1cdaf756f4f7ae9",
"name": "Account 1"
},
"0xd7c0cd9e7d2701c710d64fc492c7086679bdf7b4": {
"address": "0xd7c0cd9e7d2701c710d64fc492c7086679bdf7b4",
"name": "Account 2"
},
"0x1acfb961c5a8268eac8e09d6241a26cbeff42241": {
"address": "0x1acfb961c5a8268eac8e09d6241a26cbeff42241",
"name": "Account 3"
},
"0xabc2bca51709b8615147352c62420f547a63a00c": {
"address": "0xabc2bca51709b8615147352c62420f547a63a00c",
"name": "Account 4"
}
},
"unconfTxs": {
"7992944905869041": {
"id": 7992944905869041,
"txParams": {
"from": "0xac39b311dceb2a4b2f5d8461c1cdaf756f4f7ae9",
"value": "0x0",
"data": "0x606060405234610000575b60da806100186000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630dbe671f14603c575b6000565b3460005760466088565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b600060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16815600a165627a7a72305820a99dfa6091771f518dd1ae8d1ee347bae3304dffd98fd24b1b99a8380bc60a750029",
"gas": "0x1af75",
"metamaskId": 7992944905869041,
"metamaskNetworkId": "3"
},
"time": 1482279685589,
"status": "unconfirmed",
"gasMultiplier": 1,
"metamaskNetworkId": "3",
"gasLimitSpecified": true,
"estimatedGas": "0x1af75",
"simulationFails": true
}
},
"currentFiat": "USD",
"conversionRate": 7.69158136,
"conversionDate": 1482279663,
"noActiveNotices": true,
"network": "3",
"accounts": {
"0xac39b311dceb2a4b2f5d8461c1cdaf756f4f7ae9": {
"code": "0x",
"nonce": "0x3",
"balance": "0x11f646fe14c9c000",
"address": "0xac39b311dceb2a4b2f5d8461c1cdaf756f4f7ae9"
},
"0xd7c0cd9e7d2701c710d64fc492c7086679bdf7b4": {
"code": "0x",
"nonce": "0x0",
"balance": "0x0",
"address": "0xd7c0cd9e7d2701c710d64fc492c7086679bdf7b4"
},
"0x1acfb961c5a8268eac8e09d6241a26cbeff42241": {
"code": "0x",
"balance": "0x0",
"nonce": "0x0",
"address": "0x1acfb961c5a8268eac8e09d6241a26cbeff42241"
},
"0xabc2bca51709b8615147352c62420f547a63a00c": {
"code": "0x",
"balance": "0x0",
"nonce": "0x0",
"address": "0xabc2bca51709b8615147352c62420f547a63a00c"
}
},
"transactions": [
{
"id": 7992944905869041,
"txParams": {
"from": "0xac39b311dceb2a4b2f5d8461c1cdaf756f4f7ae9",
"value": "0x0",
"data": "0x606060405234610000575b60da806100186000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630dbe671f14603c575b6000565b3460005760466088565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b600060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16815600a165627a7a72305820a99dfa6091771f518dd1ae8d1ee347bae3304dffd98fd24b1b99a8380bc60a750029",
"gas": "0x1af75",
"metamaskId": 7992944905869041,
"metamaskNetworkId": "3"
},
"time": 1482279685589,
"status": "unconfirmed",
"gasMultiplier": 1,
"metamaskNetworkId": "3",
"gasLimitSpecified": true,
"estimatedGas": "0x1af75",
"simulationFails": true
}
],
"provider": {
"type": "testnet"
},
"selectedAccount": "0xac39b311dceb2a4b2f5d8461c1cdaf756f4f7ae9",
"seedWords": false,
"isDisclaimerConfirmed": true,
"unconfMsgs": {},
"messages": [],
"shapeShiftTxList": [],
"keyringTypes": [
"Simple Key Pair",
"HD Key Tree"
],
"lostAccounts": []
},
"appState": {
"menuOpen": false,
"currentView": {
"name": "confTx",
"context": 0
},
"accountDetail": {
"subview": "transactions"
},
"transForward": true,
"isLoading": false,
"warning": null
},
"identities": {}
}

@ -0,0 +1,92 @@
{
"metamask": {
"isInitialized": true,
"isUnlocked": true,
"rpcTarget": "https://rawtestrpc.metamask.io/",
"identities": {
"0x01208723ba84e15da2e71656544a2963b0c06d40": {
"address": "0x01208723ba84e15da2e71656544a2963b0c06d40",
"name": "Account 1"
}
},
"unconfTxs": {},
"currentFiat": "USD",
"conversionRate": 10.1219126,
"conversionDate": 1484695442,
"noActiveNotices": true,
"network": "3",
"accounts": {
"0x01208723ba84e15da2e71656544a2963b0c06d40": {
"nonce": "0x0",
"balance": "0x0",
"code": "0x",
"address": "0x01208723ba84e15da2e71656544a2963b0c06d40"
}
},
"transactions": [],
"provider": {
"type": "testnet"
},
"selectedAccount": "0x01208723ba84e15da2e71656544a2963b0c06d40",
"selectedAccountTxList": [],
"seedWords": false,
"isDisclaimerConfirmed": true,
"unconfMsgs": {},
"messages": [],
"shapeShiftTxList": [],
"keyringTypes": [
"Simple Key Pair",
"HD Key Tree"
],
"keyrings": [
{
"type": "Simple Key Pair",
"accounts": []
},
{
"type": "Simple Key Pair",
"accounts": []
},
{
"type": "Simple Key Pair",
"accounts": []
},
{
"type": "Simple Key Pair",
"accounts": []
},
{
"type": "Simple Key Pair",
"accounts": []
},
{
"type": "Simple Key Pair",
"accounts": []
},
{
"type": "Simple Key Pair",
"accounts": []
},
{
"type": "HD Key Tree",
"accounts": [
"01208723ba84e15da2e71656544a2963b0c06d40"
]
}
],
"lostAccounts": []
},
"appState": {
"menuOpen": false,
"currentView": {
"name": "import-menu"
},
"accountDetail": {
"subview": "transactions"
},
"transForward": true,
"isLoading": false,
"warning": "Invalid hex string"
},
"identities": {}
}

@ -0,0 +1,64 @@
{
"metamask": {
"isInitialized": true,
"isUnlocked": true,
"rpcTarget": "https://rawtestrpc.metamask.io/",
"identities": {
"0x01208723ba84e15da2e71656544a2963b0c06d40": {
"address": "0x01208723ba84e15da2e71656544a2963b0c06d40",
"name": "Account 1"
}
},
"unconfTxs": {},
"currentFiat": "USD",
"conversionRate": 10.10788584,
"conversionDate": 1484694362,
"noActiveNotices": true,
"network": "3",
"accounts": {
"0x01208723ba84e15da2e71656544a2963b0c06d40": {
"balance": "0x0",
"code": "0x",
"nonce": "0x0",
"address": "0x01208723ba84e15da2e71656544a2963b0c06d40"
}
},
"transactions": [],
"provider": {
"type": "testnet"
},
"selectedAccount": "0x01208723ba84e15da2e71656544a2963b0c06d40",
"selectedAccountTxList": [],
"seedWords": null,
"isDisclaimerConfirmed": true,
"unconfMsgs": {},
"messages": [],
"shapeShiftTxList": [],
"keyringTypes": [
"Simple Key Pair",
"HD Key Tree"
],
"keyrings": [
{
"type": "HD Key Tree",
"accounts": [
"01208723ba84e15da2e71656544a2963b0c06d40"
]
}
],
"lostAccounts": []
},
"appState": {
"menuOpen": false,
"currentView": {
"name": "import-menu"
},
"accountDetail": {
"subview": "transactions"
},
"transForward": true,
"isLoading": false,
"warning": null
},
"identities": {}
}

@ -0,0 +1,66 @@
{
"metamask": {
"isInitialized": true,
"isUnlocked": true,
"rpcTarget": "https://rawtestrpc.metamask.io/",
"identities": {
"0xa6ef573d60594731178b7f85d80da13cc2af52dd": {
"address": "0xa6ef573d60594731178b7f85d80da13cc2af52dd",
"name": "Dan! 1"
},
"0xf9f52e84ad2c9122caa87478d27041ddaa215666": {
"address": "0xf9f52e84ad2c9122caa87478d27041ddaa215666",
"name": "Account 2"
}
},
"unconfTxs": {},
"currentFiat": "USD",
"conversionRate": 10.92067835,
"conversionDate": 1478282884,
"network": null,
"accounts": {
"0xa6ef573d60594731178b7f85d80da13cc2af52dd": {
"balance": "0x00",
"nonce": "0x100000",
"code": "0x",
"address": "0xa6ef573d60594731178b7f85d80da13cc2af52dd"
},
"0xf9f52e84ad2c9122caa87478d27041ddaa215666": {
"balance": "0x00",
"nonce": "0x100000",
"code": "0x",
"address": "0xf9f52e84ad2c9122caa87478d27041ddaa215666"
}
},
"transactions": [],
"provider": {
"type": "testnet"
},
"selectedAccount": "0xa6ef573d60594731178b7f85d80da13cc2af52dd",
"isConfirmed": true,
"unconfMsgs": {},
"messages": [],
"selectedAddress": "0xa6ef573d60594731178b7f85d80da13cc2af52dd",
"shapeShiftTxList": [],
"keyringTypes": [
"Simple Key Pair",
"HD Key Tree"
]
},
"appState": {
"menuOpen": false,
"currentView": {
"name": "new-account"
},
"accountDetail": {
"subview": "transactions"
},
"transForward": true,
"isLoading": false,
"warning": null,
"forgottenPassword": null,
"detailView": {},
"scrollToBottom": false
},
"identities": {}
}

@ -1,12 +0,0 @@
Due to [recent events](https://blog.ethereum.org/2016/11/20/from-morden-to-ropsten/), MetaMask is now deprecating support for the Morden Test Network.
Users will still be able to access Morden through a locally hosted node, but we will no longer be providing hosted access to this network through [Infura](http://infura.io/).
Please use the new Ropsten Network as your new default test network.
You can fund your Ropsten account using the buy button on your account page.
Best wishes!
The MetaMask Team

@ -84,19 +84,22 @@
"react-hyperscript": "^2.2.2", "react-hyperscript": "^2.2.2",
"react-markdown": "^2.3.0", "react-markdown": "^2.3.0",
"react-redux": "^4.4.5", "react-redux": "^4.4.5",
"react-select": "^1.0.0-rc.2",
"react-simple-file-input": "^1.0.0",
"react-tooltip-component": "^0.3.0", "react-tooltip-component": "^0.3.0",
"readable-stream": "^2.1.2", "readable-stream": "^2.1.2",
"redux": "^3.0.5", "redux": "^3.0.5",
"redux-logger": "^2.3.1", "redux-logger": "^2.3.1",
"redux-thunk": "^1.0.2", "redux-thunk": "^1.0.2",
"sandwich-expando": "^1.0.5", "sandwich-expando": "^1.0.5",
"semaphore": "^1.0.5",
"textarea-caret": "^3.0.1", "textarea-caret": "^3.0.1",
"three.js": "^0.73.2", "three.js": "^0.73.2",
"through2": "^2.0.1", "through2": "^2.0.1",
"valid-url": "^1.0.9", "valid-url": "^1.0.9",
"vreme": "^3.0.2", "vreme": "^3.0.2",
"web3": "0.17.0-beta", "web3": "0.17.0-beta",
"web3-provider-engine": "^8.2.0", "web3-provider-engine": "^8.4.0",
"web3-stream-provider": "^2.0.6", "web3-stream-provider": "^2.0.6",
"xtend": "^4.0.1" "xtend": "^4.0.1"
}, },

@ -66,7 +66,8 @@ QUnit.test('agree to terms', function (assert) {
}).then(function() { }).then(function() {
var sandwich = app.find('.menu-droppo')[0] var sandwich = app.find('.menu-droppo')[0]
var lock = sandwich.children[2] var children = sandwich.children
var lock = children[children.length - 2]
assert.ok(lock, 'Lock menu item found') assert.ok(lock, 'Lock menu item found')
lock.click() lock.click()

@ -4,7 +4,7 @@ var linkGen = require('../../ui/lib/explorer-link')
describe('explorer-link', function() { describe('explorer-link', function() {
it('adds testnet prefix to morden test network', function() { it('adds testnet prefix to morden test network', function() {
var result = linkGen('hash', '2') var result = linkGen('hash', '3')
assert.notEqual(result.indexOf('testnet'), -1, 'testnet injected') assert.notEqual(result.indexOf('testnet'), -1, 'testnet injected')
}) })

@ -1,5 +1,6 @@
const assert = require('assert') const assert = require('assert')
const extend = require('xtend') const extend = require('xtend')
const ethUtil = require('ethereumjs-util')
const SimpleKeyring = require('../../../app/scripts/keyrings/simple') const SimpleKeyring = require('../../../app/scripts/keyrings/simple')
const TYPE_STR = 'Simple Key Pair' const TYPE_STR = 'Simple Key Pair'
@ -48,6 +49,24 @@ describe('simple-keyring', function() {
}) })
}) })
describe('#signMessage', function() {
const address = '0x9858e7d8b79fc3e6d989636721584498926da38a'
const message = '0x879a053d4800c6354e76c7985a865d2922c82fb5b3f4577b2fe08b998954f2e0'
const privateKey = '0x7dd98753d7b4394095de7d176c58128e2ed6ee600abe97c9f6d9fd65015d9b18'
const expectedResult = '0x28fcb6768e5110144a55b2e6ce9d1ea5a58103033632d272d2b5cf506906f7941a00b539383fd872109633d8c71c404e13dba87bc84166ee31b0e36061a69e161c'
it('passes the dennis test', function(done) {
keyring.deserialize([ privateKey ])
.then(() => {
return keyring.signMessage(address, message)
})
.then((result) => {
assert.equal(result, expectedResult)
done()
})
})
})
describe('#addAccounts', function() { describe('#addAccounts', function() {
describe('with no arguments', function() { describe('with no arguments', function() {
it('creates a single wallet', function() { it('creates a single wallet', function() {
@ -72,14 +91,10 @@ describe('simple-keyring', function() {
it('calls getAddress on each wallet', function(done) { it('calls getAddress on each wallet', function(done) {
// Push a mock wallet // Push a mock wallet
const desiredOutput = 'foo' const desiredOutput = '0x18a3462427bcc9133bb46e88bcbe39cd7ef0e761'
keyring.wallets.push({ keyring.wallets.push({
getAddress() { getAddress() {
return { return ethUtil.toBuffer(desiredOutput)
toString() {
return desiredOutput
}
}
} }
}) })

@ -27,24 +27,6 @@ describe('MetaMaskController', function() {
this.sinon.restore() this.sinon.restore()
}) })
describe('#enforceTxValidations', function () {
it('returns null for positive values', function() {
var sample = {
value: '0x01'
}
var res = controller.enforceTxValidations(sample)
assert.equal(res, null, 'no error')
})
it('returns error for negative values', function() {
var sample = {
value: '-0x01'
}
var res = controller.enforceTxValidations(sample)
assert.ok(res, 'error')
})
})
}) })

@ -5,13 +5,14 @@ const nock = require('nock')
const configManagerGen = require('../lib/mock-config-manager') const configManagerGen = require('../lib/mock-config-manager')
const NoticeController = require('../../app/scripts/notice-controller') const NoticeController = require('../../app/scripts/notice-controller')
const STORAGE_KEY = 'metamask-persistance-key' const STORAGE_KEY = 'metamask-persistance-key'
// Hacking localStorage support into JSDom
window.localStorage = {}
describe('notice-controller', function() { describe('notice-controller', function() {
var noticeController var noticeController
beforeEach(function() { beforeEach(function() {
// simple localStorage polyfill
window.localStorage = {}
if (window.localStorage.clear) window.localStorage.clear()
let configManager = configManagerGen() let configManager = configManagerGen()
noticeController = new NoticeController({ noticeController = new NoticeController({
configManager: configManager, configManager: configManager,

@ -15,6 +15,28 @@ describe('Transaction Manager', function() {
provider: "testnet", provider: "testnet",
txHistoryLimit: 10, txHistoryLimit: 10,
blockTracker: new EventEmitter(), blockTracker: new EventEmitter(),
getNetwork: function(){ return 'unit test' }
})
})
describe('#validateTxParams', function () {
it('returns null for positive values', function() {
var sample = {
value: '0x01'
}
var res = txManager.txProviderUtils.validateTxParams(sample, (err) => {
assert.equal(err, null, 'no error')
})
})
it('returns error for negative values', function() {
var sample = {
value: '-0x01'
}
var res = txManager.txProviderUtils.validateTxParams(sample, (err) => {
assert.ok(err, 'error')
})
}) })
}) })
@ -31,7 +53,7 @@ describe('Transaction Manager', function() {
describe('#_saveTxList', function() { describe('#_saveTxList', function() {
it('saves the submitted data to the tx list', function() { it('saves the submitted data to the tx list', function() {
var target = [{ foo: 'bar' }] var target = [{ foo: 'bar', metamaskNetworkId: 'unit test' }]
txManager._saveTxList(target) txManager._saveTxList(target)
var result = txManager.getTxList() var result = txManager.getTxList()
assert.equal(result[0].foo, 'bar') assert.equal(result[0].foo, 'bar')
@ -40,7 +62,7 @@ describe('Transaction Manager', function() {
describe('#addTx', function() { describe('#addTx', function() {
it('adds a tx returned in getTxList', function() { it('adds a tx returned in getTxList', function() {
var tx = { id: 1, status: 'confirmed',} var tx = { id: 1, status: 'confirmed', metamaskNetworkId: 'unit test' }
txManager.addTx(tx, onTxDoneCb) txManager.addTx(tx, onTxDoneCb)
var result = txManager.getTxList() var result = txManager.getTxList()
assert.ok(Array.isArray(result)) assert.ok(Array.isArray(result))
@ -51,7 +73,7 @@ describe('Transaction Manager', function() {
it('cuts off early txs beyond a limit', function() { it('cuts off early txs beyond a limit', function() {
const limit = txManager.txHistoryLimit const limit = txManager.txHistoryLimit
for (let i = 0; i < limit + 1; i++) { for (let i = 0; i < limit + 1; i++) {
let tx = { id: i, time: new Date(), status: 'confirmed'} let tx = { id: i, time: new Date(), status: 'confirmed', metamaskNetworkId: 'unit test' }
txManager.addTx(tx, onTxDoneCb) txManager.addTx(tx, onTxDoneCb)
} }
var result = txManager.getTxList() var result = txManager.getTxList()
@ -59,10 +81,10 @@ describe('Transaction Manager', function() {
assert.equal(result[0].id, 1, 'early txs truncted') assert.equal(result[0].id, 1, 'early txs truncted')
}) })
it('cuts off early txs beyond a limit weather or not it is confirmed or rejected', function() { it('cuts off early txs beyond a limit whether or not it is confirmed or rejected', function() {
const limit = txManager.txHistoryLimit const limit = txManager.txHistoryLimit
for (let i = 0; i < limit + 1; i++) { for (let i = 0; i < limit + 1; i++) {
let tx = { id: i, time: new Date(), status: 'rejected'} let tx = { id: i, time: new Date(), status: 'rejected', metamaskNetworkId: 'unit test' }
txManager.addTx(tx, onTxDoneCb) txManager.addTx(tx, onTxDoneCb)
} }
var result = txManager.getTxList() var result = txManager.getTxList()
@ -71,11 +93,11 @@ describe('Transaction Manager', function() {
}) })
it('cuts off early txs beyond a limit but does not cut unapproved txs', function() { it('cuts off early txs beyond a limit but does not cut unapproved txs', function() {
var unconfirmedTx = { id: 0, time: new Date(), status: 'unapproved'} var unconfirmedTx = { id: 0, time: new Date(), status: 'unapproved', metamaskNetworkId: 'unit test' }
txManager.addTx(unconfirmedTx, onTxDoneCb) txManager.addTx(unconfirmedTx, onTxDoneCb)
const limit = txManager.txHistoryLimit const limit = txManager.txHistoryLimit
for (let i = 1; i < limit + 1; i++) { for (let i = 1; i < limit + 1; i++) {
let tx = { id: i, time: new Date(), status: 'confirmed'} let tx = { id: i, time: new Date(), status: 'confirmed', metamaskNetworkId: 'unit test' }
txManager.addTx(tx, onTxDoneCb) txManager.addTx(tx, onTxDoneCb)
} }
var result = txManager.getTxList() var result = txManager.getTxList()
@ -88,7 +110,7 @@ describe('Transaction Manager', function() {
describe('#setTxStatusSigned', function() { describe('#setTxStatusSigned', function() {
it('sets the tx status to signed', function() { it('sets the tx status to signed', function() {
var tx = { id: 1, status: 'unapproved' } var tx = { id: 1, status: 'unapproved', metamaskNetworkId: 'unit test' }
txManager.addTx(tx, onTxDoneCb) txManager.addTx(tx, onTxDoneCb)
txManager.setTxStatusSigned(1) txManager.setTxStatusSigned(1)
var result = txManager.getTxList() var result = txManager.getTxList()
@ -99,20 +121,21 @@ describe('Transaction Manager', function() {
it('should emit a signed event to signal the exciton of callback', (done) => { it('should emit a signed event to signal the exciton of callback', (done) => {
this.timeout(10000) this.timeout(10000)
var tx = { id: 1, status: 'unapproved' } var tx = { id: 1, status: 'unapproved', metamaskNetworkId: 'unit test' }
let onTxDoneCb = function (err, txId) { let onTxDoneCb = function () {
assert(true, 'event listener has been triggered and onTxDoneCb executed') assert(true, 'event listener has been triggered and onTxDoneCb executed')
done() done()
} }
txManager.addTx(tx, onTxDoneCb) txManager.addTx(tx)
txManager.on('1:signed', onTxDoneCb)
txManager.setTxStatusSigned(1) txManager.setTxStatusSigned(1)
}) })
}) })
describe('#setTxStatusRejected', function() { describe('#setTxStatusRejected', function() {
it('sets the tx status to rejected', function() { it('sets the tx status to rejected', function() {
var tx = { id: 1, status: 'unapproved' } var tx = { id: 1, status: 'unapproved', metamaskNetworkId: 'unit test' }
txManager.addTx(tx, onTxDoneCb) txManager.addTx(tx)
txManager.setTxStatusRejected(1) txManager.setTxStatusRejected(1)
var result = txManager.getTxList() var result = txManager.getTxList()
assert.ok(Array.isArray(result)) assert.ok(Array.isArray(result))
@ -122,12 +145,13 @@ describe('Transaction Manager', function() {
it('should emit a rejected event to signal the exciton of callback', (done) => { it('should emit a rejected event to signal the exciton of callback', (done) => {
this.timeout(10000) this.timeout(10000)
var tx = { id: 1, status: 'unapproved' } var tx = { id: 1, status: 'unapproved', metamaskNetworkId: 'unit test' }
txManager.addTx(tx)
let onTxDoneCb = function (err, txId) { let onTxDoneCb = function (err, txId) {
assert(true, 'event listener has been triggered and onTxDoneCb executed') assert(true, 'event listener has been triggered and onTxDoneCb executed')
done() done()
} }
txManager.addTx(tx, onTxDoneCb) txManager.on('1:rejected', onTxDoneCb)
txManager.setTxStatusRejected(1) txManager.setTxStatusRejected(1)
}) })
@ -135,9 +159,9 @@ describe('Transaction Manager', function() {
describe('#updateTx', function() { describe('#updateTx', function() {
it('replaces the tx with the same id', function() { it('replaces the tx with the same id', function() {
txManager.addTx({ id: '1', status: 'unapproved' }, onTxDoneCb) txManager.addTx({ id: '1', status: 'unapproved', metamaskNetworkId: 'unit test' }, onTxDoneCb)
txManager.addTx({ id: '2', status: 'confirmed' }, onTxDoneCb) txManager.addTx({ id: '2', status: 'confirmed', metamaskNetworkId: 'unit test' }, onTxDoneCb)
txManager.updateTx({ id: '1', status: 'blah', hash: 'foo' }) txManager.updateTx({ id: '1', status: 'blah', hash: 'foo', metamaskNetworkId: 'unit test' })
var result = txManager.getTx('1') var result = txManager.getTx('1')
assert.equal(result.hash, 'foo') assert.equal(result.hash, 'foo')
}) })
@ -145,8 +169,8 @@ describe('Transaction Manager', function() {
describe('#getUnapprovedTxList', function() { describe('#getUnapprovedTxList', function() {
it('returns unapproved txs in a hash', function() { it('returns unapproved txs in a hash', function() {
txManager.addTx({ id: '1', status: 'unapproved' }, onTxDoneCb) txManager.addTx({ id: '1', status: 'unapproved', metamaskNetworkId: 'unit test' }, onTxDoneCb)
txManager.addTx({ id: '2', status: 'confirmed' }, onTxDoneCb) txManager.addTx({ id: '2', status: 'confirmed', metamaskNetworkId: 'unit test' }, onTxDoneCb)
let result = txManager.getUnapprovedTxList() let result = txManager.getUnapprovedTxList()
assert.equal(typeof result, 'object') assert.equal(typeof result, 'object')
assert.equal(result['1'].status, 'unapproved') assert.equal(result['1'].status, 'unapproved')
@ -156,8 +180,8 @@ describe('Transaction Manager', function() {
describe('#getTx', function() { describe('#getTx', function() {
it('returns a tx with the requested id', function() { it('returns a tx with the requested id', function() {
txManager.addTx({ id: '1', status: 'unapproved' }, onTxDoneCb) txManager.addTx({ id: '1', status: 'unapproved', metamaskNetworkId: 'unit test' }, onTxDoneCb)
txManager.addTx({ id: '2', status: 'confirmed' }, onTxDoneCb) txManager.addTx({ id: '2', status: 'confirmed', metamaskNetworkId: 'unit test' }, onTxDoneCb)
assert.equal(txManager.getTx('1').status, 'unapproved') assert.equal(txManager.getTx('1').status, 'unapproved')
assert.equal(txManager.getTx('2').status, 'confirmed') assert.equal(txManager.getTx('2').status, 'confirmed')
}) })
@ -171,6 +195,7 @@ describe('Transaction Manager', function() {
let everyOther = i % 2 let everyOther = i % 2
txManager.addTx({ id: i, txManager.addTx({ id: i,
status: everyOther ? 'unapproved' : 'confirmed', status: everyOther ? 'unapproved' : 'confirmed',
metamaskNetworkId: 'unit test',
txParams: { txParams: {
from: everyOther ? 'foop' : 'zoop', from: everyOther ? 'foop' : 'zoop',
to: everyOther ? 'zoop' : 'foop', to: everyOther ? 'zoop' : 'foop',

@ -26,11 +26,10 @@ function mapStateToProps (state) {
accounts: state.metamask.accounts, accounts: state.metamask.accounts,
address: state.metamask.selectedAccount, address: state.metamask.selectedAccount,
accountDetail: state.appState.accountDetail, accountDetail: state.appState.accountDetail,
transactions: state.metamask.transactions,
network: state.metamask.network, network: state.metamask.network,
unconfTxs: valuesFor(state.metamask.unconfTxs),
unconfMsgs: valuesFor(state.metamask.unconfMsgs), unconfMsgs: valuesFor(state.metamask.unconfMsgs),
shapeShiftTxList: state.metamask.shapeShiftTxList, shapeShiftTxList: state.metamask.shapeShiftTxList,
transactions: state.metamask.selectedAccountTxList || [],
} }
} }
@ -248,20 +247,10 @@ AccountDetailScreen.prototype.subview = function () {
} }
AccountDetailScreen.prototype.transactionList = function () { AccountDetailScreen.prototype.transactionList = function () {
const { transactions, unconfTxs, unconfMsgs, address, network, shapeShiftTxList } = this.props const {transactions, unconfMsgs, address, network, shapeShiftTxList } = this.props
var txsToRender = transactions.concat(unconfTxs)
// only transactions that are from the current address
.filter(tx => tx.txParams.from === address)
// only transactions that are on the current network
.filter(tx => tx.txParams.metamaskNetworkId === network)
// sort by recency
.sort((a, b) => b.time - a.time)
return h(TransactionList, { return h(TransactionList, {
txsToRender, transactions: transactions.sort((a, b) => b.time - a.time),
network, network,
unconfTxs,
unconfMsgs, unconfMsgs,
address, address,
shapeShiftTxList, shapeShiftTxList,

@ -0,0 +1,91 @@
const inherits = require('util').inherits
const Component = require('react').Component
const h = require('react-hyperscript')
const connect = require('react-redux').connect
import Select from 'react-select'
// Subviews
const JsonImportView = require('./json.js')
const PrivateKeyImportView = require('./private-key.js')
const menuItems = [
'Private Key',
'JSON File',
]
module.exports = connect(mapStateToProps)(AccountImportSubview)
function mapStateToProps (state) {
return {
menuItems,
}
}
inherits(AccountImportSubview, Component)
function AccountImportSubview () {
Component.call(this)
}
AccountImportSubview.prototype.render = function () {
const props = this.props
const state = this.state || {}
const { menuItems } = props
const { type } = state
return (
h('div', {
style: {
},
}, [
h('div', {
style: {
padding: '10px',
color: 'rgb(174, 174, 174)',
},
}, [
h('h3', { style: { padding: '3px' } }, 'SELECT TYPE'),
h('style', `
.has-value.Select--single > .Select-control .Select-value .Select-value-label, .Select-value-label {
color: rgb(174,174,174);
}
`),
h(Select, {
name: 'import-type-select',
clearable: false,
value: type || menuItems[0],
options: menuItems.map((type) => {
return {
value: type,
label: type,
}
}),
onChange: (opt) => {
this.setState({ type: opt.value })
},
}),
]),
this.renderImportView(),
])
)
}
AccountImportSubview.prototype.renderImportView = function() {
const props = this.props
const state = this.state || {}
const { type } = state
const { menuItems } = props
const current = type || menuItems[0]
switch (current) {
case 'Private Key':
return h(PrivateKeyImportView)
case 'JSON File':
return h(JsonImportView)
default:
return h(JsonImportView)
}
}

@ -0,0 +1,98 @@
const inherits = require('util').inherits
const Component = require('react').Component
const h = require('react-hyperscript')
const connect = require('react-redux').connect
const actions = require('../../actions')
const FileInput = require('react-simple-file-input').default
module.exports = connect(mapStateToProps)(JsonImportSubview)
function mapStateToProps (state) {
return {
error: state.appState.warning,
}
}
inherits(JsonImportSubview, Component)
function JsonImportSubview () {
Component.call(this)
}
JsonImportSubview.prototype.render = function () {
const { error } = this.props
return (
h('div', {
style: {
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
padding: '5px 15px 0px 15px',
},
}, [
h('p', 'Used by a variety of different clients'),
h(FileInput, {
readAs: 'text',
onLoad: this.onLoad.bind(this),
style: {
margin: '20px 0px 12px 20px',
fontSize: '15px',
},
}),
h('input.large-input.letter-spacey', {
type: 'password',
placeholder: 'Enter password',
id: 'json-password-box',
onKeyPress: this.createKeyringOnEnter.bind(this),
style: {
width: 260,
marginTop: 12,
},
}),
h('button.primary', {
onClick: this.createNewKeychain.bind(this),
style: {
margin: 12,
},
}, 'Import'),
error ? h('span.warning', error) : null,
])
)
}
JsonImportSubview.prototype.onLoad = function (event, file) {
this.setState({file: file, fileContents: event.target.result})
}
JsonImportSubview.prototype.createKeyringOnEnter = function (event) {
if (event.key === 'Enter') {
event.preventDefault()
this.createNewKeychain()
}
}
JsonImportSubview.prototype.createNewKeychain = function () {
const state = this.state
const { fileContents } = state
if (!fileContents) {
const message = 'You must select a file to import.'
return this.props.dispatch(actions.displayWarning(message))
}
const passwordInput = document.getElementById('json-password-box')
const password = passwordInput.value
if (!password) {
const message = 'You must enter a password for the selected file.'
return this.props.dispatch(actions.displayWarning(message))
}
this.props.dispatch(actions.importNewAccount('JSON File', [ fileContents, password ]))
}

@ -0,0 +1,68 @@
const inherits = require('util').inherits
const Component = require('react').Component
const h = require('react-hyperscript')
const connect = require('react-redux').connect
const actions = require('../../actions')
module.exports = connect(mapStateToProps)(PrivateKeyImportView)
function mapStateToProps (state) {
return {
error: state.appState.warning,
}
}
inherits(PrivateKeyImportView, Component)
function PrivateKeyImportView () {
Component.call(this)
}
PrivateKeyImportView.prototype.render = function () {
const { error } = this.props
return (
h('div', {
style: {
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
padding: '5px 15px 0px 15px',
},
}, [
h('span', 'Paste your private key string here'),
h('input.large-input.letter-spacey', {
type: 'password',
id: 'private-key-box',
onKeyPress: this.createKeyringOnEnter.bind(this),
style: {
width: 260,
marginTop: 12,
},
}),
h('button.primary', {
onClick: this.createNewKeychain.bind(this),
style: {
margin: 12,
},
}, 'Import'),
error ? h('span.warning', error) : null,
])
)
}
PrivateKeyImportView.prototype.createKeyringOnEnter = function (event) {
if (event.key === 'Enter') {
event.preventDefault()
this.createNewKeychain()
}
}
PrivateKeyImportView.prototype.createNewKeychain = function () {
const input = document.getElementById('private-key-box')
const privateKey = input.value
this.props.dispatch(actions.importNewAccount('Private Key', [ privateKey ]))
}

@ -0,0 +1,30 @@
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)(SeedImportSubview)
function mapStateToProps (state) {
return {}
}
inherits(SeedImportSubview, Component)
function SeedImportSubview () {
Component.call(this)
}
SeedImportSubview.prototype.render = function () {
return (
h('div', {
style: {
},
}, [
`Paste your seed phrase here!`,
h('textarea'),
h('br'),
h('button', 'Submit'),
])
)
}

@ -73,7 +73,8 @@ AccountsScreen.prototype.render = function () {
const simpleAddress = identity.address.substring(2).toLowerCase() const simpleAddress = identity.address.substring(2).toLowerCase()
const keyring = keyrings.find((kr) => { const keyring = keyrings.find((kr) => {
return kr.accounts.includes(simpleAddress) return kr.accounts.includes(simpleAddress) ||
kr.accounts.includes(identity.address)
}) })
return h(AccountListItem, { return h(AccountListItem, {
@ -154,6 +155,13 @@ AccountsScreen.prototype.addNewAccount = function () {
this.props.dispatch(actions.addNewAccount(0)) this.props.dispatch(actions.addNewAccount(0))
} }
/* An optional view proposed in this design:
* https://consensys.quip.com/zZVrAysM5znY
AccountsScreen.prototype.addNewAccount = function () {
this.props.dispatch(actions.navigateToNewAccountScreen())
}
*/
AccountsScreen.prototype.goHome = function () { AccountsScreen.prototype.goHome = function () {
this.props.dispatch(actions.goHome()) this.props.dispatch(actions.goHome())
} }

@ -32,16 +32,21 @@ var actions = {
SHOW_INIT_MENU: 'SHOW_INIT_MENU', SHOW_INIT_MENU: 'SHOW_INIT_MENU',
SHOW_NEW_VAULT_SEED: 'SHOW_NEW_VAULT_SEED', SHOW_NEW_VAULT_SEED: 'SHOW_NEW_VAULT_SEED',
SHOW_INFO_PAGE: 'SHOW_INFO_PAGE', SHOW_INFO_PAGE: 'SHOW_INFO_PAGE',
SHOW_IMPORT_PAGE: 'SHOW_IMPORT_PAGE',
unlockMetamask: unlockMetamask, unlockMetamask: unlockMetamask,
unlockFailed: unlockFailed, unlockFailed: unlockFailed,
showCreateVault: showCreateVault, showCreateVault: showCreateVault,
showRestoreVault: showRestoreVault, showRestoreVault: showRestoreVault,
showInitializeMenu: showInitializeMenu, showInitializeMenu: showInitializeMenu,
showImportPage,
createNewVaultAndKeychain: createNewVaultAndKeychain, createNewVaultAndKeychain: createNewVaultAndKeychain,
createNewVaultAndRestore: createNewVaultAndRestore, createNewVaultAndRestore: createNewVaultAndRestore,
createNewVaultInProgress: createNewVaultInProgress, createNewVaultInProgress: createNewVaultInProgress,
addNewKeyring, addNewKeyring,
importNewAccount,
addNewAccount, addNewAccount,
NEW_ACCOUNT_SCREEN: 'NEW_ACCOUNT_SCREEN',
navigateToNewAccountScreen,
showNewVaultSeed: showNewVaultSeed, showNewVaultSeed: showNewVaultSeed,
showInfoPage: showInfoPage, showInfoPage: showInfoPage,
// seed recovery actions // seed recovery actions
@ -249,7 +254,36 @@ function requestRevealSeed (password) {
} }
function addNewKeyring (type, opts) { function addNewKeyring (type, opts) {
return callBackgroundThenUpdate(background.addNewKeyring, type, opts) return (dispatch) => {
dispatch(actions.showLoadingIndication())
background.addNewKeyring(type, opts, (err, newState) => {
dispatch(actions.hideLoadingIndication())
if (err) return dispatch(actions.displayWarning(err.message))
dispatch(actions.updateMetamaskState(newState))
dispatch(actions.showAccountsPage())
})
}
}
function importNewAccount (strategy, args) {
return (dispatch) => {
dispatch(actions.showLoadingIndication('This may take a while, be patient.'))
background.importAccountWithStrategy(strategy, args, (err, newState) => {
dispatch(actions.hideLoadingIndication())
if (err) return dispatch(actions.displayWarning(err.message))
dispatch(actions.updateMetamaskState(newState))
dispatch({
type: actions.SHOW_ACCOUNT_DETAIL,
value: newState.selectedAccount,
})
})
}
}
function navigateToNewAccountScreen() {
return {
type: this.NEW_ACCOUNT_SCREEN,
}
} }
function addNewAccount (ringNumber = 0) { function addNewAccount (ringNumber = 0) {
@ -376,6 +410,12 @@ function showInitializeMenu () {
} }
} }
function showImportPage () {
return {
type: actions.SHOW_IMPORT_PAGE,
}
}
function agreeToDisclaimer () { function agreeToDisclaimer () {
return (dispatch) => { return (dispatch) => {
dispatch(this.showLoadingIndication()) dispatch(this.showLoadingIndication())
@ -590,9 +630,10 @@ function useEtherscanProvider () {
} }
} }
function showLoadingIndication () { function showLoadingIndication (message) {
return { return {
type: actions.SHOW_LOADING, type: actions.SHOW_LOADING,
value: message,
} }
} }

@ -20,6 +20,7 @@ const NoticeScreen = require('./components/notice')
const generateLostAccountsNotice = require('../lib/lost-accounts-notice') const generateLostAccountsNotice = require('../lib/lost-accounts-notice')
// other views // other views
const ConfigScreen = require('./config') const ConfigScreen = require('./config')
const Import = require('./accounts/import')
const InfoScreen = require('./info') const InfoScreen = require('./info')
const LoadingIndicator = require('./components/loading') const LoadingIndicator = require('./components/loading')
const SandwichExpando = require('sandwich-expando') const SandwichExpando = require('sandwich-expando')
@ -42,6 +43,7 @@ function mapStateToProps (state) {
return { return {
// state from plugin // state from plugin
isLoading: state.appState.isLoading, isLoading: state.appState.isLoading,
loadingMessage: state.appState.loadingMessage,
isDisclaimerConfirmed: state.metamask.isDisclaimerConfirmed, isDisclaimerConfirmed: state.metamask.isDisclaimerConfirmed,
noActiveNotices: state.metamask.noActiveNotices, noActiveNotices: state.metamask.noActiveNotices,
isInitialized: state.metamask.isInitialized, isInitialized: state.metamask.isInitialized,
@ -63,7 +65,7 @@ function mapStateToProps (state) {
App.prototype.render = function () { App.prototype.render = function () {
var props = this.props var props = this.props
const { isLoading, transForward } = props const { isLoading, loadingMessage, transForward } = props
return ( return (
@ -75,7 +77,7 @@ App.prototype.render = function () {
}, },
}, [ }, [
h(LoadingIndicator, { isLoading }), h(LoadingIndicator, { isLoading, loadingMessage }),
// app bar // app bar
this.renderAppBar(), this.renderAppBar(),
@ -304,6 +306,13 @@ App.prototype.renderDropdown = function () {
icon: h('i.fa.fa-gear.fa-lg'), icon: h('i.fa.fa-gear.fa-lg'),
}), }),
h(DropMenuItem, {
label: 'Import Account',
closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }),
action: () => this.props.dispatch(actions.showImportPage()),
icon: h('i.fa.fa-arrow-circle-o-up.fa-lg'),
}),
h(DropMenuItem, { h(DropMenuItem, {
label: 'Lock', label: 'Lock',
closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }), closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }),
@ -411,6 +420,9 @@ App.prototype.renderPrimary = function () {
case 'config': case 'config':
return h(ConfigScreen, {key: 'config'}) return h(ConfigScreen, {key: 'config'})
case 'import-menu':
return h(Import, {key: 'import-menu'})
case 'reveal-seed-conf': case 'reveal-seed-conf':
return h(RevealSeedConfirmation, {key: 'reveal-seed-conf'}) return h(RevealSeedConfirmation, {key: 'reveal-seed-conf'})

@ -7,6 +7,7 @@ const CoinbaseForm = require('./coinbase-form')
const ShapeshiftForm = require('./shapeshift-form') const ShapeshiftForm = require('./shapeshift-form')
const extension = require('../../../app/scripts/lib/extension') const extension = require('../../../app/scripts/lib/extension')
const Loading = require('./loading') const Loading = require('./loading')
const TabBar = require('./tab-bar')
module.exports = connect(mapStateToProps)(BuyButtonSubview) module.exports = connect(mapStateToProps)(BuyButtonSubview)
@ -29,7 +30,6 @@ function BuyButtonSubview () {
BuyButtonSubview.prototype.render = function () { BuyButtonSubview.prototype.render = function () {
const props = this.props const props = this.props
const currentForm = props.buyView.formView
const isLoading = props.isSubLoading const isLoading = props.isSubLoading
return ( return (
@ -53,43 +53,53 @@ BuyButtonSubview.prototype.render = function () {
h(Loading, { isLoading }), h(Loading, { isLoading }),
h('h3.flex-row.text-transform-uppercase', { h(TabBar, {
style: { tabs: [
background: '#EBEBEB', {
color: '#AEAEAE', content: [
paddingTop: '4px', 'Coinbase',
justifyContent: 'space-around', h('a', {
onClick: (event) => this.navigateTo('https://github.com/MetaMask/faq/blob/master/COINBASE.md'),
}, [
h('i.fa.fa-question-circle', {
style: {
margin: '0px 5px',
},
}),
]),
],
key: 'coinbase',
},
{
content: [
'Shapeshift',
h('a', {
href: 'https://github.com/MetaMask/faq/blob/master/COINBASE.md',
onClick: (event) => this.navigateTo('https://info.shapeshift.io/about'),
}, [
h('i.fa.fa-question-circle', {
style: {
margin: '0px 5px',
},
}),
]),
],
key: 'shapeshift',
},
],
defaultTab: 'coinbase',
tabSelected: (key) => {
switch (key) {
case 'coinbase':
props.dispatch(actions.coinBaseSubview())
break
case 'shapeshift':
props.dispatch(actions.shapeShiftSubview(props.provider.type))
break
}
}, },
}, [ }),
h(currentForm.coinbase ? '.activeForm' : '.inactiveForm.pointer', {
onClick: () => props.dispatch(actions.coinBaseSubview()),
}, 'Coinbase'),
h('a', {
onClick: (event) => this.navigateTo('https://github.com/MetaMask/faq/blob/master/COINBASE.md'),
}, [
h('i.fa.fa-question-circle', {
style: {
position: 'relative',
right: '33px',
},
}),
]),
h(currentForm.shapeshift ? '.activeForm' : '.inactiveForm.pointer', {
onClick: () => props.dispatch(actions.shapeShiftSubview(props.provider.type)),
}, 'Shapeshift'),
h('a', {
href: 'https://github.com/MetaMask/faq/blob/master/COINBASE.md',
onClick: (event) => this.navigateTo('https://info.shapeshift.io/about'),
}, [
h('i.fa.fa-question-circle', {
style: {
position: 'relative',
right: '28px',
},
}),
]),
]),
this.formVersionSubview(), this.formVersionSubview(),
]) ])
) )

@ -72,7 +72,7 @@ CoinbaseForm.prototype.render = function () {
lineHeight: '13px', lineHeight: '13px',
}, },
}, },
`there is a USD$ 5 a day max and a USD$ 50 `there is a USD$ 15 a day max and a USD$ 50
dollar limit per the life time of an account without a dollar limit per the life time of an account without a
coinbase account. A fee of 3.75% will be aplied to debit/credit cards.`), coinbase account. A fee of 3.75% will be aplied to debit/credit cards.`),
@ -136,14 +136,14 @@ CoinbaseForm.prototype.renderLoading = function () {
function isValidAmountforCoinBase (amount) { function isValidAmountforCoinBase (amount) {
amount = parseFloat(amount) amount = parseFloat(amount)
if (amount) { if (amount) {
if (amount <= 5 && amount > 0) { if (amount <= 15 && amount > 0) {
return { return {
valid: true, valid: true,
} }
} else if (amount > 5) { } else if (amount > 15) {
return { return {
valid: false, valid: false,
message: 'The amount can not be greater then $5', message: 'The amount can not be greater then $15',
} }
} else { } else {
return { return {

@ -12,7 +12,7 @@ function LoadingIndicator () {
} }
LoadingIndicator.prototype.render = function () { LoadingIndicator.prototype.render = function () {
var isLoading = this.props.isLoading const { isLoading, loadingMessage } = this.props
return ( return (
h(ReactCSSTransitionGroup, { h(ReactCSSTransitionGroup, {
@ -37,8 +37,14 @@ LoadingIndicator.prototype.render = function () {
h('img', { h('img', {
src: 'images/loading.svg', src: 'images/loading.svg',
}), }),
showMessageIfAny(loadingMessage),
]) : null, ]) : null,
]) ])
) )
} }
function showMessageIfAny (loadingMessage) {
if (!loadingMessage) return null
return h('span', loadingMessage)
}

@ -7,8 +7,6 @@ const EthBalance = require('./eth-balance')
const util = require('../util') const util = require('../util')
const addressSummary = util.addressSummary const addressSummary = util.addressSummary
const nameForAddress = require('../../lib/contract-namer') const nameForAddress = require('../../lib/contract-namer')
const ethUtil = require('ethereumjs-util')
const BN = ethUtil.BN
module.exports = PendingTxDetails module.exports = PendingTxDetails
@ -29,15 +27,9 @@ PTXP.render = function () {
var account = props.accounts[address] var account = props.accounts[address]
var balance = account ? account.balance : '0x0' var balance = account ? account.balance : '0x0'
var gasMultiplier = txData.gasMultiplier var txFee = txData.txFee || ''
var gasCost = new BN(ethUtil.stripHexPrefix(txParams.gas || txData.estimatedGas), 16) var maxCost = txData.maxCost || ''
var gasPrice = new BN(ethUtil.stripHexPrefix(txParams.gasPrice || '0x4a817c800'), 16)
gasPrice = gasPrice.mul(new BN(gasMultiplier * 100), 10).div(new BN(100, 10))
var txFee = gasCost.mul(gasPrice)
var txValue = new BN(ethUtil.stripHexPrefix(txParams.value || '0x0'), 16)
var maxCost = txValue.add(txFee)
var dataLength = txParams.data ? (txParams.data.length - 2) / 2 : 0 var dataLength = txParams.data ? (txParams.data.length - 2) / 2 : 0
var imageify = props.imageifyIdenticons === undefined ? true : props.imageifyIdenticons var imageify = props.imageifyIdenticons === undefined ? true : props.imageifyIdenticons
return ( return (

@ -0,0 +1,35 @@
const Component = require('react').Component
const h = require('react-hyperscript')
const inherits = require('util').inherits
module.exports = TabBar
inherits(TabBar, Component)
function TabBar () {
Component.call(this)
}
TabBar.prototype.render = function () {
const props = this.props
const state = this.state || {}
const { tabs = [], defaultTab, tabSelected } = props
const { subview = defaultTab } = state
return (
h('.flex-row.space-around.text-transform-uppercase', {
style: {
background: '#EBEBEB',
color: '#AEAEAE',
paddingTop: '4px',
},
}, tabs.map((tab) => {
const { key, content } = tab
return h(subview === key ? '.activeForm' : '.inactiveForm.pointer', {
onClick: () => {
this.setState({ subview: key })
tabSelected(key)
},
}, content)
}))
)
}

@ -13,13 +13,40 @@ function TransactionIcon () {
TransactionIcon.prototype.render = function () { TransactionIcon.prototype.render = function () {
const { transaction, txParams, isMsg } = this.props const { transaction, txParams, isMsg } = this.props
switch (transaction.status) {
case 'unapproved':
return h('.unapproved-tx', {
style: {
width: '24px',
height: '24px',
background: '#4dffff',
border: 'solid',
borderColor: '#AEAEAE',
borderWidth: '0.5px',
borderRadius: '13px',
},
})
if (transaction.status === 'rejected') { case 'rejected':
return h('i.fa.fa-exclamation-triangle.fa-lg.warning', { return h('i.fa.fa-exclamation-triangle.fa-lg.warning', {
style: { style: {
width: '24px', width: '24px',
}, },
}) })
case 'failed':
return h('i.fa.fa-exclamation-triangle.fa-lg.error', {
style: {
width: '24px',
},
})
case 'submitted':
return h('i.fa.fa-ellipsis-h', {
style: {
fontSize: '27px',
},
})
} }
if (isMsg) { if (isMsg) {

@ -8,6 +8,7 @@ const explorerLink = require('../../lib/explorer-link')
const CopyButton = require('./copyButton') const CopyButton = require('./copyButton')
const vreme = new (require('vreme')) const vreme = new (require('vreme'))
const extension = require('../../../app/scripts/lib/extension') const extension = require('../../../app/scripts/lib/extension')
const Tooltip = require('./tooltip')
const TransactionIcon = require('./transaction-list-item-icon') const TransactionIcon = require('./transaction-list-item-icon')
const ShiftListItem = require('./shift-list-item') const ShiftListItem = require('./shift-list-item')
@ -27,7 +28,7 @@ TransactionListItem.prototype.render = function () {
let isLinkable = false let isLinkable = false
const numericNet = parseInt(network) const numericNet = parseInt(network)
isLinkable = numericNet === 1 || numericNet === 2 isLinkable = numericNet === 1 || numericNet === 3
var isMsg = ('msgParams' in transaction) var isMsg = ('msgParams' in transaction)
var isTx = ('txParams' in transaction) var isTx = ('txParams' in transaction)
@ -41,7 +42,6 @@ TransactionListItem.prototype.render = function () {
} }
const isClickable = ('hash' in transaction && isLinkable) || isPending const isClickable = ('hash' in transaction && isLinkable) || isPending
return ( return (
h(`.transaction-list-item.flex-row.flex-space-between${isClickable ? '.pointer' : ''}`, { h(`.transaction-list-item.flex-row.flex-space-between${isClickable ? '.pointer' : ''}`, {
onClick: (event) => { onClick: (event) => {
@ -59,11 +59,7 @@ TransactionListItem.prototype.render = function () {
}, [ }, [
h('.identicon-wrapper.flex-column.flex-center.select-none', [ h('.identicon-wrapper.flex-column.flex-center.select-none', [
transaction.status === 'unapproved' ? h('i.fa.fa-ellipsis-h', { h('.pop-hover', {
style: {
fontSize: '27px',
},
}) : h('.pop-hover', {
onClick: (event) => { onClick: (event) => {
event.stopPropagation() event.stopPropagation()
if (!isTx || isPending) return if (!isTx || isPending) return
@ -139,7 +135,14 @@ function failIfFailed (transaction) {
if (transaction.status === 'rejected') { if (transaction.status === 'rejected') {
return h('span.error', ' (Rejected)') return h('span.error', ' (Rejected)')
} }
if (transaction.status === 'failed') { if (transaction.err) {
return h('span.error', ' (Failed)')
return h(Tooltip, {
title: transaction.err.message,
position: 'bottom',
}, [
h('span.error', ' (Failed)'),
])
} }
} }

@ -13,12 +13,13 @@ function TransactionList () {
} }
TransactionList.prototype.render = function () { TransactionList.prototype.render = function () {
const { txsToRender, network, unconfMsgs } = this.props const { transactions, network, unconfMsgs } = this.props
var shapeShiftTxList var shapeShiftTxList
if (network === '1') { if (network === '1') {
shapeShiftTxList = this.props.shapeShiftTxList shapeShiftTxList = this.props.shapeShiftTxList
} }
const transactions = !shapeShiftTxList ? txsToRender.concat(unconfMsgs) : txsToRender.concat(unconfMsgs, shapeShiftTxList) const txsToRender = !shapeShiftTxList ? transactions.concat(unconfMsgs) : transactions.concat(unconfMsgs, shapeShiftTxList)
.sort((a, b) => b.time - a.time) .sort((a, b) => b.time - a.time)
return ( return (
@ -55,8 +56,8 @@ TransactionList.prototype.render = function () {
}, },
}, [ }, [
transactions.length txsToRender.length
? transactions.map((transaction, i) => { ? txsToRender.map((transaction, i) => {
let key let key
switch (transaction.key) { switch (transaction.key) {
case 'shapeshift': case 'shapeshift':

@ -41,11 +41,13 @@ ConfirmTxScreen.prototype.render = function () {
var provider = state.provider var provider = state.provider
var unconfTxs = state.unconfTxs var unconfTxs = state.unconfTxs
var unconfMsgs = state.unconfMsgs var unconfMsgs = state.unconfMsgs
var unconfTxList = txHelper(unconfTxs, unconfMsgs, network) var unconfTxList = txHelper(unconfTxs, unconfMsgs, network)
var index = state.index !== undefined ? state.index : 0 var index = state.index !== undefined && unconfTxList[index] ? state.index : 0
var txData = unconfTxList[index] || unconfTxList[0] || {} var txData = unconfTxList[index] || {}
var txParams = txData.txParams || {} var txParams = txData.params || {}
var isNotification = isPopupOrNotification() === 'notification' var isNotification = isPopupOrNotification() === 'notification'
if (unconfTxList.length === 0) return null
return ( return (
@ -115,27 +117,24 @@ ConfirmTxScreen.prototype.render = function () {
} }
function currentTxView (opts) { function currentTxView (opts) {
if ('txParams' in opts.txData) { const { txData } = opts
const { txParams, msgParams } = txData
if (txParams) {
// This is a pending transaction // This is a pending transaction
return h(PendingTx, opts) return h(PendingTx, opts)
} else if ('msgParams' in opts.txData) { } else if (msgParams) {
// This is a pending message to sign // This is a pending message to sign
return h(PendingMsg, opts) return h(PendingMsg, opts)
} }
} }
ConfirmTxScreen.prototype.checkBalanceAgainstTx = function (txData) { ConfirmTxScreen.prototype.checkBalanceAgainstTx = function (txData) {
if (!txData.txParams) return false
var state = this.props var state = this.props
var address = txData.txParams.from || state.selectedAccount
var txParams = txData.txParams || {}
var address = txParams.from || state.selectedAccount
var account = state.accounts[address] var account = state.accounts[address]
var balance = account ? account.balance : '0x0' var balance = account ? account.balance : '0x0'
var maxCost = new BN(txData.maxCost)
var gasCost = new BN(ethUtil.stripHexPrefix(txParams.gas || txData.estimatedGas), 16)
var gasPrice = new BN(ethUtil.stripHexPrefix(txParams.gasPrice || '0x4a817c800'), 16)
var txFee = gasCost.mul(gasPrice)
var txValue = new BN(ethUtil.stripHexPrefix(txParams.value || '0x0'), 16)
var maxCost = txValue.add(txFee)
var balanceBn = new BN(ethUtil.stripHexPrefix(balance), 16) var balanceBn = new BN(ethUtil.stripHexPrefix(balance), 16)
return maxCost.gt(balanceBn) return maxCost.gt(balanceBn)

@ -23,6 +23,14 @@
flex-direction: column; flex-direction: column;
} }
.space-between {
justify-content: space-between;
}
.space-around {
justify-content: space-around;
}
.flex-column-bottom { .flex-column-bottom {
display: flex; display: flex;
flex-direction: column-reverse; flex-direction: column-reverse;

@ -110,7 +110,7 @@ InfoScreen.prototype.render = function () {
onClick (event) { this.navigateTo(event.target.href) }, onClick (event) { this.navigateTo(event.target.href) },
}, [ }, [
h('img.icon-size', { h('img.icon-size', {
src: manifest.icons[128], src: manifest.icons['128'],
style: { style: {
filter: 'grayscale(100%)', /* IE6-9 */ filter: 'grayscale(100%)', /* IE6-9 */
WebkitFilter: 'grayscale(100%)', /* Microsoft Edge and Firefox 35+ */ WebkitFilter: 'grayscale(100%)', /* Microsoft Edge and Firefox 35+ */

@ -99,6 +99,14 @@ function reduceApp (state, action) {
transForward: action.value, transForward: action.value,
}) })
case actions.SHOW_IMPORT_PAGE:
return extend(appState, {
currentView: {
name: 'import-menu',
},
transForward: true,
})
case actions.SHOW_INFO_PAGE: case actions.SHOW_INFO_PAGE:
return extend(appState, { return extend(appState, {
currentView: { currentView: {
@ -128,6 +136,15 @@ function reduceApp (state, action) {
isLoading: false, isLoading: false,
}) })
case actions.NEW_ACCOUNT_SCREEN:
return extend(appState, {
currentView: {
name: 'new-account',
context: appState.currentView.context,
},
transForward: true,
})
case actions.SHOW_SEND_PAGE: case actions.SHOW_SEND_PAGE:
return extend(appState, { return extend(appState, {
currentView: { currentView: {
@ -369,6 +386,7 @@ function reduceApp (state, action) {
case actions.SHOW_LOADING: case actions.SHOW_LOADING:
return extend(appState, { return extend(appState, {
isLoading: true, isLoading: true,
loadingMessage: action.value,
}) })
case actions.HIDE_LOADING: case actions.HIDE_LOADING:
@ -446,7 +464,7 @@ function reduceApp (state, action) {
}, },
buyView: { buyView: {
subview: 'buyForm', subview: 'buyForm',
amount: '5.00', amount: '15.00',
buyAddress: action.value, buyAddress: action.value,
formView: { formView: {
coinbase: true, coinbase: true,

@ -26,7 +26,7 @@ UnlockScreen.prototype.render = function () {
const state = this.props const state = this.props
const warning = state.warning const warning = state.warning
return ( return (
h('.flex-column.hey-im-here', [ h('.flex-column', [
h('.unlock-screen.flex-column.flex-center.flex-grow', [ h('.unlock-screen.flex-column.flex-center.flex-grow', [
h(Mascot, { h(Mascot, {

@ -10,6 +10,7 @@ var cssFiles = {
'index.css': fs.readFileSync(path.join(__dirname, '/app/css/index.css'), 'utf8'), 'index.css': fs.readFileSync(path.join(__dirname, '/app/css/index.css'), 'utf8'),
'transitions.css': fs.readFileSync(path.join(__dirname, '/app/css/transitions.css'), 'utf8'), 'transitions.css': fs.readFileSync(path.join(__dirname, '/app/css/transitions.css'), 'utf8'),
'react-tooltip-component.css': fs.readFileSync(path.join(__dirname, '..', 'node_modules', 'react-tooltip-component', 'dist', 'react-tooltip-component.css'), 'utf8'), 'react-tooltip-component.css': fs.readFileSync(path.join(__dirname, '..', 'node_modules', 'react-tooltip-component', 'dist', 'react-tooltip-component.css'), 'utf8'),
'react-css': fs.readFileSync(path.join(__dirname, '..', 'node_modules', 'react-select', 'dist', 'react-select.css'), 'utf8'),
} }
function bundleCss () { function bundleCss () {

@ -5,7 +5,7 @@ module.exports = function (hash, network) {
case 1: // main net case 1: // main net
prefix = '' prefix = ''
break break
case 2: // morden test net case 3: // morden test net
prefix = 'testnet.' prefix = 'testnet.'
break break
default: default:

Loading…
Cancel
Save