aboutsummaryrefslogtreecommitdiffstats
path: root/app/scripts/controllers
diff options
context:
space:
mode:
Diffstat (limited to 'app/scripts/controllers')
-rw-r--r--app/scripts/controllers/network/createMetamaskMiddleware.js2
-rw-r--r--app/scripts/controllers/preferences.js138
-rw-r--r--app/scripts/controllers/transactions/enums.js12
-rw-r--r--app/scripts/controllers/transactions/index.js50
-rw-r--r--app/scripts/controllers/transactions/tx-state-manager.js1
5 files changed, 192 insertions, 11 deletions
diff --git a/app/scripts/controllers/network/createMetamaskMiddleware.js b/app/scripts/controllers/network/createMetamaskMiddleware.js
index 8b17829b7..9e6a45888 100644
--- a/app/scripts/controllers/network/createMetamaskMiddleware.js
+++ b/app/scripts/controllers/network/createMetamaskMiddleware.js
@@ -38,6 +38,6 @@ function createPendingNonceMiddleware ({ getPendingNonce }) {
const address = req.params[0]
const blockRef = req.params[1]
if (blockRef !== 'pending') return next()
- req.result = await getPendingNonce(address)
+ res.result = await getPendingNonce(address)
})
}
diff --git a/app/scripts/controllers/preferences.js b/app/scripts/controllers/preferences.js
index 707fd7de9..928ebdf1f 100644
--- a/app/scripts/controllers/preferences.js
+++ b/app/scripts/controllers/preferences.js
@@ -1,5 +1,6 @@
const ObservableStore = require('obs-store')
const normalizeAddress = require('eth-sig-util').normalize
+const { isValidAddress } = require('ethereumjs-util')
const extend = require('xtend')
@@ -14,6 +15,7 @@ class PreferencesController {
* @property {string} store.currentAccountTab Indicates the selected tab in the ui
* @property {array} store.tokens The tokens the user wants display in their token lists
* @property {object} store.accountTokens The tokens stored per account and then per network type
+ * @property {object} store.assetImages Contains assets objects related to assets added
* @property {boolean} store.useBlockie The users preference for blockie identicons within the UI
* @property {object} store.featureFlags A key-boolean map, where keys refer to features and booleans to whether the
* user wishes to see that feature
@@ -26,22 +28,43 @@ class PreferencesController {
frequentRpcList: [],
currentAccountTab: 'history',
accountTokens: {},
+ assetImages: {},
tokens: [],
+ suggestedTokens: {},
useBlockie: false,
featureFlags: {},
currentLocale: opts.initLangCode,
identities: {},
lostIdentities: {},
+ seedWords: null,
+ forgottenPassword: false,
}, opts.initState)
this.diagnostics = opts.diagnostics
this.network = opts.network
this.store = new ObservableStore(initState)
+ this.showWatchAssetUi = opts.showWatchAssetUi
this._subscribeProviderType()
}
// PUBLIC METHODS
/**
+ * Sets the {@code forgottenPassword} state property
+ * @param {boolean} forgottenPassword whether or not the user has forgotten their password
+ */
+ setPasswordForgotten (forgottenPassword) {
+ this.store.updateState({ forgottenPassword })
+ }
+
+ /**
+ * Sets the {@code seedWords} seed words
+ * @param {string|null} seedWords the seed words
+ */
+ setSeedWords (seedWords) {
+ this.store.updateState({ seedWords })
+ }
+
+ /**
* Setter for the `useBlockie` property
*
* @param {boolean} val Whether or not the user prefers blockie indicators
@@ -51,6 +74,53 @@ class PreferencesController {
this.store.updateState({ useBlockie: val })
}
+ getSuggestedTokens () {
+ return this.store.getState().suggestedTokens
+ }
+
+ getAssetImages () {
+ return this.store.getState().assetImages
+ }
+
+ addSuggestedERC20Asset (tokenOpts) {
+ this._validateERC20AssetParams(tokenOpts)
+ const suggested = this.getSuggestedTokens()
+ const { rawAddress, symbol, decimals, image } = tokenOpts
+ const address = normalizeAddress(rawAddress)
+ const newEntry = { address, symbol, decimals, image }
+ suggested[address] = newEntry
+ this.store.updateState({ suggestedTokens: suggested })
+ }
+
+ /**
+ * RPC engine middleware for requesting new asset added
+ *
+ * @param req
+ * @param res
+ * @param {Function} - next
+ * @param {Function} - end
+ */
+ async requestWatchAsset (req, res, next, end) {
+ if (req.method === 'metamask_watchAsset') {
+ const { type, options } = req.params
+ switch (type) {
+ case 'ERC20':
+ const result = await this._handleWatchAssetERC20(options)
+ if (result instanceof Error) {
+ end(result)
+ } else {
+ res.result = result
+ end()
+ }
+ break
+ default:
+ end(new Error(`Asset of type ${type} not supported`))
+ }
+ } else {
+ next()
+ }
+ }
+
/**
* Getter for the `useBlockie` property
*
@@ -186,6 +256,13 @@ class PreferencesController {
return selected
}
+ removeSuggestedTokens () {
+ return new Promise((resolve, reject) => {
+ this.store.updateState({ suggestedTokens: {} })
+ resolve({})
+ })
+ }
+
/**
* Setter for the `selectedAddress` property
*
@@ -232,11 +309,11 @@ class PreferencesController {
* @returns {Promise<array>} Promises the new array of AddedToken objects.
*
*/
- async addToken (rawAddress, symbol, decimals) {
+ async addToken (rawAddress, symbol, decimals, image) {
const address = normalizeAddress(rawAddress)
const newEntry = { address, symbol, decimals }
-
const tokens = this.store.getState().tokens
+ const assetImages = this.getAssetImages()
const previousEntry = tokens.find((token, index) => {
return token.address === address
})
@@ -247,7 +324,8 @@ class PreferencesController {
} else {
tokens.push(newEntry)
}
- this._updateAccountTokens(tokens)
+ assetImages[address] = image
+ this._updateAccountTokens(tokens, assetImages)
return Promise.resolve(tokens)
}
@@ -260,8 +338,10 @@ class PreferencesController {
*/
removeToken (rawAddress) {
const tokens = this.store.getState().tokens
+ const assetImages = this.getAssetImages()
const updatedTokens = tokens.filter(token => token.address !== rawAddress)
- this._updateAccountTokens(updatedTokens)
+ delete assetImages[rawAddress]
+ this._updateAccountTokens(updatedTokens, assetImages)
return Promise.resolve(updatedTokens)
}
@@ -322,7 +402,7 @@ class PreferencesController {
/**
* Returns an updated rpcList based on the passed url and the current list.
- * The returned list will have a max length of 2. If the _url currently exists it the list, it will be moved to the
+ * The returned list will have a max length of 3. If the _url currently exists it the list, it will be moved to the
* end of the list. The current list is modified and returned as a promise.
*
* @param {string} _url The rpc url to add to the frequentRpcList.
@@ -338,7 +418,7 @@ class PreferencesController {
if (_url !== 'http://localhost:8545') {
rpcList.push(_url)
}
- if (rpcList.length > 2) {
+ if (rpcList.length > 3) {
rpcList.shift()
}
return Promise.resolve(rpcList)
@@ -387,6 +467,7 @@ class PreferencesController {
//
// PRIVATE METHODS
//
+
/**
* Subscription to network provider type.
*
@@ -405,10 +486,10 @@ class PreferencesController {
* @param {array} tokens Array of tokens to be updated.
*
*/
- _updateAccountTokens (tokens) {
+ _updateAccountTokens (tokens, assetImages) {
const { accountTokens, providerType, selectedAddress } = this._getTokenRelatedStates()
accountTokens[selectedAddress][providerType] = tokens
- this.store.updateState({ accountTokens, tokens })
+ this.store.updateState({ accountTokens, tokens, assetImages })
}
/**
@@ -438,6 +519,47 @@ class PreferencesController {
const tokens = accountTokens[selectedAddress][providerType]
return { tokens, accountTokens, providerType, selectedAddress }
}
+
+ /**
+ * Handle the suggestion of an ERC20 asset through `watchAsset`
+ * *
+ * @param {Promise} promise Promise according to addition of ERC20 token
+ *
+ */
+ async _handleWatchAssetERC20 (options) {
+ const { address, symbol, decimals, image } = options
+ const rawAddress = address
+ try {
+ this._validateERC20AssetParams({ rawAddress, symbol, decimals })
+ } catch (err) {
+ return err
+ }
+ const tokenOpts = { rawAddress, decimals, symbol, image }
+ this.addSuggestedERC20Asset(tokenOpts)
+ return this.showWatchAssetUi().then(() => {
+ const tokenAddresses = this.getTokens().filter(token => token.address === normalizeAddress(rawAddress))
+ return tokenAddresses.length > 0
+ })
+ }
+
+ /**
+ * Validates that the passed options for suggested token have all required properties.
+ *
+ * @param {Object} opts The options object to validate
+ * @throws {string} Throw a custom error indicating that address, symbol and/or decimals
+ * doesn't fulfill requirements
+ *
+ */
+ _validateERC20AssetParams (opts) {
+ const { rawAddress, symbol, decimals } = opts
+ if (!rawAddress || !symbol || !decimals) throw new Error(`Cannot suggest token without address, symbol, and decimals`)
+ if (!(symbol.length < 6)) throw new Error(`Invalid symbol ${symbol} more than five characters`)
+ const numDecimals = parseInt(decimals, 10)
+ if (isNaN(numDecimals) || numDecimals > 36 || numDecimals < 0) {
+ throw new Error(`Invalid decimals ${decimals} must be at least 0, and not over 36`)
+ }
+ if (!isValidAddress(rawAddress)) throw new Error(`Invalid address ${rawAddress}`)
+ }
}
module.exports = PreferencesController
diff --git a/app/scripts/controllers/transactions/enums.js b/app/scripts/controllers/transactions/enums.js
new file mode 100644
index 000000000..be6f16e0d
--- /dev/null
+++ b/app/scripts/controllers/transactions/enums.js
@@ -0,0 +1,12 @@
+const TRANSACTION_TYPE_CANCEL = 'cancel'
+const TRANSACTION_TYPE_RETRY = 'retry'
+const TRANSACTION_TYPE_STANDARD = 'standard'
+
+const TRANSACTION_STATUS_APPROVED = 'approved'
+
+module.exports = {
+ TRANSACTION_TYPE_CANCEL,
+ TRANSACTION_TYPE_RETRY,
+ TRANSACTION_TYPE_STANDARD,
+ TRANSACTION_STATUS_APPROVED,
+}
diff --git a/app/scripts/controllers/transactions/index.js b/app/scripts/controllers/transactions/index.js
index 5d7d6d6da..e2965ceb6 100644
--- a/app/scripts/controllers/transactions/index.js
+++ b/app/scripts/controllers/transactions/index.js
@@ -11,6 +11,14 @@ const txUtils = require('./lib/util')
const cleanErrorStack = require('../../lib/cleanErrorStack')
const log = require('loglevel')
const recipientBlacklistChecker = require('./lib/recipient-blacklist-checker')
+const {
+ TRANSACTION_TYPE_CANCEL,
+ TRANSACTION_TYPE_RETRY,
+ TRANSACTION_TYPE_STANDARD,
+ TRANSACTION_STATUS_APPROVED,
+} = require('./enums')
+
+const { hexToBn, bnToHex, BnMultiplyByFraction } = require('../../lib/util')
/**
Transaction Controller is an aggregate of sub-controllers and trackers
@@ -160,7 +168,10 @@ class TransactionController extends EventEmitter {
const normalizedTxParams = txUtils.normalizeTxParams(txParams)
txUtils.validateTxParams(normalizedTxParams)
// construct txMeta
- let txMeta = this.txStateManager.generateTxMeta({ txParams: normalizedTxParams })
+ let txMeta = this.txStateManager.generateTxMeta({
+ txParams: normalizedTxParams,
+ type: TRANSACTION_TYPE_STANDARD,
+ })
this.addTx(txMeta)
this.emit('newUnapprovedTx', txMeta)
@@ -214,6 +225,7 @@ class TransactionController extends EventEmitter {
txParams: originalTxMeta.txParams,
lastGasPrice,
loadingDefaults: false,
+ type: TRANSACTION_TYPE_RETRY,
})
this.addTx(txMeta)
this.emit('newUnapprovedTx', txMeta)
@@ -221,6 +233,40 @@ class TransactionController extends EventEmitter {
}
/**
+ * Creates a new approved transaction to attempt to cancel a previously submitted transaction. The
+ * new transaction contains the same nonce as the previous, is a basic ETH transfer of 0x value to
+ * the sender's address, and has a higher gasPrice than that of the previous transaction.
+ * @param {number} originalTxId - the id of the txMeta that you want to attempt to cancel
+ * @param {string=} customGasPrice - the hex value to use for the cancel transaction
+ * @returns {txMeta}
+ */
+ async createCancelTransaction (originalTxId, customGasPrice) {
+ const originalTxMeta = this.txStateManager.getTx(originalTxId)
+ const { txParams } = originalTxMeta
+ const { gasPrice: lastGasPrice, from, nonce } = txParams
+
+ const newGasPrice = customGasPrice || bnToHex(BnMultiplyByFraction(hexToBn(lastGasPrice), 11, 10))
+ const newTxMeta = this.txStateManager.generateTxMeta({
+ txParams: {
+ from,
+ to: from,
+ nonce,
+ gas: '0x5208',
+ value: '0x0',
+ gasPrice: newGasPrice,
+ },
+ lastGasPrice,
+ loadingDefaults: false,
+ status: TRANSACTION_STATUS_APPROVED,
+ type: TRANSACTION_TYPE_CANCEL,
+ })
+
+ this.addTx(newTxMeta)
+ await this.approveTransaction(newTxMeta.id)
+ return newTxMeta
+ }
+
+ /**
updates the txMeta in the txStateManager
@param txMeta {Object} - the updated txMeta
*/
@@ -393,7 +439,7 @@ class TransactionController extends EventEmitter {
})
this.txStateManager.getFilteredTxList({
- status: 'approved',
+ status: TRANSACTION_STATUS_APPROVED,
}).forEach((txMeta) => {
const txSignError = new Error('Transaction found as "approved" during boot - possibly stuck during signing')
this.txStateManager.setTxStatusFailed(txMeta.id, txSignError)
diff --git a/app/scripts/controllers/transactions/tx-state-manager.js b/app/scripts/controllers/transactions/tx-state-manager.js
index 28a18ca2e..daa6cc388 100644
--- a/app/scripts/controllers/transactions/tx-state-manager.js
+++ b/app/scripts/controllers/transactions/tx-state-manager.js
@@ -353,6 +353,7 @@ class TransactionStateManager extends EventEmitter {
const txMeta = this.getTx(txId)
txMeta.err = {
message: err.toString(),
+ rpc: err.value,
stack: err.stack,
}
this.updateTx(txMeta)