aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorbitpshr <mail@bitpshr.net>2018-04-16 23:21:06 +0800
committerbitpshr <mail@bitpshr.net>2018-04-16 23:47:06 +0800
commitd0447f90583275868bb72aa7ae8f670bf3668173 (patch)
tree66e9b6bce487e22f030bfc3f892500f744a1ebb8
parenta350e80feea6747a5e10088ac6ec15171a590a65 (diff)
downloadtangerine-wallet-browser-d0447f90583275868bb72aa7ae8f670bf3668173.tar
tangerine-wallet-browser-d0447f90583275868bb72aa7ae8f670bf3668173.tar.gz
tangerine-wallet-browser-d0447f90583275868bb72aa7ae8f670bf3668173.tar.bz2
tangerine-wallet-browser-d0447f90583275868bb72aa7ae8f670bf3668173.tar.lz
tangerine-wallet-browser-d0447f90583275868bb72aa7ae8f670bf3668173.tar.xz
tangerine-wallet-browser-d0447f90583275868bb72aa7ae8f670bf3668173.tar.zst
tangerine-wallet-browser-d0447f90583275868bb72aa7ae8f670bf3668173.zip
Maintain token prices using a background service
-rw-r--r--app/scripts/controllers/token-rates.js76
-rw-r--r--app/scripts/metamask-controller.js7
-rw-r--r--test/unit/token-rates-controller.js28
-rw-r--r--ui/app/actions.js58
-rw-r--r--ui/app/components/send/send-v2-container.js1
-rw-r--r--ui/app/reducers/metamask.js17
-rw-r--r--ui/app/send-v2.js11
7 files changed, 117 insertions, 81 deletions
diff --git a/app/scripts/controllers/token-rates.js b/app/scripts/controllers/token-rates.js
new file mode 100644
index 000000000..85a8ca24e
--- /dev/null
+++ b/app/scripts/controllers/token-rates.js
@@ -0,0 +1,76 @@
+const ObservableStore = require('obs-store')
+
+// By default, poll every 3 minutes
+const DEFAULT_INTERVAL = 180 * 1000
+
+/**
+ * A controller that polls for token exchange
+ * rates based on a user's current token list
+ */
+class TokenRatesController {
+ /**
+ * Creates a TokenRatesController
+ *
+ * @param {Object} [config] - Options to configure controller
+ */
+ constructor ({ interval = DEFAULT_INTERVAL, preferences } = {}) {
+ this.store = new ObservableStore()
+ this.preferences = preferences
+ this.interval = interval
+ }
+
+ /**
+ * Updates exchange rates for all tokens
+ */
+ async updateExchangeRates () {
+ const contractExchangeRates = {}
+ for (const i in this._tokens) {
+ const address = this._tokens[i].address
+ contractExchangeRates[address] = await this.fetchExchangeRate(address)
+ }
+ this.store.putState({ contractExchangeRates })
+ }
+
+ /**
+ * Fetches a token exchange rate by address
+ *
+ * @param {String} address - Token contract address
+ */
+ async fetchExchangeRate (address) {
+ try {
+ const response = await fetch(`https://exchanges.balanc3.net/prices?from=${address}&to=ETH&autoConversion=false&summaryOnly=true`)
+ const json = await response.json()
+ return json && json.length ? json[0].averagePrice : 0
+ } catch (error) { }
+ }
+
+ /**
+ * @type {Number} - Interval used to poll for exchange rates
+ */
+ set interval (interval) {
+ this._handle && clearInterval(this._handle)
+ if (!interval) { return }
+ this._handle = setInterval(() => { this.updateExchangeRates() }, interval)
+ }
+
+ /**
+ * @type {Object} - Preferences controller instance
+ */
+ set preferences (preferences) {
+ this._preferences && this._preferences.unsubscribe()
+ if (!preferences) { return }
+ this._preferences = preferences
+ this.tokens = preferences.getState().tokens
+ preferences.subscribe(({ tokens = [] }) => { this.tokens = tokens })
+ }
+
+ /**
+ * @type {Array} - Array of token objects with contract addresses
+ */
+ set tokens (tokens) {
+ this._tokens = tokens
+ this.updateExchangeRates()
+ }
+}
+
+module.exports = TokenRatesController
diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js
index fa7890c50..750a97b86 100644
--- a/app/scripts/metamask-controller.js
+++ b/app/scripts/metamask-controller.js
@@ -34,6 +34,7 @@ const PersonalMessageManager = require('./lib/personal-message-manager')
const TypedMessageManager = require('./lib/typed-message-manager')
const TransactionController = require('./controllers/transactions')
const BalancesController = require('./controllers/computed-balances')
+const TokenRatesController = require('./controllers/token-rates')
const ConfigManager = require('./lib/config-manager')
const nodeify = require('./lib/nodeify')
const accountImporter = require('./account-import-strategies')
@@ -104,6 +105,11 @@ module.exports = class MetamaskController extends EventEmitter {
this.provider = this.initializeProvider()
this.blockTracker = this.provider._blockTracker
+ // token exchange rate tracker
+ this.tokenRatesController = new TokenRatesController({
+ preferences: this.preferencesController.store,
+ })
+
this.recentBlocksController = new RecentBlocksController({
blockTracker: this.blockTracker,
provider: this.provider,
@@ -201,6 +207,7 @@ module.exports = class MetamaskController extends EventEmitter {
AccountTracker: this.accountTracker.store,
TxController: this.txController.memStore,
BalancesController: this.balancesController.store,
+ TokenRatesController: this.tokenRatesController.store,
MessageManager: this.messageManager.memStore,
PersonalMessageManager: this.personalMessageManager.memStore,
TypesMessageManager: this.typedMessageManager.memStore,
diff --git a/test/unit/token-rates-controller.js b/test/unit/token-rates-controller.js
new file mode 100644
index 000000000..55bfe7823
--- /dev/null
+++ b/test/unit/token-rates-controller.js
@@ -0,0 +1,28 @@
+const assert = require('assert')
+const sinon = require('sinon')
+const TokenRatesController = require('../../app/scripts/controllers/token-rates')
+const ObservableStore = require('obs-store')
+
+describe('TokenRatesController', () => {
+ it('should listen for preferences store updates', () => {
+ const preferences = new ObservableStore({ tokens: [] })
+ const controller = new TokenRatesController({ preferences })
+ preferences.putState({ tokens: ['foo'] })
+ assert.deepEqual(controller._tokens, ['foo'])
+ })
+
+ it('should poll on correct interval', async () => {
+ const stub = sinon.stub(global, 'setInterval')
+ new TokenRatesController({ interval: 1337 }) // eslint-disable-line no-new
+ assert.strictEqual(stub.getCall(0).args[1], 1337)
+ stub.restore()
+ })
+
+ it('should fetch each token rate based on address', async () => {
+ const controller = new TokenRatesController()
+ controller.fetchExchangeRate = address => address
+ controller.tokens = [{ address: 'foo' }, { address: 'bar' }]
+ await controller.updateExchangeRates()
+ assert.deepEqual(controller.store.getState().contractExchangeRates, { foo: 'foo', bar: 'bar' })
+ })
+})
diff --git a/ui/app/actions.js b/ui/app/actions.js
index 6453a2bc2..46f34e149 100644
--- a/ui/app/actions.js
+++ b/ui/app/actions.js
@@ -220,10 +220,6 @@ var actions = {
coinBaseSubview: coinBaseSubview,
SHAPESHIFT_SUBVIEW: 'SHAPESHIFT_SUBVIEW',
shapeShiftSubview: shapeShiftSubview,
- UPDATE_CONTRACT_EXCHANGE_RATES: 'UPDATE_CONTRACT_EXCHANGE_RATES',
- UPDATE_CONTRACT_EXCHANGE_RATE: 'UPDATE_CONTRACT_EXCHANGE_RATE',
- updateContractExchangeRates,
- updateContractExchangeRate,
PAIR_UPDATE: 'PAIR_UPDATE',
pairUpdate: pairUpdate,
coinShiftRquest: coinShiftRquest,
@@ -1082,12 +1078,9 @@ function unlockMetamask (account) {
}
function updateMetamaskState (newState) {
- return async dispatch => {
- await dispatch({
- type: actions.UPDATE_METAMASK_STATE,
- value: newState,
- })
- dispatch(updateContractExchangeRates())
+ return {
+ type: actions.UPDATE_METAMASK_STATE,
+ value: newState,
}
}
@@ -1300,12 +1293,9 @@ function addTokens (tokens) {
}
function updateTokens (newTokens) {
- return async dispatch => {
- await dispatch({
- type: actions.UPDATE_TOKENS,
- newTokens,
- })
- dispatch(updateContractExchangeRates())
+ return {
+ type: actions.UPDATE_TOKENS,
+ newTokens,
}
}
@@ -1759,42 +1749,6 @@ function shapeShiftRequest (query, options, cb) {
}
}
-async function fetchContractRate (address) {
- try {
- const response = await fetch(`https://exchanges.balanc3.net/prices?from=${address}&to=ETH&autoConversion=false&summaryOnly=true`)
- const json = await response.json()
- const rate = json && json.length ? json[0].averagePrice : 0
- return { address, rate }
- } catch (error) { }
-}
-
-function updateContractExchangeRates () {
- return async (dispatch, getState) => {
- const { metamask: { tokens = [] } } = getState()
- const newExchangeRates = {}
-
- for (const i in tokens) {
- const address = tokens[i].address
- newExchangeRates[address] = (await fetchContractRate(address)).rate
- }
-
- dispatch({
- type: actions.UPDATE_CONTRACT_EXCHANGE_RATES,
- payload: { newExchangeRates },
- })
- }
-}
-
-function updateContractExchangeRate (address) {
- return async dispatch => {
- const { address, rate } = await fetchContractRate(address)
- dispatch({
- type: actions.UPDATE_CONTRACT_EXCHANGE_RATE,
- payload: { address, rate },
- })
- }
-}
-
function setFeatureFlag (feature, activated, notificationType) {
return (dispatch) => {
dispatch(actions.showLoadingIndication())
diff --git a/ui/app/components/send/send-v2-container.js b/ui/app/components/send/send-v2-container.js
index 75f16b382..adfc91240 100644
--- a/ui/app/components/send/send-v2-container.js
+++ b/ui/app/components/send/send-v2-container.js
@@ -66,7 +66,6 @@ function mapDispatchToProps (dispatch) {
showCustomizeGasModal: () => dispatch(actions.showModal({ name: 'CUSTOMIZE_GAS' })),
estimateGas: params => dispatch(actions.estimateGas(params)),
getGasPrice: () => dispatch(actions.getGasPrice()),
- updateContractExchangeRate: address => dispatch(actions.updateContractExchangeRate(address)),
signTokenTx: (tokenAddress, toAddress, amount, txData) => (
dispatch(actions.signTokenTx(tokenAddress, toAddress, amount, txData))
),
diff --git a/ui/app/reducers/metamask.js b/ui/app/reducers/metamask.js
index d6b6de3b0..1ed049be2 100644
--- a/ui/app/reducers/metamask.js
+++ b/ui/app/reducers/metamask.js
@@ -177,23 +177,6 @@ function reduceMetamask (state, action) {
conversionDate: action.value.conversionDate,
})
- case actions.UPDATE_CONTRACT_EXCHANGE_RATES:
- const { payload: { newExchangeRates } } = action
- return {
- ...metamaskState,
- contractExchangeRates: newExchangeRates,
- }
-
- case actions.UPDATE_CONTRACT_EXCHANGE_RATE:
- const { payload: { address, rate } } = action
- return {
- ...metamaskState,
- contractExchangeRates: {
- ...metamaskState.contractExchangeRates,
- [address]: rate,
- },
- }
-
case actions.UPDATE_TOKENS:
return extend(metamaskState, {
tokens: action.newTokens,
diff --git a/ui/app/send-v2.js b/ui/app/send-v2.js
index 12cf55f62..30d3d3152 100644
--- a/ui/app/send-v2.js
+++ b/ui/app/send-v2.js
@@ -88,17 +88,6 @@ SendTransactionScreen.prototype.updateSendTokenBalance = function (usersToken) {
}
SendTransactionScreen.prototype.componentWillMount = function () {
- const {
- updateContractExchangeRate,
- selectedToken = {},
- } = this.props
-
- const { address } = selectedToken || {}
-
- if (address) {
- updateContractExchangeRate(address)
- }
-
this.updateGas()
}