mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 09:52:26 +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 TypedMessageManager = require('./lib/typed-message-manager')
|
||||||
const TransactionController = require('./controllers/transactions')
|
const TransactionController = require('./controllers/transactions')
|
||||||
const BalancesController = require('./controllers/computed-balances')
|
const BalancesController = require('./controllers/computed-balances')
|
||||||
|
const TokenRatesController = require('./controllers/token-rates')
|
||||||
const ConfigManager = require('./lib/config-manager')
|
const ConfigManager = require('./lib/config-manager')
|
||||||
const nodeify = require('./lib/nodeify')
|
const nodeify = require('./lib/nodeify')
|
||||||
const accountImporter = require('./account-import-strategies')
|
const accountImporter = require('./account-import-strategies')
|
||||||
@ -104,6 +105,11 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||||||
this.provider = this.initializeProvider()
|
this.provider = this.initializeProvider()
|
||||||
this.blockTracker = this.provider._blockTracker
|
this.blockTracker = this.provider._blockTracker
|
||||||
|
|
||||||
|
// token exchange rate tracker
|
||||||
|
this.tokenRatesController = new TokenRatesController({
|
||||||
|
preferences: this.preferencesController.store,
|
||||||
|
})
|
||||||
|
|
||||||
this.recentBlocksController = new RecentBlocksController({
|
this.recentBlocksController = new RecentBlocksController({
|
||||||
blockTracker: this.blockTracker,
|
blockTracker: this.blockTracker,
|
||||||
provider: this.provider,
|
provider: this.provider,
|
||||||
@ -201,6 +207,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||||||
AccountTracker: this.accountTracker.store,
|
AccountTracker: this.accountTracker.store,
|
||||||
TxController: this.txController.memStore,
|
TxController: this.txController.memStore,
|
||||||
BalancesController: this.balancesController.store,
|
BalancesController: this.balancesController.store,
|
||||||
|
TokenRatesController: this.tokenRatesController.store,
|
||||||
MessageManager: this.messageManager.memStore,
|
MessageManager: this.messageManager.memStore,
|
||||||
PersonalMessageManager: this.personalMessageManager.memStore,
|
PersonalMessageManager: this.personalMessageManager.memStore,
|
||||||
TypesMessageManager: this.typedMessageManager.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,
|
coinBaseSubview: coinBaseSubview,
|
||||||
SHAPESHIFT_SUBVIEW: 'SHAPESHIFT_SUBVIEW',
|
SHAPESHIFT_SUBVIEW: 'SHAPESHIFT_SUBVIEW',
|
||||||
shapeShiftSubview: shapeShiftSubview,
|
shapeShiftSubview: shapeShiftSubview,
|
||||||
UPDATE_CONTRACT_EXCHANGE_RATES: 'UPDATE_CONTRACT_EXCHANGE_RATES',
|
|
||||||
UPDATE_CONTRACT_EXCHANGE_RATE: 'UPDATE_CONTRACT_EXCHANGE_RATE',
|
|
||||||
updateContractExchangeRates,
|
|
||||||
updateContractExchangeRate,
|
|
||||||
PAIR_UPDATE: 'PAIR_UPDATE',
|
PAIR_UPDATE: 'PAIR_UPDATE',
|
||||||
pairUpdate: pairUpdate,
|
pairUpdate: pairUpdate,
|
||||||
coinShiftRquest: coinShiftRquest,
|
coinShiftRquest: coinShiftRquest,
|
||||||
@ -1082,12 +1078,9 @@ function unlockMetamask (account) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function updateMetamaskState (newState) {
|
function updateMetamaskState (newState) {
|
||||||
return async dispatch => {
|
return {
|
||||||
await dispatch({
|
|
||||||
type: actions.UPDATE_METAMASK_STATE,
|
type: actions.UPDATE_METAMASK_STATE,
|
||||||
value: newState,
|
value: newState,
|
||||||
})
|
|
||||||
dispatch(updateContractExchangeRates())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1300,12 +1293,9 @@ function addTokens (tokens) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function updateTokens (newTokens) {
|
function updateTokens (newTokens) {
|
||||||
return async dispatch => {
|
return {
|
||||||
await dispatch({
|
|
||||||
type: actions.UPDATE_TOKENS,
|
type: actions.UPDATE_TOKENS,
|
||||||
newTokens,
|
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) {
|
function setFeatureFlag (feature, activated, notificationType) {
|
||||||
return (dispatch) => {
|
return (dispatch) => {
|
||||||
dispatch(actions.showLoadingIndication())
|
dispatch(actions.showLoadingIndication())
|
||||||
|
@ -66,7 +66,6 @@ function mapDispatchToProps (dispatch) {
|
|||||||
showCustomizeGasModal: () => dispatch(actions.showModal({ name: 'CUSTOMIZE_GAS' })),
|
showCustomizeGasModal: () => dispatch(actions.showModal({ name: 'CUSTOMIZE_GAS' })),
|
||||||
estimateGas: params => dispatch(actions.estimateGas(params)),
|
estimateGas: params => dispatch(actions.estimateGas(params)),
|
||||||
getGasPrice: () => dispatch(actions.getGasPrice()),
|
getGasPrice: () => dispatch(actions.getGasPrice()),
|
||||||
updateContractExchangeRate: address => dispatch(actions.updateContractExchangeRate(address)),
|
|
||||||
signTokenTx: (tokenAddress, toAddress, amount, txData) => (
|
signTokenTx: (tokenAddress, toAddress, amount, txData) => (
|
||||||
dispatch(actions.signTokenTx(tokenAddress, toAddress, amount, txData))
|
dispatch(actions.signTokenTx(tokenAddress, toAddress, amount, txData))
|
||||||
),
|
),
|
||||||
|
@ -177,23 +177,6 @@ function reduceMetamask (state, action) {
|
|||||||
conversionDate: action.value.conversionDate,
|
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:
|
case actions.UPDATE_TOKENS:
|
||||||
return extend(metamaskState, {
|
return extend(metamaskState, {
|
||||||
tokens: action.newTokens,
|
tokens: action.newTokens,
|
||||||
|
@ -88,17 +88,6 @@ SendTransactionScreen.prototype.updateSendTokenBalance = function (usersToken) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SendTransactionScreen.prototype.componentWillMount = function () {
|
SendTransactionScreen.prototype.componentWillMount = function () {
|
||||||
const {
|
|
||||||
updateContractExchangeRate,
|
|
||||||
selectedToken = {},
|
|
||||||
} = this.props
|
|
||||||
|
|
||||||
const { address } = selectedToken || {}
|
|
||||||
|
|
||||||
if (address) {
|
|
||||||
updateContractExchangeRate(address)
|
|
||||||
}
|
|
||||||
|
|
||||||
this.updateGas()
|
this.updateGas()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user