aboutsummaryrefslogtreecommitdiffstats
path: root/app
diff options
context:
space:
mode:
authorkumavis <kumavis@users.noreply.github.com>2017-01-22 02:06:50 +0800
committerGitHub <noreply@github.com>2017-01-22 02:06:50 +0800
commit74dc20bdf179cbd8b25f6fae007553784466e3ff (patch)
tree4b28d3230bf38c68b68e40efcb5b1bf810eacf3e /app
parentbc1615f032186e88aebfa9dac38039dcff263162 (diff)
parent247f7aad5e358fae33616fd310e695e06673dd19 (diff)
downloadtangerine-wallet-browser-74dc20bdf179cbd8b25f6fae007553784466e3ff.tar.gz
tangerine-wallet-browser-74dc20bdf179cbd8b25f6fae007553784466e3ff.tar.zst
tangerine-wallet-browser-74dc20bdf179cbd8b25f6fae007553784466e3ff.zip
Merge branch 'dev' into obs-store2
Diffstat (limited to 'app')
-rw-r--r--app/manifest.json2
-rw-r--r--app/scripts/account-import-strategies/index.js45
-rw-r--r--app/scripts/background.js2
-rw-r--r--app/scripts/keyring-controller.js21
-rw-r--r--app/scripts/keyrings/hd.js2
-rw-r--r--app/scripts/keyrings/simple.js28
-rw-r--r--app/scripts/lib/config-manager.js2
-rw-r--r--app/scripts/lib/eth-store.js4
-rw-r--r--app/scripts/lib/tx-utils.js58
-rw-r--r--app/scripts/metamask-controller.js74
-rw-r--r--app/scripts/transaction-manager.js227
11 files changed, 329 insertions, 136 deletions
diff --git a/app/manifest.json b/app/manifest.json
index 95dcfc31a..c34b17e72 100644
--- a/app/manifest.json
+++ b/app/manifest.json
@@ -1,7 +1,7 @@
{
"name": "MetaMask",
"short_name": "Metamask",
- "version": "2.14.1",
+ "version": "3.1.1",
"manifest_version": 2,
"author": "https://metamask.io",
"description": "Ethereum Browser Extension",
diff --git a/app/scripts/account-import-strategies/index.js b/app/scripts/account-import-strategies/index.js
new file mode 100644
index 000000000..d5124eb7f
--- /dev/null
+++ b/app/scripts/account-import-strategies/index.js
@@ -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
diff --git a/app/scripts/background.js b/app/scripts/background.js
index 1f269da7b..f95e194dd 100644
--- a/app/scripts/background.js
+++ b/app/scripts/background.js
@@ -16,6 +16,7 @@ const firstTimeState = require('./first-time-state')
const STORAGE_KEY = 'metamask-config'
const METAMASK_DEBUG = 'GULP_METAMASK_DEBUG'
+
let popupIsOpen = false
// state persistence
@@ -135,6 +136,7 @@ function setupController (initState) {
// User Interface setup
//
+ updateBadge()
controller.txManager.on('updateBadge', updateBadge)
// plugin badge text
diff --git a/app/scripts/keyring-controller.js b/app/scripts/keyring-controller.js
index d4c0d863e..86c93f5a3 100644
--- a/app/scripts/keyring-controller.js
+++ b/app/scripts/keyring-controller.js
@@ -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,
@@ -173,7 +172,9 @@ module.exports = class KeyringController extends EventEmitter {
// Used when creating a first vault, to allow confirmation.
// Also used when revealing the seed words in the confirmation view.
placeSeedWords () {
- const firstKeyring = this.keyrings[0]
+ 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()
.then((serialized) => {
const seedWords = serialized.mnemonic
@@ -235,7 +236,10 @@ module.exports = class KeyringController extends EventEmitter {
addNewKeyring (type, opts) {
const Keyring = this.getKeyringClassForType(type)
const keyring = new Keyring(opts)
- return keyring.getAccounts()
+ return keyring.deserialize(opts)
+ .then(() => {
+ return keyring.getAccounts()
+ })
.then((accounts) => {
this.keyrings.push(keyring)
return this.setupAccounts(accounts)
@@ -317,13 +321,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 +402,7 @@ module.exports = class KeyringController extends EventEmitter {
}).then((rawSig) => {
cb(null, rawSig)
approvalCb(null, true)
+ messageManager.confirmMsg(msgId)
return rawSig
})
} catch (e) {
diff --git a/app/scripts/keyrings/hd.js b/app/scripts/keyrings/hd.js
index 80b713b58..1b9796e07 100644
--- a/app/scripts/keyrings/hd.js
+++ b/app/scripts/keyrings/hd.js
@@ -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))
diff --git a/app/scripts/keyrings/simple.js b/app/scripts/keyrings/simple.js
index 9717f1c45..46687fcaf 100644
--- a/app/scripts/keyrings/simple.js
+++ b/app/scripts/keyrings/simple.js
@@ -20,13 +20,19 @@ class SimpleKeyring extends EventEmitter {
}
deserialize (privateKeys = []) {
- this.wallets = privateKeys.map((privateKey) => {
- const stripped = ethUtil.stripHexPrefix(privateKey)
- const buffer = new Buffer(stripped, 'hex')
- const wallet = Wallet.fromPrivateKey(buffer)
- return wallet
+ return new Promise((resolve, reject) => {
+ try {
+ this.wallets = privateKeys.map((privateKey) => {
+ const stripped = ethUtil.stripHexPrefix(privateKey)
+ const buffer = new Buffer(stripped, 'hex')
+ const wallet = Wallet.fromPrivateKey(buffer)
+ return wallet
+ })
+ } catch (e) {
+ reject(e)
+ }
+ resolve()
})
- return Promise.resolve()
}
addAccounts (n = 1) {
@@ -35,12 +41,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 +60,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 +76,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
}
}
diff --git a/app/scripts/lib/config-manager.js b/app/scripts/lib/config-manager.js
index 6d7305377..daba8bc7b 100644
--- a/app/scripts/lib/config-manager.js
+++ b/app/scripts/lib/config-manager.js
@@ -281,7 +281,7 @@ ConfigManager.prototype.updateConversionRate = function () {
this.setConversionPrice(parsedResponse.ticker.price)
this.setConversionDate(parsedResponse.timestamp)
}).catch((err) => {
- console.error('Error in conversion.', err)
+ console.warn('MetaMask - Failed to query currency conversion.')
this.setConversionPrice(0)
this.setConversionDate('N/A')
})
diff --git a/app/scripts/lib/eth-store.js b/app/scripts/lib/eth-store.js
index a42b2417f..7e2caf884 100644
--- a/app/scripts/lib/eth-store.js
+++ b/app/scripts/lib/eth-store.js
@@ -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) {
diff --git a/app/scripts/lib/tx-utils.js b/app/scripts/lib/tx-utils.js
index d1fb98f42..5116cb93b 100644
--- a/app/scripts/lib/tx-utils.js
+++ b/app/scripts/lib/tx-utils.js
@@ -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
}
diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js
index e15844a56..2847873bd 100644
--- a/app/scripts/metamask-controller.js
+++ b/app/scripts/metamask-controller.js
@@ -15,6 +15,8 @@ const IdStoreMigrator = require('./lib/idStore-migrator')
const ObservableStore = require('./lib/observable/')
const HostStore = require('./lib/observable/host')
const synchronizeStore = require('./lib/observable/util/sync')
+const accountImporter = require('./account-import-strategies')
+
const version = require('../manifest.json').version
module.exports = class MetamaskController extends EventEmitter {
@@ -57,6 +59,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,
})
@@ -77,6 +80,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 () {
@@ -125,7 +129,22 @@ module.exports = class MetamaskController extends EventEmitter {
.then((newState) => { cb(null, newState) })
.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),
setSelectedAccount: nodeify(keyringController.setSelectedAccount).bind(keyringController),
saveAccountLabel: nodeify(keyringController.saveAccountLabel).bind(keyringController),
@@ -200,26 +219,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) => {
@@ -259,24 +259,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) {
diff --git a/app/scripts/transaction-manager.js b/app/scripts/transaction-manager.js
index 6becfa6d1..6d0121afd 100644
--- a/app/scripts/transaction-manager.js
+++ b/app/scripts/transaction-manager.js
@@ -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,43 @@ 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()
+ })
}
- // 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}) {
- // 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)
+ 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.setTxStatusSigned(txMeta.id)
+ cb(null, ethUtil.bufferToHex(ethTx.serialize()))
+ }).catch((err) => {
+ 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
}
+ // 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 +296,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: 'submitted'})
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 +334,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 +342,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.