UNPKG

sparrow-controllers

Version:

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

269 lines 11.5 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()); }); }; var __rest = (this && this.__rest) || function (s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; } return t; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.TokenListController = void 0; const ubq_contract_metadata_1 = __importDefault(require("ubq-contract-metadata")); const async_mutex_1 = require("async-mutex"); // eslint-disable-next-line import/no-named-as-default const abort_controller_1 = __importDefault(require("abort-controller")); const BaseControllerV2_1 = require("../BaseControllerV2"); const util_1 = require("../util"); const token_service_1 = require("../apis/token-service"); const DEFAULT_INTERVAL = 60 * 60 * 1000; const DEFAULT_THRESHOLD = 60 * 30 * 1000; const name = 'TokenListController'; const metadata = { tokenList: { persist: true, anonymous: true }, tokensChainsCache: { persist: true, anonymous: true }, }; const defaultState = { tokenList: {}, tokensChainsCache: {}, }; /** * Controller that passively polls on a set interval for the list of tokens from metaswaps api */ class TokenListController extends BaseControllerV2_1.BaseController { // private abortSignal: AbortSignal; /** * Creates a TokenListController instance. * * @param options - The controller options. * @param options.chainId - The chain ID of the current network. * @param options.useStaticTokenList - Indicates whether to use the static token list or not. * @param options.onNetworkStateChange - A function for registering an event handler for network state changes. * @param options.onPreferencesStateChange -A function for registering an event handler for preference state changes. * @param options.interval - The polling interval, in milliseconds. * @param options.cacheRefreshThreshold - The token cache expiry time, in milliseconds. * @param options.messenger - A restricted controller messenger. * @param options.state - Initial state to set on this controller. */ constructor({ chainId, useStaticTokenList, onNetworkStateChange, onPreferencesStateChange, interval = DEFAULT_INTERVAL, cacheRefreshThreshold = DEFAULT_THRESHOLD, messenger, state, }) { super({ name, metadata, messenger, state: Object.assign(Object.assign({}, defaultState), state), }); this.mutex = new async_mutex_1.Mutex(); this.intervalDelay = interval; this.cacheRefreshThreshold = cacheRefreshThreshold; this.chainId = chainId; this.useStaticTokenList = useStaticTokenList; this.abortController = new abort_controller_1.default(); onNetworkStateChange((networkState) => __awaiter(this, void 0, void 0, function* () { if (this.chainId !== networkState.provider.chainId) { this.abortController.abort(); this.abortController = new abort_controller_1.default(); this.chainId = networkState.provider.chainId; yield this.restart(); } })); onPreferencesStateChange((preferencesState) => __awaiter(this, void 0, void 0, function* () { if (this.useStaticTokenList !== preferencesState.useStaticTokenList) { this.abortController.abort(); this.abortController = new abort_controller_1.default(); this.useStaticTokenList = preferencesState.useStaticTokenList; yield this.restart(); } })); } /** * Start polling for the token list. */ start() { return __awaiter(this, void 0, void 0, function* () { yield this.startPolling(); }); } /** * Restart polling for the token list. */ restart() { return __awaiter(this, void 0, void 0, function* () { this.stopPolling(); yield this.startPolling(); }); } /** * Stop polling for the token list. */ stop() { this.stopPolling(); } /** * Prepare to discard this controller. * * This stops any active polling. */ destroy() { super.destroy(); this.stopPolling(); } stopPolling() { if (this.intervalId) { clearInterval(this.intervalId); } } /** * Starts a new polling interval. */ startPolling() { return __awaiter(this, void 0, void 0, function* () { yield (0, util_1.safelyExecute)(() => this.fetchTokenList()); this.intervalId = setInterval(() => __awaiter(this, void 0, void 0, function* () { yield (0, util_1.safelyExecute)(() => this.fetchTokenList()); }), this.intervalDelay); }); } /** * Fetching token list. */ fetchTokenList() { return __awaiter(this, void 0, void 0, function* () { if (this.useStaticTokenList) { yield this.fetchFromStaticTokenList(); } else { yield this.fetchFromDynamicTokenList(); } }); } /** * Fetching token list from the contract-metadata as a fallback. */ fetchFromStaticTokenList() { return __awaiter(this, void 0, void 0, function* () { const tokenList = {}; for (const tokenAddress in ubq_contract_metadata_1.default) { const _a = ubq_contract_metadata_1.default[tokenAddress], { erc20, logo: filePath } = _a, token = __rest(_a, ["erc20", "logo"]); if (erc20) { tokenList[tokenAddress] = Object.assign(Object.assign({}, token), { address: tokenAddress, iconUrl: filePath, occurrences: null }); } } this.update(() => { return { tokenList, tokensChainsCache: {}, }; }); }); } /** * Fetching token list from the Token Service API. */ fetchFromDynamicTokenList() { return __awaiter(this, void 0, void 0, function* () { const releaseLock = yield this.mutex.acquire(); try { const cachedTokens = yield (0, util_1.safelyExecute)(() => this.fetchFromCache()); const _a = this.state, { tokensChainsCache } = _a, tokensData = __rest(_a, ["tokensChainsCache"]); const tokenList = {}; if (cachedTokens) { for (const token of cachedTokens) { tokenList[token.address] = token; } } else { const tokensFromAPI = yield (0, util_1.safelyExecute)(() => (0, token_service_1.fetchTokenList)(this.chainId, this.abortController.signal)); if (!tokensFromAPI) { const backupTokenList = tokensChainsCache[this.chainId] ? tokensChainsCache[this.chainId].data : []; for (const token of backupTokenList) { tokenList[token.address] = token; } this.update(() => { return Object.assign(Object.assign({}, tokensData), { tokenList, tokensChainsCache }); }); return; } // filtering out tokens with less than 2 occurrences const filteredTokenList = tokensFromAPI.filter((token) => token.occurrences && token.occurrences >= 2); // removing the tokens with symbol conflicts const symbolsList = filteredTokenList.map((token) => token.symbol); const duplicateSymbols = [ ...new Set(symbolsList.filter((symbol, index) => symbolsList.indexOf(symbol) !== index)), ]; const uniqueTokenList = filteredTokenList.filter((token) => !duplicateSymbols.includes(token.symbol)); for (const token of uniqueTokenList) { tokenList[token.address] = token; } } const updatedTokensChainsCache = Object.assign(Object.assign({}, tokensChainsCache), { [this.chainId]: { timestamp: Date.now(), data: Object.values(tokenList), } }); this.update(() => { return Object.assign(Object.assign({}, tokensData), { tokenList, tokensChainsCache: updatedTokensChainsCache }); }); } finally { releaseLock(); } }); } /** * Checks if the Cache timestamp is valid, * if yes data in cache will be returned * otherwise null will be returned. * * @returns The cached data, or `null` if the cache was expired. */ fetchFromCache() { return __awaiter(this, void 0, void 0, function* () { const { tokensChainsCache } = this.state; const dataCache = tokensChainsCache[this.chainId]; const now = Date.now(); if ((dataCache === null || dataCache === void 0 ? void 0 : dataCache.data) && now - (dataCache === null || dataCache === void 0 ? void 0 : dataCache.timestamp) < this.cacheRefreshThreshold) { return dataCache.data; } return null; }); } /** * Fetch metadata for a token. * * @param tokenAddress - The address of the token. * @returns The token metadata. */ fetchTokenMetadata(tokenAddress) { return __awaiter(this, void 0, void 0, function* () { const releaseLock = yield this.mutex.acquire(); try { const token = (yield (0, token_service_1.fetchTokenMetadata)(this.chainId, tokenAddress, this.abortController.signal)); return token; } finally { releaseLock(); } }); } } exports.TokenListController = TokenListController; exports.default = TokenListController; //# sourceMappingURL=TokenListController.js.map