mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-11-22 09:57:02 +01:00
Merge remote-tracking branch 'origin/kumavis-readme-gource' into RevertTxManager
This commit is contained in:
commit
2dbbc0dce7
@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
## Current Master
|
## Current Master
|
||||||
|
|
||||||
|
- Add a check for when a tx is included in a block.
|
||||||
|
|
||||||
## 2.14.1 2016-12-20
|
## 2.14.1 2016-12-20
|
||||||
|
|
||||||
- Temporarily disable extension reload detection causing infinite reload bug.
|
- Temporarily disable extension reload detection causing infinite reload bug.
|
||||||
|
36
README.md
36
README.md
@ -117,3 +117,39 @@ To write tests that will be run in the browser using QUnit, add your test files
|
|||||||
3. Upload the latest zip file from `builds/metamask-$PLATFORM-$VERSION.zip` as the updated package.
|
3. Upload the latest zip file from `builds/metamask-$PLATFORM-$VERSION.zip` as the updated package.
|
||||||
|
|
||||||
[1]: http://www.nomnoml.com/#view/%5B%3Cactor%3Euser%5D%0A%0A%5Bmetamask-ui%7C%0A%20%20%20%5Btools%7C%0A%20%20%20%20%20react%0A%20%20%20%20%20redux%0A%20%20%20%20%20thunk%0A%20%20%20%20%20ethUtils%0A%20%20%20%20%20jazzicon%0A%20%20%20%5D%0A%20%20%20%5Bcomponents%7C%0A%20%20%20%20%20app%0A%20%20%20%20%20account-detail%0A%20%20%20%20%20accounts%0A%20%20%20%20%20locked-screen%0A%20%20%20%20%20restore-vault%0A%20%20%20%20%20identicon%0A%20%20%20%20%20config%0A%20%20%20%20%20info%0A%20%20%20%5D%0A%20%20%20%5Breducers%7C%0A%20%20%20%20%20app%0A%20%20%20%20%20metamask%0A%20%20%20%20%20identities%0A%20%20%20%5D%0A%20%20%20%5Bactions%7C%0A%20%20%20%20%20%5BaccountManager%5D%0A%20%20%20%5D%0A%20%20%20%5Bcomponents%5D%3A-%3E%5Bactions%5D%0A%20%20%20%5Bactions%5D%3A-%3E%5Breducers%5D%0A%20%20%20%5Breducers%5D%3A-%3E%5Bcomponents%5D%0A%5D%0A%0A%5Bweb%20dapp%7C%0A%20%20%5Bui%20code%5D%0A%20%20%5Bweb3%5D%0A%20%20%5Bmetamask-inpage%5D%0A%20%20%0A%20%20%5B%3Cactor%3Eui%20developer%5D%0A%20%20%5Bui%20developer%5D-%3E%5Bui%20code%5D%0A%20%20%5Bui%20code%5D%3C-%3E%5Bweb3%5D%0A%20%20%5Bweb3%5D%3C-%3E%5Bmetamask-inpage%5D%0A%5D%0A%0A%5Bmetamask-background%7C%0A%20%20%5Bprovider-engine%5D%0A%20%20%5Bhooked%20wallet%20subprovider%5D%0A%20%20%5Bid%20store%5D%0A%20%20%0A%20%20%5Bprovider-engine%5D%3C-%3E%5Bhooked%20wallet%20subprovider%5D%0A%20%20%5Bhooked%20wallet%20subprovider%5D%3C-%3E%5Bid%20store%5D%0A%20%20%5Bconfig%20manager%7C%0A%20%20%20%20%5Brpc%20configuration%5D%0A%20%20%20%20%5Bencrypted%20keys%5D%0A%20%20%20%20%5Bwallet%20nicknames%5D%0A%20%20%5D%0A%20%20%0A%20%20%5Bprovider-engine%5D%3C-%5Bconfig%20manager%5D%0A%20%20%5Bid%20store%5D%3C-%3E%5Bconfig%20manager%5D%0A%5D%0A%0A%5Buser%5D%3C-%3E%5Bmetamask-ui%5D%0A%0A%5Buser%5D%3C%3A--%3A%3E%5Bweb%20dapp%5D%0A%0A%5Bmetamask-contentscript%7C%0A%20%20%5Bplugin%20restart%20detector%5D%0A%20%20%5Brpc%20passthrough%5D%0A%5D%0A%0A%5Brpc%20%7C%0A%20%20%5Bethereum%20blockchain%20%7C%0A%20%20%20%20%5Bcontracts%5D%0A%20%20%20%20%5Baccounts%5D%0A%20%20%5D%0A%5D%0A%0A%5Bweb%20dapp%5D%3C%3A--%3A%3E%5Bmetamask-contentscript%5D%0A%5Bmetamask-contentscript%5D%3C-%3E%5Bmetamask-background%5D%0A%5Bmetamask-background%5D%3C-%3E%5Bmetamask-ui%5D%0A%5Bmetamask-background%5D%3C-%3E%5Brpc%5D%0A
|
[1]: http://www.nomnoml.com/#view/%5B%3Cactor%3Euser%5D%0A%0A%5Bmetamask-ui%7C%0A%20%20%20%5Btools%7C%0A%20%20%20%20%20react%0A%20%20%20%20%20redux%0A%20%20%20%20%20thunk%0A%20%20%20%20%20ethUtils%0A%20%20%20%20%20jazzicon%0A%20%20%20%5D%0A%20%20%20%5Bcomponents%7C%0A%20%20%20%20%20app%0A%20%20%20%20%20account-detail%0A%20%20%20%20%20accounts%0A%20%20%20%20%20locked-screen%0A%20%20%20%20%20restore-vault%0A%20%20%20%20%20identicon%0A%20%20%20%20%20config%0A%20%20%20%20%20info%0A%20%20%20%5D%0A%20%20%20%5Breducers%7C%0A%20%20%20%20%20app%0A%20%20%20%20%20metamask%0A%20%20%20%20%20identities%0A%20%20%20%5D%0A%20%20%20%5Bactions%7C%0A%20%20%20%20%20%5BaccountManager%5D%0A%20%20%20%5D%0A%20%20%20%5Bcomponents%5D%3A-%3E%5Bactions%5D%0A%20%20%20%5Bactions%5D%3A-%3E%5Breducers%5D%0A%20%20%20%5Breducers%5D%3A-%3E%5Bcomponents%5D%0A%5D%0A%0A%5Bweb%20dapp%7C%0A%20%20%5Bui%20code%5D%0A%20%20%5Bweb3%5D%0A%20%20%5Bmetamask-inpage%5D%0A%20%20%0A%20%20%5B%3Cactor%3Eui%20developer%5D%0A%20%20%5Bui%20developer%5D-%3E%5Bui%20code%5D%0A%20%20%5Bui%20code%5D%3C-%3E%5Bweb3%5D%0A%20%20%5Bweb3%5D%3C-%3E%5Bmetamask-inpage%5D%0A%5D%0A%0A%5Bmetamask-background%7C%0A%20%20%5Bprovider-engine%5D%0A%20%20%5Bhooked%20wallet%20subprovider%5D%0A%20%20%5Bid%20store%5D%0A%20%20%0A%20%20%5Bprovider-engine%5D%3C-%3E%5Bhooked%20wallet%20subprovider%5D%0A%20%20%5Bhooked%20wallet%20subprovider%5D%3C-%3E%5Bid%20store%5D%0A%20%20%5Bconfig%20manager%7C%0A%20%20%20%20%5Brpc%20configuration%5D%0A%20%20%20%20%5Bencrypted%20keys%5D%0A%20%20%20%20%5Bwallet%20nicknames%5D%0A%20%20%5D%0A%20%20%0A%20%20%5Bprovider-engine%5D%3C-%5Bconfig%20manager%5D%0A%20%20%5Bid%20store%5D%3C-%3E%5Bconfig%20manager%5D%0A%5D%0A%0A%5Buser%5D%3C-%3E%5Bmetamask-ui%5D%0A%0A%5Buser%5D%3C%3A--%3A%3E%5Bweb%20dapp%5D%0A%0A%5Bmetamask-contentscript%7C%0A%20%20%5Bplugin%20restart%20detector%5D%0A%20%20%5Brpc%20passthrough%5D%0A%5D%0A%0A%5Brpc%20%7C%0A%20%20%5Bethereum%20blockchain%20%7C%0A%20%20%20%20%5Bcontracts%5D%0A%20%20%20%20%5Baccounts%5D%0A%20%20%5D%0A%5D%0A%0A%5Bweb%20dapp%5D%3C%3A--%3A%3E%5Bmetamask-contentscript%5D%0A%5Bmetamask-contentscript%5D%3C-%3E%5Bmetamask-background%5D%0A%5Bmetamask-background%5D%3C-%3E%5Bmetamask-ui%5D%0A%5Bmetamask-background%5D%3C-%3E%5Brpc%5D%0A
|
||||||
|
|
||||||
|
|
||||||
|
### Generate Development Visualization
|
||||||
|
|
||||||
|
This will generate a video of the repo commit history.
|
||||||
|
|
||||||
|
Install preqs:
|
||||||
|
```
|
||||||
|
brew install gource
|
||||||
|
brew install ffmpeg
|
||||||
|
```
|
||||||
|
|
||||||
|
From the repo dir, pipe `gource` into `ffmpeg`:
|
||||||
|
```
|
||||||
|
gource \
|
||||||
|
--seconds-per-day .1 \
|
||||||
|
--user-scale 1.5 \
|
||||||
|
--default-user-image "./images/icon-512.png" \
|
||||||
|
--viewport 1280x720 \
|
||||||
|
--auto-skip-seconds .1 \
|
||||||
|
--multi-sampling \
|
||||||
|
--stop-at-end \
|
||||||
|
--highlight-users \
|
||||||
|
--hide mouse,progress \
|
||||||
|
--file-idle-time 0 \
|
||||||
|
--max-files 0 \
|
||||||
|
--background-colour 000000 \
|
||||||
|
--font-size 18 \
|
||||||
|
--date-format "%b %d, %Y" \
|
||||||
|
--highlight-dirs \
|
||||||
|
--user-friction 0.1 \
|
||||||
|
--title "MetaMask Development History" \
|
||||||
|
--output-ppm-stream - \
|
||||||
|
--output-framerate 30 \
|
||||||
|
| ffmpeg -y -r 30 -f image2pipe -vcodec ppm -i - -b 65536K metamask-dev-history.mp4
|
||||||
|
```
|
||||||
|
@ -17,13 +17,13 @@ const controller = new MetamaskController({
|
|||||||
// User confirmation callbacks:
|
// User confirmation callbacks:
|
||||||
showUnconfirmedMessage: triggerUi,
|
showUnconfirmedMessage: triggerUi,
|
||||||
unlockAccountMessage: triggerUi,
|
unlockAccountMessage: triggerUi,
|
||||||
showUnconfirmedTx: triggerUi,
|
showUnapprovedTx: triggerUi,
|
||||||
// Persistence Methods:
|
// Persistence Methods:
|
||||||
setData,
|
setData,
|
||||||
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,15 +97,14 @@ function setupControllerConnection (stream) {
|
|||||||
// plugin badge text
|
// plugin badge text
|
||||||
//
|
//
|
||||||
|
|
||||||
keyringController.on('update', updateBadge)
|
txManager.on('updateBadge', updateBadge)
|
||||||
|
|
||||||
function updateBadge () {
|
function updateBadge () {
|
||||||
var label = ''
|
var label = ''
|
||||||
var unconfTxs = controller.configManager.unconfirmedTxs()
|
var unapprovedTxCount = controller.txManager.unapprovedTxCount
|
||||||
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 = unapprovedTxCount + unconfMsgLen
|
||||||
if (count) {
|
if (count) {
|
||||||
label = String(count)
|
label = String(count)
|
||||||
}
|
}
|
||||||
@ -113,6 +112,8 @@ function updateBadge () {
|
|||||||
extension.browserAction.setBadgeBackgroundColor({ color: '#506F8B' })
|
extension.browserAction.setBadgeBackgroundColor({ color: '#506F8B' })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// data :: setters/getters
|
||||||
|
|
||||||
function loadData () {
|
function loadData () {
|
||||||
var oldData = getOldStyleData()
|
var oldData = getOldStyleData()
|
||||||
var newData
|
var newData
|
||||||
|
@ -1,8 +1,5 @@
|
|||||||
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 EventEmitter = require('events').EventEmitter
|
const EventEmitter = require('events').EventEmitter
|
||||||
const filter = require('promise-filter')
|
const filter = require('promise-filter')
|
||||||
const encryptor = require('browser-passworder')
|
const encryptor = require('browser-passworder')
|
||||||
@ -36,11 +33,9 @@ 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.keyrings = []
|
this.keyrings = []
|
||||||
this.identities = {} // Essentially a name hash
|
this.identities = {} // Essentially a name hash
|
||||||
|
|
||||||
this._unconfTxCbs = {}
|
|
||||||
this._unconfMsgCbs = {}
|
this._unconfMsgCbs = {}
|
||||||
|
|
||||||
this.getNetwork = opts.getNetwork
|
this.getNetwork = opts.getNetwork
|
||||||
@ -319,202 +314,18 @@ module.exports = class KeyringController extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// SIGNING RELATED METHODS
|
// SIGNING METHODS
|
||||||
//
|
//
|
||||||
// SIGN, SUBMIT TX, CANCEL, AND APPROVE.
|
// This method signs tx and returns a promise for
|
||||||
// THIS SECTION INVOLVES THE REQUEST, STORING, AND SIGNING OF DATA
|
// TX Manager to update the state after signing
|
||||||
// WITH THE KEYS STORED IN THIS CONTROLLER.
|
signTransaction (ethTx, selectedAddress, txId, cb) {
|
||||||
|
|
||||||
|
|
||||||
// Add Unconfirmed Transaction
|
|
||||||
// @object txParams
|
|
||||||
// @function onTxDoneCb
|
|
||||||
// @function cb
|
|
||||||
//
|
|
||||||
// Calls back `cb` with @object txData = { txParams }
|
|
||||||
// Calls back `onTxDoneCb` with `true` or an `error` depending on result.
|
|
||||||
//
|
|
||||||
// Prepares the given `txParams` for final confirmation and approval.
|
|
||||||
// Estimates gas and other preparatory steps.
|
|
||||||
// Caches the requesting Dapp's callback, `onTxDoneCb`, for resolution later.
|
|
||||||
addUnconfirmedTransaction (txParams, onTxDoneCb, cb) {
|
|
||||||
const configManager = this.configManager
|
|
||||||
|
|
||||||
// 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: 'unconfirmed',
|
|
||||||
gasMultiplier: configManager.getGasMultiplier() || 1,
|
|
||||||
metamaskNetworkId: this.getNetwork(),
|
|
||||||
}
|
|
||||||
|
|
||||||
// keep the onTxDoneCb around for after approval/denial (requires user interaction)
|
|
||||||
// This onTxDoneCb fires completion to the Dapp's write operation.
|
|
||||||
this._unconfTxCbs[txId] = onTxDoneCb
|
|
||||||
|
|
||||||
var provider = this.ethStore._query.currentProvider
|
|
||||||
var query = new EthQuery(provider)
|
|
||||||
|
|
||||||
// calculate metadata for tx
|
|
||||||
this.analyzeTxGasUsage(query, txData, this.txDidComplete.bind(this, txData, cb))
|
|
||||||
}
|
|
||||||
|
|
||||||
estimateTxGas (query, 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
|
|
||||||
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)
|
|
||||||
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)
|
|
||||||
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
|
|
||||||
// @string txId
|
|
||||||
// @function cb
|
|
||||||
//
|
|
||||||
// Calls back `cb` with no error if provided.
|
|
||||||
//
|
|
||||||
// Forgets any tx matching `txId`.
|
|
||||||
cancelTransaction (txId, cb) {
|
|
||||||
const configManager = this.configManager
|
|
||||||
var approvalCb = this._unconfTxCbs[txId] || noop
|
|
||||||
|
|
||||||
// reject tx
|
|
||||||
approvalCb(null, false)
|
|
||||||
// clean up
|
|
||||||
configManager.rejectTx(txId)
|
|
||||||
delete this._unconfTxCbs[txId]
|
|
||||||
|
|
||||||
if (cb && typeof cb === 'function') {
|
|
||||||
cb()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Approve Transaction
|
|
||||||
// @string txId
|
|
||||||
// @function cb
|
|
||||||
//
|
|
||||||
// Calls back `cb` with no error always.
|
|
||||||
//
|
|
||||||
// Attempts to sign a Transaction with `txId`
|
|
||||||
// and submit it to the blockchain.
|
|
||||||
//
|
|
||||||
// Calls back the cached Dapp's confirmation callback, also.
|
|
||||||
approveTransaction (txId, cb) {
|
|
||||||
const configManager = this.configManager
|
|
||||||
var approvalCb = this._unconfTxCbs[txId] || noop
|
|
||||||
|
|
||||||
// accept tx
|
|
||||||
cb()
|
|
||||||
approvalCb(null, true)
|
|
||||||
// clean up
|
|
||||||
configManager.confirmTx(txId)
|
|
||||||
delete this._unconfTxCbs[txId]
|
|
||||||
this.emit('update')
|
|
||||||
}
|
|
||||||
|
|
||||||
signTransaction (txParams, cb) {
|
|
||||||
try {
|
try {
|
||||||
const address = normalize(txParams.from)
|
const address = normalize(selectedAddress)
|
||||||
return this.getKeyringForAccount(address)
|
return this.getKeyringForAccount(address)
|
||||||
.then((keyring) => {
|
.then((keyring) => {
|
||||||
// Handle gas pricing
|
return keyring.signTransaction(address, ethTx)
|
||||||
var gasMultiplier = this.configManager.getGasMultiplier() || 1
|
}).then((tx) => {
|
||||||
var gasPrice = new BN(ethUtil.stripHexPrefix(txParams.gasPrice), 16)
|
this.emit(`${txId}:signed`, {tx, txId, cb})
|
||||||
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 tx = new Transaction(txParams)
|
|
||||||
return keyring.signTransaction(address, tx)
|
|
||||||
})
|
|
||||||
.then((tx) => {
|
|
||||||
// Add the tx hash to the persisted meta-tx object
|
|
||||||
var txHash = ethUtil.bufferToHex(tx.hash())
|
|
||||||
var metaTx = this.configManager.getTx(txParams.metamaskId)
|
|
||||||
metaTx.hash = txHash
|
|
||||||
this.configManager.updateTx(metaTx)
|
|
||||||
|
|
||||||
// return raw serialized tx
|
|
||||||
var rawTx = ethUtil.bufferToHex(tx.serialize())
|
|
||||||
cb(null, rawTx)
|
|
||||||
})
|
})
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
cb(e)
|
cb(e)
|
||||||
|
@ -8,7 +8,6 @@ const normalize = require('./sig-util').normalize
|
|||||||
const TESTNET_RPC = MetamaskConfig.network.testnet
|
const TESTNET_RPC = MetamaskConfig.network.testnet
|
||||||
const MAINNET_RPC = MetamaskConfig.network.mainnet
|
const MAINNET_RPC = MetamaskConfig.network.mainnet
|
||||||
const MORDEN_RPC = MetamaskConfig.network.morden
|
const MORDEN_RPC = MetamaskConfig.network.morden
|
||||||
const txLimit = 40
|
|
||||||
|
|
||||||
/* The config-manager is a convenience object
|
/* The config-manager is a convenience object
|
||||||
* wrapping a pojo-migrator.
|
* wrapping a pojo-migrator.
|
||||||
@ -19,8 +18,6 @@ const txLimit = 40
|
|||||||
*/
|
*/
|
||||||
module.exports = ConfigManager
|
module.exports = ConfigManager
|
||||||
function ConfigManager (opts) {
|
function ConfigManager (opts) {
|
||||||
this.txLimit = txLimit
|
|
||||||
|
|
||||||
// ConfigManager is observable and will emit updates
|
// ConfigManager is observable and will emit updates
|
||||||
this._subs = []
|
this._subs = []
|
||||||
|
|
||||||
@ -209,61 +206,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
|
||||||
|
|
||||||
|
@ -1,19 +1,14 @@
|
|||||||
const EventEmitter = require('events').EventEmitter
|
const EventEmitter = require('events').EventEmitter
|
||||||
const inherits = require('util').inherits
|
const inherits = require('util').inherits
|
||||||
const async = require('async')
|
|
||||||
const ethUtil = require('ethereumjs-util')
|
const ethUtil = require('ethereumjs-util')
|
||||||
const BN = ethUtil.BN
|
|
||||||
const EthQuery = require('eth-query')
|
|
||||||
const KeyStore = require('eth-lightwallet').keystore
|
const KeyStore = require('eth-lightwallet').keystore
|
||||||
const clone = require('clone')
|
const clone = require('clone')
|
||||||
const extend = require('xtend')
|
const extend = require('xtend')
|
||||||
const createId = require('./random-id')
|
|
||||||
const ethBinToOps = require('eth-bin-to-ops')
|
|
||||||
const autoFaucet = require('./auto-faucet')
|
const autoFaucet = require('./auto-faucet')
|
||||||
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')
|
||||||
|
|
||||||
|
|
||||||
module.exports = IdentityStore
|
module.exports = IdentityStore
|
||||||
|
|
||||||
inherits(IdentityStore, EventEmitter)
|
inherits(IdentityStore, EventEmitter)
|
||||||
@ -34,10 +29,7 @@ function IdentityStore (opts = {}) {
|
|||||||
selectedAddress: null,
|
selectedAddress: null,
|
||||||
identities: {},
|
identities: {},
|
||||||
}
|
}
|
||||||
|
|
||||||
// not part of serilized metamask state - only kept in memory
|
// not part of serilized metamask state - only kept in memory
|
||||||
this._unconfTxCbs = {}
|
|
||||||
this._unconfMsgCbs = {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
@ -103,10 +95,6 @@ IdentityStore.prototype.getState = function () {
|
|||||||
isUnlocked: this._isUnlocked(),
|
isUnlocked: this._isUnlocked(),
|
||||||
seedWords: seedWords,
|
seedWords: seedWords,
|
||||||
isDisclaimerConfirmed: configManager.getConfirmedDisclaimer(),
|
isDisclaimerConfirmed: configManager.getConfirmedDisclaimer(),
|
||||||
unconfTxs: configManager.unconfirmedTxs(),
|
|
||||||
transactions: configManager.getTxList(),
|
|
||||||
unconfMsgs: messageManager.unconfirmedMsgs(),
|
|
||||||
messages: messageManager.getMsgList(),
|
|
||||||
selectedAddress: configManager.getSelectedAccount(),
|
selectedAddress: configManager.getSelectedAccount(),
|
||||||
shapeShiftTxList: configManager.getShapeShiftTxList(),
|
shapeShiftTxList: configManager.getShapeShiftTxList(),
|
||||||
currentFiat: configManager.getCurrentFiat(),
|
currentFiat: configManager.getCurrentFiat(),
|
||||||
@ -206,245 +194,6 @@ IdentityStore.prototype.exportAccount = function (address, cb) {
|
|||||||
return privateKey
|
return privateKey
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
|
||||||
// Transactions
|
|
||||||
//
|
|
||||||
|
|
||||||
// comes from dapp via zero-client hooked-wallet provider
|
|
||||||
IdentityStore.prototype.addUnconfirmedTransaction = function (txParams, onTxDoneCb, cb) {
|
|
||||||
const configManager = this.configManager
|
|
||||||
|
|
||||||
var self = this
|
|
||||||
// create txData obj with parameters and meta data
|
|
||||||
var time = (new Date()).getTime()
|
|
||||||
var txId = createId()
|
|
||||||
txParams.metamaskId = txId
|
|
||||||
txParams.metamaskNetworkId = self._currentState.network
|
|
||||||
var txData = {
|
|
||||||
id: txId,
|
|
||||||
txParams: txParams,
|
|
||||||
time: time,
|
|
||||||
status: 'unconfirmed',
|
|
||||||
gasMultiplier: configManager.getGasMultiplier() || 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('addUnconfirmedTransaction:', txData)
|
|
||||||
|
|
||||||
// keep the onTxDoneCb around for after approval/denial (requires user interaction)
|
|
||||||
// This onTxDoneCb fires completion to the Dapp's write operation.
|
|
||||||
self._unconfTxCbs[txId] = onTxDoneCb
|
|
||||||
|
|
||||||
var provider = self._ethStore._query.currentProvider
|
|
||||||
var query = new EthQuery(provider)
|
|
||||||
|
|
||||||
// calculate metadata for tx
|
|
||||||
async.parallel([
|
|
||||||
analyzeForDelegateCall,
|
|
||||||
estimateGas,
|
|
||||||
], didComplete)
|
|
||||||
|
|
||||||
// perform static analyis on the target contract code
|
|
||||||
function analyzeForDelegateCall (cb) {
|
|
||||||
if (txParams.to) {
|
|
||||||
query.getCode(txParams.to, (err, result) => {
|
|
||||||
if (err) return cb(err.message || err)
|
|
||||||
var containsDelegateCall = self.checkForDelegateCall(result)
|
|
||||||
txData.containsDelegateCall = containsDelegateCall
|
|
||||||
cb()
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
cb()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function estimateGas (cb) {
|
|
||||||
var estimationParams = extend(txParams)
|
|
||||||
query.getBlockByNumber('latest', true, function (err, block) {
|
|
||||||
if (err) return cb(err)
|
|
||||||
// check if gasLimit is already specified
|
|
||||||
const gasLimitSpecified = Boolean(txParams.gas)
|
|
||||||
// if not, fallback to block gasLimit
|
|
||||||
if (!gasLimitSpecified) {
|
|
||||||
estimationParams.gas = block.gasLimit
|
|
||||||
}
|
|
||||||
// run tx, see if it will OOG
|
|
||||||
query.estimateGas(estimationParams, function (err, estimatedGasHex) {
|
|
||||||
if (err) return cb(err.message || err)
|
|
||||||
// all gas used - must be an error
|
|
||||||
if (estimatedGasHex === estimationParams.gas) {
|
|
||||||
txData.simulationFails = true
|
|
||||||
txData.estimatedGas = estimatedGasHex
|
|
||||||
txData.txParams.gas = estimatedGasHex
|
|
||||||
cb()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// otherwise, did not use all gas, must be ok
|
|
||||||
|
|
||||||
// if specified gasLimit and no error, we're done
|
|
||||||
if (gasLimitSpecified) {
|
|
||||||
txData.estimatedGas = txParams.gas
|
|
||||||
cb()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// try adding an additional gas buffer to our estimation for safety
|
|
||||||
const estimatedGasBn = new BN(ethUtil.stripHexPrefix(estimatedGasHex), 16)
|
|
||||||
const blockGasLimitBn = new BN(ethUtil.stripHexPrefix(block.gasLimit), 16)
|
|
||||||
const estimationWithBuffer = self.addGasBuffer(estimatedGasBn)
|
|
||||||
// added gas buffer is too high
|
|
||||||
if (estimationWithBuffer.gt(blockGasLimitBn)) {
|
|
||||||
txData.estimatedGas = estimatedGasHex
|
|
||||||
txData.txParams.gas = estimatedGasHex
|
|
||||||
// added gas buffer is safe
|
|
||||||
} else {
|
|
||||||
const gasWithBufferHex = ethUtil.intToHex(estimationWithBuffer)
|
|
||||||
txData.estimatedGas = gasWithBufferHex
|
|
||||||
txData.txParams.gas = gasWithBufferHex
|
|
||||||
}
|
|
||||||
cb()
|
|
||||||
return
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function didComplete (err) {
|
|
||||||
if (err) return cb(err.message || err)
|
|
||||||
configManager.addTx(txData)
|
|
||||||
// signal update
|
|
||||||
self._didUpdate()
|
|
||||||
// signal completion of add tx
|
|
||||||
cb(null, txData)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
IdentityStore.prototype.checkForDelegateCall = function (codeHex) {
|
|
||||||
const code = ethUtil.toBuffer(codeHex)
|
|
||||||
if (code !== '0x') {
|
|
||||||
const ops = ethBinToOps(code)
|
|
||||||
const containsDelegateCall = ops.some((op) => op.name === 'DELEGATECALL')
|
|
||||||
return containsDelegateCall
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
IdentityStore.prototype.addGasBuffer = function (gasBn) {
|
|
||||||
// add 20% to specified gas
|
|
||||||
const gasBuffer = gasBn.div(new BN('5', 10))
|
|
||||||
const gasWithBuffer = gasBn.add(gasBuffer)
|
|
||||||
return gasWithBuffer
|
|
||||||
}
|
|
||||||
|
|
||||||
// comes from metamask ui
|
|
||||||
IdentityStore.prototype.approveTransaction = function (txId, cb) {
|
|
||||||
const configManager = this.configManager
|
|
||||||
var approvalCb = this._unconfTxCbs[txId] || noop
|
|
||||||
|
|
||||||
// accept tx
|
|
||||||
cb()
|
|
||||||
approvalCb(null, true)
|
|
||||||
// clean up
|
|
||||||
configManager.confirmTx(txId)
|
|
||||||
delete this._unconfTxCbs[txId]
|
|
||||||
this._didUpdate()
|
|
||||||
}
|
|
||||||
|
|
||||||
// comes from metamask ui
|
|
||||||
IdentityStore.prototype.cancelTransaction = function (txId) {
|
|
||||||
const configManager = this.configManager
|
|
||||||
var approvalCb = this._unconfTxCbs[txId] || noop
|
|
||||||
|
|
||||||
// reject tx
|
|
||||||
approvalCb(null, false)
|
|
||||||
// clean up
|
|
||||||
configManager.rejectTx(txId)
|
|
||||||
delete this._unconfTxCbs[txId]
|
|
||||||
this._didUpdate()
|
|
||||||
}
|
|
||||||
|
|
||||||
// performs the actual signing, no autofill of params
|
|
||||||
IdentityStore.prototype.signTransaction = function (txParams, cb) {
|
|
||||||
try {
|
|
||||||
console.log('signing tx...', txParams)
|
|
||||||
var rawTx = this._idmgmt.signTx(txParams)
|
|
||||||
cb(null, rawTx)
|
|
||||||
} catch (err) {
|
|
||||||
cb(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Messages
|
|
||||||
//
|
|
||||||
|
|
||||||
// comes from dapp via zero-client hooked-wallet provider
|
|
||||||
IdentityStore.prototype.addUnconfirmedMessage = function (msgParams, cb) {
|
|
||||||
// create txData obj with parameters and meta data
|
|
||||||
var time = (new Date()).getTime()
|
|
||||||
var msgId = createId()
|
|
||||||
var msgData = {
|
|
||||||
id: msgId,
|
|
||||||
msgParams: msgParams,
|
|
||||||
time: time,
|
|
||||||
status: 'unconfirmed',
|
|
||||||
}
|
|
||||||
messageManager.addMsg(msgData)
|
|
||||||
console.log('addUnconfirmedMessage:', msgData)
|
|
||||||
|
|
||||||
// keep the cb around for after approval (requires user interaction)
|
|
||||||
// This cb fires completion to the Dapp's write operation.
|
|
||||||
this._unconfMsgCbs[msgId] = cb
|
|
||||||
|
|
||||||
// signal update
|
|
||||||
this._didUpdate()
|
|
||||||
|
|
||||||
return msgId
|
|
||||||
}
|
|
||||||
|
|
||||||
// comes from metamask ui
|
|
||||||
IdentityStore.prototype.approveMessage = function (msgId, cb) {
|
|
||||||
var approvalCb = this._unconfMsgCbs[msgId] || noop
|
|
||||||
|
|
||||||
// accept msg
|
|
||||||
cb()
|
|
||||||
approvalCb(null, true)
|
|
||||||
// clean up
|
|
||||||
messageManager.confirmMsg(msgId)
|
|
||||||
delete this._unconfMsgCbs[msgId]
|
|
||||||
this._didUpdate()
|
|
||||||
}
|
|
||||||
|
|
||||||
// comes from metamask ui
|
|
||||||
IdentityStore.prototype.cancelMessage = function (msgId) {
|
|
||||||
var approvalCb = this._unconfMsgCbs[msgId] || noop
|
|
||||||
|
|
||||||
// reject tx
|
|
||||||
approvalCb(null, false)
|
|
||||||
// clean up
|
|
||||||
messageManager.rejectMsg(msgId)
|
|
||||||
delete this._unconfTxCbs[msgId]
|
|
||||||
this._didUpdate()
|
|
||||||
}
|
|
||||||
|
|
||||||
// performs the actual signing, no autofill of params
|
|
||||||
IdentityStore.prototype.signMessage = function (msgParams, cb) {
|
|
||||||
try {
|
|
||||||
console.log('signing msg...', msgParams.data)
|
|
||||||
var rawMsg = this._idmgmt.signMsg(msgParams.from, msgParams.data)
|
|
||||||
if ('metamaskId' in msgParams) {
|
|
||||||
var id = msgParams.metamaskId
|
|
||||||
delete msgParams.metamaskId
|
|
||||||
|
|
||||||
this.approveMessage(id, cb)
|
|
||||||
} else {
|
|
||||||
cb(null, rawMsg)
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
cb(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// private
|
// private
|
||||||
//
|
//
|
||||||
|
|
||||||
@ -599,4 +348,3 @@ IdentityStore.prototype._autoFaucet = function () {
|
|||||||
|
|
||||||
// util
|
// util
|
||||||
|
|
||||||
function noop () {}
|
|
||||||
|
87
app/scripts/lib/tx-utils.js
Normal file
87
app/scripts/lib/tx-utils.js
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
const async = require('async')
|
||||||
|
const EthQuery = require('eth-query')
|
||||||
|
const ethUtil = require('ethereumjs-util')
|
||||||
|
const BN = ethUtil.BN
|
||||||
|
|
||||||
|
/*
|
||||||
|
tx-utils are utility methods for Transaction manager
|
||||||
|
its passed a provider and that is passed to ethquery
|
||||||
|
and used to do things like calculate gas of a tx.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
}
|
@ -4,6 +4,7 @@ const MetaMaskProvider = require('web3-provider-engine/zero.js')
|
|||||||
const KeyringController = require('./keyring-controller')
|
const KeyringController = require('./keyring-controller')
|
||||||
const NoticeController = require('./notice-controller')
|
const NoticeController = require('./notice-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')
|
||||||
@ -12,7 +13,6 @@ const autoFaucet = require('./lib/auto-faucet')
|
|||||||
const nodeify = require('./lib/nodeify')
|
const nodeify = require('./lib/nodeify')
|
||||||
const IdStoreMigrator = require('./lib/idStore-migrator')
|
const IdStoreMigrator = require('./lib/idStore-migrator')
|
||||||
|
|
||||||
|
|
||||||
module.exports = class MetamaskController {
|
module.exports = class MetamaskController {
|
||||||
|
|
||||||
constructor (opts) {
|
constructor (opts) {
|
||||||
@ -36,6 +36,16 @@ module.exports = class MetamaskController {
|
|||||||
this.keyringController.setStore(this.ethStore)
|
this.keyringController.setStore(this.ethStore)
|
||||||
this.getNetwork()
|
this.getNetwork()
|
||||||
this.messageManager = messageManager
|
this.messageManager = messageManager
|
||||||
|
this.txManager = new TxManager({
|
||||||
|
txList: this.configManager.getTxList(),
|
||||||
|
txHistoryLimit: 40,
|
||||||
|
setTxList: this.configManager.setTxList.bind(this.configManager),
|
||||||
|
getSelectedAccount: this.configManager.getSelectedAccount.bind(this.configManager),
|
||||||
|
getGasMultiplier: this.configManager.getGasMultiplier.bind(this.configManager),
|
||||||
|
getNetwork: this.getStateNetwork.bind(this),
|
||||||
|
provider: this.provider,
|
||||||
|
blockTracker: this.provider,
|
||||||
|
})
|
||||||
this.publicConfigStore = this.initPublicConfigStore()
|
this.publicConfigStore = this.initPublicConfigStore()
|
||||||
|
|
||||||
var currentFiat = this.configManager.getCurrentFiat() || 'USD'
|
var currentFiat = this.configManager.getCurrentFiat() || 'USD'
|
||||||
@ -69,6 +79,7 @@ module.exports = class MetamaskController {
|
|||||||
|
|
||||||
getApi () {
|
getApi () {
|
||||||
const keyringController = this.keyringController
|
const keyringController = this.keyringController
|
||||||
|
const txManager = this.txManager
|
||||||
const noticeController = this.noticeController
|
const noticeController = this.noticeController
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -103,8 +114,8 @@ module.exports = class MetamaskController {
|
|||||||
exportAccount: nodeify(keyringController.exportAccount).bind(keyringController),
|
exportAccount: nodeify(keyringController.exportAccount).bind(keyringController),
|
||||||
|
|
||||||
// signing methods
|
// signing methods
|
||||||
approveTransaction: keyringController.approveTransaction.bind(keyringController),
|
approveTransaction: txManager.approveTransaction.bind(txManager),
|
||||||
cancelTransaction: keyringController.cancelTransaction.bind(keyringController),
|
cancelTransaction: txManager.cancelTransaction.bind(txManager),
|
||||||
signMessage: keyringController.signMessage.bind(keyringController),
|
signMessage: keyringController.signMessage.bind(keyringController),
|
||||||
cancelMessage: keyringController.cancelMessage.bind(keyringController),
|
cancelMessage: keyringController.cancelMessage.bind(keyringController),
|
||||||
|
|
||||||
@ -168,7 +179,8 @@ module.exports = class MetamaskController {
|
|||||||
// tx signing
|
// tx signing
|
||||||
approveTransaction: this.newUnsignedTransaction.bind(this),
|
approveTransaction: this.newUnsignedTransaction.bind(this),
|
||||||
signTransaction: (...args) => {
|
signTransaction: (...args) => {
|
||||||
keyringController.signTransaction(...args)
|
this.setupSigningListners(...args)
|
||||||
|
this.txManager.formatTxForSigining(...args)
|
||||||
this.sendUpdate()
|
this.sendUpdate()
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -184,7 +196,6 @@ 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
|
||||||
|
|
||||||
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))
|
||||||
|
|
||||||
@ -238,16 +249,23 @@ module.exports = class MetamaskController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
newUnsignedTransaction (txParams, onTxDoneCb) {
|
newUnsignedTransaction (txParams, onTxDoneCb) {
|
||||||
const keyringController = this.keyringController
|
const txManager = this.txManager
|
||||||
const err = this.enforceTxValidations(txParams)
|
const err = this.enforceTxValidations(txParams)
|
||||||
if (err) return onTxDoneCb(err)
|
if (err) return onTxDoneCb(err)
|
||||||
keyringController.addUnconfirmedTransaction(txParams, onTxDoneCb, (err, txData) => {
|
txManager.addUnapprovedTransaction(txParams, onTxDoneCb, (err, txData) => {
|
||||||
if (err) return onTxDoneCb(err)
|
if (err) return onTxDoneCb(err)
|
||||||
this.sendUpdate()
|
this.sendUpdate()
|
||||||
this.opts.showUnconfirmedTx(txParams, txData, onTxDoneCb)
|
this.opts.showUnapprovedTx(txParams, txData, onTxDoneCb)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setupSigningListners (txParams) {
|
||||||
|
var txId = txParams.metamaskId
|
||||||
|
// apply event listeners for signing and formating events
|
||||||
|
this.txManager.once(`${txId}:formatted`, this.keyringController.signTransaction.bind(this.keyringController))
|
||||||
|
this.keyringController.once(`${txId}:signed`, this.txManager.resolveSignedTransaction.bind(this.txManager))
|
||||||
|
}
|
||||||
|
|
||||||
enforceTxValidations (txParams) {
|
enforceTxValidations (txParams) {
|
||||||
if (('value' in txParams) && txParams.value.indexOf('-') === 0) {
|
if (('value' in txParams) && txParams.value.indexOf('-') === 0) {
|
||||||
const msg = `Invalid transaction value of ${txParams.value} not a positive number.`
|
const msg = `Invalid transaction value of ${txParams.value} not a positive number.`
|
||||||
|
288
app/scripts/transaction-manager.js
Normal file
288
app/scripts/transaction-manager.js
Normal file
@ -0,0 +1,288 @@
|
|||||||
|
const EventEmitter = require('events')
|
||||||
|
const extend = require('xtend')
|
||||||
|
const ethUtil = require('ethereumjs-util')
|
||||||
|
const Transaction = require('ethereumjs-tx')
|
||||||
|
const BN = ethUtil.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) {
|
||||||
|
super()
|
||||||
|
this.txList = opts.txList || []
|
||||||
|
this._setTxList = opts.setTxList
|
||||||
|
this.txHistoryLimit = opts.txHistoryLimit
|
||||||
|
this.getSelectedAccount = opts.getSelectedAccount
|
||||||
|
this.provider = opts.provider
|
||||||
|
this.blockTracker = opts.blockTracker
|
||||||
|
this.txProviderUtils = new TxProviderUtil(this.provider)
|
||||||
|
this.blockTracker.on('block', this.checkForTxInBlock.bind(this))
|
||||||
|
this.getGasMultiplier = opts.getGasMultiplier
|
||||||
|
this.getNetwork = opts.getNetwork
|
||||||
|
}
|
||||||
|
|
||||||
|
getState () {
|
||||||
|
var selectedAccount = this.getSelectedAccount()
|
||||||
|
return {
|
||||||
|
transactions: this.getTxList(),
|
||||||
|
unconfTxs: this.getUnapprovedTxList(),
|
||||||
|
selectedAccountTxList: this.getFilteredTxList({metamaskNetworkId: this.getNetwork(), from: selectedAccount}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the tx list
|
||||||
|
getTxList () {
|
||||||
|
return this.txList
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adds a tx to the txlist
|
||||||
|
addTx (txMeta, onTxDoneCb = warn) {
|
||||||
|
var txList = this.getTxList()
|
||||||
|
var txHistoryLimit = this.txHistoryLimit
|
||||||
|
|
||||||
|
// checks if the length of th tx history is
|
||||||
|
// longer then desired persistence limit
|
||||||
|
// and then if it is removes only confirmed
|
||||||
|
// or rejected tx's.
|
||||||
|
// not tx's that are pending or unapproved
|
||||||
|
if (txList.length > txHistoryLimit - 1) {
|
||||||
|
var index = txList.findIndex((metaTx) => metaTx.status === 'confirmed' || metaTx.status === 'rejected')
|
||||||
|
txList.splice(index, 1)
|
||||||
|
}
|
||||||
|
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')
|
||||||
|
this.emit(`${txMeta.id}:unapproved`, txMeta)
|
||||||
|
}
|
||||||
|
|
||||||
|
// gets tx by Id and returns it
|
||||||
|
getTx (txId, cb) {
|
||||||
|
var txList = this.getTxList()
|
||||||
|
var txMeta = txList.find(txData => txData.id === txId)
|
||||||
|
return cb ? cb(txMeta) : txMeta
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
updateTx (txMeta) {
|
||||||
|
var txId = txMeta.id
|
||||||
|
var txList = this.getTxList()
|
||||||
|
var index = txList.findIndex(txData => txData.id === txId)
|
||||||
|
txList[index] = txMeta
|
||||||
|
this._saveTxList(txList)
|
||||||
|
}
|
||||||
|
|
||||||
|
get unapprovedTxCount () {
|
||||||
|
return Object.keys(this.getUnapprovedTxList()).length
|
||||||
|
}
|
||||||
|
|
||||||
|
get pendingTxCount () {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
txDidComplete (txMeta, onTxDoneCb, cb, err) {
|
||||||
|
if (err) return cb(err)
|
||||||
|
this.addTx(txMeta, onTxDoneCb)
|
||||||
|
cb(null, txMeta)
|
||||||
|
}
|
||||||
|
|
||||||
|
getUnapprovedTxList () {
|
||||||
|
var txList = this.getTxList()
|
||||||
|
return txList.filter((txMeta) => txMeta.status === 'unapproved')
|
||||||
|
.reduce((result, tx) => {
|
||||||
|
result[tx.id] = tx
|
||||||
|
return result
|
||||||
|
}, {})
|
||||||
|
}
|
||||||
|
|
||||||
|
approveTransaction (txId, cb = warn) {
|
||||||
|
this.setTxStatusSigned(txId)
|
||||||
|
cb()
|
||||||
|
}
|
||||||
|
|
||||||
|
cancelTransaction (txId, cb = warn) {
|
||||||
|
this.setTxStatusRejected(txId)
|
||||||
|
cb()
|
||||||
|
}
|
||||||
|
|
||||||
|
// formats txParams so the keyringController can sign it
|
||||||
|
formatTxForSigining (txParams, cb) {
|
||||||
|
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)
|
||||||
|
|
||||||
|
// listener is assigned in metamaskController
|
||||||
|
this.emit(`${txParams.metamaskId}:formatted`, ethTx, address, txParams.metamaskId, 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())
|
||||||
|
cb(null, rawTx)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Takes an object of fields to search for eg:
|
||||||
|
var thingsToLookFor = {
|
||||||
|
to: '0x0..',
|
||||||
|
from: '0x0..',
|
||||||
|
status: 'signed',
|
||||||
|
}
|
||||||
|
and returns a list of tx with all
|
||||||
|
options matching
|
||||||
|
|
||||||
|
this is for things like filtering a the tx list
|
||||||
|
for only tx's from 1 account
|
||||||
|
or for filltering for all txs from one account
|
||||||
|
and that have been 'confirmed'
|
||||||
|
*/
|
||||||
|
getFilteredTxList (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((txMeta) => {
|
||||||
|
if (key in txMeta.txParams) {
|
||||||
|
return txMeta.txParams[key] === value
|
||||||
|
} else {
|
||||||
|
return txMeta[key] === value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// STATUS METHODS
|
||||||
|
// get::set status
|
||||||
|
|
||||||
|
// should return the status of the tx.
|
||||||
|
getTxStatus (txId) {
|
||||||
|
const txMeta = this.getTx(txId)
|
||||||
|
return txMeta.status
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 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')
|
||||||
|
}
|
||||||
|
|
||||||
|
setTxStatusConfirmed (txId) {
|
||||||
|
this._setTxStatus(txId, 'confirmed')
|
||||||
|
}
|
||||||
|
|
||||||
|
// merges txParams obj onto txData.txParams
|
||||||
|
// use extend to ensure that all fields are filled
|
||||||
|
updateTxParams (txId, txParams) {
|
||||||
|
var txMeta = this.getTx(txId)
|
||||||
|
txMeta.txParams = extend(txMeta.txParams, txParams)
|
||||||
|
this.updateTx(txMeta)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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})
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
if (txMeta.blockNumber) {
|
||||||
|
this.setTxStatusConfirmed(txId)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// PRIVATE METHODS
|
||||||
|
|
||||||
|
// 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 txMeta = this.getTx(txId)
|
||||||
|
txMeta.status = status
|
||||||
|
this.emit(`${txMeta.id}:${status}`, txId)
|
||||||
|
this.updateTx(txMeta)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Saves the new/updated txList.
|
||||||
|
// Function is intended only for internal use
|
||||||
|
_saveTxList (txList) {
|
||||||
|
this.txList = txList
|
||||||
|
this._setTxList(txList)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const warn = () => console.warn('warn was used no cb provided')
|
@ -21,7 +21,7 @@ function initializeZeroClient() {
|
|||||||
// User confirmation callbacks:
|
// User confirmation callbacks:
|
||||||
showUnconfirmedMessage,
|
showUnconfirmedMessage,
|
||||||
unlockAccountMessage,
|
unlockAccountMessage,
|
||||||
showUnconfirmedTx,
|
showUnapprovedTx,
|
||||||
// Persistence Methods:
|
// Persistence Methods:
|
||||||
setData,
|
setData,
|
||||||
loadData,
|
loadData,
|
||||||
@ -36,8 +36,8 @@ function initializeZeroClient() {
|
|||||||
console.log('notif stub - showUnconfirmedMessage')
|
console.log('notif stub - showUnconfirmedMessage')
|
||||||
}
|
}
|
||||||
|
|
||||||
function showUnconfirmedTx (txParams, txData, onTxDoneCb) {
|
function showUnapprovedTx (txParams, txData, onTxDoneCb) {
|
||||||
console.log('notif stub - showUnconfirmedTx')
|
console.log('notif stub - showUnapprovedTx')
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
@ -156,4 +156,4 @@ function initializeZeroClient() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -46,7 +46,7 @@ const controller = new MetamaskController({
|
|||||||
// User confirmation callbacks:
|
// User confirmation callbacks:
|
||||||
showUnconfirmedMessage: noop,
|
showUnconfirmedMessage: noop,
|
||||||
unlockAccountMessage: noop,
|
unlockAccountMessage: noop,
|
||||||
showUnconfirmedTx: noop,
|
showUnapprovedTx: noop,
|
||||||
// Persistence Methods:
|
// Persistence Methods:
|
||||||
setData,
|
setData,
|
||||||
loadData,
|
loadData,
|
||||||
|
@ -9,6 +9,7 @@ module.exports = function() {
|
|||||||
function loadData () {
|
function loadData () {
|
||||||
var oldData = getOldStyleData()
|
var oldData = getOldStyleData()
|
||||||
var newData
|
var newData
|
||||||
|
|
||||||
try {
|
try {
|
||||||
newData = JSON.parse(window.localStorage[STORAGE_KEY])
|
newData = JSON.parse(window.localStorage[STORAGE_KEY])
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
|
@ -215,7 +215,7 @@ describe('config-manager', function() {
|
|||||||
|
|
||||||
describe('transactions', function() {
|
describe('transactions', function() {
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
configManager._saveTxList([])
|
configManager.setTxList([])
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('#getTxList', function() {
|
describe('#getTxList', function() {
|
||||||
@ -226,90 +226,13 @@ describe('config-manager', function() {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('#_saveTxList', function() {
|
describe('#setTxList', function() {
|
||||||
it('saves the submitted data to the tx list', function() {
|
it('saves the submitted data to the tx list', function() {
|
||||||
var target = [{ foo: 'bar' }]
|
var target = [{ foo: 'bar' }]
|
||||||
configManager._saveTxList(target)
|
configManager.setTxList(target)
|
||||||
var result = configManager.getTxList()
|
var result = configManager.getTxList()
|
||||||
assert.equal(result[0].foo, 'bar')
|
assert.equal(result[0].foo, 'bar')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('#addTx', function() {
|
|
||||||
it('adds a tx returned in getTxList', function() {
|
|
||||||
var tx = { id: 1 }
|
|
||||||
configManager.addTx(tx)
|
|
||||||
var result = configManager.getTxList()
|
|
||||||
assert.ok(Array.isArray(result))
|
|
||||||
assert.equal(result.length, 1)
|
|
||||||
assert.equal(result[0].id, 1)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('cuts off early txs beyond a limit', function() {
|
|
||||||
const limit = configManager.txLimit
|
|
||||||
for (let i = 0; i < limit + 1; i++) {
|
|
||||||
let tx = { id: i }
|
|
||||||
configManager.addTx(tx)
|
|
||||||
}
|
|
||||||
var result = configManager.getTxList()
|
|
||||||
assert.equal(result.length, limit, `limit of ${limit} txs enforced`)
|
|
||||||
assert.equal(result[0].id, 1, 'early txs truncted')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('#confirmTx', function() {
|
|
||||||
it('sets the tx status to confirmed', function() {
|
|
||||||
var tx = { id: 1, status: 'unconfirmed' }
|
|
||||||
configManager.addTx(tx)
|
|
||||||
configManager.confirmTx(1)
|
|
||||||
var result = configManager.getTxList()
|
|
||||||
assert.ok(Array.isArray(result))
|
|
||||||
assert.equal(result.length, 1)
|
|
||||||
assert.equal(result[0].status, 'confirmed')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('#rejectTx', function() {
|
|
||||||
it('sets the tx status to rejected', function() {
|
|
||||||
var tx = { id: 1, status: 'unconfirmed' }
|
|
||||||
configManager.addTx(tx)
|
|
||||||
configManager.rejectTx(1)
|
|
||||||
var result = configManager.getTxList()
|
|
||||||
assert.ok(Array.isArray(result))
|
|
||||||
assert.equal(result.length, 1)
|
|
||||||
assert.equal(result[0].status, 'rejected')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('#updateTx', function() {
|
|
||||||
it('replaces the tx with the same id', function() {
|
|
||||||
configManager.addTx({ id: '1', status: 'unconfirmed' })
|
|
||||||
configManager.addTx({ id: '2', status: 'confirmed' })
|
|
||||||
configManager.updateTx({ id: '1', status: 'blah', hash: 'foo' })
|
|
||||||
var result = configManager.getTx('1')
|
|
||||||
assert.equal(result.hash, 'foo')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('#unconfirmedTxs', function() {
|
|
||||||
it('returns unconfirmed txs in a hash', function() {
|
|
||||||
configManager.addTx({ id: '1', status: 'unconfirmed' })
|
|
||||||
configManager.addTx({ id: '2', status: 'confirmed' })
|
|
||||||
let result = configManager.unconfirmedTxs()
|
|
||||||
assert.equal(typeof result, 'object')
|
|
||||||
assert.equal(result['1'].status, 'unconfirmed')
|
|
||||||
assert.equal(result['0'], undefined)
|
|
||||||
assert.equal(result['2'], undefined)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('#getTx', function() {
|
|
||||||
it('returns a tx with the requested id', function() {
|
|
||||||
configManager.addTx({ id: '1', status: 'unconfirmed' })
|
|
||||||
configManager.addTx({ id: '2', status: 'confirmed' })
|
|
||||||
assert.equal(configManager.getTx('1').status, 'unconfirmed')
|
|
||||||
assert.equal(configManager.getTx('2').status, 'confirmed')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -68,6 +68,10 @@ describe('IdentityStore to KeyringController migration', function() {
|
|||||||
addAccount(acct) { newAccounts.push(ethUtil.addHexPrefix(acct)) },
|
addAccount(acct) { newAccounts.push(ethUtil.addHexPrefix(acct)) },
|
||||||
del(acct) { delete newAccounts[acct] },
|
del(acct) { delete newAccounts[acct] },
|
||||||
},
|
},
|
||||||
|
txManager: {
|
||||||
|
getTxList: () => [],
|
||||||
|
getUnapprovedTxList: () => []
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
// Stub out the browser crypto for a mock encryptor.
|
// Stub out the browser crypto for a mock encryptor.
|
||||||
|
@ -139,54 +139,4 @@ describe('IdentityStore', function() {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('#addGasBuffer', function() {
|
|
||||||
it('formats the result correctly', function() {
|
|
||||||
const idStore = new IdentityStore({
|
|
||||||
configManager: configManagerGen(),
|
|
||||||
ethStore: {
|
|
||||||
addAccount(acct) { accounts.push(ethUtil.addHexPrefix(acct)) },
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const gas = '0x01'
|
|
||||||
const bnGas = new BN(gas, 16)
|
|
||||||
const bnResult = idStore.addGasBuffer(bnGas)
|
|
||||||
|
|
||||||
assert.ok(bnResult.gt(gas), 'added more gas as buffer.')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('buffers 20%', function() {
|
|
||||||
const idStore = new IdentityStore({
|
|
||||||
configManager: configManagerGen(),
|
|
||||||
ethStore: {
|
|
||||||
addAccount(acct) { accounts.push(ethUtil.addHexPrefix(acct)) },
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const gas = '0x04ee59' // Actual estimated gas example
|
|
||||||
const bnGas = new BN(ethUtil.stripHexPrefix(gas), 16)
|
|
||||||
const five = new BN('5', 10)
|
|
||||||
const correctBuffer = bnGas.div(five)
|
|
||||||
const correct = bnGas.add(correctBuffer)
|
|
||||||
|
|
||||||
const bnResult = idStore.addGasBuffer(bnGas)
|
|
||||||
|
|
||||||
assert(bnResult.gt(bnGas), 'Estimate increased in value.')
|
|
||||||
assert.equal(bnResult.sub(bnGas).toString(10), correctBuffer.toString(10), 'added 20% gas')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('#checkForDelegateCall', function() {
|
|
||||||
const idStore = new IdentityStore({
|
|
||||||
configManager: configManagerGen(),
|
|
||||||
ethStore: {
|
|
||||||
addAccount(acct) { accounts.push(ethUtil.addHexPrefix(acct)) },
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
var result = idStore.checkForDelegateCall(delegateCallCode)
|
|
||||||
assert.equal(result, true, 'no delegate call in provided code')
|
|
||||||
})
|
|
||||||
|
|
||||||
})
|
})
|
||||||
|
@ -23,6 +23,10 @@ describe('KeyringController', function() {
|
|||||||
|
|
||||||
keyringController = new KeyringController({
|
keyringController = new KeyringController({
|
||||||
configManager: configManagerGen(),
|
configManager: configManagerGen(),
|
||||||
|
txManager: {
|
||||||
|
getTxList: () => [],
|
||||||
|
getUnapprovedTxList: () => []
|
||||||
|
},
|
||||||
ethStore: {
|
ethStore: {
|
||||||
addAccount(acct) { accounts.push(ethUtil.addHexPrefix(acct)) },
|
addAccount(acct) { accounts.push(ethUtil.addHexPrefix(acct)) },
|
||||||
},
|
},
|
||||||
|
@ -9,7 +9,7 @@ describe('MetaMaskController', function() {
|
|||||||
let controller = new MetaMaskController({
|
let controller = new MetaMaskController({
|
||||||
showUnconfirmedMessage: noop,
|
showUnconfirmedMessage: noop,
|
||||||
unlockAccountMessage: noop,
|
unlockAccountMessage: noop,
|
||||||
showUnconfirmedTx: noop,
|
showUnapprovedTx: noop,
|
||||||
setData,
|
setData,
|
||||||
loadData,
|
loadData,
|
||||||
})
|
})
|
||||||
|
190
test/unit/tx-manager-test.js
Normal file
190
test/unit/tx-manager-test.js
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
const assert = require('assert')
|
||||||
|
const extend = require('xtend')
|
||||||
|
const EventEmitter = require('events')
|
||||||
|
const STORAGE_KEY = 'metamask-persistance-key'
|
||||||
|
const TransactionManager = require('../../app/scripts/transaction-manager')
|
||||||
|
|
||||||
|
describe('Transaction Manager', function() {
|
||||||
|
let txManager
|
||||||
|
|
||||||
|
const onTxDoneCb = () => true
|
||||||
|
beforeEach(function() {
|
||||||
|
txManager = new TransactionManager ({
|
||||||
|
txList: [],
|
||||||
|
setTxList: () => {},
|
||||||
|
provider: "testnet",
|
||||||
|
txHistoryLimit: 10,
|
||||||
|
blockTracker: new EventEmitter(),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('#getTxList', function() {
|
||||||
|
it('when new should return empty array', function() {
|
||||||
|
var result = txManager.getTxList()
|
||||||
|
assert.ok(Array.isArray(result))
|
||||||
|
assert.equal(result.length, 0)
|
||||||
|
})
|
||||||
|
it('should also return transactions from local storage if any', function() {
|
||||||
|
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('#_saveTxList', function() {
|
||||||
|
it('saves the submitted data to the tx list', function() {
|
||||||
|
var target = [{ foo: 'bar' }]
|
||||||
|
txManager._saveTxList(target)
|
||||||
|
var result = txManager.getTxList()
|
||||||
|
assert.equal(result[0].foo, 'bar')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('#addTx', function() {
|
||||||
|
it('adds a tx returned in getTxList', function() {
|
||||||
|
var tx = { id: 1, status: 'confirmed',}
|
||||||
|
txManager.addTx(tx, onTxDoneCb)
|
||||||
|
var result = txManager.getTxList()
|
||||||
|
assert.ok(Array.isArray(result))
|
||||||
|
assert.equal(result.length, 1)
|
||||||
|
assert.equal(result[0].id, 1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('cuts off early txs beyond a limit', function() {
|
||||||
|
const limit = txManager.txHistoryLimit
|
||||||
|
for (let i = 0; i < limit + 1; i++) {
|
||||||
|
let tx = { id: i, time: new Date(), status: 'confirmed'}
|
||||||
|
txManager.addTx(tx, onTxDoneCb)
|
||||||
|
}
|
||||||
|
var result = txManager.getTxList()
|
||||||
|
assert.equal(result.length, limit, `limit of ${limit} txs enforced`)
|
||||||
|
assert.equal(result[0].id, 1, 'early txs truncted')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('cuts off early txs beyond a limit weather or not it is confirmed or rejected', function() {
|
||||||
|
const limit = txManager.txHistoryLimit
|
||||||
|
for (let i = 0; i < limit + 1; i++) {
|
||||||
|
let tx = { id: i, time: new Date(), status: 'rejected'}
|
||||||
|
txManager.addTx(tx, onTxDoneCb)
|
||||||
|
}
|
||||||
|
var result = txManager.getTxList()
|
||||||
|
assert.equal(result.length, limit, `limit of ${limit} txs enforced`)
|
||||||
|
assert.equal(result[0].id, 1, 'early txs truncted')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('cuts off early txs beyond a limit but does not cut unapproved txs', function() {
|
||||||
|
var unconfirmedTx = { id: 0, time: new Date(), status: 'unapproved'}
|
||||||
|
txManager.addTx(unconfirmedTx, onTxDoneCb)
|
||||||
|
const limit = txManager.txHistoryLimit
|
||||||
|
for (let i = 1; i < limit + 1; i++) {
|
||||||
|
let tx = { id: i, time: new Date(), status: 'confirmed'}
|
||||||
|
txManager.addTx(tx, onTxDoneCb)
|
||||||
|
}
|
||||||
|
var result = txManager.getTxList()
|
||||||
|
assert.equal(result.length, limit, `limit of ${limit} txs enforced`)
|
||||||
|
assert.equal(result[0].id, 0, 'first tx should still be there')
|
||||||
|
assert.equal(result[0].status, 'unapproved', 'first tx should be unapproved')
|
||||||
|
assert.equal(result[1].id, 2, 'early txs truncted')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('#setTxStatusSigned', function() {
|
||||||
|
it('sets the tx status to signed', function() {
|
||||||
|
var tx = { id: 1, status: 'unapproved' }
|
||||||
|
txManager.addTx(tx, onTxDoneCb)
|
||||||
|
txManager.setTxStatusSigned(1)
|
||||||
|
var result = txManager.getTxList()
|
||||||
|
assert.ok(Array.isArray(result))
|
||||||
|
assert.equal(result.length, 1)
|
||||||
|
assert.equal(result[0].status, 'signed')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should emit a signed event to signal the exciton of callback', (done) => {
|
||||||
|
this.timeout(10000)
|
||||||
|
var tx = { id: 1, status: 'unapproved' }
|
||||||
|
let onTxDoneCb = function (err, txId) {
|
||||||
|
assert(true, 'event listener has been triggered and onTxDoneCb executed')
|
||||||
|
done()
|
||||||
|
}
|
||||||
|
txManager.addTx(tx, onTxDoneCb)
|
||||||
|
txManager.setTxStatusSigned(1)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('#setTxStatusRejected', function() {
|
||||||
|
it('sets the tx status to rejected', function() {
|
||||||
|
var tx = { id: 1, status: 'unapproved' }
|
||||||
|
txManager.addTx(tx, onTxDoneCb)
|
||||||
|
txManager.setTxStatusRejected(1)
|
||||||
|
var result = txManager.getTxList()
|
||||||
|
assert.ok(Array.isArray(result))
|
||||||
|
assert.equal(result.length, 1)
|
||||||
|
assert.equal(result[0].status, 'rejected')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should emit a rejected event to signal the exciton of callback', (done) => {
|
||||||
|
this.timeout(10000)
|
||||||
|
var tx = { id: 1, status: 'unapproved' }
|
||||||
|
let onTxDoneCb = function (err, txId) {
|
||||||
|
assert(true, 'event listener has been triggered and onTxDoneCb executed')
|
||||||
|
done()
|
||||||
|
}
|
||||||
|
txManager.addTx(tx, onTxDoneCb)
|
||||||
|
txManager.setTxStatusRejected(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('#updateTx', function() {
|
||||||
|
it('replaces the tx with the same id', function() {
|
||||||
|
txManager.addTx({ id: '1', status: 'unapproved' }, onTxDoneCb)
|
||||||
|
txManager.addTx({ id: '2', status: 'confirmed' }, onTxDoneCb)
|
||||||
|
txManager.updateTx({ id: '1', status: 'blah', hash: 'foo' })
|
||||||
|
var result = txManager.getTx('1')
|
||||||
|
assert.equal(result.hash, 'foo')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('#getUnapprovedTxList', function() {
|
||||||
|
it('returns unapproved txs in a hash', function() {
|
||||||
|
txManager.addTx({ id: '1', status: 'unapproved' }, onTxDoneCb)
|
||||||
|
txManager.addTx({ id: '2', status: 'confirmed' }, onTxDoneCb)
|
||||||
|
let result = txManager.getUnapprovedTxList()
|
||||||
|
assert.equal(typeof result, 'object')
|
||||||
|
assert.equal(result['1'].status, 'unapproved')
|
||||||
|
assert.equal(result['2'], undefined)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('#getTx', function() {
|
||||||
|
it('returns a tx with the requested id', function() {
|
||||||
|
txManager.addTx({ id: '1', status: 'unapproved' }, onTxDoneCb)
|
||||||
|
txManager.addTx({ id: '2', status: 'confirmed' }, onTxDoneCb)
|
||||||
|
assert.equal(txManager.getTx('1').status, 'unapproved')
|
||||||
|
assert.equal(txManager.getTx('2').status, 'confirmed')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('#getFilteredTxList', function() {
|
||||||
|
it('returns a tx with the requested data', function() {
|
||||||
|
var foop = 0
|
||||||
|
var zoop = 0
|
||||||
|
for (let i = 0; i < 10; ++i ){
|
||||||
|
let everyOther = i % 2
|
||||||
|
txManager.addTx({ id: i,
|
||||||
|
status: everyOther ? 'unapproved' : 'confirmed',
|
||||||
|
txParams: {
|
||||||
|
from: everyOther ? 'foop' : 'zoop',
|
||||||
|
to: everyOther ? 'zoop' : 'foop',
|
||||||
|
}
|
||||||
|
}, onTxDoneCb)
|
||||||
|
everyOther ? ++foop : ++zoop
|
||||||
|
}
|
||||||
|
assert.equal(txManager.getFilteredTxList({status: 'confirmed', from: 'zoop'}).length, zoop)
|
||||||
|
assert.equal(txManager.getFilteredTxList({status: 'confirmed', to: 'foop'}).length, zoop)
|
||||||
|
assert.equal(txManager.getFilteredTxList({status: 'confirmed', from: 'foop'}).length, 0)
|
||||||
|
assert.equal(txManager.getFilteredTxList({status: 'confirmed'}).length, zoop)
|
||||||
|
assert.equal(txManager.getFilteredTxList({from: 'foop'}).length, foop)
|
||||||
|
assert.equal(txManager.getFilteredTxList({from: 'zoop'}).length, zoop)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
@ -31,7 +31,7 @@ TransactionListItem.prototype.render = function () {
|
|||||||
|
|
||||||
var isMsg = ('msgParams' in transaction)
|
var isMsg = ('msgParams' in transaction)
|
||||||
var isTx = ('txParams' in transaction)
|
var isTx = ('txParams' in transaction)
|
||||||
var isPending = transaction.status === 'unconfirmed'
|
var isPending = transaction.status === 'unapproved'
|
||||||
|
|
||||||
let txParams
|
let txParams
|
||||||
if (isTx) {
|
if (isTx) {
|
||||||
@ -59,7 +59,7 @@ TransactionListItem.prototype.render = function () {
|
|||||||
}, [
|
}, [
|
||||||
|
|
||||||
h('.identicon-wrapper.flex-column.flex-center.select-none', [
|
h('.identicon-wrapper.flex-column.flex-center.select-none', [
|
||||||
transaction.status === 'unconfirmed' ? h('i.fa.fa-ellipsis-h', {
|
transaction.status === 'unapproved' ? h('i.fa.fa-ellipsis-h', {
|
||||||
style: {
|
style: {
|
||||||
fontSize: '27px',
|
fontSize: '27px',
|
||||||
},
|
},
|
||||||
|
Loading…
Reference in New Issue
Block a user