UNPKG

@pgchain/blockchain-libs

Version:
178 lines 7.15 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.PriceController = void 0; const bignumber_js_1 = __importDefault(require("bignumber.js")); const coingecko_1 = require("./channels/coingecko"); const uniswap_1 = require("./channels/uniswap"); const UNISWAPV2_CONFIG = { eth: { router_address: '0x7a250d5630b4cf539739df2c5dacb4c659f2488d', base_token_address: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', media_token_addresses: [ '0x2260fac5e5542a773aa44fbcfedf7c193bc2c599', '0xdac17f958d2ee523a2206206994597c13d831ec7', ], }, }; const UNISWAPV3_CONFIG = { eth: { quoter_address: '0xb27308f9f90d607463bb33ea1bebb41c27ce5ab6', base_token_address: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', media_token_addresses: [ '0x2260fac5e5542a773aa44fbcfedf7c193bc2c599', '0xdac17f958d2ee523a2206206994597c13d831ec7', ], }, }; class PriceController { constructor(providerController, storage) { this.priceCache = {}; this.providerController = providerController; this.storage = storage; } get channels() { if (!this._channels) { this._channels = this.initChannels(); } return this._channels; } static generateSearchPaths(coin, unit) { const paths = [[coin.code, unit]]; if (coin.code === 'btc' || unit === 'btc') { return paths; } paths.push([coin.code, 'btc', unit]); if (coin.chainCode && ![coin.code, unit, 'btc'].includes(coin.chainCode)) { paths.push([coin.code, coin.chainCode, unit]); paths.push([coin.code, coin.chainCode, 'btc', unit]); } return paths; } static splitPathsToPairs(paths) { const pairs = new Set(); for (const path of paths) { if (path.length < 2) { continue; } for (let i = 0, end = path.length - 1; i < end; ++i) { const [codeA, codeB] = path.slice(i, i + 2); if (codeA !== codeB) { pairs.add(`${codeA}-${codeB}`); pairs.add(`${codeB}-${codeA}`); } } } return Array.from(pairs.values()); } initChannels() { return [ new coingecko_1.Coingecko(), new uniswap_1.UniswapV2(UNISWAPV2_CONFIG, this.providerController), new uniswap_1.UniswapV3(UNISWAPV3_CONFIG, this.providerController), ]; } async pricing(coins) { if (coins.length <= 0) { return; } const cache = {}; for (const channel of this.channels) { try { const prices = await channel.pricing(coins); for (const price of prices) { const pair = `${price.coin}-${price.unit}`.toUpperCase(); if (typeof cache[pair] === 'undefined') { cache[pair] = []; } cache[pair].push(price.value); } } catch (e) { console.error(`Error in running channel. channel: ${channel.constructor.name}, error: `, e); } } const results = Promise.all(Object.entries(cache) .map(([pair, values]) => [pair, bignumber_js_1.default.sum(...values).div(values.length)]) .filter(([_, value]) => value.isFinite() && value.gt(0)) .map(([pair, value]) => this.storage.set(`PRICE-${pair}`, value.toString()).then((value) => ({ status: 'fulfilled', value }), (reason) => ({ status: 'rejected', reason })))); console.debug('results of pricing: ', results); } async getPrice(coin, unit, orDefault = 0) { const pair = `${coin.code}-${unit}`; let [value, expiredAt] = this.priceCache[pair] || []; if (value && expiredAt && expiredAt >= Date.now()) { return value; } delete this.priceCache[pair]; value = await this.getPriceNoCache(coin, unit, orDefault); expiredAt = Date.now() + PriceController.PRICE_CACHE_TIMEOUT; this.priceCache[pair] = [value, expiredAt]; return value; } async getPriceNoCache(coin, unit, orDefault = 0) { if (coin.code === unit) { return new bignumber_js_1.default(1); } orDefault = new bignumber_js_1.default(orDefault); const paths = PriceController.generateSearchPaths(coin, unit); const pairs = PriceController.splitPathsToPairs(paths); if (pairs.length <= 0) { return orDefault; } const cachedPrices = (await this.storage.get(pairs.map((i) => `PRICE-${i}`.toUpperCase()))); if (cachedPrices.length !== pairs.length) { console.error(`The length of the price returned from storage does not match the length of the pair. ${cachedPrices.length} !== ${pairs.length}`); return orDefault; } const pricesOfPairs = cachedPrices.reduce((acc, cur, index) => { if (typeof cur === 'string' && !!cur) { const value = new bignumber_js_1.default(cur); if (value.isFinite() && value.gt(0)) { acc[pairs[index]] = value; } } return acc; }, {}); let price = new bignumber_js_1.default(0); for (const path of paths) { if (path.length < 2) { continue; } price = new bignumber_js_1.default(1); for (let i = 0, end = path.length - 1; i < end; ++i) { const [codeA, codeB] = path.slice(i, i + 2); let rate; if (codeA === codeB) { rate = 1; } else { rate = pricesOfPairs[`${codeA}-${codeB}`]; if (!rate || rate.lte(0)) { const revertedRate = pricesOfPairs[`${codeB}-${codeA}`]; if (revertedRate && revertedRate.gt(0)) { rate = new bignumber_js_1.default(1).div(revertedRate); } } rate = rate && rate.isFinite() && rate.gte(0) ? rate : new bignumber_js_1.default(0); } price = price.multipliedBy(rate); if (!price.isFinite() || price.lte(0)) { // invalid path, try next break; } } if (price.isFinite() && price.gt(0)) { // got price already break; } } return price.isFinite() && price.gte(0) ? price : orDefault; } } exports.PriceController = PriceController; PriceController.PRICE_CACHE_TIMEOUT = 5 * 60 * 1000; //# sourceMappingURL=index.js.map