UNPKG

sparrow-controllers

Version:

Collection of platform-agnostic modules for creating secure data models for cryptocurrency wallets

285 lines 12.4 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.TokenRatesController = void 0; const BaseController_1 = require("../BaseController"); const util_1 = require("../util"); const constants_1 = require("../constants"); const crypto_compare_1 = require("../apis/crypto-compare"); const CoinGeckoApi = { BASE_URL: 'https://api.coingecko.com/api/v3', getTokenPriceURL(chainSlug, query) { return `${this.BASE_URL}/simple/token_price/${chainSlug}?${query}`; }, getPlatformsURL() { return `${this.BASE_URL}/asset_platforms`; }, getSupportedVsCurrencies() { return `${this.BASE_URL}/simple/supported_vs_currencies`; }, }; /** * Finds the chain slug in the data array given a chainId. * * @param chainId - The current chain ID. * @param data - A list platforms supported by the CoinGecko API. * @returns The CoinGecko slug for the given chain ID, or `null` if the slug was not found. */ function findChainSlug(chainId, data) { var _a; if (!data) { return null; } const chain = (_a = data.find(({ chain_identifier }) => chain_identifier !== null && String(chain_identifier) === chainId)) !== null && _a !== void 0 ? _a : null; return (chain === null || chain === void 0 ? void 0 : chain.id) || null; } /** * Controller that passively polls on a set interval for token-to-fiat exchange rates * for tokens stored in the TokensController */ class TokenRatesController extends BaseController_1.BaseController { /** * Creates a TokenRatesController instance. * * @param options - The controller options. * @param options.onTokensStateChange - Allows subscribing to token controller state changes. * @param options.onCurrencyRateStateChange - Allows subscribing to currency rate controller state changes. * @param options.onNetworkStateChange - Allows subscribing to network state changes. * @param config - Initial options used to configure this controller. * @param state - Initial state to set on this controller. */ constructor({ onTokensStateChange, onCurrencyRateStateChange, onNetworkStateChange, }, config, state) { super(config, state); this.tokenList = []; this.supportedChains = { timestamp: 0, data: null, }; this.supportedVsCurrencies = { timestamp: 0, data: [], }; /** * Name of this controller used during composition */ this.name = 'TokenRatesController'; this.defaultConfig = { disabled: true, interval: 3 * 60 * 1000, nativeCurrency: 'ubq', chainId: '', tokens: [], threshold: 6 * 60 * 60 * 1000, }; this.defaultState = { contractExchangeRates: {}, }; this.initialize(); this.configure({ disabled: false }, false, false); onTokensStateChange((tokensState) => { this.configure({ tokens: tokensState.tokens }); }); onCurrencyRateStateChange((currencyRateState) => { this.configure({ nativeCurrency: currencyRateState.nativeCurrency }); }); onNetworkStateChange(({ provider }) => { const { chainId } = provider; this.update({ contractExchangeRates: {} }); this.configure({ chainId }); }); this.poll(); } /** * Sets a new polling interval. * * @param interval - Polling interval used to fetch new token rates. */ poll(interval) { return __awaiter(this, void 0, void 0, function* () { interval && this.configure({ interval }, false, false); this.handle && clearTimeout(this.handle); yield (0, util_1.safelyExecute)(() => this.updateExchangeRates()); this.handle = setTimeout(() => { this.poll(this.config.interval); }, this.config.interval); }); } /** * Sets a new chainId. * * TODO: Replace this with a method. * * @param _chainId - The current chain ID. */ set chainId(_chainId) { !this.disabled && (0, util_1.safelyExecute)(() => this.updateExchangeRates()); } get chainId() { throw new Error('Property only used for setting'); } /** * Sets a new token list to track prices. * * TODO: Replace this with a method. * * @param tokens - List of tokens to track exchange rates for. */ set tokens(tokens) { this.tokenList = tokens; !this.disabled && (0, util_1.safelyExecute)(() => this.updateExchangeRates()); } get tokens() { throw new Error('Property only used for setting'); } /** * Fetches a pairs of token address and native currency. * * @param chainSlug - Chain string identifier. * @param vsCurrency - Query according to tokens in tokenList and native currency. * @returns The exchange rates for the given pairs. */ fetchExchangeRate(chainSlug, vsCurrency) { return __awaiter(this, void 0, void 0, function* () { const tokenPairs = this.tokenList.map((token) => token.address).join(','); const query = `contract_addresses=${tokenPairs}&vs_currencies=${vsCurrency.toLowerCase()}`; return (0, util_1.handleFetch)(CoinGeckoApi.getTokenPriceURL(chainSlug, query)); }); } /** * Checks if the current native currency is a supported vs currency to use * to query for token exchange rates. * * @param nativeCurrency - The native currency of the currently active network. * @returns A boolean indicating whether it's a supported vsCurrency. */ checkIsSupportedVsCurrency(nativeCurrency) { return __awaiter(this, void 0, void 0, function* () { const { threshold } = this.config; const { timestamp, data } = this.supportedVsCurrencies; const now = Date.now(); if (now - timestamp > threshold) { const currencies = yield (0, util_1.handleFetch)(CoinGeckoApi.getSupportedVsCurrencies()); this.supportedVsCurrencies = { data: currencies, timestamp: Date.now(), }; return currencies.includes(nativeCurrency.toLowerCase()); } return data.includes(nativeCurrency.toLowerCase()); }); } /** * Gets current chain ID slug from cached supported platforms CoinGecko API response. * If cached supported platforms response is stale, fetches and updates it. * * @returns The CoinGecko slug for the current chain ID. */ getChainSlug() { return __awaiter(this, void 0, void 0, function* () { const { threshold, chainId } = this.config; const { data, timestamp } = this.supportedChains; const now = Date.now(); if (now - timestamp > threshold) { const platforms = yield (0, util_1.handleFetch)(CoinGeckoApi.getPlatformsURL()); this.supportedChains = { data: platforms, timestamp: Date.now(), }; return findChainSlug(chainId, platforms); } return findChainSlug(chainId, data); }); } /** * Updates exchange rates for all tokens. */ updateExchangeRates() { return __awaiter(this, void 0, void 0, function* () { if (this.tokenList.length === 0 || this.disabled) { return; } const slug = yield this.getChainSlug(); let newContractExchangeRates = {}; if (!slug) { this.tokenList.forEach((token) => { const address = (0, util_1.toChecksumHexAddress)(token.address); newContractExchangeRates[address] = undefined; }); } else { const { nativeCurrency } = this.config; newContractExchangeRates = yield this.fetchAndMapExchangeRates(nativeCurrency, slug); } this.update({ contractExchangeRates: newContractExchangeRates }); }); } /** * Checks if the active network's native currency is supported by the coingecko API. * If supported, it fetches and maps contractExchange rates to a format to be consumed by the UI. * If not supported, it fetches contractExchange rates and maps them from token/fallback-currency * to token/nativeCurrency. * * @param nativeCurrency - The native currency of the currently active network. * @param slug - The unique slug used to id the chain by the coingecko api * should be used to query token exchange rates. * @returns An object with conversion rates for each token * related to the network's native currency. */ fetchAndMapExchangeRates(nativeCurrency, slug) { return __awaiter(this, void 0, void 0, function* () { const contractExchangeRates = {}; // check if native currency is supported as a vs_currency by the API const nativeCurrencySupported = yield this.checkIsSupportedVsCurrency(nativeCurrency); if (nativeCurrencySupported) { // If it is we can do a simple fetch against the CoinGecko API const prices = yield this.fetchExchangeRate(slug, nativeCurrency); this.tokenList.forEach((token) => { const price = prices[token.address.toLowerCase()]; contractExchangeRates[(0, util_1.toChecksumHexAddress)(token.address)] = price ? price[nativeCurrency.toLowerCase()] : 0; }); } else { // if native currency is not supported we need to use a fallback vsCurrency, get the exchange rates // in token/fallback-currency format and convert them to expected token/nativeCurrency format. let tokenExchangeRates; let vsCurrencyToNativeCurrencyConversionRate = 0; try { [ tokenExchangeRates, { conversionRate: vsCurrencyToNativeCurrencyConversionRate }, ] = yield Promise.all([ this.fetchExchangeRate(slug, constants_1.FALL_BACK_VS_CURRENCY), (0, crypto_compare_1.fetchExchangeRate)(nativeCurrency, constants_1.FALL_BACK_VS_CURRENCY, false), ]); } catch (error) { if (error instanceof Error && error.message.includes('market does not exist for this coin pair')) { return {}; } throw error; } for (const [tokenAddress, conversion] of Object.entries(tokenExchangeRates)) { const tokenToVsCurrencyConversionRate = conversion[constants_1.FALL_BACK_VS_CURRENCY.toLowerCase()]; contractExchangeRates[(0, util_1.toChecksumHexAddress)(tokenAddress)] = tokenToVsCurrencyConversionRate * vsCurrencyToNativeCurrencyConversionRate; } } return contractExchangeRates; }); } } exports.TokenRatesController = TokenRatesController; exports.default = TokenRatesController; //# sourceMappingURL=TokenRatesController.js.map