|
|
@ -1,10 +1,9 @@ |
|
|
|
const EventEmitter = require('events') |
|
|
|
const EventEmitter = require('events') |
|
|
|
const async = require('async') |
|
|
|
|
|
|
|
const extend = require('xtend') |
|
|
|
const extend = require('xtend') |
|
|
|
const clone = require('clone') |
|
|
|
const clone = require('clone') |
|
|
|
const ObservableStore = require('obs-store') |
|
|
|
const ObservableStore = require('obs-store') |
|
|
|
const ethUtil = require('ethereumjs-util') |
|
|
|
const ethUtil = require('ethereumjs-util') |
|
|
|
const pify = require('pify') |
|
|
|
const EthQuery = require('ethjs-query') |
|
|
|
const TxProviderUtil = require('../lib/tx-utils') |
|
|
|
const TxProviderUtil = require('../lib/tx-utils') |
|
|
|
const getStack = require('../lib/util').getStack |
|
|
|
const getStack = require('../lib/util').getStack |
|
|
|
const createId = require('../lib/random-id') |
|
|
|
const createId = require('../lib/random-id') |
|
|
@ -32,7 +31,7 @@ module.exports = class TransactionController extends EventEmitter { |
|
|
|
}) |
|
|
|
}) |
|
|
|
}, |
|
|
|
}, |
|
|
|
}) |
|
|
|
}) |
|
|
|
this.query = opts.ethQuery |
|
|
|
this.query = new EthQuery(this.provider) |
|
|
|
this.txProviderUtils = new TxProviderUtil(this.query) |
|
|
|
this.txProviderUtils = new TxProviderUtil(this.query) |
|
|
|
this.blockTracker.on('rawBlock', this.checkForTxInBlock.bind(this)) |
|
|
|
this.blockTracker.on('rawBlock', this.checkForTxInBlock.bind(this)) |
|
|
|
// this is a little messy but until ethstore has been either
|
|
|
|
// this is a little messy but until ethstore has been either
|
|
|
@ -61,13 +60,6 @@ module.exports = class TransactionController extends EventEmitter { |
|
|
|
return this.preferencesStore.getState().selectedAddress |
|
|
|
return this.preferencesStore.getState().selectedAddress |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Returns the tx list
|
|
|
|
|
|
|
|
getTxList () { |
|
|
|
|
|
|
|
const network = this.getNetwork() |
|
|
|
|
|
|
|
const fullTxList = this.getFullTxList() |
|
|
|
|
|
|
|
return fullTxList.filter(txMeta => txMeta.metamaskNetworkId === network) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Returns the number of txs for the current network.
|
|
|
|
// Returns the number of txs for the current network.
|
|
|
|
getTxCount () { |
|
|
|
getTxCount () { |
|
|
|
return this.getTxList().length |
|
|
|
return this.getTxList().length |
|
|
@ -78,6 +70,58 @@ module.exports = class TransactionController extends EventEmitter { |
|
|
|
return this.store.getState().transactions |
|
|
|
return this.store.getState().transactions |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
getUnapprovedTxCount () { |
|
|
|
|
|
|
|
return Object.keys(this.getUnapprovedTxList()).length |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
getPendingTxCount () { |
|
|
|
|
|
|
|
return this.getTxsByMetaData('status', 'signed').length |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Returns the tx list
|
|
|
|
|
|
|
|
getTxList () { |
|
|
|
|
|
|
|
const network = this.getNetwork() |
|
|
|
|
|
|
|
const fullTxList = this.getFullTxList() |
|
|
|
|
|
|
|
return this.getTxsByMetaData('metamaskNetworkId', network, fullTxList) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// gets tx by Id and returns it
|
|
|
|
|
|
|
|
getTx (txId) { |
|
|
|
|
|
|
|
const txList = this.getTxList() |
|
|
|
|
|
|
|
const txMeta = txList.find(txData => txData.id === txId) |
|
|
|
|
|
|
|
return txMeta |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
getUnapprovedTxList () { |
|
|
|
|
|
|
|
const txList = this.getTxList() |
|
|
|
|
|
|
|
return txList.filter((txMeta) => txMeta.status === 'unapproved') |
|
|
|
|
|
|
|
.reduce((result, tx) => { |
|
|
|
|
|
|
|
result[tx.id] = tx |
|
|
|
|
|
|
|
return result |
|
|
|
|
|
|
|
}, {}) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
updateTx (txMeta) { |
|
|
|
|
|
|
|
// create txMeta snapshot for history
|
|
|
|
|
|
|
|
const txMetaForHistory = clone(txMeta) |
|
|
|
|
|
|
|
// dont include previous history in this snapshot
|
|
|
|
|
|
|
|
delete txMetaForHistory.history |
|
|
|
|
|
|
|
// add stack to help understand why tx was updated
|
|
|
|
|
|
|
|
txMetaForHistory.stack = getStack() |
|
|
|
|
|
|
|
// add snapshot to tx history
|
|
|
|
|
|
|
|
if (!txMeta.history) txMeta.history = [] |
|
|
|
|
|
|
|
txMeta.history.push(txMetaForHistory) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const txId = txMeta.id |
|
|
|
|
|
|
|
const txList = this.getFullTxList() |
|
|
|
|
|
|
|
const index = txList.findIndex(txData => txData.id === txId) |
|
|
|
|
|
|
|
if (!txMeta.history) txMeta.history = [] |
|
|
|
|
|
|
|
txMeta.history.push(txMetaForHistory) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
txList[index] = txMeta |
|
|
|
|
|
|
|
this._saveTxList(txList) |
|
|
|
|
|
|
|
this.emit('update') |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Adds a tx to the txlist
|
|
|
|
// Adds a tx to the txlist
|
|
|
|
addTx (txMeta) { |
|
|
|
addTx (txMeta) { |
|
|
|
const txCount = this.getTxCount() |
|
|
|
const txCount = this.getTxCount() |
|
|
@ -91,7 +135,7 @@ module.exports = class TransactionController extends EventEmitter { |
|
|
|
// or rejected tx's.
|
|
|
|
// or rejected tx's.
|
|
|
|
// not tx's that are pending or unapproved
|
|
|
|
// not tx's that are pending or unapproved
|
|
|
|
if (txCount > txHistoryLimit - 1) { |
|
|
|
if (txCount > txHistoryLimit - 1) { |
|
|
|
var index = fullTxList.findIndex((metaTx) => ((metaTx.status === 'confirmed' || metaTx.status === 'rejected') && network === txMeta.metamaskNetworkId)) |
|
|
|
const index = fullTxList.findIndex((metaTx) => ((metaTx.status === 'confirmed' || metaTx.status === 'rejected') && network === txMeta.metamaskNetworkId)) |
|
|
|
fullTxList.splice(index, 1) |
|
|
|
fullTxList.splice(index, 1) |
|
|
|
} |
|
|
|
} |
|
|
|
fullTxList.push(txMeta) |
|
|
|
fullTxList.push(txMeta) |
|
|
@ -109,92 +153,59 @@ module.exports = class TransactionController extends EventEmitter { |
|
|
|
this.emit(`${txMeta.id}:unapproved`, txMeta) |
|
|
|
this.emit(`${txMeta.id}:unapproved`, txMeta) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// gets tx by Id and returns it
|
|
|
|
async newUnapprovedTransaction (txParams) { |
|
|
|
getTx (txId, cb) { |
|
|
|
log.debug(`MetaMaskController newUnapprovedTransaction ${JSON.stringify(txParams)}`) |
|
|
|
var txList = this.getTxList() |
|
|
|
const txMeta = await this.addUnapprovedTransaction(txParams) |
|
|
|
var txMeta = txList.find(txData => txData.id === txId) |
|
|
|
this.emit('newUnaprovedTx', txMeta) |
|
|
|
return cb ? cb(txMeta) : txMeta |
|
|
|
// listen for tx completion (success, fail)
|
|
|
|
} |
|
|
|
return new Promise((resolve, reject) => { |
|
|
|
|
|
|
|
this.once(`${txMeta.id}:finished`, (completedTx) => { |
|
|
|
//
|
|
|
|
switch (completedTx.status) { |
|
|
|
updateTx (txMeta) { |
|
|
|
case 'submitted': |
|
|
|
// create txMeta snapshot for history
|
|
|
|
return resolve(completedTx.hash) |
|
|
|
const txMetaForHistory = clone(txMeta) |
|
|
|
case 'rejected': |
|
|
|
// dont include previous history in this snapshot
|
|
|
|
return reject(new Error('MetaMask Tx Signature: User denied transaction signature.')) |
|
|
|
delete txMetaForHistory.history |
|
|
|
default: |
|
|
|
// add stack to help understand why tx was updated
|
|
|
|
return reject(new Error(`MetaMask Tx Signature: Unknown problem: ${JSON.stringify(completedTx.txParams)}`)) |
|
|
|
txMetaForHistory.stack = getStack() |
|
|
|
} |
|
|
|
// add snapshot to tx history
|
|
|
|
}) |
|
|
|
if (!txMeta.history) txMeta.history = [] |
|
|
|
}) |
|
|
|
txMeta.history.push(txMetaForHistory) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// update the tx
|
|
|
|
|
|
|
|
var txId = txMeta.id |
|
|
|
|
|
|
|
var txList = this.getFullTxList() |
|
|
|
|
|
|
|
var index = txList.findIndex(txData => txData.id === txId) |
|
|
|
|
|
|
|
txList[index] = txMeta |
|
|
|
|
|
|
|
this._saveTxList(txList) |
|
|
|
|
|
|
|
this.emit('update') |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
get unapprovedTxCount () { |
|
|
|
|
|
|
|
return Object.keys(this.getUnapprovedTxList()).length |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
get pendingTxCount () { |
|
|
|
|
|
|
|
return this.getTxsByMetaData('status', 'signed').length |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
addUnapprovedTransaction (txParams, done) { |
|
|
|
async addUnapprovedTransaction (txParams) { |
|
|
|
let txMeta = {} |
|
|
|
// validate
|
|
|
|
async.waterfall([ |
|
|
|
await this.txProviderUtils.validateTxParams(txParams) |
|
|
|
// validate
|
|
|
|
// construct txMeta
|
|
|
|
(cb) => this.txProviderUtils.validateTxParams(txParams, cb), |
|
|
|
const txMeta = { |
|
|
|
// construct txMeta
|
|
|
|
id: createId(), |
|
|
|
(cb) => { |
|
|
|
time: (new Date()).getTime(), |
|
|
|
txMeta = { |
|
|
|
status: 'unapproved', |
|
|
|
id: createId(), |
|
|
|
metamaskNetworkId: this.getNetwork(), |
|
|
|
time: (new Date()).getTime(), |
|
|
|
txParams: txParams, |
|
|
|
status: 'unapproved', |
|
|
|
history: [], |
|
|
|
metamaskNetworkId: this.getNetwork(), |
|
|
|
} |
|
|
|
txParams: txParams, |
|
|
|
// add default tx params
|
|
|
|
history: [], |
|
|
|
await this.addTxDefaults(txMeta) |
|
|
|
} |
|
|
|
// save txMeta
|
|
|
|
cb() |
|
|
|
this.addTx(txMeta) |
|
|
|
}, |
|
|
|
return txMeta |
|
|
|
// add default tx params
|
|
|
|
|
|
|
|
(cb) => this.addTxDefaults(txMeta, cb), |
|
|
|
|
|
|
|
// save txMeta
|
|
|
|
|
|
|
|
(cb) => { |
|
|
|
|
|
|
|
this.addTx(txMeta) |
|
|
|
|
|
|
|
cb(null, txMeta) |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
], done) |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
addTxDefaults (txMeta, cb) { |
|
|
|
async addTxDefaults (txMeta) { |
|
|
|
const txParams = txMeta.txParams |
|
|
|
const txParams = txMeta.txParams |
|
|
|
// ensure value
|
|
|
|
// ensure value
|
|
|
|
txParams.value = txParams.value || '0x0' |
|
|
|
txParams.value = txParams.value || '0x0' |
|
|
|
if (!txParams.gasPrice) { |
|
|
|
if (!txParams.gasPrice) { |
|
|
|
this.query.gasPrice((err, gasPrice) => { |
|
|
|
const gasPrice = await this.query.gasPrice() |
|
|
|
|
|
|
|
txParams.gasPrice = gasPrice |
|
|
|
if (err) return cb(err) |
|
|
|
|
|
|
|
// set gasPrice
|
|
|
|
|
|
|
|
txParams.gasPrice = gasPrice |
|
|
|
|
|
|
|
}) |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
// set gasLimit
|
|
|
|
// set gasLimit
|
|
|
|
this.txProviderUtils.analyzeGasUsage(txMeta, cb) |
|
|
|
return await this.txProviderUtils.analyzeGasUsage(txMeta) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
getUnapprovedTxList () { |
|
|
|
async updateAndApproveTransaction (txMeta) { |
|
|
|
var txList = this.getTxList() |
|
|
|
this.updateTx(txMeta) |
|
|
|
return txList.filter((txMeta) => txMeta.status === 'unapproved') |
|
|
|
await this.approveTransaction(txMeta.id) |
|
|
|
.reduce((result, tx) => { |
|
|
|
|
|
|
|
result[tx.id] = tx |
|
|
|
|
|
|
|
return result |
|
|
|
|
|
|
|
}, {}) |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
async approveTransaction (txId) { |
|
|
|
async approveTransaction (txId) { |
|
|
@ -230,26 +241,6 @@ module.exports = class TransactionController extends EventEmitter { |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
cancelTransaction (txId, cb = warn) { |
|
|
|
|
|
|
|
this.setTxStatusRejected(txId) |
|
|
|
|
|
|
|
cb() |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async updateAndApproveTransaction (txMeta) { |
|
|
|
|
|
|
|
this.updateTx(txMeta) |
|
|
|
|
|
|
|
await this.approveTransaction(txMeta.id) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
getChainId () { |
|
|
|
|
|
|
|
const networkState = this.networkStore.getState() |
|
|
|
|
|
|
|
const getChainId = parseInt(networkState) |
|
|
|
|
|
|
|
if (Number.isNaN(getChainId)) { |
|
|
|
|
|
|
|
return 0 |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
return getChainId |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async signTransaction (txId) { |
|
|
|
async signTransaction (txId) { |
|
|
|
const txMeta = this.getTx(txId) |
|
|
|
const txMeta = this.getTx(txId) |
|
|
|
const txParams = txMeta.txParams |
|
|
|
const txParams = txMeta.txParams |
|
|
@ -257,10 +248,9 @@ module.exports = class TransactionController extends EventEmitter { |
|
|
|
// add network/chain id
|
|
|
|
// add network/chain id
|
|
|
|
txParams.chainId = this.getChainId() |
|
|
|
txParams.chainId = this.getChainId() |
|
|
|
const ethTx = this.txProviderUtils.buildEthTxFromParams(txParams) |
|
|
|
const ethTx = this.txProviderUtils.buildEthTxFromParams(txParams) |
|
|
|
const rawTx = await this.signEthTx(ethTx, fromAddress).then(() => { |
|
|
|
await this.signEthTx(ethTx, fromAddress) |
|
|
|
this.setTxStatusSigned(txMeta.id) |
|
|
|
this.setTxStatusSigned(txMeta.id) |
|
|
|
return ethUtil.bufferToHex(ethTx.serialize()) |
|
|
|
const rawTx = ethUtil.bufferToHex(ethTx.serialize()) |
|
|
|
}) |
|
|
|
|
|
|
|
return rawTx |
|
|
|
return rawTx |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
@ -268,10 +258,24 @@ module.exports = class TransactionController extends EventEmitter { |
|
|
|
const txMeta = this.getTx(txId) |
|
|
|
const txMeta = this.getTx(txId) |
|
|
|
txMeta.rawTx = rawTx |
|
|
|
txMeta.rawTx = rawTx |
|
|
|
this.updateTx(txMeta) |
|
|
|
this.updateTx(txMeta) |
|
|
|
await this.txProviderUtils.publishTransaction(rawTx).then((txHash) => { |
|
|
|
const txHash = await this.txProviderUtils.publishTransaction(rawTx) |
|
|
|
this.setTxHash(txId, txHash) |
|
|
|
this.setTxHash(txId, txHash) |
|
|
|
this.setTxStatusSubmitted(txId) |
|
|
|
this.setTxStatusSubmitted(txId) |
|
|
|
}) |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async cancelTransaction (txId) { |
|
|
|
|
|
|
|
this.setTxStatusRejected(txId) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
getChainId () { |
|
|
|
|
|
|
|
const networkState = this.networkStore.getState() |
|
|
|
|
|
|
|
const getChainId = parseInt(networkState) |
|
|
|
|
|
|
|
if (Number.isNaN(getChainId)) { |
|
|
|
|
|
|
|
return 0 |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
return getChainId |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// receives a txHash records the tx as signed
|
|
|
|
// receives a txHash records the tx as signed
|
|
|
@ -284,7 +288,7 @@ module.exports = class TransactionController extends EventEmitter { |
|
|
|
|
|
|
|
|
|
|
|
/* |
|
|
|
/* |
|
|
|
Takes an object of fields to search for eg: |
|
|
|
Takes an object of fields to search for eg: |
|
|
|
var thingsToLookFor = { |
|
|
|
let thingsToLookFor = { |
|
|
|
to: '0x0..', |
|
|
|
to: '0x0..', |
|
|
|
from: '0x0..', |
|
|
|
from: '0x0..', |
|
|
|
status: 'signed', |
|
|
|
status: 'signed', |
|
|
@ -307,7 +311,7 @@ module.exports = class TransactionController extends EventEmitter { |
|
|
|
and that have been 'confirmed' |
|
|
|
and that have been 'confirmed' |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
getFilteredTxList (opts) { |
|
|
|
getFilteredTxList (opts) { |
|
|
|
var filteredTxList |
|
|
|
let filteredTxList |
|
|
|
Object.keys(opts).forEach((key) => { |
|
|
|
Object.keys(opts).forEach((key) => { |
|
|
|
filteredTxList = this.getTxsByMetaData(key, opts[key], filteredTxList) |
|
|
|
filteredTxList = this.getTxsByMetaData(key, opts[key], filteredTxList) |
|
|
|
}) |
|
|
|
}) |
|
|
@ -368,7 +372,7 @@ module.exports = class TransactionController extends EventEmitter { |
|
|
|
// merges txParams obj onto txData.txParams
|
|
|
|
// merges txParams obj onto txData.txParams
|
|
|
|
// use extend to ensure that all fields are filled
|
|
|
|
// use extend to ensure that all fields are filled
|
|
|
|
updateTxParams (txId, txParams) { |
|
|
|
updateTxParams (txId, txParams) { |
|
|
|
var txMeta = this.getTx(txId) |
|
|
|
const txMeta = this.getTx(txId) |
|
|
|
txMeta.txParams = extend(txMeta.txParams, txParams) |
|
|
|
txMeta.txParams = extend(txMeta.txParams, txParams) |
|
|
|
this.updateTx(txMeta) |
|
|
|
this.updateTx(txMeta) |
|
|
|
} |
|
|
|
} |
|
|
@ -376,20 +380,19 @@ module.exports = class TransactionController extends EventEmitter { |
|
|
|
// checks if a signed tx is in a block and
|
|
|
|
// checks if a signed tx is in a block and
|
|
|
|
// if included sets the tx status as 'confirmed'
|
|
|
|
// if included sets the tx status as 'confirmed'
|
|
|
|
checkForTxInBlock (block) { |
|
|
|
checkForTxInBlock (block) { |
|
|
|
var signedTxList = this.getFilteredTxList({status: 'submitted'}) |
|
|
|
const signedTxList = this.getFilteredTxList({status: 'submitted'}) |
|
|
|
if (!signedTxList.length) return |
|
|
|
if (!signedTxList.length) return |
|
|
|
signedTxList.forEach((txMeta) => { |
|
|
|
signedTxList.forEach((txMeta) => { |
|
|
|
var txHash = txMeta.hash |
|
|
|
const txHash = txMeta.hash |
|
|
|
var txId = txMeta.id |
|
|
|
const txId = txMeta.id |
|
|
|
|
|
|
|
|
|
|
|
if (!txHash) { |
|
|
|
if (!txHash) { |
|
|
|
return this.setTxStatusFailed(txId, { |
|
|
|
const noTxHashErr = new Error('We had an error while submitting this transaction, please try again.') |
|
|
|
stack: 'checkForTxInBlock: custom tx-controller error message', |
|
|
|
noTxHashErr.name = 'NoTxHashError' |
|
|
|
errCode: 'No hash was provided', |
|
|
|
this.setTxStatusFailed(txId, noTxHashErr) |
|
|
|
message: 'We had an error while submitting this transaction, please try again.', |
|
|
|
|
|
|
|
}) |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
block.transactions.forEach((tx) => { |
|
|
|
block.transactions.forEach((tx) => { |
|
|
|
if (tx.hash === txHash) this.setTxStatusConfirmed(txId) |
|
|
|
if (tx.hash === txHash) this.setTxStatusConfirmed(txId) |
|
|
|
}) |
|
|
|
}) |
|
|
@ -407,7 +410,45 @@ module.exports = class TransactionController extends EventEmitter { |
|
|
|
if (diff > 1) this._checkPendingTxs() |
|
|
|
if (diff > 1) this._checkPendingTxs() |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// PRIVATE METHODS
|
|
|
|
resubmitPendingTxs () { |
|
|
|
|
|
|
|
const pending = this.getTxsByMetaData('status', 'submitted') |
|
|
|
|
|
|
|
// only try resubmitting if their are transactions to resubmit
|
|
|
|
|
|
|
|
if (!pending.length) return |
|
|
|
|
|
|
|
pending.forEach((txMeta) => this._resubmitTx(txMeta).catch((err) => { |
|
|
|
|
|
|
|
/* |
|
|
|
|
|
|
|
Dont marked as failed if the error is a "known" transaction warning |
|
|
|
|
|
|
|
"there is already a transaction with the same sender-nonce |
|
|
|
|
|
|
|
but higher/same gas price" |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
const errorMessage = err.message.toLowerCase() |
|
|
|
|
|
|
|
const isKnownTx = ( |
|
|
|
|
|
|
|
// geth
|
|
|
|
|
|
|
|
errorMessage.includes('replacement transaction underpriced') |
|
|
|
|
|
|
|
|| errorMessage.includes('known transaction') |
|
|
|
|
|
|
|
// parity
|
|
|
|
|
|
|
|
|| errorMessage.includes('gas price too low to replace') |
|
|
|
|
|
|
|
|| errorMessage.includes('transaction with the same hash was already imported') |
|
|
|
|
|
|
|
// other
|
|
|
|
|
|
|
|
|| errorMessage.includes('gateway timeout') |
|
|
|
|
|
|
|
|| errorMessage.includes('nonce too low') |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
// ignore resubmit warnings, return early
|
|
|
|
|
|
|
|
if (isKnownTx) return |
|
|
|
|
|
|
|
// encountered real error - transition to error state
|
|
|
|
|
|
|
|
this.setTxStatusFailed(txMeta.id, { |
|
|
|
|
|
|
|
stack: err.stack || err.message, |
|
|
|
|
|
|
|
errCode: err.errCode || err, |
|
|
|
|
|
|
|
message: err.message, |
|
|
|
|
|
|
|
}) |
|
|
|
|
|
|
|
})) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* _____________________________________ |
|
|
|
|
|
|
|
| | |
|
|
|
|
|
|
|
| PRIVATE METHODS | |
|
|
|
|
|
|
|
|______________________________________*/ |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Should find the tx in the tx list and
|
|
|
|
// Should find the tx in the tx list and
|
|
|
|
// update it.
|
|
|
|
// update it.
|
|
|
@ -420,7 +461,7 @@ module.exports = class TransactionController extends EventEmitter { |
|
|
|
// - `'confirmed'` the tx has been included in a block.
|
|
|
|
// - `'confirmed'` the tx has been included in a block.
|
|
|
|
// - `'failed'` the tx failed for some reason, included on tx data.
|
|
|
|
// - `'failed'` the tx failed for some reason, included on tx data.
|
|
|
|
_setTxStatus (txId, status) { |
|
|
|
_setTxStatus (txId, status) { |
|
|
|
var txMeta = this.getTx(txId) |
|
|
|
const txMeta = this.getTx(txId) |
|
|
|
txMeta.status = status |
|
|
|
txMeta.status = status |
|
|
|
this.emit(`${txMeta.id}:${status}`, txId) |
|
|
|
this.emit(`${txMeta.id}:${status}`, txId) |
|
|
|
if (status === 'submitted' || status === 'rejected') { |
|
|
|
if (status === 'submitted' || status === 'rejected') { |
|
|
@ -445,39 +486,6 @@ module.exports = class TransactionController extends EventEmitter { |
|
|
|
this.memStore.updateState({ unapprovedTxs, selectedAddressTxList }) |
|
|
|
this.memStore.updateState({ unapprovedTxs, selectedAddressTxList }) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
resubmitPendingTxs () { |
|
|
|
|
|
|
|
const pending = this.getTxsByMetaData('status', 'submitted') |
|
|
|
|
|
|
|
// only try resubmitting if their are transactions to resubmit
|
|
|
|
|
|
|
|
if (!pending.length) return |
|
|
|
|
|
|
|
pending.forEach((txMeta) => this._resubmitTx(txMeta).catch((err) => { |
|
|
|
|
|
|
|
/* |
|
|
|
|
|
|
|
Dont marked as failed if the error is a "known" transaction warning |
|
|
|
|
|
|
|
"there is already a transaction with the same sender-nonce |
|
|
|
|
|
|
|
but higher/same gas price" |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
const errorMessage = err.message.toLowerCase() |
|
|
|
|
|
|
|
const isKnownTx = ( |
|
|
|
|
|
|
|
// geth
|
|
|
|
|
|
|
|
errorMessage.includes('replacement transaction underpriced') |
|
|
|
|
|
|
|
|| errorMessage.includes('known transaction') |
|
|
|
|
|
|
|
// parity
|
|
|
|
|
|
|
|
|| errorMessage.includes('gas price too low to replace') |
|
|
|
|
|
|
|
|| errorMessage.includes('transaction with the same hash was already imported') |
|
|
|
|
|
|
|
// other
|
|
|
|
|
|
|
|
|| errorMessage.includes('gateway timeout') |
|
|
|
|
|
|
|
|| errorMessage.includes('nonce too low') |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
// ignore resubmit warnings, return early
|
|
|
|
|
|
|
|
if (isKnownTx) return |
|
|
|
|
|
|
|
// encountered real error - transition to error state
|
|
|
|
|
|
|
|
this.setTxStatusFailed(txMeta.id, { |
|
|
|
|
|
|
|
stack: err.stack || err.message, |
|
|
|
|
|
|
|
errCode: err.errCode || err, |
|
|
|
|
|
|
|
message: err.message, |
|
|
|
|
|
|
|
}) |
|
|
|
|
|
|
|
})) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async _resubmitTx (txMeta) { |
|
|
|
async _resubmitTx (txMeta) { |
|
|
|
const address = txMeta.txParams.from |
|
|
|
const address = txMeta.txParams.from |
|
|
|
const balance = this.ethStore.getState().accounts[address].balance |
|
|
|
const balance = this.ethStore.getState().accounts[address].balance |
|
|
@ -524,17 +532,14 @@ module.exports = class TransactionController extends EventEmitter { |
|
|
|
// extra check in case there was an uncaught error during the
|
|
|
|
// extra check in case there was an uncaught error during the
|
|
|
|
// signature and submission process
|
|
|
|
// signature and submission process
|
|
|
|
if (!txHash) { |
|
|
|
if (!txHash) { |
|
|
|
this.setTxStatusFailed(txId, { |
|
|
|
const noTxHashErr = new Error('We had an error while submitting this transaction, please try again.') |
|
|
|
stack: '_checkPendingTxs: custom tx-controller error message', |
|
|
|
noTxHashErr.name = 'NoTxHashError' |
|
|
|
errCode: 'No hash was provided', |
|
|
|
this.setTxStatusFailed(txId, noTxHashErr) |
|
|
|
message: 'We had an error while submitting this transaction, please try again.', |
|
|
|
|
|
|
|
}) |
|
|
|
|
|
|
|
return |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
// get latest transaction status
|
|
|
|
// get latest transaction status
|
|
|
|
let txParams |
|
|
|
let txParams |
|
|
|
try { |
|
|
|
try { |
|
|
|
txParams = await pify((cb) => this.query.getTransactionByHash(txHash, cb))() |
|
|
|
txParams = await this.query.getTransactionByHash(txHash) |
|
|
|
if (!txParams) return |
|
|
|
if (!txParams) return |
|
|
|
if (txParams.blockNumber) { |
|
|
|
if (txParams.blockNumber) { |
|
|
|
this.setTxStatusConfirmed(txId) |
|
|
|
this.setTxStatusConfirmed(txId) |
|
|
@ -547,12 +552,8 @@ module.exports = class TransactionController extends EventEmitter { |
|
|
|
message: 'There was a problem loading this transaction.', |
|
|
|
message: 'There was a problem loading this transaction.', |
|
|
|
} |
|
|
|
} |
|
|
|
this.updateTx(txMeta) |
|
|
|
this.updateTx(txMeta) |
|
|
|
log.error(err) |
|
|
|
throw err |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const warn = () => log.warn('warn was used no cb provided') |
|
|
|
|
|
|
|