|
|
@ -1,11 +1,10 @@ |
|
|
|
const async = require('async') |
|
|
|
const async = require('async') |
|
|
|
const bind = require('ap').partial |
|
|
|
|
|
|
|
const ethUtil = require('ethereumjs-util') |
|
|
|
const ethUtil = require('ethereumjs-util') |
|
|
|
const EthQuery = require('eth-query') |
|
|
|
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 |
|
|
|
|
|
|
|
const filter = require('promise-filter') |
|
|
|
const normalize = require('./lib/sig-util').normalize |
|
|
|
const normalize = require('./lib/sig-util').normalize |
|
|
|
const encryptor = require('./lib/encryptor') |
|
|
|
const encryptor = require('./lib/encryptor') |
|
|
|
const messageManager = require('./lib/message-manager') |
|
|
|
const messageManager = require('./lib/message-manager') |
|
|
@ -341,7 +340,6 @@ module.exports = class KeyringController extends EventEmitter { |
|
|
|
// Estimates gas and other preparatory steps.
|
|
|
|
// Estimates gas and other preparatory steps.
|
|
|
|
// 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) { |
|
|
|
var self = this |
|
|
|
|
|
|
|
const configManager = this.configManager |
|
|
|
const configManager = this.configManager |
|
|
|
|
|
|
|
|
|
|
|
// create txData obj with parameters and meta data
|
|
|
|
// create txData obj with parameters and meta data
|
|
|
@ -358,7 +356,6 @@ module.exports = class KeyringController extends EventEmitter { |
|
|
|
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 |
|
|
|
this._unconfTxCbs[txId] = onTxDoneCb |
|
|
@ -367,81 +364,80 @@ module.exports = class KeyringController extends EventEmitter { |
|
|
|
var query = new EthQuery(provider) |
|
|
|
var query = new EthQuery(provider) |
|
|
|
|
|
|
|
|
|
|
|
// calculate metadata for tx
|
|
|
|
// calculate metadata for tx
|
|
|
|
async.parallel([ |
|
|
|
this.analyzeTxGasUsage(query, txData, this.txDidComplete.bind(this, txData, cb)) |
|
|
|
analyzeGasUsage, |
|
|
|
} |
|
|
|
], didComplete) |
|
|
|
|
|
|
|
|
|
|
|
estimateTxGas (query, txData, blockGasLimitHex, cb) { |
|
|
|
function analyzeGasUsage (cb) { |
|
|
|
const txParams = txData.txParams |
|
|
|
query.getBlockByNumber('latest', true, function (err, block) { |
|
|
|
// check if gasLimit is already specified
|
|
|
|
if (err) return cb(err) |
|
|
|
txData.gasLimitSpecified = Boolean(txParams.gas) |
|
|
|
async.waterfall([ |
|
|
|
// if not, fallback to block gasLimit
|
|
|
|
bind(estimateGas, query, txData, block.gasLimit), |
|
|
|
if (!txData.gasLimitSpecified) { |
|
|
|
bind(checkForGasError, txData), |
|
|
|
txParams.gas = blockGasLimitHex |
|
|
|
bind(setTxGas, txData, block.gasLimit), |
|
|
|
|
|
|
|
], cb) |
|
|
|
|
|
|
|
}) |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// run tx, see if it will OOG
|
|
|
|
|
|
|
|
query.estimateGas(txParams, cb) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
function estimateGas (query, txData, blockGasLimitHex, cb) { |
|
|
|
checkForTxGasError (txData, estimatedGasHex, cb) { |
|
|
|
const txParams = txData.txParams |
|
|
|
txData.estimatedGas = estimatedGasHex |
|
|
|
// check if gasLimit is already specified
|
|
|
|
// all gas used - must be an error
|
|
|
|
txData.gasLimitSpecified = Boolean(txParams.gas) |
|
|
|
if (estimatedGasHex === txData.txParams.gas) { |
|
|
|
// if not, fallback to block gasLimit
|
|
|
|
txData.simulationFails = true |
|
|
|
if (!txData.gasLimitSpecified) { |
|
|
|
|
|
|
|
txParams.gas = blockGasLimitHex |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
// run tx, see if it will OOG
|
|
|
|
|
|
|
|
query.estimateGas(txParams, cb) |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
cb() |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
function checkForGasError (txData, estimatedGasHex, cb) { |
|
|
|
setTxGas (txData, blockGasLimitHex, cb) { |
|
|
|
txData.estimatedGas = estimatedGasHex |
|
|
|
const txParams = txData.txParams |
|
|
|
// all gas used - must be an error
|
|
|
|
// if OOG, nothing more to do
|
|
|
|
if (estimatedGasHex === txData.txParams.gas) { |
|
|
|
if (txData.simulationFails) { |
|
|
|
txData.simulationFails = true |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
cb() |
|
|
|
cb() |
|
|
|
|
|
|
|
return |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// if gasLimit was specified and doesnt OOG,
|
|
|
|
function setTxGas (txData, blockGasLimitHex, cb) { |
|
|
|
// use original specified amount
|
|
|
|
const txParams = txData.txParams |
|
|
|
if (txData.gasLimitSpecified) { |
|
|
|
// if OOG, nothing more to do
|
|
|
|
txData.estimatedGas = txParams.gas |
|
|
|
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(self.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() |
|
|
|
cb() |
|
|
|
return |
|
|
|
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 |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
function didComplete (err) { |
|
|
|
txDidComplete (txData, cb, err) { |
|
|
|
|
|
|
|
if (err) return cb(err) |
|
|
|
|
|
|
|
const configManager = this.configManager |
|
|
|
|
|
|
|
configManager.addTx(txData) |
|
|
|
|
|
|
|
// signal update
|
|
|
|
|
|
|
|
this.emit('update') |
|
|
|
|
|
|
|
// signal completion of add tx
|
|
|
|
|
|
|
|
cb(null, txData) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
analyzeTxGasUsage (query, txData, cb) { |
|
|
|
|
|
|
|
query.getBlockByNumber('latest', true, (err, block) => { |
|
|
|
if (err) return cb(err) |
|
|
|
if (err) return cb(err) |
|
|
|
configManager.addTx(txData) |
|
|
|
async.waterfall([ |
|
|
|
// signal update
|
|
|
|
this.estimateTxGas.bind(this, query, txData, block.gasLimit), |
|
|
|
self.emit('update') |
|
|
|
this.checkForTxGasError.bind(this, txData), |
|
|
|
// signal completion of add tx
|
|
|
|
this.setTxGas.bind(this, txData, block.gasLimit), |
|
|
|
cb(null, txData) |
|
|
|
], cb) |
|
|
|
} |
|
|
|
}) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Cancel Transaction
|
|
|
|
// Cancel Transaction
|
|
|
@ -488,6 +484,7 @@ module.exports = class KeyringController extends EventEmitter { |
|
|
|
delete this._unconfTxCbs[txId] |
|
|
|
delete this._unconfTxCbs[txId] |
|
|
|
this.emit('update') |
|
|
|
this.emit('update') |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
signTransaction (txParams, cb) { |
|
|
|
signTransaction (txParams, cb) { |
|
|
|
try { |
|
|
|
try { |
|
|
|
const address = normalize(txParams.from) |
|
|
|
const address = normalize(txParams.from) |
|
|
@ -828,28 +825,23 @@ module.exports = class KeyringController extends EventEmitter { |
|
|
|
// the specified `address` if one exists.
|
|
|
|
// the specified `address` if one exists.
|
|
|
|
getKeyringForAccount (address) { |
|
|
|
getKeyringForAccount (address) { |
|
|
|
const hexed = normalize(address) |
|
|
|
const hexed = normalize(address) |
|
|
|
return new Promise((resolve, reject) => { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Get all the keyrings, and associate them with their account list:
|
|
|
|
|
|
|
|
Promise.all(this.keyrings.map((keyring) => { |
|
|
|
|
|
|
|
const accounts = keyring.getAccounts() |
|
|
|
|
|
|
|
return Promise.all({ |
|
|
|
|
|
|
|
keyring, |
|
|
|
|
|
|
|
accounts, |
|
|
|
|
|
|
|
}) |
|
|
|
|
|
|
|
})) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Find the keyring with the matching account and return it:
|
|
|
|
return Promise.all(this.keyrings.map((keyring) => { |
|
|
|
.then((result) => { |
|
|
|
return Promise.all([ |
|
|
|
const match = result.find((candidate) => { |
|
|
|
keyring, |
|
|
|
return candidate.accounts.map(normalize).includes(hexed) |
|
|
|
keyring.getAccounts(), |
|
|
|
}) |
|
|
|
]) |
|
|
|
if (match) { |
|
|
|
})) |
|
|
|
resolve(match.keyring) |
|
|
|
.then(filter((candidate) => { |
|
|
|
} else { |
|
|
|
const accounts = candidate[1].map(normalize) |
|
|
|
reject('No keyring found for the requested account.') |
|
|
|
return accounts.includes(hexed) |
|
|
|
} |
|
|
|
})) |
|
|
|
}) |
|
|
|
.then((winners) => { |
|
|
|
|
|
|
|
if (winners && winners.length > 0) { |
|
|
|
|
|
|
|
return winners[0][0] |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
throw new Error('No keyring found for the requested account.') |
|
|
|
|
|
|
|
} |
|
|
|
}) |
|
|
|
}) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
@ -888,4 +880,5 @@ module.exports = class KeyringController extends EventEmitter { |
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function noop () {} |
|
|
|
function noop () {} |
|
|
|