mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-22 17:33:23 +01:00
Maintain token prices using a background service
This commit is contained in:
parent
a350e80fee
commit
d0447f9058
76
app/scripts/controllers/token-rates.js
Normal file
76
app/scripts/controllers/token-rates.js
Normal file
@ -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
|
@ -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,
|
||||
|
28
test/unit/token-rates-controller.js
Normal file
28
test/unit/token-rates-controller.js
Normal file
@ -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' })
|
||||
})
|
||||
})
|
@ -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({
|
||||
return {
|
||||
type: actions.UPDATE_METAMASK_STATE,
|
||||
value: newState,
|
||||
})
|
||||
dispatch(updateContractExchangeRates())
|
||||
}
|
||||
}
|
||||
|
||||
@ -1300,12 +1293,9 @@ function addTokens (tokens) {
|
||||
}
|
||||
|
||||
function updateTokens (newTokens) {
|
||||
return async dispatch => {
|
||||
await dispatch({
|
||||
return {
|
||||
type: actions.UPDATE_TOKENS,
|
||||
newTokens,
|
||||
})
|
||||
dispatch(updateContractExchangeRates())
|
||||
}
|
||||
}
|
||||
|
||||
@ -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())
|
||||
|
@ -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))
|
||||
),
|
||||
|
@ -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,
|
||||
|
@ -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()
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user