-
-
Notifications
You must be signed in to change notification settings - Fork 199
/
TokenRatesController.ts
171 lines (156 loc) · 4.82 KB
/
TokenRatesController.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
import 'isomorphic-fetch';
import BaseController, { BaseConfig, BaseState } from './BaseController';
import AssetsController from './AssetsController';
import { safelyExecute } from './util';
import CurrencyRateController from './CurrencyRateController';
const { toChecksumAddress } = require('ethereumjs-util');
/**
* @type Balanc3Response
*
* Balanc3 API response representation
*
* @property prices - Array of prices, corresponding to objects with pair and price
*/
export interface Balanc3Response {
prices: Array<{ pair: string; price: number }>;
}
/**
* @type Token
*
* Token representation
*
* @property address - Hex address of the token contract
* @property decimals - Number of decimals the token uses
* @property symbol - Symbol of the token
*/
export interface Token {
address: string;
decimals: number;
symbol: string;
}
/**
* @type TokenRatesConfig
*
* Token rates controller configuration
*
* @property interval - Polling interval used to fetch new token rates
* @property tokens - List of tokens to track exchange rates for
*/
export interface TokenRatesConfig extends BaseConfig {
interval: number;
nativeCurrency: string;
tokens: Token[];
}
/**
* @type TokenRatesState
*
* Token rates controller state
*
* @property contractExchangeRates - Hash of token contract addresses to exchange rates
*/
export interface TokenRatesState extends BaseState {
contractExchangeRates: { [address: string]: number };
}
/**
* Controller that passively polls on a set interval for token-to-fiat exchange rates
* for tokens stored in the AssetsController
*/
export class TokenRatesController extends BaseController<TokenRatesConfig, TokenRatesState> {
private handle?: NodeJS.Timer;
private tokenList: Token[] = [];
private getPricingURL(query: string) {
return `https://exchanges.balanc3.net/pie?${query}&autoConversion=true`;
}
/**
* Name of this controller used during composition
*/
name = 'TokenRatesController';
/**
* List of required sibling controllers this controller needs to function
*/
requiredControllers = ['AssetsController', 'CurrencyRateController'];
/**
* Creates a TokenRatesController instance
*
* @param config - Initial options used to configure this controller
* @param state - Initial state to set on this controller
*/
constructor(config?: Partial<TokenRatesConfig>, state?: Partial<TokenRatesState>) {
super(config, state);
this.defaultConfig = {
interval: 180000,
nativeCurrency: 'eth',
tokens: []
};
this.defaultState = { contractExchangeRates: {} };
this.initialize();
}
/**
* Sets a new polling interval
*
* @param interval - Polling interval used to fetch new token rates
*/
set interval(interval: number) {
this.handle && clearInterval(this.handle);
safelyExecute(() => this.updateExchangeRates());
this.handle = setInterval(() => {
safelyExecute(() => this.updateExchangeRates());
}, interval);
}
/**
* Sets a new token list to track prices
*
* @param tokens - List of tokens to track exchange rates for
*/
set tokens(tokens: Token[]) {
this.tokenList = tokens;
safelyExecute(() => this.updateExchangeRates());
}
/**
* Fetches a pairs of token address and native currency
*
* @param query - Query according to tokens in tokenList and native currency
* @returns - Promise resolving to exchange rates for given pairs
*/
async fetchExchangeRate(query: string): Promise<Balanc3Response> {
const response = await fetch(this.getPricingURL(query));
const json = await response.json();
return json;
}
/**
* Extension point called if and when this controller is composed
* with other controllers using a ComposableController
*/
onComposed() {
super.onComposed();
const assets = this.context.AssetsController as AssetsController;
const currencyRate = this.context.CurrencyRateController as CurrencyRateController;
assets.subscribe(() => {
this.configure({ tokens: assets.state.tokens });
});
currencyRate.subscribe(() => {
this.configure({ nativeCurrency: currencyRate.state.nativeCurrency });
});
}
/**
* Updates exchange rates for all tokens
*
* @returns Promise resolving when this operation completes
*/
async updateExchangeRates() {
if (this.disabled || this.tokenList.length === 0) {
return;
}
const newContractExchangeRates: { [address: string]: number } = {};
const { nativeCurrency } = this.config;
const pairs = this.tokenList.map((token) => `pairs[]=${token.address}/${nativeCurrency}`);
const query = pairs.join('&');
const { prices = [] } = await this.fetchExchangeRate(query);
prices.forEach(({ pair, price }) => {
const address = toChecksumAddress(pair.split('/')[0].toLowerCase());
newContractExchangeRates[address] = typeof price === 'number' ? price : 0;
});
this.update({ contractExchangeRates: newContractExchangeRates });
}
}
export default TokenRatesController;