Merge branch 'dev' into conversion-api-err

feature/default_network_editable
Dan Finlay 8 years ago committed by GitHub
commit c53932a19a
  1. 11
      CHANGELOG.md
  2. 2
      app/manifest.json
  3. 6
      app/scripts/background.js
  4. 14
      app/scripts/keyring-controller.js
  5. 2
      app/scripts/keyrings/hd.js
  6. 10
      app/scripts/keyrings/simple.js
  7. 4
      app/scripts/lib/eth-store.js
  8. 4
      app/scripts/lib/port-stream.js
  9. 58
      app/scripts/lib/tx-utils.js
  10. 55
      app/scripts/metamask-controller.js
  11. 226
      app/scripts/transaction-manager.js
  12. 12
      notices/notice_0.md
  13. 3
      package.json
  14. 2
      test/unit/explorer-link-test.js
  15. 27
      test/unit/keyrings/simple-test.js
  16. 18
      test/unit/metamask-controller-test.js
  17. 5
      test/unit/notice-controller-test.js
  18. 69
      test/unit/tx-manager-test.js
  19. 17
      ui/app/account-detail.js
  20. 9
      ui/app/actions.js
  21. 8
      ui/app/components/coinbase-form.js
  22. 12
      ui/app/components/pending-tx-details.js
  23. 39
      ui/app/components/transaction-list-item-icon.js
  24. 21
      ui/app/components/transaction-list-item.js
  25. 9
      ui/app/components/transaction-list.js
  26. 27
      ui/app/conf-tx.js
  27. 2
      ui/app/reducers/app.js
  28. 2
      ui/lib/explorer-link.js

