From 6433164ba9d494e6ea51f662e3548206b6df432c Mon Sep 17 00:00:00 2001 From: Inokentii Mazhara Date: Wed, 15 Nov 2023 18:49:09 +0200 Subject: [PATCH 1/9] TW-1147 Improve getting exchange rates --- package.json | 1 + src/index.ts | 22 ++--- src/utils/three-route.ts | 25 ++--- src/utils/tokens.ts | 206 ++++++++++++++++++++++++++++++++------- 4 files changed, 191 insertions(+), 63 deletions(-) diff --git a/package.json b/package.json index 8a8f9fc..e8c3441 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "express": "^4.18.2", "firebase-admin": "^10.0.2", "ioredis": "^5.3.2", + "lodash": "^4.17.21", "memoizee": "^0.4.15", "pino": "^6.11.2", "pino-http": "^5.5.0", diff --git a/src/index.ts b/src/index.ts index c3c5826..e21e9e8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -31,7 +31,7 @@ import logger from './utils/logger'; import { getSignedMoonPayUrl } from './utils/moonpay/get-signed-moonpay-url'; import SingleQueryDataProvider from './utils/SingleQueryDataProvider'; import { tezExchangeRateProvider } from './utils/tezos'; -import { tokensExchangeRatesProvider } from './utils/tokens'; +import { getExchangeRatesFromDB } from './utils/tokens'; const PINO_LOGGER = { logger: logger.child({ name: 'web' }), @@ -166,28 +166,18 @@ app.get('/api/abtest', (_, res) => { app.get('/api/exchange-rates/tez', makeProviderDataRequestHandler(tezExchangeRateProvider)); app.get('/api/exchange-rates', async (_req, res) => { - const { data: tokensExchangeRates, error: tokensExchangeRatesError } = await getProviderStateWithTimeout( - tokensExchangeRatesProvider - ); + const tokensExchangeRates = await getExchangeRatesFromDB(); const { data: tezExchangeRate, error: tezExchangeRateError } = await getProviderStateWithTimeout( tezExchangeRateProvider ); - if (tokensExchangeRatesError !== undefined) { - return res.status(500).send({ - error: tokensExchangeRatesError.message - }); - } else if (tezExchangeRateError !== undefined) { + + if (tezExchangeRateError !== undefined) { return res.status(500).send({ error: tezExchangeRateError.message }); - } else { - if (tokensExchangeRates !== undefined && tezExchangeRate !== undefined) { - return res.json([ - ...tokensExchangeRates.map(({ ...restProps }) => restProps), - { exchangeRate: tezExchangeRate.toString() } - ]); - } } + + res.json([...tokensExchangeRates, { exchangeRate: tezExchangeRate.toString() }]); }); app.get('/api/moonpay-sign', async (_req, res) => { diff --git a/src/utils/three-route.ts b/src/utils/three-route.ts index 4782feb..1a89c89 100644 --- a/src/utils/three-route.ts +++ b/src/utils/three-route.ts @@ -25,7 +25,7 @@ export interface ThreeRouteChain { } // TODO: add axios adapter and change type if precision greater than of standard js number type is necessary -export interface ThreeRouteSwapResponse { +export interface ThreeRouteClassicSwapResponse { input: number; output: number; chains: ThreeRouteChain[]; @@ -34,8 +34,8 @@ export interface ThreeRouteSwapResponse { export interface ThreeRouteSirsSwapResponse { input: number; output: number; - tzbtcChain: ThreeRouteSwapResponse; - xtzChain: ThreeRouteSwapResponse; + tzbtcChain: ThreeRouteClassicSwapResponse; + xtzChain: ThreeRouteClassicSwapResponse; } interface ThreeRouteTokenCommon { @@ -98,11 +98,13 @@ export interface ThreeRouteDex { type ThreeRouteQueryParams = object | SwapQueryParams; type ThreeRouteQueryResponse = - | ThreeRouteSwapResponse + | ThreeRouteClassicSwapResponse | ThreeRouteSirsSwapResponse | ThreeRouteDex[] | ThreeRouteToken[]; +export type ThreeRouteSwapResponse = ThreeRouteClassicSwapResponse | ThreeRouteSirsSwapResponse; + export const THREE_ROUTE_SIRS_SYMBOL = 'SIRS'; const threeRouteBuildQueryFn = makeBuildQueryFn( @@ -111,18 +113,17 @@ const threeRouteBuildQueryFn = makeBuildQueryFn(({ inputTokenSymbol, outputTokenSymbol, realAmount }) => { - const isSirsSwap = inputTokenSymbol === THREE_ROUTE_SIRS_SYMBOL || outputTokenSymbol === THREE_ROUTE_SIRS_SYMBOL; +export const getThreeRouteSwap = threeRouteBuildQueryFn( + ({ inputTokenSymbol, outputTokenSymbol, realAmount }) => { + const isSirsSwap = inputTokenSymbol === THREE_ROUTE_SIRS_SYMBOL || outputTokenSymbol === THREE_ROUTE_SIRS_SYMBOL; - return `/${isSirsSwap ? 'swap-sirs' : 'swap'}/${inputTokenSymbol}/${outputTokenSymbol}/${realAmount}`; -}); + return `/${isSirsSwap ? 'swap-sirs' : 'swap'}/${inputTokenSymbol}/${outputTokenSymbol}/${realAmount}`; + } +); export const getThreeRouteDexes = threeRouteBuildQueryFn('/dexes', []); export const getThreeRouteTokens = threeRouteBuildQueryFn('/tokens', []); -export const getChains = (response: ThreeRouteSwapResponse | ThreeRouteSirsSwapResponse) => +export const getChains = (response: ThreeRouteSwapResponse) => 'chains' in response ? response.chains : [...response.xtzChain.chains, ...response.tzbtcChain.chains]; diff --git a/src/utils/tokens.ts b/src/utils/tokens.ts index 5bcf340..2212f3d 100644 --- a/src/utils/tokens.ts +++ b/src/utils/tokens.ts @@ -1,6 +1,8 @@ import { BigNumber } from 'bignumber.js'; +import { differenceBy, isEqual } from 'lodash'; import { IPriceHistory } from '../interfaces/price-history.interfaces'; +import { redisClient } from '../redis'; import { blockFinder, EMPTY_BLOCK } from './block-finder'; import DataProvider from './DataProvider'; import fetch from './fetch'; @@ -18,31 +20,74 @@ import { ThreeRouteFa12Token, ThreeRouteFa2Token, getChains, - THREE_ROUTE_SIRS_SYMBOL + THREE_ROUTE_SIRS_SYMBOL, + ThreeRouteSwapResponse, + ThreeRouteDex } from './three-route'; import { BcdTokenData, mapTzktTokenDataToBcdTokenData, tokensMetadataProvider } from './tzkt'; +interface SwapsResponse { + directSwap: ThreeRouteSwapResponse; + invertedSwap: ThreeRouteSwapResponse; + updatedAt: string; +} + +interface TokenExchangeRateEntry { + tokenAddress: string; + tokenId?: number; + exchangeRate: BigNumber; + metadata?: BcdTokenData; + swapsUpdatedAt?: string; +} + +class TimeoutError extends Error {} + const tokensListProvider = new SingleQueryDataProvider(30000, () => getThreeRouteTokens({})); const dexesListProvider = new SingleQueryDataProvider(30000, () => getThreeRouteDexes({})); const THREE_ROUTE_TEZ_SYMBOL = 'XTZ'; -const PROBE_TEZ_AMOUNT = 10; -const probeSwapsProvider = new DataProvider(Infinity, async (outputTokenSymbol: string) => { +const EMPTY_SWAP = { + input: 0, + output: 0, + chains: [] +}; + +const ASPENCOIN_ADDRESS = 'KT1S5iPRQ612wcNm6mXDqDhTNegGFcvTV7vM'; + +const EXCHANGE_RATES_STORAGE_KEY = 'exchange_rates'; +const PREV_DEXES_LIST_STORAGE_KEY = 'prev_dexes_list'; + +const getToTezExchangeRatesVersions = ({ directSwap, invertedSwap }: SwapsResponse) => { + const toTezExchangeRatesVersions: BigNumber[] = []; + if (directSwap.output !== 0) { + toTezExchangeRatesVersions.push(new BigNumber(directSwap.input).div(directSwap.output)); + } + if (invertedSwap.output !== 0) { + toTezExchangeRatesVersions.push(new BigNumber(invertedSwap.output).div(invertedSwap.input)); + } + + return toTezExchangeRatesVersions; +}; + +const assertSmallRatesDifference = (swapResponse: SwapsResponse) => { + const toTezExchangeRatesVersions = getToTezExchangeRatesVersions(swapResponse); + const minRate = BigNumber.min(...toTezExchangeRatesVersions); + const maxRate = BigNumber.max(...toTezExchangeRatesVersions); + if (minRate.div(maxRate).lt(0.9)) { + throw new Error('Prices difference is too big'); + } +}; + +const getSwapsByTezAmount = async (outputTokenSymbol: string, tezAmount: number) => { + const updatedAt = new Date().toISOString(); const directSwap = await getThreeRouteSwap({ inputTokenSymbol: THREE_ROUTE_TEZ_SYMBOL, outputTokenSymbol, - realAmount: PROBE_TEZ_AMOUNT + realAmount: tezAmount }); if (directSwap.output === 0) { - return { - directSwap, - invertedSwap: { - input: 0, - output: 0, - chains: [] - } - }; + throw new Error('Failed to get direct swap'); } const invertedSwap = await getThreeRouteSwap({ @@ -51,19 +96,54 @@ const probeSwapsProvider = new DataProvider(Infinity, async (outputTokenSymbol: realAmount: directSwap.output }); - return { directSwap, invertedSwap }; -}); + const response = { directSwap, invertedSwap, updatedAt }; + assertSmallRatesDifference(response); -export class TimeoutError extends Error {} + return response; +}; -export type TokenExchangeRateEntry = { - tokenAddress: string; - tokenId?: number; - exchangeRate: BigNumber; - metadata?: BcdTokenData; +const getSwapsByOneToken = async (outputTokenSymbol: string) => { + const updatedAt = new Date().toISOString(); + const invertedSwap = await getThreeRouteSwap({ + inputTokenSymbol: outputTokenSymbol, + outputTokenSymbol: THREE_ROUTE_TEZ_SYMBOL, + realAmount: 1 + }); + + if (invertedSwap.output === 0) { + throw new Error(`Failed to get swaps for 1 ${outputTokenSymbol}`); + } + + const directSwap = await getThreeRouteSwap({ + inputTokenSymbol: THREE_ROUTE_TEZ_SYMBOL, + outputTokenSymbol, + realAmount: invertedSwap.output + }); + + return { directSwap, invertedSwap, updatedAt }; }; -export const ASPENCOIN_ADDRESS = 'KT1S5iPRQ612wcNm6mXDqDhTNegGFcvTV7vM'; +const getSwaps = async (outputTokenSymbol: string) => { + try { + return await getSwapsByTezAmount(outputTokenSymbol, 10); + } catch {} + + try { + return await getSwapsByTezAmount(outputTokenSymbol, 1); + } catch {} + + try { + return await getSwapsByOneToken(outputTokenSymbol); + } catch { + return { directSwap: EMPTY_SWAP, invertedSwap: EMPTY_SWAP, updatedAt: new Date().toISOString() }; + } +}; + +const probeSwapsProvider = new DataProvider(Infinity, getSwaps); + +const rejectOnTimeout = (timeoutMs: number) => + new Promise((_, rej) => setTimeout(() => rej(new TimeoutError()), timeoutMs)); + tokensMetadataProvider.subscribe(ASPENCOIN_ADDRESS); const getTokensExchangeRates = async (): Promise => { @@ -82,14 +162,13 @@ const getTokensExchangeRates = async (): Promise => { (token): token is ThreeRouteFa12Token | ThreeRouteFa2Token => token.standard !== ThreeRouteStandardEnum.xtz ) .map(async (token): Promise => { - logger.info(`Getting exchange rate for ${token.symbol}`); const { contract, tokenId: rawTokenId } = token; const tokenId = isDefined(rawTokenId) ? Number(rawTokenId) : undefined; await probeSwapsProvider.subscribe(token.symbol); try { const { data: probeSwaps, error: swapError } = await Promise.race([ probeSwapsProvider.get(token.symbol), - new Promise((_, rej) => setTimeout(() => rej(new TimeoutError()), 10000)) + rejectOnTimeout(10000) ]); await tokensMetadataProvider.subscribe(contract, tokenId); const { data: metadata } = await tokensMetadataProvider.get(contract, tokenId); @@ -99,14 +178,7 @@ const getTokensExchangeRates = async (): Promise => { throw swapError; } - const { directSwap, invertedSwap } = probeSwaps; - const toTezExchangeRatesVersions: BigNumber[] = []; - if (directSwap.output !== 0) { - toTezExchangeRatesVersions.push(new BigNumber(PROBE_TEZ_AMOUNT).div(directSwap.output)); - } - if (invertedSwap.output !== 0) { - toTezExchangeRatesVersions.push(new BigNumber(invertedSwap.output).div(invertedSwap.input)); - } + const toTezExchangeRatesVersions = getToTezExchangeRatesVersions(probeSwaps); const exchangeRate = toTezExchangeRatesVersions.length === 0 ? new BigNumber(0) @@ -118,7 +190,8 @@ const getTokensExchangeRates = async (): Promise => { tokenAddress: contract, tokenId, exchangeRate, - metadata: mapTzktTokenDataToBcdTokenData(metadata?.[0]) + metadata: mapTzktTokenDataToBcdTokenData(metadata?.[0]), + swapsUpdatedAt: probeSwaps.updatedAt }; } catch (e) { if (e instanceof TimeoutError) { @@ -166,7 +239,46 @@ const getTokensExchangeRates = async (): Promise => { return [...exchangeRates].filter(({ exchangeRate }) => !exchangeRate.eq(0)); }; -export const tokensExchangeRatesProvider = new SingleQueryDataProvider(60000, getTokensExchangeRates); +const tokensExchangeRatesProvider = new SingleQueryDataProvider(60000, getTokensExchangeRates); + +export const getExchangeRatesFromDB = async (): Promise => { + const rawValue = await redisClient.get(EXCHANGE_RATES_STORAGE_KEY); + + return JSON.parse(rawValue ?? '[]'); +}; + +const updateExchangeRatesInDB = async () => { + const prevExchangeRates = await getExchangeRatesFromDB(); + const prevIndexedExchangeRates = Object.fromEntries( + prevExchangeRates.map(exchangeRate => [`${exchangeRate.tokenAddress}_${exchangeRate.tokenId}`, exchangeRate]) + ); + const { data: exchangeRatesUpdates, error: exchangeRatesError } = await tokensExchangeRatesProvider.getState(); + + if (exchangeRatesError) { + return; + } + + const indexedExchangeRatesUpdates = Object.fromEntries( + exchangeRatesUpdates.map(exchangeRate => [`${exchangeRate.tokenAddress}_${exchangeRate.tokenId}`, exchangeRate]) + ); + + const newExchangeRates = Object.values({ + ...prevIndexedExchangeRates, + ...indexedExchangeRatesUpdates + }); + + await redisClient.set(EXCHANGE_RATES_STORAGE_KEY, JSON.stringify(newExchangeRates)); +}; +updateExchangeRatesInDB().catch(logger.error); + +const getPrevDexesList = async (): Promise => { + const rawValue = await redisClient.get(PREV_DEXES_LIST_STORAGE_KEY); + + return isDefined(rawValue) ? JSON.parse(rawValue) : null; +}; + +const setPrevDexesList = async (dexesList: ThreeRouteDex[]) => + redisClient.set(PREV_DEXES_LIST_STORAGE_KEY, JSON.stringify(dexesList)); const swapsUpdateSemaphore = new PromisifiedSemaphore(); blockFinder(EMPTY_BLOCK, async block => @@ -180,14 +292,29 @@ blockFinder(EMPTY_BLOCK, async block => throw tokensError ?? dexesError; } + let dexesListChanged = false; + const prevDexesList = await getPrevDexesList(); + if (prevDexesList) { + const createdOrUpdatedDexesList = differenceBy(dexes, prevDexesList, isEqual); + const deletedOrUpdatedDexesList = differenceBy(prevDexesList, dexes, isEqual); + dexesListChanged = createdOrUpdatedDexesList.length > 0 || deletedOrUpdatedDexesList.length > 0; + } + await setPrevDexesList(dexes); + + if (dexesListChanged) { + logger.info('dexes list changed, refreshing all tokens exchange rates'); + } + const outputsUpdatesFlags = await Promise.all( tokens.map(async token => { if (token.symbol === THREE_ROUTE_TEZ_SYMBOL) { return false; } - if (token.symbol === THREE_ROUTE_SIRS_SYMBOL) { + if (dexesListChanged || token.symbol === THREE_ROUTE_SIRS_SYMBOL) { // Swap output for SIRS should be updated each block because of baking subsidy + await probeSwapsProvider.refetchInSubscription(token.symbol); + return true; } @@ -195,7 +322,7 @@ blockFinder(EMPTY_BLOCK, async block => await probeSwapsProvider.subscribe(token.symbol); const { data: probeSwaps, error: swapError } = await Promise.race([ probeSwapsProvider.get(token.symbol), - new Promise((_, rej) => setTimeout(() => rej(new TimeoutError()), 10000)) + rejectOnTimeout(10000) ]); if (swapError) { @@ -205,6 +332,14 @@ blockFinder(EMPTY_BLOCK, async block => const { directSwap, invertedSwap } = probeSwaps; const directSwapChains = getChains(directSwap); const invertedSwapChains = getChains(invertedSwap); + + if (directSwapChains.length === 0) { + logger.info(`updating swap output for token ${token.symbol} because of direct swap chains absence`); + await probeSwapsProvider.refetchInSubscription(token.symbol); + + return true; + } + const dexesAddresses = directSwapChains .concat(invertedSwapChains) .map(chain => chain.hops.map(hop => dexes.find(dex => dex.id === hop.dex)?.contract).filter(isDefined)) @@ -229,6 +364,7 @@ blockFinder(EMPTY_BLOCK, async block => if (outputsUpdatesFlags.some(flag => flag)) { logger.info('refreshing tokens exchange rates because of swaps outputs updates'); await tokensExchangeRatesProvider.refetch(); + await updateExchangeRatesInDB(); } logger.info(`stats updated for level ${block.header.level}`); }) From 5192645cad2aa8099f345ab03df7d118b81ed976 Mon Sep 17 00:00:00 2001 From: Inokentii Mazhara Date: Thu, 16 Nov 2023 11:59:12 +0200 Subject: [PATCH 2/9] TW-1147 Refactoring according to comments --- src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index e21e9e8..7da90a9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -171,7 +171,7 @@ app.get('/api/exchange-rates', async (_req, res) => { tezExchangeRateProvider ); - if (tezExchangeRateError !== undefined) { + if (isDefined(tezExchangeRateError)) { return res.status(500).send({ error: tezExchangeRateError.message }); From 2efda86de10af29d92c62a22b445f346ff8716f9 Mon Sep 17 00:00:00 2001 From: Inokentii Mazhara Date: Thu, 16 Nov 2023 12:01:17 +0200 Subject: [PATCH 3/9] TW-1147 Revert recent changes --- src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index 7da90a9..e21e9e8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -171,7 +171,7 @@ app.get('/api/exchange-rates', async (_req, res) => { tezExchangeRateProvider ); - if (isDefined(tezExchangeRateError)) { + if (tezExchangeRateError !== undefined) { return res.status(500).send({ error: tezExchangeRateError.message }); From 07543554ce64217942c3c4bdb6668c8b29103cc4 Mon Sep 17 00:00:00 2001 From: Inokentii Mazhara Date: Fri, 17 Nov 2023 11:53:22 +0200 Subject: [PATCH 4/9] TW-1147 Remove getting exchange rate for Aspencoin --- src/utils/tokens.ts | 29 ----------------------------- 1 file changed, 29 deletions(-) diff --git a/src/utils/tokens.ts b/src/utils/tokens.ts index 2212f3d..cc392be 100644 --- a/src/utils/tokens.ts +++ b/src/utils/tokens.ts @@ -1,11 +1,9 @@ import { BigNumber } from 'bignumber.js'; import { differenceBy, isEqual } from 'lodash'; -import { IPriceHistory } from '../interfaces/price-history.interfaces'; import { redisClient } from '../redis'; import { blockFinder, EMPTY_BLOCK } from './block-finder'; import DataProvider from './DataProvider'; -import fetch from './fetch'; import { getRecentDestinations } from './get-recent-destinations'; import { isDefined } from './helpers'; import logger from './logger'; @@ -207,33 +205,6 @@ const getTokensExchangeRates = async (): Promise => { const exchangeRates = exchangeRatesWithHoles.filter(isDefined); - if (!exchangeRates.some(({ tokenAddress }) => tokenAddress === ASPENCOIN_ADDRESS)) { - logger.info('Getting exchange rate for Aspencoin'); - try { - const { data: aspencoinMetadata, error: aspencoinMetadataError } = await tokensMetadataProvider.get( - ASPENCOIN_ADDRESS - ); - if (aspencoinMetadataError) { - throw aspencoinMetadataError; - } - const priceHistory = await fetch( - 'https://gateway-web-markets.tzero.com/mdt/public-pricehistory/ASPD?page=1' - ); - const latestValidEntry = priceHistory.priceHistories.find(({ close }) => close !== null); - const tokenPrice = latestValidEntry ? new BigNumber(latestValidEntry.close ?? 0) : new BigNumber(0); - - exchangeRates.push({ - tokenAddress: ASPENCOIN_ADDRESS, - tokenId: undefined, - exchangeRate: tokenPrice, - metadata: mapTzktTokenDataToBcdTokenData(aspencoinMetadata?.[0]) - }); - } catch (e) { - logger.error('Failed to get exchange rate for Aspencoin'); - logger.error(e as Error); - } - } - logger.info('Successfully got tokens exchange rates'); return [...exchangeRates].filter(({ exchangeRate }) => !exchangeRate.eq(0)); From 480be5d0867d7ae2be7c0de2ac3f87beb4e1998f Mon Sep 17 00:00:00 2001 From: Inokentii Mazhara Date: Mon, 27 Nov 2023 11:41:17 +0200 Subject: [PATCH 5/9] TW-1194 Replace TZPRO API with Binance API --- .env.dist | 1 - src/config.ts | 3 +-- src/interfaces/ticker.interface.ts | 16 ++-------------- src/utils/tezos.ts | 15 ++++----------- 4 files changed, 7 insertions(+), 28 deletions(-) diff --git a/.env.dist b/.env.dist index 32c3803..2b50f2b 100644 --- a/.env.dist +++ b/.env.dist @@ -10,4 +10,3 @@ THREE_ROUTE_API_AUTH_TOKEN= REDIS_URL= ADD_NOTIFICATION_USERNAME= ADD_NOTIFICATION_PASSWORD= -TZPRO_API_KEY= diff --git a/src/config.ts b/src/config.ts index 81ec1d8..4cfd913 100644 --- a/src/config.ts +++ b/src/config.ts @@ -12,8 +12,7 @@ export const EnvVars = { THREE_ROUTE_API_AUTH_TOKEN: getEnv('THREE_ROUTE_API_AUTH_TOKEN'), REDIS_URL: getEnv('REDIS_URL'), ADD_NOTIFICATION_USERNAME: getEnv('ADD_NOTIFICATION_USERNAME'), - ADD_NOTIFICATION_PASSWORD: getEnv('ADD_NOTIFICATION_PASSWORD'), - TZPRO_API_KEY: getEnv('TZPRO_API_KEY') + ADD_NOTIFICATION_PASSWORD: getEnv('ADD_NOTIFICATION_PASSWORD') }; for (const name in EnvVars) { diff --git a/src/interfaces/ticker.interface.ts b/src/interfaces/ticker.interface.ts index 0658fae..15010cd 100644 --- a/src/interfaces/ticker.interface.ts +++ b/src/interfaces/ticker.interface.ts @@ -1,16 +1,4 @@ export interface ITicker { - pair: string; - base: string; - quote: string; - exchange: string; - open: number; - high: number; - low: number; - last: number; - change: number; - vwap: number; - n_trades: number; - volume_base: number; - volume_quote: number; - timestamp: string; + symbol: string; + price: string; } diff --git a/src/utils/tezos.ts b/src/utils/tezos.ts index 669ee3f..f8b87a7 100644 --- a/src/utils/tezos.ts +++ b/src/utils/tezos.ts @@ -3,7 +3,6 @@ import { tzip12 } from '@taquito/tzip12'; import { tzip16 } from '@taquito/tzip16'; import memoizee from 'memoizee'; -import { EnvVars } from '../config'; import { ITicker } from '../interfaces/ticker.interface'; import fetch from './fetch'; import SingleQueryDataProvider from './SingleQueryDataProvider'; @@ -54,18 +53,12 @@ export const getStorage = memoizee( ); const getTezExchangeRate = async () => { - const marketTickers = await fetch>( - `https://api.tzpro.io/markets/tickers?api_key=${EnvVars.TZPRO_API_KEY}` - ); - const usdTickers = marketTickers.filter(e => e.quote === 'USD' && e.base === 'XTZ'); - // price index: use all USD ticker last prices with equal weight - const vol = usdTickers.reduce((s, t) => s + t.volume_base, 0) || null; - const price = vol === null ? 1 : usdTickers.reduce((s, t) => s + (t.last * t.volume_base) / vol, 0); - - return price; + const { price: rawPrice } = await fetch('https://api.binance.com/api/v3/ticker/price?symbol=XTZUSDT'); + + return Number(rawPrice); }; -export const tezExchangeRateProvider = new SingleQueryDataProvider(30000, getTezExchangeRate); +export const tezExchangeRateProvider = new SingleQueryDataProvider(60000, getTezExchangeRate); export class MetadataParseError extends Error {} From baae24792269e78201102ecb3d4492c116ae7525 Mon Sep 17 00:00:00 2001 From: Inokentii Mazhara Date: Mon, 27 Nov 2023 14:23:23 +0200 Subject: [PATCH 6/9] TW-1194 Remove old 'fetch' method --- package.json | 1 - src/utils/fetch.ts | 23 ----------------------- src/utils/tezos.ts | 24 +++++++++++++++++++++--- yarn.lock | 9 +-------- 4 files changed, 22 insertions(+), 35 deletions(-) delete mode 100644 src/utils/fetch.ts diff --git a/package.json b/package.json index e8c3441..3d580b2 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,6 @@ "bignumber.js": "^9.1.0", "body-parser": "^1.20.2", "cors": "^2.8.5", - "cross-fetch": "^3.1.5", "dotenv": "^9.0.2", "express": "^4.18.2", "firebase-admin": "^10.0.2", diff --git a/src/utils/fetch.ts b/src/utils/fetch.ts deleted file mode 100644 index 235f38c..0000000 --- a/src/utils/fetch.ts +++ /dev/null @@ -1,23 +0,0 @@ -import crossFetch from 'cross-fetch'; - -export class InvalidStatusError extends Error { - constructor(message: string, public status: number) { - super(message); - } -} - -export class NotOkFetchError extends Error {} - -export default async function fetch(url: string, init?: RequestInit): Promise { - const res = await crossFetch(url, init); - - const body = await res.json(); - if (res.status >= 400) { - throw new InvalidStatusError(body.message, res.status); - } - if (!res.ok) { - throw new NotOkFetchError('An error occurred while fetching'); - } - - return body; -} diff --git a/src/utils/tezos.ts b/src/utils/tezos.ts index f8b87a7..49754f6 100644 --- a/src/utils/tezos.ts +++ b/src/utils/tezos.ts @@ -1,10 +1,12 @@ import { compose, MichelCodecPacker, Signer, TezosToolkit } from '@taquito/taquito'; import { tzip12 } from '@taquito/tzip12'; import { tzip16 } from '@taquito/tzip16'; +import axios, { AxiosError } from 'axios'; import memoizee from 'memoizee'; import { ITicker } from '../interfaces/ticker.interface'; -import fetch from './fetch'; +import { isDefined } from './helpers'; +import logger from './logger'; import SingleQueryDataProvider from './SingleQueryDataProvider'; import { BcdTokenData } from './tzkt'; @@ -53,9 +55,25 @@ export const getStorage = memoizee( ); const getTezExchangeRate = async () => { - const { price: rawPrice } = await fetch('https://api.binance.com/api/v3/ticker/price?symbol=XTZUSDT'); + try { + const { data } = await axios.get('https://api.binance.com/api/v3/ticker/price?symbol=XTZUSDT'); + + return Number(data.price); + } catch (e) { + if (!(e instanceof AxiosError)) { + logger.error('Request for TEZ exchange rate failed with unknown error'); + } else if (isDefined(e.response) && isDefined(e.response.data)) { + logger.error( + `Request for TEZ exchange rate failed with status ${e.response.status} and message ${e.response.data}` + ); + } else if (isDefined(e.response) && isDefined(e.response.status)) { + logger.error(`Request for TEZ exchange rate failed with status ${e.response.status}`); + } else { + logger.error('Request for TEZ exchange rate failed without response'); + } - return Number(rawPrice); + throw e; + } }; export const tezExchangeRateProvider = new SingleQueryDataProvider(60000, getTezExchangeRate); diff --git a/yarn.lock b/yarn.lock index aef39d5..47b0342 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1311,13 +1311,6 @@ cross-env@^7.0.3: dependencies: cross-spawn "^7.0.1" -cross-fetch@^3.1.5: - version "3.1.5" - resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.5.tgz#e1389f44d9e7ba767907f7af8454787952ab534f" - integrity sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw== - dependencies: - node-fetch "2.6.7" - cross-spawn@^6.0.5: version "6.0.5" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" @@ -3168,7 +3161,7 @@ nice-try@^1.0.4: resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== -node-fetch@2.6.7, node-fetch@^2.6.1: +node-fetch@^2.6.1: version "2.6.7" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== From 5fa39bb08c5895f16246252a6aff6d2c9e7a9370 Mon Sep 17 00:00:00 2001 From: Inokentii Mazhara Date: Mon, 27 Nov 2023 16:10:37 +0200 Subject: [PATCH 7/9] TW-1194 Try coingecko for getting TEZ exchange rate --- src/interfaces/ticker.interface.ts | 4 ---- src/utils/tezos.ts | 8 ++++---- 2 files changed, 4 insertions(+), 8 deletions(-) delete mode 100644 src/interfaces/ticker.interface.ts diff --git a/src/interfaces/ticker.interface.ts b/src/interfaces/ticker.interface.ts deleted file mode 100644 index 15010cd..0000000 --- a/src/interfaces/ticker.interface.ts +++ /dev/null @@ -1,4 +0,0 @@ -export interface ITicker { - symbol: string; - price: string; -} diff --git a/src/utils/tezos.ts b/src/utils/tezos.ts index 49754f6..2d098c6 100644 --- a/src/utils/tezos.ts +++ b/src/utils/tezos.ts @@ -1,10 +1,10 @@ import { compose, MichelCodecPacker, Signer, TezosToolkit } from '@taquito/taquito'; import { tzip12 } from '@taquito/tzip12'; import { tzip16 } from '@taquito/tzip16'; -import axios, { AxiosError } from 'axios'; +import { AxiosError } from 'axios'; import memoizee from 'memoizee'; -import { ITicker } from '../interfaces/ticker.interface'; +import { getMarketsBySymbols } from './coingecko'; import { isDefined } from './helpers'; import logger from './logger'; import SingleQueryDataProvider from './SingleQueryDataProvider'; @@ -56,9 +56,9 @@ export const getStorage = memoizee( const getTezExchangeRate = async () => { try { - const { data } = await axios.get('https://api.binance.com/api/v3/ticker/price?symbol=XTZUSDT'); + const [xtzMarket] = await getMarketsBySymbols(['xtz']); - return Number(data.price); + return xtzMarket.current_price; } catch (e) { if (!(e instanceof AxiosError)) { logger.error('Request for TEZ exchange rate failed with unknown error'); From 20c1c7576ee59c087f66a3cc2b19a92721ee1326 Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 30 Nov 2023 13:07:18 +0200 Subject: [PATCH 8/9] [TW Extension E2E] Notifications --- src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index e21e9e8..1a87be7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -150,7 +150,7 @@ app.post('/api/notifications', basicAuth, async (req, res) => { await redisClient.lpush('notifications', JSON.stringify(newNotification)); - res.status(200).send({ message: 'Notification added successfully' }); + res.status(200).send({ message: 'Notification added successfully', notification: newNotification }); } catch (error: any) { res.status(500).send({ error: error.message }); } From f5ae85b9ddbba2961bdd148b813f68f38212a773 Mon Sep 17 00:00:00 2001 From: lendihop Date: Thu, 7 Dec 2023 11:09:38 +0100 Subject: [PATCH 9/9] link update --- src/utils/dapp-list-constants.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/dapp-list-constants.ts b/src/utils/dapp-list-constants.ts index 06656c1..752588b 100644 --- a/src/utils/dapp-list-constants.ts +++ b/src/utils/dapp-list-constants.ts @@ -27,7 +27,7 @@ export const dappList: DappList[] = [ }, { name: 'Yupana', - dappUrl: 'https://yupana.finance', + dappUrl: 'https://app.yupana.finance', type: DappType.DeFi, logo: 'https://pbs.twimg.com/profile_images/1450382829062393859/NSu06S5C_400x400.png', slug: 'yupana',