mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 09:52:26 +01:00
Create a TxManager
This commit is contained in:
parent
9e3fa3cfba
commit
090935f90a
@ -23,7 +23,7 @@ const controller = new MetamaskController({
|
|||||||
loadData,
|
loadData,
|
||||||
})
|
})
|
||||||
const keyringController = controller.keyringController
|
const keyringController = controller.keyringController
|
||||||
|
const txManager = controller.txManager
|
||||||
function triggerUi () {
|
function triggerUi () {
|
||||||
if (!popupIsOpen) notification.show()
|
if (!popupIsOpen) notification.show()
|
||||||
}
|
}
|
||||||
@ -97,12 +97,11 @@ function setupControllerConnection (stream) {
|
|||||||
// plugin badge text
|
// plugin badge text
|
||||||
//
|
//
|
||||||
|
|
||||||
keyringController.on('update', updateBadge)
|
txManager.on('update', updateBadge)
|
||||||
|
|
||||||
function updateBadge () {
|
function updateBadge () {
|
||||||
var label = ''
|
var label = ''
|
||||||
var unconfTxs = controller.configManager.unconfirmedTxs()
|
var unconfTxLen = controller.txManager.unConftxCount
|
||||||
var unconfTxLen = Object.keys(unconfTxs).length
|
|
||||||
var unconfMsgs = messageManager.unconfirmedMsgs()
|
var unconfMsgs = messageManager.unconfirmedMsgs()
|
||||||
var unconfMsgLen = Object.keys(unconfMsgs).length
|
var unconfMsgLen = Object.keys(unconfMsgs).length
|
||||||
var count = unconfTxLen + unconfMsgLen
|
var count = unconfTxLen + unconfMsgLen
|
||||||
@ -113,6 +112,25 @@ function updateBadge () {
|
|||||||
extension.browserAction.setBadgeBackgroundColor({ color: '#506F8B' })
|
extension.browserAction.setBadgeBackgroundColor({ color: '#506F8B' })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// txManger :: tx approvals and rejection cb's
|
||||||
|
|
||||||
|
txManager.on('signed', function (txId) {
|
||||||
|
var approvalCb = this._unconfTxCbs[txId]
|
||||||
|
|
||||||
|
approvalCb(null, true)
|
||||||
|
// clean up
|
||||||
|
delete this._unconfTxCbs[txId]
|
||||||
|
})
|
||||||
|
|
||||||
|
txManager.on('rejected', function (txId) {
|
||||||
|
var approvalCb = this._unconfTxCbs[txId]
|
||||||
|
approvalCb(null, false)
|
||||||
|
// clean up
|
||||||
|
delete this._unconfTxCbs[txId]
|
||||||
|
})
|
||||||
|
|
||||||
|
// data :: setters/getters
|
||||||
|
|
||||||
function loadData () {
|
function loadData () {
|
||||||
var oldData = getOldStyleData()
|
var oldData = getOldStyleData()
|
||||||
var newData
|
var newData
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
const async = require('async')
|
|
||||||
const ethUtil = require('ethereumjs-util')
|
const ethUtil = require('ethereumjs-util')
|
||||||
const EthQuery = require('eth-query')
|
|
||||||
const bip39 = require('bip39')
|
const bip39 = require('bip39')
|
||||||
const Transaction = require('ethereumjs-tx')
|
const Transaction = require('ethereumjs-tx')
|
||||||
const EventEmitter = require('events').EventEmitter
|
const EventEmitter = require('events').EventEmitter
|
||||||
@ -36,7 +34,7 @@ module.exports = class KeyringController extends EventEmitter {
|
|||||||
this.ethStore = opts.ethStore
|
this.ethStore = opts.ethStore
|
||||||
this.encryptor = encryptor
|
this.encryptor = encryptor
|
||||||
this.keyringTypes = keyringTypes
|
this.keyringTypes = keyringTypes
|
||||||
|
this.txManager = opts.txManager
|
||||||
this.keyrings = []
|
this.keyrings = []
|
||||||
this.identities = {} // Essentially a name hash
|
this.identities = {} // Essentially a name hash
|
||||||
|
|
||||||
@ -73,7 +71,7 @@ module.exports = class KeyringController extends EventEmitter {
|
|||||||
// or accept a state-resolving promise to consume their results.
|
// or accept a state-resolving promise to consume their results.
|
||||||
//
|
//
|
||||||
// Not all methods end with this, that might be a nice refactor.
|
// Not all methods end with this, that might be a nice refactor.
|
||||||
fullUpdate() {
|
fullUpdate () {
|
||||||
this.emit('update')
|
this.emit('update')
|
||||||
return Promise.resolve(this.getState())
|
return Promise.resolve(this.getState())
|
||||||
}
|
}
|
||||||
@ -102,8 +100,8 @@ module.exports = class KeyringController extends EventEmitter {
|
|||||||
isInitialized: (!!wallet || !!vault),
|
isInitialized: (!!wallet || !!vault),
|
||||||
isUnlocked: Boolean(this.password),
|
isUnlocked: Boolean(this.password),
|
||||||
isDisclaimerConfirmed: this.configManager.getConfirmedDisclaimer(), // AUDIT this.configManager.getConfirmedDisclaimer(),
|
isDisclaimerConfirmed: this.configManager.getConfirmedDisclaimer(), // AUDIT this.configManager.getConfirmedDisclaimer(),
|
||||||
unconfTxs: this.configManager.unconfirmedTxs(),
|
transactions: this.txManager.getTxList(),
|
||||||
transactions: this.configManager.getTxList(),
|
unconfTxs: this.txManager.getUnapprovedTxList(),
|
||||||
unconfMsgs: messageManager.unconfirmedMsgs(),
|
unconfMsgs: messageManager.unconfirmedMsgs(),
|
||||||
messages: messageManager.getMsgList(),
|
messages: messageManager.getMsgList(),
|
||||||
selectedAccount: address,
|
selectedAccount: address,
|
||||||
@ -341,7 +339,7 @@ module.exports = class KeyringController extends EventEmitter {
|
|||||||
// Caches the requesting Dapp's callback, `onTxDoneCb`, for resolution later.
|
// Caches the requesting Dapp's callback, `onTxDoneCb`, for resolution later.
|
||||||
addUnconfirmedTransaction (txParams, onTxDoneCb, cb) {
|
addUnconfirmedTransaction (txParams, onTxDoneCb, cb) {
|
||||||
const configManager = this.configManager
|
const configManager = this.configManager
|
||||||
|
const txManager = this.txManager
|
||||||
// create txData obj with parameters and meta data
|
// create txData obj with parameters and meta data
|
||||||
var time = (new Date()).getTime()
|
var time = (new Date()).getTime()
|
||||||
var txId = createId()
|
var txId = createId()
|
||||||
@ -351,95 +349,26 @@ module.exports = class KeyringController extends EventEmitter {
|
|||||||
id: txId,
|
id: txId,
|
||||||
txParams: txParams,
|
txParams: txParams,
|
||||||
time: time,
|
time: time,
|
||||||
status: 'unconfirmed',
|
status: 'unapproved',
|
||||||
gasMultiplier: configManager.getGasMultiplier() || 1,
|
gasMultiplier: configManager.getGasMultiplier() || 1,
|
||||||
metamaskNetworkId: this.getNetwork(),
|
metamaskNetworkId: this.getNetwork(),
|
||||||
}
|
}
|
||||||
|
|
||||||
// keep the onTxDoneCb around for after approval/denial (requires user interaction)
|
// keep the onTxDoneCb around for after approval/denial (requires user interaction)
|
||||||
// This onTxDoneCb fires completion to the Dapp's write operation.
|
// This onTxDoneCb fires completion to the Dapp's write operation.
|
||||||
this._unconfTxCbs[txId] = onTxDoneCb
|
txManager.txProviderUtils.analyzeGasUsage(txData, this.txDidComplete.bind(this, txData, onTxDoneCb, cb))
|
||||||
|
|
||||||
var provider = this.ethStore._query.currentProvider
|
|
||||||
var query = new EthQuery(provider)
|
|
||||||
|
|
||||||
// calculate metadata for tx
|
// calculate metadata for tx
|
||||||
this.analyzeTxGasUsage(query, txData, this.txDidComplete.bind(this, txData, cb))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
estimateTxGas (query, txData, blockGasLimitHex, cb) {
|
txDidComplete (txData, onTxDoneCb, cb, err) {
|
||||||
const txParams = txData.txParams
|
|
||||||
// check if gasLimit is already specified
|
|
||||||
txData.gasLimitSpecified = Boolean(txParams.gas)
|
|
||||||
// if not, fallback to block gasLimit
|
|
||||||
if (!txData.gasLimitSpecified) {
|
|
||||||
txParams.gas = blockGasLimitHex
|
|
||||||
}
|
|
||||||
// run tx, see if it will OOG
|
|
||||||
query.estimateGas(txParams, cb)
|
|
||||||
}
|
|
||||||
|
|
||||||
checkForTxGasError (txData, estimatedGasHex, cb) {
|
|
||||||
txData.estimatedGas = estimatedGasHex
|
|
||||||
// all gas used - must be an error
|
|
||||||
if (estimatedGasHex === txData.txParams.gas) {
|
|
||||||
txData.simulationFails = true
|
|
||||||
}
|
|
||||||
cb()
|
|
||||||
}
|
|
||||||
|
|
||||||
setTxGas (txData, blockGasLimitHex, cb) {
|
|
||||||
const txParams = txData.txParams
|
|
||||||
// if OOG, nothing more to do
|
|
||||||
if (txData.simulationFails) {
|
|
||||||
cb()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// if gasLimit was specified and doesnt OOG,
|
|
||||||
// use original specified amount
|
|
||||||
if (txData.gasLimitSpecified) {
|
|
||||||
txData.estimatedGas = txParams.gas
|
|
||||||
cb()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// if gasLimit not originally specified,
|
|
||||||
// try adding an additional gas buffer to our estimation for safety
|
|
||||||
const estimatedGasBn = new BN(ethUtil.stripHexPrefix(txData.estimatedGas), 16)
|
|
||||||
const blockGasLimitBn = new BN(ethUtil.stripHexPrefix(blockGasLimitHex), 16)
|
|
||||||
const estimationWithBuffer = new BN(this.addGasBuffer(estimatedGasBn), 16)
|
|
||||||
// added gas buffer is too high
|
|
||||||
if (estimationWithBuffer.gt(blockGasLimitBn)) {
|
|
||||||
txParams.gas = txData.estimatedGas
|
|
||||||
// added gas buffer is safe
|
|
||||||
} else {
|
|
||||||
const gasWithBufferHex = ethUtil.intToHex(estimationWithBuffer)
|
|
||||||
txParams.gas = gasWithBufferHex
|
|
||||||
}
|
|
||||||
cb()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
txDidComplete (txData, cb, err) {
|
|
||||||
if (err) return cb(err)
|
if (err) return cb(err)
|
||||||
const configManager = this.configManager
|
const txManager = this.txManager
|
||||||
configManager.addTx(txData)
|
txManager.addTx(txData, onTxDoneCb)
|
||||||
// signal update
|
// signal update
|
||||||
this.emit('update')
|
this.emit('update')
|
||||||
// signal completion of add tx
|
// signal completion of add tx
|
||||||
cb(null, txData)
|
cb(null, txData)
|
||||||
}
|
}
|
||||||
|
|
||||||
analyzeTxGasUsage (query, txData, cb) {
|
|
||||||
query.getBlockByNumber('latest', true, (err, block) => {
|
|
||||||
if (err) return cb(err)
|
|
||||||
async.waterfall([
|
|
||||||
this.estimateTxGas.bind(this, query, txData, block.gasLimit),
|
|
||||||
this.checkForTxGasError.bind(this, txData),
|
|
||||||
this.setTxGas.bind(this, txData, block.gasLimit),
|
|
||||||
], cb)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cancel Transaction
|
// Cancel Transaction
|
||||||
// @string txId
|
// @string txId
|
||||||
// @function cb
|
// @function cb
|
||||||
@ -448,14 +377,8 @@ module.exports = class KeyringController extends EventEmitter {
|
|||||||
//
|
//
|
||||||
// Forgets any tx matching `txId`.
|
// Forgets any tx matching `txId`.
|
||||||
cancelTransaction (txId, cb) {
|
cancelTransaction (txId, cb) {
|
||||||
const configManager = this.configManager
|
const txManager = this.txManager
|
||||||
var approvalCb = this._unconfTxCbs[txId] || noop
|
txManager.setTxStatusRejected(txId)
|
||||||
|
|
||||||
// reject tx
|
|
||||||
approvalCb(null, false)
|
|
||||||
// clean up
|
|
||||||
configManager.rejectTx(txId)
|
|
||||||
delete this._unconfTxCbs[txId]
|
|
||||||
|
|
||||||
if (cb && typeof cb === 'function') {
|
if (cb && typeof cb === 'function') {
|
||||||
cb()
|
cb()
|
||||||
@ -473,16 +396,10 @@ module.exports = class KeyringController extends EventEmitter {
|
|||||||
//
|
//
|
||||||
// Calls back the cached Dapp's confirmation callback, also.
|
// Calls back the cached Dapp's confirmation callback, also.
|
||||||
approveTransaction (txId, cb) {
|
approveTransaction (txId, cb) {
|
||||||
const configManager = this.configManager
|
const txManager = this.txManager
|
||||||
var approvalCb = this._unconfTxCbs[txId] || noop
|
txManager.setTxStatusSigned(txId)
|
||||||
|
|
||||||
// accept tx
|
|
||||||
cb()
|
|
||||||
approvalCb(null, true)
|
|
||||||
// clean up
|
|
||||||
configManager.confirmTx(txId)
|
|
||||||
delete this._unconfTxCbs[txId]
|
|
||||||
this.emit('update')
|
this.emit('update')
|
||||||
|
cb()
|
||||||
}
|
}
|
||||||
|
|
||||||
signTransaction (txParams, cb) {
|
signTransaction (txParams, cb) {
|
||||||
@ -510,9 +427,9 @@ module.exports = class KeyringController extends EventEmitter {
|
|||||||
.then((tx) => {
|
.then((tx) => {
|
||||||
// Add the tx hash to the persisted meta-tx object
|
// Add the tx hash to the persisted meta-tx object
|
||||||
var txHash = ethUtil.bufferToHex(tx.hash())
|
var txHash = ethUtil.bufferToHex(tx.hash())
|
||||||
var metaTx = this.configManager.getTx(txParams.metamaskId)
|
var metaTx = this.txManager.getTx(txParams.metamaskId)
|
||||||
metaTx.hash = txHash
|
metaTx.hash = txHash
|
||||||
this.configManager.updateTx(metaTx)
|
this.txManager.updateTx(metaTx)
|
||||||
|
|
||||||
// return raw serialized tx
|
// return raw serialized tx
|
||||||
var rawTx = ethUtil.bufferToHex(tx.serialize())
|
var rawTx = ethUtil.bufferToHex(tx.serialize())
|
||||||
@ -586,7 +503,6 @@ module.exports = class KeyringController extends EventEmitter {
|
|||||||
// Attempts to sign the provided @object msgParams.
|
// Attempts to sign the provided @object msgParams.
|
||||||
signMessage (msgParams, cb) {
|
signMessage (msgParams, cb) {
|
||||||
try {
|
try {
|
||||||
|
|
||||||
const msgId = msgParams.metamaskId
|
const msgId = msgParams.metamaskId
|
||||||
delete msgParams.metamaskId
|
delete msgParams.metamaskId
|
||||||
const approvalCb = this._unconfMsgCbs[msgId] || noop
|
const approvalCb = this._unconfMsgCbs[msgId] || noop
|
||||||
|
@ -209,61 +209,12 @@ ConfigManager.prototype.getTxList = function () {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ConfigManager.prototype.unconfirmedTxs = function () {
|
ConfigManager.prototype.setTxList = function (txList) {
|
||||||
var transactions = this.getTxList()
|
|
||||||
return transactions.filter(tx => tx.status === 'unconfirmed')
|
|
||||||
.reduce((result, tx) => { result[tx.id] = tx; return result }, {})
|
|
||||||
}
|
|
||||||
|
|
||||||
ConfigManager.prototype._saveTxList = function (txList) {
|
|
||||||
var data = this.migrator.getData()
|
var data = this.migrator.getData()
|
||||||
data.transactions = txList
|
data.transactions = txList
|
||||||
this.setData(data)
|
this.setData(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
ConfigManager.prototype.addTx = function (tx) {
|
|
||||||
var transactions = this.getTxList()
|
|
||||||
while (transactions.length > this.txLimit - 1) {
|
|
||||||
transactions.shift()
|
|
||||||
}
|
|
||||||
transactions.push(tx)
|
|
||||||
this._saveTxList(transactions)
|
|
||||||
}
|
|
||||||
|
|
||||||
ConfigManager.prototype.getTx = function (txId) {
|
|
||||||
var transactions = this.getTxList()
|
|
||||||
var matching = transactions.filter(tx => tx.id === txId)
|
|
||||||
return matching.length > 0 ? matching[0] : null
|
|
||||||
}
|
|
||||||
|
|
||||||
ConfigManager.prototype.confirmTx = function (txId) {
|
|
||||||
this._setTxStatus(txId, 'confirmed')
|
|
||||||
}
|
|
||||||
|
|
||||||
ConfigManager.prototype.rejectTx = function (txId) {
|
|
||||||
this._setTxStatus(txId, 'rejected')
|
|
||||||
}
|
|
||||||
|
|
||||||
ConfigManager.prototype._setTxStatus = function (txId, status) {
|
|
||||||
var tx = this.getTx(txId)
|
|
||||||
tx.status = status
|
|
||||||
this.updateTx(tx)
|
|
||||||
}
|
|
||||||
|
|
||||||
ConfigManager.prototype.updateTx = function (tx) {
|
|
||||||
var transactions = this.getTxList()
|
|
||||||
var found, index
|
|
||||||
transactions.forEach((otherTx, i) => {
|
|
||||||
if (otherTx.id === tx.id) {
|
|
||||||
found = true
|
|
||||||
index = i
|
|
||||||
}
|
|
||||||
})
|
|
||||||
if (found) {
|
|
||||||
transactions[index] = tx
|
|
||||||
}
|
|
||||||
this._saveTxList(transactions)
|
|
||||||
}
|
|
||||||
|
|
||||||
// wallet nickname methods
|
// wallet nickname methods
|
||||||
|
|
||||||
|
@ -13,6 +13,8 @@ const autoFaucet = require('./auto-faucet')
|
|||||||
const messageManager = require('./message-manager')
|
const messageManager = require('./message-manager')
|
||||||
const DEFAULT_RPC = 'https://testrpc.metamask.io/'
|
const DEFAULT_RPC = 'https://testrpc.metamask.io/'
|
||||||
const IdManagement = require('./id-management')
|
const IdManagement = require('./id-management')
|
||||||
|
const TxManager = require('../transaction-manager')
|
||||||
|
|
||||||
|
|
||||||
module.exports = IdentityStore
|
module.exports = IdentityStore
|
||||||
|
|
||||||
@ -36,6 +38,11 @@ function IdentityStore (opts = {}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// not part of serilized metamask state - only kept in memory
|
// not part of serilized metamask state - only kept in memory
|
||||||
|
this.txManager = new TxManager({
|
||||||
|
TxListFromStore: opts.configManager.getTxList(),
|
||||||
|
setTxList: opts.configManager.setTxList.bind(opts.configManager),
|
||||||
|
txLimit: 40,
|
||||||
|
})
|
||||||
this._unconfTxCbs = {}
|
this._unconfTxCbs = {}
|
||||||
this._unconfMsgCbs = {}
|
this._unconfMsgCbs = {}
|
||||||
}
|
}
|
||||||
@ -87,6 +94,7 @@ IdentityStore.prototype.recoverFromSeed = function (password, seed, cb) {
|
|||||||
|
|
||||||
IdentityStore.prototype.setStore = function (store) {
|
IdentityStore.prototype.setStore = function (store) {
|
||||||
this._ethStore = store
|
this._ethStore = store
|
||||||
|
this.txManager.setProvider(this._ethStore._query.currentProvider)
|
||||||
}
|
}
|
||||||
|
|
||||||
IdentityStore.prototype.clearSeedWordCache = function (cb) {
|
IdentityStore.prototype.clearSeedWordCache = function (cb) {
|
||||||
@ -97,14 +105,15 @@ IdentityStore.prototype.clearSeedWordCache = function (cb) {
|
|||||||
|
|
||||||
IdentityStore.prototype.getState = function () {
|
IdentityStore.prototype.getState = function () {
|
||||||
const configManager = this.configManager
|
const configManager = this.configManager
|
||||||
|
const TxManager = this.txManager
|
||||||
var seedWords = this.getSeedIfUnlocked()
|
var seedWords = this.getSeedIfUnlocked()
|
||||||
return clone(extend(this._currentState, {
|
return clone(extend(this._currentState, {
|
||||||
isInitialized: !!configManager.getWallet() && !seedWords,
|
isInitialized: !!configManager.getWallet() && !seedWords,
|
||||||
isUnlocked: this._isUnlocked(),
|
isUnlocked: this._isUnlocked(),
|
||||||
seedWords: seedWords,
|
seedWords: seedWords,
|
||||||
isDisclaimerConfirmed: configManager.getConfirmedDisclaimer(),
|
isDisclaimerConfirmed: configManager.getConfirmedDisclaimer(),
|
||||||
unconfTxs: configManager.unconfirmedTxs(),
|
unconfTxs: TxManager.getUnapprovedTxList(),
|
||||||
transactions: configManager.getTxList(),
|
transactions: TxManager.getTxList(),
|
||||||
unconfMsgs: messageManager.unconfirmedMsgs(),
|
unconfMsgs: messageManager.unconfirmedMsgs(),
|
||||||
messages: messageManager.getMsgList(),
|
messages: messageManager.getMsgList(),
|
||||||
selectedAddress: configManager.getSelectedAccount(),
|
selectedAddress: configManager.getSelectedAccount(),
|
||||||
|
106
app/scripts/lib/provider-utils.js
Normal file
106
app/scripts/lib/provider-utils.js
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
const async = require('async')
|
||||||
|
const EthQuery = require('eth-query')
|
||||||
|
const ethUtil = require('ethereumjs-util')
|
||||||
|
const BN = ethUtil.BN
|
||||||
|
const ethBinToOps = require('eth-bin-to-ops')
|
||||||
|
|
||||||
|
module.exports = class txProviderUtils {
|
||||||
|
constructor (provider) {
|
||||||
|
this.provider = provider
|
||||||
|
this.query = new EthQuery(provider)
|
||||||
|
}
|
||||||
|
analyzeGasUsage (txData, cb) {
|
||||||
|
var self = this
|
||||||
|
this.query.getBlockByNumber('latest', true, (err, block) => {
|
||||||
|
if (err) return cb(err)
|
||||||
|
async.waterfall([
|
||||||
|
self.estimateTxGas.bind(self, txData, block.gasLimit),
|
||||||
|
self.checkForTxGasError.bind(self, txData),
|
||||||
|
self.setTxGas.bind(self, txData, block.gasLimit),
|
||||||
|
], cb)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// perform static analyis on the target contract code
|
||||||
|
analyzeForDelegateCall (txParams, cb) {
|
||||||
|
if (txParams.to) {
|
||||||
|
this.query.getCode(txParams.to, function (err, result) {
|
||||||
|
if (err) return cb(err)
|
||||||
|
|
||||||
|
var code = ethUtil.toBuffer(result)
|
||||||
|
if (code !== '0x') {
|
||||||
|
var ops = ethBinToOps(code)
|
||||||
|
var containsDelegateCall = ops.some((op) => op.name === 'DELEGATECALL')
|
||||||
|
cb(containsDelegateCall)
|
||||||
|
} else {
|
||||||
|
cb()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
cb()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
estimateTxGas (txData, blockGasLimitHex, cb) {
|
||||||
|
const txParams = txData.txParams
|
||||||
|
// check if gasLimit is already specified
|
||||||
|
txData.gasLimitSpecified = Boolean(txParams.gas)
|
||||||
|
// if not, fallback to block gasLimit
|
||||||
|
if (!txData.gasLimitSpecified) {
|
||||||
|
txParams.gas = blockGasLimitHex
|
||||||
|
}
|
||||||
|
// run tx, see if it will OOG
|
||||||
|
this.query.estimateGas(txParams, cb)
|
||||||
|
}
|
||||||
|
|
||||||
|
checkForTxGasError (txData, estimatedGasHex, cb) {
|
||||||
|
txData.estimatedGas = estimatedGasHex
|
||||||
|
// all gas used - must be an error
|
||||||
|
if (estimatedGasHex === txData.txParams.gas) {
|
||||||
|
txData.simulationFails = true
|
||||||
|
}
|
||||||
|
cb()
|
||||||
|
}
|
||||||
|
|
||||||
|
handleFork (block) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
setTxGas (txData, blockGasLimitHex, cb) {
|
||||||
|
const txParams = txData.txParams
|
||||||
|
// if OOG, nothing more to do
|
||||||
|
if (txData.simulationFails) {
|
||||||
|
cb()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// if gasLimit was specified and doesnt OOG,
|
||||||
|
// use original specified amount
|
||||||
|
if (txData.gasLimitSpecified) {
|
||||||
|
txData.estimatedGas = txParams.gas
|
||||||
|
cb()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// if gasLimit not originally specified,
|
||||||
|
// try adding an additional gas buffer to our estimation for safety
|
||||||
|
const estimatedGasBn = new BN(ethUtil.stripHexPrefix(txData.estimatedGas), 16)
|
||||||
|
const blockGasLimitBn = new BN(ethUtil.stripHexPrefix(blockGasLimitHex), 16)
|
||||||
|
const estimationWithBuffer = new BN(this.addGasBuffer(estimatedGasBn), 16)
|
||||||
|
// added gas buffer is too high
|
||||||
|
if (estimationWithBuffer.gt(blockGasLimitBn)) {
|
||||||
|
txParams.gas = txData.estimatedGas
|
||||||
|
// added gas buffer is safe
|
||||||
|
} else {
|
||||||
|
const gasWithBufferHex = ethUtil.intToHex(estimationWithBuffer)
|
||||||
|
txParams.gas = gasWithBufferHex
|
||||||
|
}
|
||||||
|
cb()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
addGasBuffer (gas) {
|
||||||
|
const gasBuffer = new BN('100000', 10)
|
||||||
|
const bnGas = new BN(ethUtil.stripHexPrefix(gas), 16)
|
||||||
|
const correct = bnGas.add(gasBuffer)
|
||||||
|
return ethUtil.addHexPrefix(correct.toString(16))
|
||||||
|
}
|
||||||
|
}
|
@ -3,6 +3,7 @@ const EthStore = require('eth-store')
|
|||||||
const MetaMaskProvider = require('web3-provider-engine/zero.js')
|
const MetaMaskProvider = require('web3-provider-engine/zero.js')
|
||||||
const KeyringController = require('./keyring-controller')
|
const KeyringController = require('./keyring-controller')
|
||||||
const messageManager = require('./lib/message-manager')
|
const messageManager = require('./lib/message-manager')
|
||||||
|
const TxManager = require('./transaction-manager')
|
||||||
const HostStore = require('./lib/remote-store.js').HostStore
|
const HostStore = require('./lib/remote-store.js').HostStore
|
||||||
const Web3 = require('web3')
|
const Web3 = require('web3')
|
||||||
const ConfigManager = require('./lib/config-manager')
|
const ConfigManager = require('./lib/config-manager')
|
||||||
@ -18,13 +19,21 @@ module.exports = class MetamaskController {
|
|||||||
this.opts = opts
|
this.opts = opts
|
||||||
this.listeners = []
|
this.listeners = []
|
||||||
this.configManager = new ConfigManager(opts)
|
this.configManager = new ConfigManager(opts)
|
||||||
|
this.txManager = new TxManager({
|
||||||
|
TxListFromStore: this.configManager.getTxList(),
|
||||||
|
txLimit: this.configManager.txLimit,
|
||||||
|
setTxList: this.configManager.setTxList.bind(this.configManager),
|
||||||
|
})
|
||||||
|
|
||||||
this.keyringController = new KeyringController({
|
this.keyringController = new KeyringController({
|
||||||
configManager: this.configManager,
|
configManager: this.configManager,
|
||||||
|
txManager: this.txManager,
|
||||||
getNetwork: this.getStateNetwork.bind(this),
|
getNetwork: this.getStateNetwork.bind(this),
|
||||||
})
|
})
|
||||||
this.provider = this.initializeProvider(opts)
|
this.provider = this.initializeProvider(opts)
|
||||||
this.ethStore = new EthStore(this.provider)
|
this.ethStore = new EthStore(this.provider)
|
||||||
this.keyringController.setStore(this.ethStore)
|
this.keyringController.setStore(this.ethStore)
|
||||||
|
this.txManager.setProvider(this.provider)
|
||||||
this.getNetwork()
|
this.getNetwork()
|
||||||
this.messageManager = messageManager
|
this.messageManager = messageManager
|
||||||
this.publicConfigStore = this.initPublicConfigStore()
|
this.publicConfigStore = this.initPublicConfigStore()
|
||||||
@ -49,7 +58,7 @@ module.exports = class MetamaskController {
|
|||||||
|
|
||||||
getApi () {
|
getApi () {
|
||||||
const keyringController = this.keyringController
|
const keyringController = this.keyringController
|
||||||
|
const txManager = this.txManager
|
||||||
return {
|
return {
|
||||||
getState: (cb) => { cb(null, this.getState()) },
|
getState: (cb) => { cb(null, this.getState()) },
|
||||||
setRpcTarget: this.setRpcTarget.bind(this),
|
setRpcTarget: this.setRpcTarget.bind(this),
|
||||||
@ -81,6 +90,9 @@ module.exports = class MetamaskController {
|
|||||||
signMessage: keyringController.signMessage.bind(keyringController),
|
signMessage: keyringController.signMessage.bind(keyringController),
|
||||||
cancelMessage: keyringController.cancelMessage.bind(keyringController),
|
cancelMessage: keyringController.cancelMessage.bind(keyringController),
|
||||||
|
|
||||||
|
// forward directly to txManager
|
||||||
|
getUnapprovedTxList: txManager.getTxList.bind(txManager),
|
||||||
|
getFilterdTxList: txManager.getFilterdTxList.bind(txManager),
|
||||||
// coinbase
|
// coinbase
|
||||||
buyEth: this.buyEth.bind(this),
|
buyEth: this.buyEth.bind(this),
|
||||||
// shapeshift
|
// shapeshift
|
||||||
@ -154,7 +166,7 @@ module.exports = class MetamaskController {
|
|||||||
var web3 = new Web3(provider)
|
var web3 = new Web3(provider)
|
||||||
this.web3 = web3
|
this.web3 = web3
|
||||||
keyringController.web3 = web3
|
keyringController.web3 = web3
|
||||||
|
this.txManager.web3 = web3
|
||||||
provider.on('block', this.processBlock.bind(this))
|
provider.on('block', this.processBlock.bind(this))
|
||||||
provider.on('error', this.getNetwork.bind(this))
|
provider.on('error', this.getNetwork.bind(this))
|
||||||
|
|
||||||
|
179
app/scripts/transaction-manager.js
Normal file
179
app/scripts/transaction-manager.js
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
const EventEmitter = require('events')
|
||||||
|
const extend = require('xtend')
|
||||||
|
const TxProviderUtil = require('./lib/provider-utils')
|
||||||
|
|
||||||
|
module.exports = class TransactionManager extends EventEmitter {
|
||||||
|
constructor (opts) {
|
||||||
|
super()
|
||||||
|
this.txList = opts.TxListFromStore || []
|
||||||
|
this._persistTxList = opts.setTxList
|
||||||
|
this._unconfTxCbs = {}
|
||||||
|
this.txLimit = opts.txLimit
|
||||||
|
this.provider = opts.provider
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the tx list
|
||||||
|
getTxList () {
|
||||||
|
return this.txList
|
||||||
|
}
|
||||||
|
|
||||||
|
// Saves the new/updated txList.
|
||||||
|
// Function is intended only for internal use
|
||||||
|
_saveTxList (txList) {
|
||||||
|
this.txList = txList
|
||||||
|
this._persistTxList(txList)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adds a tx to the txlist
|
||||||
|
addTx (txData, onTxDoneCb) {
|
||||||
|
var txList = this.getTxList()
|
||||||
|
var txLimit = this.txLimit
|
||||||
|
if (txList.length > txLimit - 1) {
|
||||||
|
txList.shift()
|
||||||
|
}
|
||||||
|
txList.push(txData)
|
||||||
|
this._saveTxList(txList)
|
||||||
|
this.addOnTxDoneCb(txData.id, onTxDoneCb)
|
||||||
|
this.emit('unapproved', txData)
|
||||||
|
this.emit('update')
|
||||||
|
}
|
||||||
|
|
||||||
|
getTx (txId, cb) {
|
||||||
|
var txList = this.getTxList()
|
||||||
|
var tx = txList.find((tx) => tx.id === txId)
|
||||||
|
return cb ? cb(tx) : tx
|
||||||
|
}
|
||||||
|
|
||||||
|
updateTx (txData) {
|
||||||
|
var txId = txData.id
|
||||||
|
var txList = this.getTxList()
|
||||||
|
|
||||||
|
var updatedTxList = txList.map((tx) => {
|
||||||
|
if (tx.id === txId) {
|
||||||
|
tx = txData
|
||||||
|
}
|
||||||
|
return tx
|
||||||
|
})
|
||||||
|
this._saveTxList(updatedTxList)
|
||||||
|
}
|
||||||
|
|
||||||
|
get unConftxCount () {
|
||||||
|
return Object.keys(this.getUnapprovedTxList()).length
|
||||||
|
}
|
||||||
|
|
||||||
|
get pendingTxCount () {
|
||||||
|
return this.getTxsByMetaData('status', 'signed').length
|
||||||
|
}
|
||||||
|
|
||||||
|
getUnapprovedTxList () {
|
||||||
|
var txList = this.getTxList()
|
||||||
|
return txList.filter((tx) => {
|
||||||
|
return tx.status === 'unapproved'
|
||||||
|
}).reduce((result, tx) => {
|
||||||
|
result[tx.id] = tx
|
||||||
|
return result
|
||||||
|
}, {})
|
||||||
|
}
|
||||||
|
|
||||||
|
getFilterdTxList (opts) {
|
||||||
|
var filteredTxList
|
||||||
|
Object.keys(opts).forEach((key) => {
|
||||||
|
filteredTxList = this.getTxsByMetaData(key, opts[key], filteredTxList)
|
||||||
|
})
|
||||||
|
return filteredTxList
|
||||||
|
}
|
||||||
|
|
||||||
|
getTxsByMetaData (key, value, txList = this.getTxList()) {
|
||||||
|
return txList.filter((tx) => {
|
||||||
|
if (key in tx.txParams) {
|
||||||
|
return tx.txParams[key] === value
|
||||||
|
} else {
|
||||||
|
return tx[key] === value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
addOnTxDoneCb (txId, cb) {
|
||||||
|
this._unconfTxCbs[txId] = cb || noop
|
||||||
|
}
|
||||||
|
|
||||||
|
// should return the tx
|
||||||
|
|
||||||
|
// Should find the tx in the tx list and
|
||||||
|
// update it.
|
||||||
|
// should set the status in txData
|
||||||
|
// // - `'unapproved'` the user has not responded
|
||||||
|
// // - `'rejected'` the user has responded no!
|
||||||
|
// // - `'signed'` the tx is signed
|
||||||
|
// // - `'submitted'` the tx is sent to a server
|
||||||
|
// // - `'confirmed'` the tx has been included in a block.
|
||||||
|
setTxStatus (txId, status) {
|
||||||
|
var txData = this.getTx(txId)
|
||||||
|
txData.status = status
|
||||||
|
this.emit(status, txId)
|
||||||
|
this.updateTx(txData, status)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// should return the status of the tx.
|
||||||
|
getTxStatus (txId, cb) {
|
||||||
|
const txData = this.getTx(txId)
|
||||||
|
return cb ? cb(txData.staus) : txData.status
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// should update the status of the tx to 'signed'.
|
||||||
|
setTxStatusSigned (txId) {
|
||||||
|
this.setTxStatus(txId, 'signed')
|
||||||
|
this.emit('update')
|
||||||
|
}
|
||||||
|
|
||||||
|
// should update the status of the tx to 'rejected'.
|
||||||
|
setTxStatusRejected (txId) {
|
||||||
|
this.setTxStatus(txId, 'rejected')
|
||||||
|
this.emit('update')
|
||||||
|
}
|
||||||
|
|
||||||
|
setTxStatusConfirmed (txId) {
|
||||||
|
this.setTxStatus(txId, 'confirmed')
|
||||||
|
// this.removeListener(`check${txId}`, this.checkForTxInBlock)
|
||||||
|
}
|
||||||
|
|
||||||
|
// merges txParams obj onto txData.txParams
|
||||||
|
// use extend to ensure that all fields are filled
|
||||||
|
updateTxParams (txId, txParams) {
|
||||||
|
var txData = this.getTx(txId)
|
||||||
|
txData.txParams = extend(txData, txParams)
|
||||||
|
this.updateTx(txData)
|
||||||
|
}
|
||||||
|
|
||||||
|
setProvider (provider) {
|
||||||
|
this.provider = provider
|
||||||
|
this.txProviderUtils = new TxProviderUtil(provider)
|
||||||
|
this.provider.on('block', this.checkForTxInBlock.bind(this))
|
||||||
|
}
|
||||||
|
|
||||||
|
checkForTxInBlock () {
|
||||||
|
var signedTxList = this.getFilterdTxList({status: 'signed'})
|
||||||
|
if (!signedTxList.length) return
|
||||||
|
var self = this
|
||||||
|
signedTxList.forEach((tx) => {
|
||||||
|
var txHash = tx.hash
|
||||||
|
var txId = tx.id
|
||||||
|
if (!txHash) return
|
||||||
|
// var d
|
||||||
|
this.txProviderUtils.query.getTransactionByHash(txHash, (err, txData) => {
|
||||||
|
if (err) {
|
||||||
|
tx
|
||||||
|
|
||||||
|
return console.error(err)
|
||||||
|
}
|
||||||
|
if (txData.blockNumber !== null) {
|
||||||
|
self.setTxStatusConfirmed(txId)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function noop () {}
|
Loading…
Reference in New Issue
Block a user