diff options
author | kumavis <kumavis@users.noreply.github.com> | 2017-01-22 02:06:50 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-01-22 02:06:50 +0800 |
commit | 74dc20bdf179cbd8b25f6fae007553784466e3ff (patch) | |
tree | 4b28d3230bf38c68b68e40efcb5b1bf810eacf3e /app | |
parent | bc1615f032186e88aebfa9dac38039dcff263162 (diff) | |
parent | 247f7aad5e358fae33616fd310e695e06673dd19 (diff) | |
download | tangerine-wallet-browser-74dc20bdf179cbd8b25f6fae007553784466e3ff.tar tangerine-wallet-browser-74dc20bdf179cbd8b25f6fae007553784466e3ff.tar.gz tangerine-wallet-browser-74dc20bdf179cbd8b25f6fae007553784466e3ff.tar.bz2 tangerine-wallet-browser-74dc20bdf179cbd8b25f6fae007553784466e3ff.tar.lz tangerine-wallet-browser-74dc20bdf179cbd8b25f6fae007553784466e3ff.tar.xz 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.json | 2 | ||||
-rw-r--r-- | app/scripts/account-import-strategies/index.js | 45 | ||||
-rw-r--r-- | app/scripts/background.js | 2 | ||||
-rw-r--r-- | app/scripts/keyring-controller.js | 21 | ||||
-rw-r--r-- | app/scripts/keyrings/hd.js | 2 | ||||
-rw-r--r-- | app/scripts/keyrings/simple.js | 28 | ||||
-rw-r--r-- | app/scripts/lib/config-manager.js | 2 | ||||
-rw-r--r-- | app/scripts/lib/eth-store.js | 4 | ||||
-rw-r--r-- | app/scripts/lib/tx-utils.js | 58 | ||||
-rw-r--r-- | app/scripts/metamask-controller.js | 74 | ||||
-rw-r--r-- | app/scripts/transaction-manager.js | 227 |
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. |