@ -2,6 +2,15 @@
## Current Master
## 3.0.1 2017-1-17
- Fixed bug that prevented eth.sign from working.
## 3.0.0 2017-1-16
- Fix seed word account generation (https://medium.com/metamask/metamask-3-migration-guide-914b79533cdd#.t4i1qmmsz).
- Fix Bug where you see a empty transaction flash by on the confirm transaction view.
- Create visible difference in transaction history between a approved but not yet included in a block transaction and a transaction who has been confirmed.
- Fix memory leak in RPC Cache
- Override RPC commands eth_syncing and web3_clientVersion
- Remove certain non-essential permissions from certain builds.
@ -14,6 +23,8 @@
## 2.14.1 2016-12-20
- Update Coinbase info. and increase the buy amount to $15
- Fixed ropsten transaction links
- Temporarily disable extension reload detection causing infinite reload bug.
- Implemented basic checking for valid RPC URIs.

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

@ -22,12 +22,11 @@ const controller = new MetamaskController({
setData,
loadData,
})
const txManager = controller.txManager
function triggerUi () {
if (!popupIsOpen) notification.show()
}
// On first install, open a window to MetaMask website to how-it-works.
extension.runtime.onInstalled.addListener(function (details) {
if ((details.reason === 'install') && (!METAMASK_DEBUG)) {
extension.tabs.create({url: 'https://metamask.io/#how-it-works'})
@ -94,7 +93,8 @@ function setupControllerConnection (stream) {
// plugin badge text
//
txManager.on('updateBadge', updateBadge)
controller.txManager.on('updateBadge', updateBadge)
updateBadge()
function updateBadge () {
var label = ''

@ -95,7 +95,6 @@ module.exports = class KeyringController extends EventEmitter {
isInitialized: (!!wallet || !!vault),
isUnlocked: Boolean(this.password),
isDisclaimerConfirmed: this.configManager.getConfirmedDisclaimer(),
transactions: this.configManager.getTxList(),
unconfMsgs: messageManager.unconfirmedMsgs(),
messages: messageManager.getMsgList(),
selectedAccount: address,
@ -273,7 +272,7 @@ module.exports = class KeyringController extends EventEmitter {
setSelectedAccount (address) {
var addr = normalize(address)
this.configManager.setSelectedAccount(addr)
return Promise.resolve(addr)
return this.fullUpdate()
}
// Save Account Label
@ -317,13 +316,11 @@ module.exports = class KeyringController extends EventEmitter {
// This method signs tx and returns a promise for
// TX Manager to update the state after signing
signTransaction (ethTx, selectedAddress, txId) {
const address = normalize(selectedAddress)
return this.getKeyringForAccount(address)
signTransaction (ethTx, _fromAddress) {
const fromAddress = normalize(_fromAddress)
return this.getKeyringForAccount(fromAddress)
.then((keyring) => {
return keyring.signTransaction(address, ethTx)
}).then((tx) => {
return {tx, txId}
return keyring.signTransaction(fromAddress, ethTx)
})
}
// Add Unconfirmed Message
@ -400,6 +397,7 @@ module.exports = class KeyringController extends EventEmitter {
}).then((rawSig) => {
cb(null, rawSig)
approvalCb(null, true)
messageManager.confirmMsg(msgId)
return rawSig
})
} catch (e) {

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

@ -35,12 +35,12 @@ class SimpleKeyring extends EventEmitter {
newWallets.push(Wallet.generate())
}
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)
}
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.
@ -54,7 +54,7 @@ class SimpleKeyring extends EventEmitter {
// For eth_sign, we need to sign transactions:
signMessage (withAccount, data) {
const wallet = this._getWalletForAccount(withAccount)
const message = ethUtil.removeHexPrefix(data)
const message = ethUtil.stripHexPrefix(data)
var privKey = wallet.getPrivateKey()
var msgSig = ethUtil.ecsign(new Buffer(message, 'hex'), privKey)
var rawMsgSig = ethUtil.bufferToHex(sigUtil.concatSig(msgSig.v, msgSig.r, msgSig.s))
@ -70,7 +70,9 @@ class SimpleKeyring extends EventEmitter {
/* PRIVATE METHODS */
_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
}
}

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

@ -51,11 +51,11 @@ PortDuplexStream.prototype._write = function (msg, encoding, cb) {
// console.log('PortDuplexStream - sent message', msg)
this._port.postMessage(msg)
}
cb()
} catch (err) {
// console.error(err)
cb(new Error('PortDuplexStream - disconnected'))
return cb(new Error('PortDuplexStream - disconnected'))
}
cb()
}
// util

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

@ -45,6 +45,7 @@ module.exports = class MetamaskController extends EventEmitter {
getSelectedAccount: this.configManager.getSelectedAccount.bind(this.configManager),
getGasMultiplier: this.configManager.getGasMultiplier.bind(this.configManager),
getNetwork: this.getStateNetwork.bind(this),
signTransaction: this.keyringController.signTransaction.bind(this.keyringController),
provider: this.provider,
blockTracker: this.provider,
})
@ -65,6 +66,7 @@ module.exports = class MetamaskController extends EventEmitter {
this.ethStore.on('update', this.sendUpdate.bind(this))
this.keyringController.on('update', this.sendUpdate.bind(this))
this.txManager.on('update', this.sendUpdate.bind(this))
}
getState () {
@ -188,26 +190,7 @@ module.exports = class MetamaskController extends EventEmitter {
cb(null, result)
},
// tx signing
approveTransaction: this.newUnsignedTransaction.bind(this),
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)
})
},
processTransaction: (txParams, cb) => this.newUnapprovedTransaction(txParams, cb),
// msg signing
approveMessage: this.newUnsignedMessage.bind(this),
signMessage: (...args) => {
@ -256,24 +239,26 @@ module.exports = class MetamaskController extends EventEmitter {
return publicConfigStore
}
newUnsignedTransaction (txParams, onTxDoneCb) {
const txManager = this.txManager
const err = this.enforceTxValidations(txParams)
if (err) return onTxDoneCb(err)
txManager.addUnapprovedTransaction(txParams, onTxDoneCb, (err, txData) => {
if (err) return onTxDoneCb(err)
this.sendUpdate()
this.opts.showUnapprovedTx(txParams, txData, onTxDoneCb)
newUnapprovedTransaction (txParams, cb) {
const self = this
self.txManager.addUnapprovedTransaction(txParams, (err, txMeta) => {
if (err) return cb(err)
self.sendUpdate()
self.opts.showUnapprovedTx(txMeta)
// listen for tx completion (success, fail)
self.txManager.once(`${txMeta.id}:finished`, (status) => {
switch (status) {
case 'submitted':
return cb(null, txMeta.hash)
case 'rejected':
return cb(new Error('MetaMask Tx Signature: User denied transaction signature.'))
default:
return cb(new Error(`MetaMask Tx Signature: Unknown problem: ${JSON.stringify(txMeta.txParams)}`))
}
})
})
}
enforceTxValidations (txParams) {
if (('value' in txParams) && txParams.value.indexOf('-') === 0) {
const msg = `Invalid transaction value of ${txParams.value} not a positive number.`
return new Error(msg)
}
}
newUnsignedMessage (msgParams, cb) {
var state = this.keyringController.getState()
if (!state.isUnlocked) {

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

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

@ -89,13 +89,14 @@
"redux-logger": "^2.3.1",
"redux-thunk": "^1.0.2",
"sandwich-expando": "^1.0.5",
"semaphore": "^1.0.5",
"textarea-caret": "^3.0.1",
"three.js": "^0.73.2",
"through2": "^2.0.1",
"valid-url": "^1.0.9",
"vreme": "^3.0.2",
"web3": "0.17.0-beta",
"web3-provider-engine": "^8.2.0",
"web3-provider-engine": "^8.4.0",
"web3-stream-provider": "^2.0.6",
"xtend": "^4.0.1"
},

@ -4,7 +4,7 @@ var linkGen = require('../../ui/lib/explorer-link')
describe('explorer-link', 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')
})

@ -1,5 +1,6 @@
const assert = require('assert')
const extend = require('xtend')
const ethUtil = require('ethereumjs-util')
const SimpleKeyring = require('../../../app/scripts/keyrings/simple')
const TYPE_STR = 'Simple Key Pair'
@ -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('with no arguments', function() {
it('creates a single wallet', function() {
@ -72,14 +91,10 @@ describe('simple-keyring', function() {
it('calls getAddress on each wallet', function(done) {
// Push a mock wallet
const desiredOutput = 'foo'
const desiredOutput = '0x18a3462427bcc9133bb46e88bcbe39cd7ef0e761'
keyring.wallets.push({
getAddress() {
return {
toString() {
return desiredOutput
}
}
return ethUtil.toBuffer(desiredOutput)
}
})

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

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

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

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

@ -263,9 +263,7 @@ function showInfoPage () {
}
function setSelectedAccount (address) {
return (dispatch) => {
background.setSelectedAccount(address)
}
return callBackgroundThenUpdate(background.setSelectedAccount, address)
}
function setCurrentFiat (fiat) {
@ -457,15 +455,16 @@ function lockMetamask () {
function showAccountDetail (address) {
return (dispatch) => {
dispatch(actions.showLoadingIndication())
background.setSelectedAccount(address, (err, address) => {
background.setSelectedAccount(address, (err, newState) => {
dispatch(actions.hideLoadingIndication())
if (err) {
return dispatch(actions.displayWarning(err.message))
}
dispatch(actions.updateMetamaskState(newState))
dispatch({
type: actions.SHOW_ACCOUNT_DETAIL,
value: address,
value: newState.selectedAccount,
})
})
}

@ -72,7 +72,7 @@ CoinbaseForm.prototype.render = function () {
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
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) {
amount = parseFloat(amount)
if (amount) {
if (amount <= 5 && amount > 0) {
if (amount <= 15 && amount > 0) {
return {
valid: true,
}
} else if (amount > 5) {
} else if (amount > 15) {
return {
valid: false,
message: 'The amount can not be greater then $5',
message: 'The amount can not be greater then $15',
}
} else {
return {

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

@ -13,13 +13,40 @@ function TransactionIcon () {
TransactionIcon.prototype.render = function () {
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') {
return h('i.fa.fa-exclamation-triangle.fa-lg.warning', {
style: {
width: '24px',
},
})
case 'rejected':
return h('i.fa.fa-exclamation-triangle.fa-lg.warning', {
style: {
width: '24px',
},
})
case 'failed':
return h('i.fa.fa-exclamation-triangle.fa-lg.error', {
style: {
width: '24px',
},
})
case 'signed':
return h('i.fa.fa-ellipsis-h', {
style: {
fontSize: '27px',
},
})
}
if (isMsg) {

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

@ -13,12 +13,13 @@ function TransactionList () {
}
TransactionList.prototype.render = function () {
const { txsToRender, network, unconfMsgs } = this.props
const { transactions, network, unconfMsgs } = this.props
var shapeShiftTxList
if (network === '1') {
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)
return (
@ -55,8 +56,8 @@ TransactionList.prototype.render = function () {
},
}, [
transactions.length
? transactions.map((transaction, i) => {
txsToRender.length
? txsToRender.map((transaction, i) => {
let key
switch (transaction.key) {
case 'shapeshift':

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

@ -446,7 +446,7 @@ function reduceApp (state, action) {
},
buyView: {
subview: 'buyForm',
amount: '5.00',
amount: '15.00',
buyAddress: action.value,
formView: {
coinbase: true,

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

Loading…
Cancel
Save