From a4232a70587871d855b357dc5479aca41bd9147f Mon Sep 17 00:00:00 2001 From: Inokentii Mazhara Date: Mon, 6 Mar 2023 11:37:54 +0200 Subject: [PATCH 01/13] TW-514 Use 3route API for exchange rates calculation --- .env.dist | 4 +- .vscode/settings.json | 1 + src/config.ts | 19 +- src/utils/DataProvider.ts | 8 + src/utils/SingleQueryDataProvider.ts | 4 +- src/utils/block-finder.ts | 57 ++++ src/utils/get-recent-destinations.ts | 33 +++ src/utils/helpers.ts | 6 + src/utils/makeBuildQueryFn.ts | 14 +- src/utils/tezos.ts | 2 +- src/utils/three-route.ts | 108 ++++++++ src/utils/tokens.ts | 396 +++++++++------------------ src/utils/tzkt.ts | 88 +++--- templewallet-backend.json | 10 +- 14 files changed, 430 insertions(+), 320 deletions(-) create mode 100644 src/utils/block-finder.ts create mode 100644 src/utils/get-recent-destinations.ts create mode 100644 src/utils/three-route.ts diff --git a/.env.dist b/.env.dist index 31a2265..d46d9b9 100644 --- a/.env.dist +++ b/.env.dist @@ -1,9 +1,9 @@ PORT=3000 -QUIPUSWAP_FA12_FACTORIES=KT1K7whn5yHucGXMN7ymfKiX5r534QeaJM29,KT1Lw8hCoaBrHeTeMXbqHPG4sS4K1xn7yKcD,KT1FWHLMk5tHbwuSsp31S4Jum4dTVmkXpfJw -QUIPUSWAP_FA2_FACTORIES=KT1MMLb2FVrrE9Do74J3FH1RNNc4QhDuVCNX,KT1SwH9P1Tx8a58Mm6qBExQFTcy2rwZyZiXS,KT1PvEyN1xCFCgorN92QCfYjw3axS6jawCiJ SHOULD_APP_CHECK_BLOCK_THE_APP=false IOS_APP_ID= ANDROID_APP_ID= MOONPAY_SECRET_KEY= ALICE_BOB_PUBLIC_KEY= ALICE_BOB_PRIVATE_KEY= +THREE_ROUTE_API_URL= +THREE_ROUTE_API_AUTH_TOKEN= diff --git a/.vscode/settings.json b/.vscode/settings.json index 7a73a41..3662b37 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,2 +1,3 @@ { + "typescript.tsdk": "node_modules/typescript/lib" } \ No newline at end of file diff --git a/src/config.ts b/src/config.ts index b472d6d..6a0fa96 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,16 +1,21 @@ +import { assert } from 'console'; + import { getEnv } from './utils/env'; export const MIN_IOS_APP_VERSION = '1.10.445'; export const MIN_ANDROID_APP_VERSION = '1.10.445'; -export const QUIPUSWAP_FA12_FACTORIES = getEnv('QUIPUSWAP_FA12_FACTORIES'); -export const QUIPUSWAP_FA2_FACTORIES = getEnv('QUIPUSWAP_FA2_FACTORIES'); export const MOONPAY_SECRET_KEY = getEnv('MOONPAY_SECRET_KEY'); export const ALICE_BOB_PRIVATE_KEY = getEnv('ALICE_BOB_PRIVATE_KEY'); export const ALICE_BOB_PUBLIC_KEY = getEnv('ALICE_BOB_PUBLIC_KEY'); +export const THREE_ROUTE_API_URL = getEnv('THREE_ROUTE_API_URL'); +export const THREE_ROUTE_API_AUTH_TOKEN = getEnv('THREE_ROUTE_API_AUTH_TOKEN'); -if (!Boolean(QUIPUSWAP_FA12_FACTORIES)) throw new Error('process.env.QUIPUSWAP_FA12_FACTORIES not found.'); -if (!Boolean(QUIPUSWAP_FA2_FACTORIES)) throw new Error('process.env.QUIPUSWAP_FA2_FACTORIES not found.'); -if (!Boolean(MOONPAY_SECRET_KEY)) throw new Error('process.env.MOONPAY_SECRET_KEY not found.'); -if (!Boolean(ALICE_BOB_PRIVATE_KEY)) throw new Error('process.env.ALICE_BOB_PRIVATE_KEY not found.'); -if (!Boolean(ALICE_BOB_PUBLIC_KEY)) throw new Error('process.env.ALICE_BOB_PUBLIC_KEY not found.'); +const variablesToAssert = [ + /* { name: 'MOONPAY_SECRET_KEY', value: MOONPAY_SECRET_KEY }, + { name: 'ALICE_BOB_PRIVATE_KEY', value: ALICE_BOB_PRIVATE_KEY }, + { name: 'ALICE_BOB_PUBLIC_KEY', value: ALICE_BOB_PUBLIC_KEY }, */ + { name: 'THREE_ROUTE_API_URL', value: THREE_ROUTE_API_URL }, + { name: 'THREE_ROUTE_API_AUTH_TOKEN', value: THREE_ROUTE_API_AUTH_TOKEN } +]; +variablesToAssert.forEach(({ name, value }) => assert(value, `process.env.${name} not found.`)); diff --git a/src/utils/DataProvider.ts b/src/utils/DataProvider.ts index cc4e647..0397032 100644 --- a/src/utils/DataProvider.ts +++ b/src/utils/DataProvider.ts @@ -38,6 +38,14 @@ export default class DataProvider { }); } + async refetchInSubscription(...args: A) { + const subscriptions = await this.subscriptions.getData(); + const subscription = subscriptions.find(({ args: subscribedArgs }) => argsAreEqual(args, subscribedArgs)); + if (subscription) { + await subscription.dataProvider.refetch(); + } + } + async get(...args: A): Promise> { const subscriptions = await this.subscriptions.getData(); const subscription = subscriptions.find(({ args: subscribedArgs }) => argsAreEqual(args, subscribedArgs)); diff --git a/src/utils/SingleQueryDataProvider.ts b/src/utils/SingleQueryDataProvider.ts index 0c7e3f0..0ebe28d 100644 --- a/src/utils/SingleQueryDataProvider.ts +++ b/src/utils/SingleQueryDataProvider.ts @@ -61,7 +61,9 @@ export default class SingleQueryDataProvider { async init() { await this.readyMutex.exec(() => this.refetch()); - this.refetchInterval = setInterval(() => this.refetch(), this.refreshParams); + if (Number.isFinite(this.refreshParams)) { + this.refetchInterval = setInterval(() => this.refetch(), this.refreshParams); + } } async getState() { diff --git a/src/utils/block-finder.ts b/src/utils/block-finder.ts new file mode 100644 index 0000000..4496f6b --- /dev/null +++ b/src/utils/block-finder.ts @@ -0,0 +1,57 @@ +import { BlockResponse, BlockFullHeader } from '@taquito/rpc'; + +import { sleep } from './helpers'; +import logger from './logger'; +import { mainnetToolkit } from './tezos'; + +export interface BlockInterface extends Pick { + header: Pick; +} + +export const EMPTY_BLOCK: BlockInterface = { + protocol: '', + chain_id: '', + hash: '', + header: { + level: 0, + timestamp: '' + } +}; + +export const blockFinder = async ( + prevBlock: BlockInterface, + onNewBlock: (block: BlockInterface) => Promise +): Promise => { + const block = await mainnetToolkit.rpc + .getBlock() + .then( + (blockResponse): BlockInterface => ({ + protocol: blockResponse.protocol, + chain_id: blockResponse.chain_id, + hash: blockResponse.hash, + header: { + level: blockResponse.header.level, + timestamp: blockResponse.header.timestamp + } + }) + ) + .catch(e => { + logger.error(e); + + return prevBlock; + }); + + const isNewBlock = block.header.level > prevBlock.header.level; + const realBlock = isNewBlock ? block : prevBlock; + + if (isNewBlock) { + await onNewBlock(realBlock).catch(e => { + logger.error('blockFinder error'); + logger.error(e); + }); + } else { + await sleep(200); + } + + return blockFinder(realBlock, onNewBlock); +}; diff --git a/src/utils/get-recent-destinations.ts b/src/utils/get-recent-destinations.ts new file mode 100644 index 0000000..a4ef713 --- /dev/null +++ b/src/utils/get-recent-destinations.ts @@ -0,0 +1,33 @@ +import { isDefined } from './helpers'; +import { blockQueryMainnet } from './tzkt'; + +const PAST_BLOCKS_DEPTH = 4; + +export const getRecentDestinations = (currentBlockLevel: number) => + Promise.all( + new Array(PAST_BLOCKS_DEPTH).fill(0).map(async (_, index) => { + const pastBlockLevel = currentBlockLevel - index; + + const { transactions } = await blockQueryMainnet({ level: pastBlockLevel, operations: true }); + + if (transactions) { + return transactions + .map(transaction => { + if (transaction?.type === 'transaction') { + return transaction.target?.address ?? undefined; + } + + return undefined; + }) + .filter((address): address is string => isDefined(address)); + } + + return []; + }) + ) + .then(destinationsArray => destinationsArray.flat()) + .catch((error): string[] => { + console.log('getRecentDestinations error', error); + + return []; + }); diff --git a/src/utils/helpers.ts b/src/utils/helpers.ts index 02fafba..89dbdfe 100644 --- a/src/utils/helpers.ts +++ b/src/utils/helpers.ts @@ -14,3 +14,9 @@ export function rangeBn(start: number, end: number, step = 1): Array // eslint-disable-next-line @typescript-eslint/no-empty-function export const emptyFn = () => {}; + +export function isDefined(value: T | undefined | null): value is T { + return value !== undefined && value !== null; +} + +export const sleep = (ms: number) => new Promise(resolve => setTimeout(() => resolve('wake'), ms)); diff --git a/src/utils/makeBuildQueryFn.ts b/src/utils/makeBuildQueryFn.ts index 0f983e1..b561079 100644 --- a/src/utils/makeBuildQueryFn.ts +++ b/src/utils/makeBuildQueryFn.ts @@ -1,6 +1,7 @@ import axios, { AxiosRequestConfig } from 'axios'; import { stringify } from 'qs'; +import logger from './logger'; import PromisifiedSemaphore from './PromisifiedSemaphore'; function pick(obj: T, keys: U[]) { @@ -21,7 +22,11 @@ function isAbsoluteURL(url) { return /^([a-z][a-z\d\+\-\.]*:)?\/\//i.test(url); } -export default function makeBuildQueryFn(baseUrl: string, maxConcurrentQueries?: number) { +export default function makeBuildQueryFn( + baseUrl: string, + maxConcurrentQueries?: number, + defaultConfig: Omit = {} +) { const semaphore = maxConcurrentQueries !== undefined ? new PromisifiedSemaphore(maxConcurrentQueries) : undefined; return function f1( @@ -41,12 +46,19 @@ export default function makeBuildQueryFn(baseUrl: string, maxConcurrentQue return new Promise((resolve, reject) => { semaphore.exec(async () => { try { + logger.debug({ + url: fullUrl, + ...defaultConfig, + ...config + }); const { data } = await axios.request({ url: fullUrl, + ...defaultConfig, ...config }); resolve(data); } catch (e) { + logger.error(fullUrl); reject(e); } }); diff --git a/src/utils/tezos.ts b/src/utils/tezos.ts index 0bbb1f5..f97232d 100644 --- a/src/utils/tezos.ts +++ b/src/utils/tezos.ts @@ -38,7 +38,7 @@ class LambdaViewSigner implements Signer { const lambdaSigner = new LambdaViewSigner(); const michelEncoder = new MichelCodecPacker(); -const mainnetToolkit = new TezosToolkit(MAINNET_RPC_URL); +export const mainnetToolkit = new TezosToolkit(MAINNET_RPC_URL); mainnetToolkit.setSignerProvider(lambdaSigner); mainnetToolkit.setPackerProvider(michelEncoder); diff --git a/src/utils/three-route.ts b/src/utils/three-route.ts new file mode 100644 index 0000000..e7720d3 --- /dev/null +++ b/src/utils/three-route.ts @@ -0,0 +1,108 @@ +import { THREE_ROUTE_API_AUTH_TOKEN, THREE_ROUTE_API_URL } from '../config'; +import makeBuildQueryFn from './makeBuildQueryFn'; + +interface SwapQueryParams { + inputTokenSymbol: string; + outputTokenSymbol: string; + realAmount: string | number; +} + +export enum ThreeRouteStandardEnum { + xtz = 'xtz', + fa12 = 'fa12', + fa2 = 'fa2' +} + +export interface ThreeRouteHop { + dex: number; + forward: boolean; +} + +export interface ThreeRouteChain { + input: number; + output: number; + hops: ThreeRouteHop[]; +} + +// TODO: add axios adapter and change type if precision greater than of standard js number type is necessary +export interface ThreeRouteSwapResponse { + input: number; + output: number; + chains: ThreeRouteChain[]; +} + +interface ThreeRouteTokenCommon { + id: number; + symbol: string; + standard: ThreeRouteStandardEnum; + contract: string | null; + tokenId: string | null; + decimals: number; +} + +export interface ThreeRouteTezosToken extends ThreeRouteTokenCommon { + standard: ThreeRouteStandardEnum.xtz; + contract: null; + tokenId: null; +} + +export interface ThreeRouteFa12Token extends ThreeRouteTokenCommon { + standard: ThreeRouteStandardEnum.fa12; + tokenId: null; + contract: string; +} + +export interface ThreeRouteFa2Token extends ThreeRouteTokenCommon { + standard: ThreeRouteStandardEnum.fa2; + tokenId: string; + contract: string; +} + +export type ThreeRouteToken = ThreeRouteTezosToken | ThreeRouteFa12Token | ThreeRouteFa2Token; + +export enum ThreeRouteDexTypeEnum { + PlentyTokenToToken = 'PlentyTokenToToken', + PlentyTokenToTokenStable = 'PlentyTokenToTokenStable', + PlentyTokenToTokenVolatile = 'PlentyTokenToTokenVolatile', + PlentyCtezStable = 'PlentyCtezStable', + QuipuSwapTokenToTokenStable = 'QuipuSwapTokenToTokenStable', + QuipuSwapTezToTokenFa12 = 'QuipuSwapTezToTokenFa12', + QuipuSwapTezToTokenFa2 = 'QuipuSwapTezToTokenFa2', + QuipuSwapTokenToToken = 'QuipuSwapTokenToToken', + QuipuSwapDex2 = 'QuipuSwapDex2', + DexterLb = 'DexterLb', + FlatYouvesStable = 'FlatYouvesStable', + VortexTokenToTokenFa12 = 'VortexTokenToTokenFa12', + VortexTokenToTokenFa2 = 'VortexTokenToTokenFa2', + SpicyTokenToToken = 'SpicyTokenToToken', + WTZSwap = 'WTZSwap', + CtezToXtz = 'CtezToXtz', + PlentyWrappedTokenBridgeSwap = 'PlentyWrappedTokenBridgeSwap', + FlatYouvesStableUXTZ = 'FlatYouvesStableUXTZ' +} + +export interface ThreeRouteDex { + id: number; + type: ThreeRouteDexTypeEnum; + contract: string; + token1: ThreeRouteToken; + token2: ThreeRouteToken; +} + +type ThreeRouteQueryParams = object | SwapQueryParams; +type ThreeRouteQueryResponse = ThreeRouteSwapResponse | ThreeRouteDex[] | ThreeRouteToken[]; + +const threeRouteBuildQueryFn = makeBuildQueryFn( + THREE_ROUTE_API_URL, + 5, + { headers: { Authorization: `Basic ${THREE_ROUTE_API_AUTH_TOKEN}` } } +); + +export const getThreeRouteSwap = threeRouteBuildQueryFn( + ({ inputTokenSymbol, outputTokenSymbol, realAmount }) => + `/swap/${inputTokenSymbol}/${outputTokenSymbol}/${realAmount}` +); + +export const getThreeRouteDexes = threeRouteBuildQueryFn('/dexes', []); + +export const getThreeRouteTokens = threeRouteBuildQueryFn('/tokens', []); diff --git a/src/utils/tokens.ts b/src/utils/tokens.ts index 663222f..3de839f 100644 --- a/src/utils/tokens.ts +++ b/src/utils/tokens.ts @@ -1,247 +1,56 @@ -import { MichelsonMap } from '@taquito/michelson-encoder'; import { BigNumber } from 'bignumber.js'; -import memoizee from 'memoizee'; -import { QUIPUSWAP_FA12_FACTORIES, QUIPUSWAP_FA2_FACTORIES } from '../config'; -import { BigMapKeyType, IContractFactoryStorage, TokenListValue } from '../interfaces/contract-factoctories'; import { IPriceHistory } from '../interfaces/price-history'; +import { blockFinder, EMPTY_BLOCK } from './block-finder'; +import DataProvider from './DataProvider'; import fetch from './fetch'; -import { rangeBn } from './helpers'; +import { getRecentDestinations } from './get-recent-destinations'; +import { isDefined } from './helpers'; import logger from './logger'; +import PromisifiedSemaphore from './PromisifiedSemaphore'; import SingleQueryDataProvider from './SingleQueryDataProvider'; -import { getTokenMetadata, getStorage, tezExchangeRateProvider } from './tezos'; +import { tezExchangeRateProvider } from './tezos'; import { - BcdTokenData, - contractTokensProvider, - mapTzktTokenDataToBcdTokenData, - tokensMetadataProvider, - TZKT_NETWORKS, - TzktTokenData -} from './tzkt'; - -const fa12Factories = QUIPUSWAP_FA12_FACTORIES.split(','); -const fa2Factories = QUIPUSWAP_FA2_FACTORIES.split(','); - -interface QuipuswapExchangerAbstract { - exchangerAddress: string; - tokenAddress: string; - tokenId?: number; -} -interface QuipuswapExchanger extends QuipuswapExchangerAbstract { - tokenMetadata: BcdTokenData | undefined; -} -interface QuipuswapExchangerRaw extends QuipuswapExchangerAbstract { - tokenMetadata: TzktTokenData | undefined; -} - -const getQuipuswapExchangers = async (): Promise => { - const fa12FactoryStorages = await Promise.all( - fa12Factories.map(factoryAddress => getStorage(factoryAddress)) - ); - const fa2FactoryStorages = await Promise.all( - fa2Factories.map(factoryAddress => getStorage(factoryAddress)) - ); - logger.info('Getting FA1.2 Quipuswap exchangers...'); - const rawFa12FactoryTokens = await Promise.all( - fa12FactoryStorages.map(storage => { - return storage.token_list.getMultipleValues(rangeBn(0, storage.counter.toNumber())); - }) - ); - const rawFa12FactoryTokensFiltered: Array> = []; - rawFa12FactoryTokens.forEach((item, index) => { - const value = item.get(new BigNumber(index)); - if (value !== undefined) { - rawFa12FactoryTokensFiltered.push(item as MichelsonMap); - } + getThreeRouteDexes, + getThreeRouteSwap, + getThreeRouteTokens, + ThreeRouteStandardEnum, + ThreeRouteFa12Token, + ThreeRouteFa2Token +} from './three-route'; +import { BcdTokenData, mapTzktTokenDataToBcdTokenData, tokensMetadataProvider } from './tzkt'; + +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 directSwap = await getThreeRouteSwap({ + inputTokenSymbol: THREE_ROUTE_TEZ_SYMBOL, + outputTokenSymbol, + realAmount: PROBE_TEZ_AMOUNT }); - const rawFa12Exchangers = await Promise.all( - fa12FactoryStorages.map((storage, index) => - storage.token_to_exchange.getMultipleValues([...rawFa12FactoryTokensFiltered[index].values()]) - ) - ); - const fa12Exchangers = ( - await Promise.all( - rawFa12Exchangers.map(rawFa12ExchangersChunk => { - return Promise.all( - [...rawFa12ExchangersChunk.entries()].map(async ([tokenAddress, exchangerAddress]) => { - await contractTokensProvider.subscribe(TZKT_NETWORKS.MAINNET, tokenAddress.toString()); - const { data: tokensMetadata, error } = await contractTokensProvider.get( - TZKT_NETWORKS.MAINNET, - tokenAddress.toString() - ); - if (error) { - throw error; - } - - return { - tokenAddress: tokenAddress.toString(), - exchangerAddress, - tokenMetadata: tokensMetadata ? tokensMetadata[0] : undefined - }; - }) - ); - }) - ) - ).flat(); - - logger.info('Getting FA2 Quipuswap exchangers...'); - const rawFa2FactoryTokens = await Promise.all( - fa2FactoryStorages.map(storage => storage.token_list.getMultipleValues(rangeBn(0, storage.counter.toNumber()))) - ); - - const rawFa2Exchangers = await Promise.all( - rawFa2FactoryTokens.map((rawTokens, index) => { - return Promise.all( - [...rawTokens.values()].map(async (token): Promise<[[string, BigNumber], string] | undefined> => { - if (token !== undefined) { - const exchange = await fa2FactoryStorages[index].token_to_exchange.get(token); - if (exchange !== undefined) { - return [token, exchange]; - } - } - }) - ); - }) - ); - const rawFa2ExchangersFiltered: [[string, BigNumber], string][][] = []; - rawFa2Exchangers.forEach(exchanger => { - exchanger.forEach(item => { - if (item !== undefined) { - rawFa2ExchangersFiltered.push([item]); + if (directSwap.output === 0) { + return { + directSwap, + invertedSwap: { + input: 0, + output: 0, + chains: [] } - }); - }); - - const fa2Exchangers = ( - await Promise.all( - rawFa2ExchangersFiltered.flat().map(async ([tokenDescriptor, exchangerAddress]) => { - const address = tokenDescriptor[0]; - const token_id = tokenDescriptor[1].toNumber(); - await contractTokensProvider.subscribe(TZKT_NETWORKS.MAINNET, address, token_id); - const { data: tokensMetadata, error } = await contractTokensProvider.get( - TZKT_NETWORKS.MAINNET, - address, - token_id - ); - if (error) { - throw error; - } - - return { - exchangerAddress, - tokenAddress: address, - tokenId: token_id, - tokenMetadata: tokensMetadata ? tokensMetadata[0] : undefined - }; - }) - ) - ).flat(); - logger.info('Successfully got Quipuswap exchangers'); + }; + } - const allExhangers = [...fa12Exchangers, ...fa2Exchangers]; - const allExhangersFiltered: Array = []; - allExhangers.forEach(item => { - if (item.exchangerAddress !== undefined) { - allExhangersFiltered.push(item as QuipuswapExchangerRaw); - } + const invertedSwap = await getThreeRouteSwap({ + inputTokenSymbol: outputTokenSymbol, + outputTokenSymbol: THREE_ROUTE_TEZ_SYMBOL, + realAmount: directSwap.output }); - return allExhangersFiltered.map(exchanger => ({ - ...exchanger, - tokenMetadata: mapTzktTokenDataToBcdTokenData(exchanger.tokenMetadata) - })); -}; -export const quipuswapExchangersDataProvider = new SingleQueryDataProvider(14 * 60 * 1000, getQuipuswapExchangers); - -const LIQUIDITY_INTERVAL = 120000; -const getPoolTokenExchangeRate = memoizee( - async ({ - contract: tokenAddress, - decimals, - token_id - }: { - decimals?: number; - token_id?: number; - contract: string; - }) => { - if (decimals === undefined) { - try { - await tokensMetadataProvider.subscribe(TZKT_NETWORKS.MAINNET, tokenAddress, token_id); - const { data: metadata } = await tokensMetadataProvider.get(TZKT_NETWORKS.MAINNET, tokenAddress, token_id); - if (!metadata || metadata.length === 0 || !metadata[0].metadata.decimals) { - decimals = await getTokenMetadata(tokenAddress, token_id) - .then(x => x.decimals) - .catch(() => (tokenAddress === 'KT1Xobej4mc6XgEjDoJoHtTKgbD1ELMvcQuL' && token_id === 0 ? 12 : 0)); - } else { - decimals = metadata[0].metadata.decimals; - } - } catch (e) { - decimals = 0; - } - } - - const { data: tezExchangeRate, error: tezExchangeRateError } = await tezExchangeRateProvider.getState(); - const { data: quipuswapExchangers, error: quipuswapExchangersError } = - await quipuswapExchangersDataProvider.getState(); - if (quipuswapExchangersError || tezExchangeRateError) { - throw quipuswapExchangersError || tezExchangeRateError; - } - let quipuswapExchangeRate = new BigNumber(0); - let quipuswapWeight = new BigNumber(0); - const dexterExchangeRate = new BigNumber(0); - const dexterWeight = new BigNumber(0); - const tokenElementaryParts = new BigNumber(10).pow(decimals); - const matchingQuipuswapExchangers = - quipuswapExchangers?.filter( - ({ tokenAddress: swappableTokenAddress, tokenId: swappableTokenId }) => - tokenAddress === swappableTokenAddress && (swappableTokenId === undefined || swappableTokenId === token_id) - ) ?? []; - if (matchingQuipuswapExchangers.length > 0) { - const exchangersCharacteristics = await Promise.all( - matchingQuipuswapExchangers.map(async ({ exchangerAddress }) => { - const { - storage: { tez_pool, token_pool } - } = await getStorage<{ - storage: { - tez_pool: BigNumber; - token_pool: BigNumber; - }; - }>(exchangerAddress); - - if (!tez_pool.eq(0) && !token_pool.eq(0)) { - return { - weight: tez_pool, - exchangeRate: tez_pool.div(1e6).div(token_pool.div(tokenElementaryParts)) - }; - } - - return { weight: new BigNumber(0), exchangeRate: new BigNumber(0) }; - }) - ); - quipuswapWeight = exchangersCharacteristics.reduce((sum, { weight }) => sum.plus(weight), new BigNumber(0)); - if (!quipuswapWeight.eq(0)) { - quipuswapExchangeRate = exchangersCharacteristics - .reduce((sum, { weight, exchangeRate }) => sum.plus(weight.multipliedBy(exchangeRate)), new BigNumber(0)) - .div(quipuswapWeight); - } - } - if (quipuswapExchangeRate.eq(0) && dexterExchangeRate.eq(0)) { - return new BigNumber(0); - } - - if (tezExchangeRate === undefined || typeof tezExchangeRate === 'boolean') { - return new BigNumber(0); - } - - return quipuswapExchangeRate - .times(quipuswapWeight) - .plus(dexterExchangeRate.times(dexterWeight)) - .div(quipuswapWeight.plus(dexterWeight)) - .times(tezExchangeRate); - }, - { promise: true, maxAge: LIQUIDITY_INTERVAL } -); + return { directSwap, invertedSwap }; +}); export type TokenExchangeRateEntry = { tokenAddress: string; @@ -251,49 +60,67 @@ export type TokenExchangeRateEntry = { }; export const ASPENCOIN_ADDRESS = 'KT1S5iPRQ612wcNm6mXDqDhTNegGFcvTV7vM'; -tokensMetadataProvider.subscribe(TZKT_NETWORKS.MAINNET, ASPENCOIN_ADDRESS); +tokensMetadataProvider.subscribe(ASPENCOIN_ADDRESS); const getTokensExchangeRates = async (): Promise => { logger.info('Getting tokens exchange rates...'); - const { data: quipuswapExchangers, error: quipuswapError } = await quipuswapExchangersDataProvider.getState(); - if (quipuswapError) { - throw quipuswapError; + logger.info('Getting exchange rates of tokens which are known to 3route...'); + const { data: tokens, error: tokensError } = await tokensListProvider.getState(); + const { data: rawTezExchangeRate, error: tezExchangeRateError } = await tezExchangeRateProvider.getState(); + const error = tokensError ?? tezExchangeRateError; + + if (error) { + throw error; } - logger.info('Getting tokens exchange rates from Quipuswap pools'); + + const tezExchangeRate = rawTezExchangeRate === false ? 1 : rawTezExchangeRate!; const exchangeRates = await Promise.all( - quipuswapExchangers - ?.reduce((onePerTokenExchangers, exchanger) => { - if ( - !onePerTokenExchangers.some( - ({ tokenAddress, tokenId }) => exchanger.tokenAddress === tokenAddress && exchanger.tokenId === tokenId - ) - ) { - onePerTokenExchangers.push(exchanger); + tokens! + .filter( + (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); + const { data: probeSwaps, error: swapError } = await probeSwapsProvider.get(token.symbol); + await tokensMetadataProvider.subscribe(contract, tokenId); + const { data: metadata } = await tokensMetadataProvider.get(contract, tokenId); + + if (swapError) { + logger.error(`Failed to get exchange rate for token ${token.symbol}`); + throw swapError; } - return onePerTokenExchangers; - }, [] as QuipuswapExchanger[]) - .map(async ({ tokenAddress, tokenId, tokenMetadata }) => { - logger.info(tokenMetadata?.name ?? tokenAddress); + 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 exchangeRate = + toTezExchangeRatesVersions.length === 0 + ? new BigNumber(0) + : BigNumber.sum(...toTezExchangeRatesVersions) + .div(toTezExchangeRatesVersions.length) + .times(tezExchangeRate); return { - tokenAddress, + tokenAddress: contract, tokenId, - exchangeRate: await getPoolTokenExchangeRate({ - contract: tokenAddress, - decimals: tokenMetadata && tokenMetadata.decimals, - token_id: tokenId - }), - metadata: tokenMetadata + exchangeRate, + metadata: mapTzktTokenDataToBcdTokenData(metadata?.[0]) }; - }) ?? [] + }) ); if (!exchangeRates.some(({ tokenAddress }) => tokenAddress === ASPENCOIN_ADDRESS)) { logger.info('Getting exchange rate for Aspencoin'); try { const { data: aspencoinMetadata, error: aspencoinMetadataError } = await tokensMetadataProvider.get( - TZKT_NETWORKS.MAINNET, ASPENCOIN_ADDRESS ); if (aspencoinMetadataError) { @@ -316,7 +143,58 @@ const getTokensExchangeRates = async (): Promise => { logger.info('Successfully got tokens exchange rates'); - return [...exchangeRates /*, ...tzwrapExchangeRates */].filter(({ exchangeRate }) => !exchangeRate.eq(0)); + return [...exchangeRates].filter(({ exchangeRate }) => !exchangeRate.eq(0)); }; -export const tokensExchangeRatesProvider = new SingleQueryDataProvider(13 * 60 * 1000, getTokensExchangeRates); +export const tokensExchangeRatesProvider = new SingleQueryDataProvider(60000, getTokensExchangeRates); + +const swapsUpdateSemaphore = new PromisifiedSemaphore(); +blockFinder(EMPTY_BLOCK, async block => + swapsUpdateSemaphore.exec(async () => { + logger.info(`updating stats for level ${block.header.level}`); + const recentDestinations = await getRecentDestinations(block.header.level); + const { data: tokens, error: tokensError } = await tokensListProvider.getState(); + const { data: dexes, error: dexesError } = await dexesListProvider.getState(); + const error = tokensError ?? dexesError; + + if (error) { + throw error; + } + + const outputsUpdatesFlags = await Promise.all( + tokens!.map(async token => { + if (token.symbol === THREE_ROUTE_TEZ_SYMBOL) { + return false; + } + + await probeSwapsProvider.subscribe(token.symbol); + const { data: probeSwaps, error: swapError } = await probeSwapsProvider.get(token.symbol); + + if (swapError) { + throw swapError; + } + + const { directSwap, invertedSwap } = probeSwaps!; + const dexesAddresses = directSwap.chains + .concat(invertedSwap.chains) + .map(chain => chain.hops.map(hop => dexes!.find(dex => dex.id === hop.dex)?.contract).filter(isDefined)) + .flat(); + + const firstUpdatedDexAddress = dexesAddresses.find(dexAddress => recentDestinations.includes(dexAddress)); + if (isDefined(firstUpdatedDexAddress)) { + logger.info(`updating swap output for token ${token.symbol} because of dex ${firstUpdatedDexAddress}`); + await probeSwapsProvider.refetchInSubscription(token.symbol); + + return true; + } + + return false; + }) + ); + if (outputsUpdatesFlags.some(flag => flag)) { + logger.info('refreshing tokens exchange rates because of swaps outputs updates'); + await tokensExchangeRatesProvider.refetch(); + } + logger.info(`stats updated for level ${block.header.level}`); + }) +); diff --git a/src/utils/tzkt.ts b/src/utils/tzkt.ts index 0e60c7c..887a019 100644 --- a/src/utils/tzkt.ts +++ b/src/utils/tzkt.ts @@ -18,14 +18,7 @@ export type BcdTokenData = { supply?: string; }; -export enum TZKT_NETWORKS { - MAINNET = 'mainnet', - GHOSTNET = 'ghostnet', - JAKARTANET = 'jakartanet' -} - const TZKT_BASE_URL_MAINNET = 'https://api.tzkt.io/v1'; -const TZKT_BASE_URL_GHOSTNET = 'https://api.ghostnet.tzkt.io/v1'; type SeriesParams = { addresses: string[]; @@ -99,58 +92,59 @@ export type TzktTokenData = { totalSupply?: string; }; +interface TzktBlockQueryParams { + level: number; + operations: boolean; +} + +interface TzktBlockOperationsResponse { + transactions: + | { + type: string; + target: { + alias: string | null; + address: string | null; + } | null; + }[] + | null; +} + const buildQueryMainnet = makeBuildQueryFn< - SeriesParams | object | { slug: string } | AccountTokenBalancesParams | ContractTokensParams, - [number, number][] | DAppsListItem[] | DAppDetails | AccountTokenBalancesResponse | TzktTokenData[] + SeriesParams | object | { slug: string } | AccountTokenBalancesParams | ContractTokensParams | TzktBlockQueryParams, + | [number, number][] + | DAppsListItem[] + | DAppDetails + | AccountTokenBalancesResponse + | TzktTokenData[] + | TzktBlockOperationsResponse >(TZKT_BASE_URL_MAINNET, 5); -const buildQueryGhostnet = makeBuildQueryFn< - SeriesParams | object | { slug: string } | AccountTokenBalancesParams | ContractTokensParams, - [number, number][] | DAppsListItem[] | DAppDetails | AccountTokenBalancesResponse | TzktTokenData[] ->(TZKT_BASE_URL_GHOSTNET, 5); +const tokensQueryMainnet = buildQueryMainnet( + () => '/tokens', + ['limit', 'offset', 'contract', 'tokenId'] +); -export const tokensMetadataProvider = new DataProvider( - 24 * 3600 * 1000, - (network: TZKT_NETWORKS, address?: string, token_id?: number) => { - const getTokensMetadata = - network === TZKT_NETWORKS.MAINNET - ? buildQueryMainnet( - () => '/tokens', - ['limit', 'offset', 'contract', 'tokenId'] - ) - : buildQueryGhostnet( - () => '/tokens', - ['limit', 'offset', 'contract', 'tokenId'] - ); - - return getTokensMetadata({ - contract: address, - tokenId: token_id - }); - } +export const blockQueryMainnet = buildQueryMainnet( + ({ level }) => `/blocks/${level}`, + ['operations'] +); + +export const tokensMetadataProvider = new DataProvider(24 * 3600 * 1000, async (address?: string, token_id?: number) => + tokensQueryMainnet({ + contract: address, + tokenId: token_id + }) ); export const contractTokensProvider = new DataProvider( 24 * 3600 * 1000, - (network: TZKT_NETWORKS, address: string, token_id?: number, size?: number, offset?: number) => { - const getContractTokens = - network === TZKT_NETWORKS.MAINNET - ? buildQueryMainnet( - () => '/tokens', - ['limit', 'offset', 'tokenId', 'contract'] - ) - : buildQueryGhostnet( - () => '/tokens', - ['limit', 'offset', 'tokenId', 'contract'] - ); - - return getContractTokens({ + async (address: string, token_id?: number, size?: number, offset?: number) => + tokensQueryMainnet({ contract: address, limit: size, offset, tokenId: token_id - }); - } + }) ); export const mapTzktTokenDataToBcdTokenData = (x?: TzktTokenData): BcdTokenData | undefined => diff --git a/templewallet-backend.json b/templewallet-backend.json index 7233092..3991301 100644 --- a/templewallet-backend.json +++ b/templewallet-backend.json @@ -5,8 +5,14 @@ "script": "./dist/index.js", "env": { "PORT": "3000", - "QUIPUSWAP_FA12_FACTORIES": "KT1K7whn5yHucGXMN7ymfKiX5r534QeaJM29,KT1Lw8hCoaBrHeTeMXbqHPG4sS4K1xn7yKcD,KT1FWHLMk5tHbwuSsp31S4Jum4dTVmkXpfJw", - "QUIPUSWAP_FA2_FACTORIES": "KT1MMLb2FVrrE9Do74J3FH1RNNc4QhDuVCNX,KT1SwH9P1Tx8a58Mm6qBExQFTcy2rwZyZiXS,KT1PvEyN1xCFCgorN92QCfYjw3axS6jawCiJ" + "SHOULD_APP_CHECK_BLOCK_THE_APP": "false", + "IOS_APP_ID": "SECRET_VALUE", + "ANDROID_APP_ID": "SECRET_VALUE", + "MOONPAY_SECRET_KEY": "SECRET_VALUE", + "ALICE_BOB_PUBLIC_KEY": "SECRET_VALUE", + "ALICE_BOB_PRIVATE_KEY": "SECRET_VALUE", + "THREE_ROUTE_API_URL": "SECRET_VALUE", + "THREE_ROUTE_API_AUTH_TOKEN": "SECRET_VALUE" } } ] From 0c90f20874836ab9ce1931389c5b223c723c24b9 Mon Sep 17 00:00:00 2001 From: Inokentii Mazhara Date: Mon, 6 Mar 2023 11:52:16 +0200 Subject: [PATCH 02/13] TW-514 Refactor changes --- src/config.ts | 4 ++-- src/utils/block-finder.ts | 4 ++-- src/utils/get-recent-destinations.ts | 10 ++++++---- src/utils/makeBuildQueryFn.ts | 2 +- src/utils/tezos.ts | 13 ++++++------- src/utils/tzkt.ts | 14 +++++++------- 6 files changed, 24 insertions(+), 23 deletions(-) diff --git a/src/config.ts b/src/config.ts index 6a0fa96..52f9ff3 100644 --- a/src/config.ts +++ b/src/config.ts @@ -12,9 +12,9 @@ export const THREE_ROUTE_API_URL = getEnv('THREE_ROUTE_API_URL'); export const THREE_ROUTE_API_AUTH_TOKEN = getEnv('THREE_ROUTE_API_AUTH_TOKEN'); const variablesToAssert = [ - /* { name: 'MOONPAY_SECRET_KEY', value: MOONPAY_SECRET_KEY }, + { name: 'MOONPAY_SECRET_KEY', value: MOONPAY_SECRET_KEY }, { name: 'ALICE_BOB_PRIVATE_KEY', value: ALICE_BOB_PRIVATE_KEY }, - { name: 'ALICE_BOB_PUBLIC_KEY', value: ALICE_BOB_PUBLIC_KEY }, */ + { name: 'ALICE_BOB_PUBLIC_KEY', value: ALICE_BOB_PUBLIC_KEY }, { name: 'THREE_ROUTE_API_URL', value: THREE_ROUTE_API_URL }, { name: 'THREE_ROUTE_API_AUTH_TOKEN', value: THREE_ROUTE_API_AUTH_TOKEN } ]; diff --git a/src/utils/block-finder.ts b/src/utils/block-finder.ts index 4496f6b..3070459 100644 --- a/src/utils/block-finder.ts +++ b/src/utils/block-finder.ts @@ -2,7 +2,7 @@ import { BlockResponse, BlockFullHeader } from '@taquito/rpc'; import { sleep } from './helpers'; import logger from './logger'; -import { mainnetToolkit } from './tezos'; +import { tezosToolkit } from './tezos'; export interface BlockInterface extends Pick { header: Pick; @@ -22,7 +22,7 @@ export const blockFinder = async ( prevBlock: BlockInterface, onNewBlock: (block: BlockInterface) => Promise ): Promise => { - const block = await mainnetToolkit.rpc + const block = await tezosToolkit.rpc .getBlock() .then( (blockResponse): BlockInterface => ({ diff --git a/src/utils/get-recent-destinations.ts b/src/utils/get-recent-destinations.ts index a4ef713..03f0c3d 100644 --- a/src/utils/get-recent-destinations.ts +++ b/src/utils/get-recent-destinations.ts @@ -1,5 +1,6 @@ import { isDefined } from './helpers'; -import { blockQueryMainnet } from './tzkt'; +import logger from './logger'; +import { makeBlockQuery } from './tzkt'; const PAST_BLOCKS_DEPTH = 4; @@ -8,9 +9,9 @@ export const getRecentDestinations = (currentBlockLevel: number) => new Array(PAST_BLOCKS_DEPTH).fill(0).map(async (_, index) => { const pastBlockLevel = currentBlockLevel - index; - const { transactions } = await blockQueryMainnet({ level: pastBlockLevel, operations: true }); + const { transactions } = await makeBlockQuery({ level: pastBlockLevel, operations: true }); - if (transactions) { + if (isDefined(transactions)) { return transactions .map(transaction => { if (transaction?.type === 'transaction') { @@ -27,7 +28,8 @@ export const getRecentDestinations = (currentBlockLevel: number) => ) .then(destinationsArray => destinationsArray.flat()) .catch((error): string[] => { - console.log('getRecentDestinations error', error); + logger.error('getRecentDestinations error'); + logger.error(error); return []; }); diff --git a/src/utils/makeBuildQueryFn.ts b/src/utils/makeBuildQueryFn.ts index b561079..e30c6be 100644 --- a/src/utils/makeBuildQueryFn.ts +++ b/src/utils/makeBuildQueryFn.ts @@ -58,7 +58,7 @@ export default function makeBuildQueryFn( }); resolve(data); } catch (e) { - logger.error(fullUrl); + logger.error(`Error while making query to ${fullUrl}`); reject(e); } }); diff --git a/src/utils/tezos.ts b/src/utils/tezos.ts index f97232d..dcd2e17 100644 --- a/src/utils/tezos.ts +++ b/src/utils/tezos.ts @@ -8,8 +8,7 @@ import fetch from './fetch'; import SingleQueryDataProvider from './SingleQueryDataProvider'; import { BcdTokenData } from './tzkt'; -const MAINNET_RPC_URL = - process.env.RPC_URL !== undefined ? process.env.RPC_URL : 'https://mainnet-node.madfish.solutions'; +const RPC_URL = process.env.RPC_URL ?? 'https://mainnet-node.madfish.solutions'; const TEMPLE_WALLET_LV_ACCOUNT_PKH = 'tz1fVQangAfb9J1hRRMP2bSB6LvASD6KpY8A'; const TEMPLE_WALLET_LV_ACCOUNT_PUBLIC_KEY = 'edpkvWbk81uh1DEvdWKR4g1bjyTGhdu1mDvznPUFE2zDwNsLXrEb9K'; @@ -38,11 +37,11 @@ class LambdaViewSigner implements Signer { const lambdaSigner = new LambdaViewSigner(); const michelEncoder = new MichelCodecPacker(); -export const mainnetToolkit = new TezosToolkit(MAINNET_RPC_URL); -mainnetToolkit.setSignerProvider(lambdaSigner); -mainnetToolkit.setPackerProvider(michelEncoder); +export const tezosToolkit = new TezosToolkit(RPC_URL); +tezosToolkit.setSignerProvider(lambdaSigner); +tezosToolkit.setPackerProvider(michelEncoder); -const getContract = memoizee((address: string) => mainnetToolkit.contract.at(address), { promise: true }); +const getContract = memoizee((address: string) => tezosToolkit.contract.at(address), { promise: true }); export const getStorage = memoizee( async (contractAddress: string) => { @@ -69,7 +68,7 @@ export class MetadataParseError extends Error {} export const getTokenMetadata = memoizee( async (tokenAddress: string, tokenId?: number): Promise => { - const contract = await mainnetToolkit.wallet.at(tokenAddress, compose(tzip12, tzip16)); + const contract = await tezosToolkit.wallet.at(tokenAddress, compose(tzip12, tzip16)); // eslint-disable-next-line @typescript-eslint/no-explicit-any let tokenData: any; diff --git a/src/utils/tzkt.ts b/src/utils/tzkt.ts index 887a019..7e9edd6 100644 --- a/src/utils/tzkt.ts +++ b/src/utils/tzkt.ts @@ -18,7 +18,7 @@ export type BcdTokenData = { supply?: string; }; -const TZKT_BASE_URL_MAINNET = 'https://api.tzkt.io/v1'; +const TZKT_BASE_URL = 'https://api.tzkt.io/v1'; type SeriesParams = { addresses: string[]; @@ -109,7 +109,7 @@ interface TzktBlockOperationsResponse { | null; } -const buildQueryMainnet = makeBuildQueryFn< +const buildTzktQuery = makeBuildQueryFn< SeriesParams | object | { slug: string } | AccountTokenBalancesParams | ContractTokensParams | TzktBlockQueryParams, | [number, number][] | DAppsListItem[] @@ -117,20 +117,20 @@ const buildQueryMainnet = makeBuildQueryFn< | AccountTokenBalancesResponse | TzktTokenData[] | TzktBlockOperationsResponse ->(TZKT_BASE_URL_MAINNET, 5); +>(TZKT_BASE_URL, 5); -const tokensQueryMainnet = buildQueryMainnet( +const makeTokensQuery = buildTzktQuery( () => '/tokens', ['limit', 'offset', 'contract', 'tokenId'] ); -export const blockQueryMainnet = buildQueryMainnet( +export const makeBlockQuery = buildTzktQuery( ({ level }) => `/blocks/${level}`, ['operations'] ); export const tokensMetadataProvider = new DataProvider(24 * 3600 * 1000, async (address?: string, token_id?: number) => - tokensQueryMainnet({ + makeTokensQuery({ contract: address, tokenId: token_id }) @@ -139,7 +139,7 @@ export const tokensMetadataProvider = new DataProvider(24 * 3600 * 1000, async ( export const contractTokensProvider = new DataProvider( 24 * 3600 * 1000, async (address: string, token_id?: number, size?: number, offset?: number) => - tokensQueryMainnet({ + makeTokensQuery({ contract: address, limit: size, offset, From c8528a42af0271aac973e4ada28d53b51f9a4fe6 Mon Sep 17 00:00:00 2001 From: Inokentii Mazhara Date: Mon, 6 Mar 2023 16:32:56 +0200 Subject: [PATCH 03/13] Refactoring according to comments --- package.json | 4 +- src/config.ts | 9 ++- src/index.ts | 10 +-- src/utils/DataProvider.ts | 2 +- src/utils/SingleQueryDataProvider.ts | 27 +++++-- src/utils/get-recent-destinations.ts | 14 ++-- src/utils/makeBuildQueryFn.ts | 36 +++++---- src/utils/tezos.ts | 6 +- src/utils/tokens.ts | 23 +++--- yarn.lock | 106 +++++++++++++++++++++------ 10 files changed, 160 insertions(+), 77 deletions(-) diff --git a/package.json b/package.json index 37f6d1f..3b1d7bb 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "npm-run-all": "^4.1.5", "prettier": "^2.8.2", "rimraf": "^3.0.2", - "ts-node-dev": "^1.1.8", - "typescript": "^4.2.4" + "ts-node-dev": "^2.0.0", + "typescript": "^4.9.5" } } diff --git a/src/config.ts b/src/config.ts index 52f9ff3..85f6c36 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,6 +1,5 @@ -import { assert } from 'console'; - import { getEnv } from './utils/env'; +import { isDefined } from './utils/helpers'; export const MIN_IOS_APP_VERSION = '1.10.445'; export const MIN_ANDROID_APP_VERSION = '1.10.445'; @@ -18,4 +17,8 @@ const variablesToAssert = [ { name: 'THREE_ROUTE_API_URL', value: THREE_ROUTE_API_URL }, { name: 'THREE_ROUTE_API_AUTH_TOKEN', value: THREE_ROUTE_API_AUTH_TOKEN } ]; -variablesToAssert.forEach(({ name, value }) => assert(value, `process.env.${name} not found.`)); +variablesToAssert.forEach(({ name, value }) => { + if (!isDefined(value)) { + throw new Error(`process.env.${name} not found.`); + } +}); diff --git a/src/index.ts b/src/index.ts index 23a2dcf..9c9f90c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -173,7 +173,7 @@ app.post('/api/alice-bob/create-order', async (_req, res) => { const orderInfo = await createAliceBobOrder(booleanIsWithdraw, exchangeInfo); res.status(200).send({ orderInfo }); - } catch (error) { + } catch (error: any) { res.status(error.response.status).send(error.response.data); } }); @@ -185,7 +185,7 @@ app.post('/api/alice-bob/cancel-order', async (_req, res) => { await cancelAliceBobOrder({ id: String(orderId) }); res.status(200); - } catch (error) { + } catch (error: any) { res.status(error.response.status).send(error.response.data); } }); @@ -197,7 +197,7 @@ app.get('/api/alice-bob/get-pair-info', async (_req, res) => { const pairInfo = await getAliceBobPairInfo(isWithdraw === 'true'); res.status(200).send({ pairInfo }); - } catch (error) { + } catch (error: any) { res.status(error.response.status).send({ error: error.response.data }); } }); @@ -209,7 +209,7 @@ app.get('/api/alice-bob/check-order', async (_req, res) => { const orderInfo = await getAliceBobOrderInfo(String(orderId)); res.status(200).send({ orderInfo }); - } catch (error) { + } catch (error: any) { res.status(error.response.status).send({ error: error.response.data }); } }); @@ -227,7 +227,7 @@ app.post('/api/alice-bob/estimate-amount', async (_req, res) => { const outputAmount = await estimateAliceBobOutput(exchangeInfo); res.status(200).send({ outputAmount }); - } catch (error) { + } catch (error: any) { res.status(error.response.status).send({ error: error.response.data }); } }); diff --git a/src/utils/DataProvider.ts b/src/utils/DataProvider.ts index 0397032..62bf613 100644 --- a/src/utils/DataProvider.ts +++ b/src/utils/DataProvider.ts @@ -57,7 +57,7 @@ export default class DataProvider { return { data }; } catch (error) { - return { error }; + return { error: error as Error }; } } } diff --git a/src/utils/SingleQueryDataProvider.ts b/src/utils/SingleQueryDataProvider.ts index 0ebe28d..49bf65c 100644 --- a/src/utils/SingleQueryDataProvider.ts +++ b/src/utils/SingleQueryDataProvider.ts @@ -3,10 +3,24 @@ import logger from './logger'; import MutexProtectedData from './MutexProtectedData'; import PromisifiedSemaphore from './PromisifiedSemaphore'; -export type SingleQueryDataProviderState = { +interface SingleQueryDataProviderStateCommon { data?: T; error?: Error; -}; +} + +interface SingleQueryDataProviderStateReady extends SingleQueryDataProviderStateCommon { + data: T; + error?: undefined; +} + +interface SingleQueryDataProviderStateError extends SingleQueryDataProviderStateCommon { + data?: undefined; + error: Error; +} + +export type SingleQueryDataProviderState = + | SingleQueryDataProviderStateReady + | SingleQueryDataProviderStateError; const defaultShouldGiveUp = (_e: Error, c: number) => c > 9; @@ -28,7 +42,7 @@ export default class SingleQueryDataProvider { ) { this.fetchMutex = new PromisifiedSemaphore(); this.readyMutex = new PromisifiedSemaphore(); - this.state = new MutexProtectedData({}); + this.state = new MutexProtectedData({ error: new Error('This error should not be displayed') }); this.init(); } @@ -44,10 +58,11 @@ export default class SingleQueryDataProvider { const result = await this.fetchFn(); await this.state.setData({ data: result }); } catch (e) { + const error = e as Error; const timeSlot = 1000; - logger.error(`Error in SingleQueryDataProvider: ${e.message}\n${e.stack}`); - if (this.shouldGiveUp(e, c)) { - await this.state.setData({ error: e }); + logger.error(`Error in SingleQueryDataProvider: ${error.message}\n${error.stack}`); + if (this.shouldGiveUp(error, c)) { + await this.state.setData({ error }); } else { await new Promise(resolve => { this.refetchRetryTimeout = setTimeout(async () => { diff --git a/src/utils/get-recent-destinations.ts b/src/utils/get-recent-destinations.ts index 03f0c3d..3f3ad3a 100644 --- a/src/utils/get-recent-destinations.ts +++ b/src/utils/get-recent-destinations.ts @@ -20,16 +20,16 @@ export const getRecentDestinations = (currentBlockLevel: number) => return undefined; }) - .filter((address): address is string => isDefined(address)); + .filter(isDefined); } return []; }) - ) - .then(destinationsArray => destinationsArray.flat()) - .catch((error): string[] => { - logger.error('getRecentDestinations error'); - logger.error(error); + ).then( + destinationsArray => destinationsArray.flat(), + (error): string[] => { + logger.error('getRecentDestinations error:', error); return []; - }); + } + ); diff --git a/src/utils/makeBuildQueryFn.ts b/src/utils/makeBuildQueryFn.ts index e30c6be..f0ad05d 100644 --- a/src/utils/makeBuildQueryFn.ts +++ b/src/utils/makeBuildQueryFn.ts @@ -4,7 +4,7 @@ import { stringify } from 'qs'; import logger from './logger'; import PromisifiedSemaphore from './PromisifiedSemaphore'; -function pick(obj: T, keys: U[]) { +function pick | object, U extends keyof T>(obj: T, keys: U[]) { const newObj: Partial = {}; keys.forEach(key => { if (key in obj) { @@ -22,7 +22,7 @@ function isAbsoluteURL(url) { return /^([a-z][a-z\d\+\-\.]*:)?\/\//i.test(url); } -export default function makeBuildQueryFn( +export default function makeBuildQueryFn

| object, R>( baseUrl: string, maxConcurrentQueries?: number, defaultConfig: Omit = {} @@ -42,31 +42,37 @@ export default function makeBuildQueryFn( const queryStr = stringify(queryParams); const noQueryParamsUrl = isAbsoluteURL(url) ? `${url}/` : `${baseUrl}${url}`; const fullUrl = `${noQueryParamsUrl}${queryStr.length === 0 ? '' : `?${queryStr}`}`; + const getData = async () => { + try { + const requestConfig = { + url: fullUrl, + ...defaultConfig, + ...config + }; + logger.debug(requestConfig); + const { data } = await axios.request(requestConfig); + + return data; + } catch (e) { + logger.error(`Error while making query to ${fullUrl}`); + throw e; + } + }; + if (semaphore) { return new Promise((resolve, reject) => { semaphore.exec(async () => { try { - logger.debug({ - url: fullUrl, - ...defaultConfig, - ...config - }); - const { data } = await axios.request({ - url: fullUrl, - ...defaultConfig, - ...config - }); + const data = await getData(); resolve(data); } catch (e) { - logger.error(`Error while making query to ${fullUrl}`); reject(e); } }); }); } - const { data } = await axios.request({ url: fullUrl, ...config }); - return data; + return getData(); }; }; } diff --git a/src/utils/tezos.ts b/src/utils/tezos.ts index dcd2e17..b5945f8 100644 --- a/src/utils/tezos.ts +++ b/src/utils/tezos.ts @@ -57,7 +57,7 @@ const getTezExchangeRate = async () => { 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 && usdTickers.reduce((s, t) => s + (t.last * t.volume_base) / vol, 0); + const price = vol === null ? 1 : usdTickers.reduce((s, t) => s + (t.last * t.volume_base) / vol, 0); return price; }; @@ -80,7 +80,7 @@ export const getTokenMetadata = memoizee( try { tokenData = await contract.tzip12().getTokenMetadata(tokenId ?? 0); } catch (err) { - latestErrMessage = err.message; + latestErrMessage = (err as Error).message; } /** @@ -92,7 +92,7 @@ export const getTokenMetadata = memoizee( const { metadata } = await contract.tzip16().getMetadata(); tokenData = metadata; } catch (err) { - latestErrMessage = err.message; + latestErrMessage = (err as Error).message; } } diff --git a/src/utils/tokens.ts b/src/utils/tokens.ts index 3de839f..072f8cd 100644 --- a/src/utils/tokens.ts +++ b/src/utils/tokens.ts @@ -66,16 +66,14 @@ const getTokensExchangeRates = async (): Promise => { logger.info('Getting tokens exchange rates...'); logger.info('Getting exchange rates of tokens which are known to 3route...'); const { data: tokens, error: tokensError } = await tokensListProvider.getState(); - const { data: rawTezExchangeRate, error: tezExchangeRateError } = await tezExchangeRateProvider.getState(); - const error = tokensError ?? tezExchangeRateError; + const { data: tezExchangeRate, error: tezExchangeRateError } = await tezExchangeRateProvider.getState(); - if (error) { - throw error; + if (tokensError ?? tezExchangeRateError) { + throw tokensError ?? tezExchangeRateError; } - const tezExchangeRate = rawTezExchangeRate === false ? 1 : rawTezExchangeRate!; const exchangeRates = await Promise.all( - tokens! + tokens .filter( (token): token is ThreeRouteFa12Token | ThreeRouteFa2Token => token.standard !== ThreeRouteStandardEnum.xtz ) @@ -93,7 +91,7 @@ const getTokensExchangeRates = async (): Promise => { throw swapError; } - const { directSwap, invertedSwap } = probeSwaps!; + const { directSwap, invertedSwap } = probeSwaps; const toTezExchangeRatesVersions: BigNumber[] = []; if (directSwap.output !== 0) { toTezExchangeRatesVersions.push(new BigNumber(PROBE_TEZ_AMOUNT).div(directSwap.output)); @@ -155,14 +153,13 @@ blockFinder(EMPTY_BLOCK, async block => const recentDestinations = await getRecentDestinations(block.header.level); const { data: tokens, error: tokensError } = await tokensListProvider.getState(); const { data: dexes, error: dexesError } = await dexesListProvider.getState(); - const error = tokensError ?? dexesError; - if (error) { - throw error; + if (tokensError ?? dexesError) { + throw tokensError ?? dexesError; } const outputsUpdatesFlags = await Promise.all( - tokens!.map(async token => { + tokens.map(async token => { if (token.symbol === THREE_ROUTE_TEZ_SYMBOL) { return false; } @@ -174,10 +171,10 @@ blockFinder(EMPTY_BLOCK, async block => throw swapError; } - const { directSwap, invertedSwap } = probeSwaps!; + const { directSwap, invertedSwap } = probeSwaps; const dexesAddresses = directSwap.chains .concat(invertedSwap.chains) - .map(chain => chain.hops.map(hop => dexes!.find(dex => dex.id === hop.dex)?.contract).filter(isDefined)) + .map(chain => chain.hops.map(hop => dexes.find(dex => dex.id === hop.dex)?.contract).filter(isDefined)) .flat(); const firstUpdatedDexAddress = dexesAddresses.find(dexAddress => recentDestinations.includes(dexAddress)); diff --git a/yarn.lock b/yarn.lock index b3a1494..59af121 100644 --- a/yarn.lock +++ b/yarn.lock @@ -23,6 +23,13 @@ chalk "^2.0.0" js-tokens "^4.0.0" +"@cspotcode/source-map-support@^0.8.0": + version "0.8.1" + resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" + integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw== + dependencies: + "@jridgewell/trace-mapping" "0.3.9" + "@eslint/eslintrc@^0.4.3": version "0.4.3" resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.4.3.tgz#9e42981ef035beb3dd49add17acb96e8ff6f394c" @@ -211,6 +218,24 @@ resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== +"@jridgewell/resolve-uri@^3.0.3": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78" + integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w== + +"@jridgewell/sourcemap-codec@^1.4.10": + version "1.4.14" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" + integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== + +"@jridgewell/trace-mapping@0.3.9": + version "0.3.9" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9" + integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ== + dependencies: + "@jridgewell/resolve-uri" "^3.0.3" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@nodelib/fs.scandir@2.1.5": version "2.1.5" resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" @@ -442,6 +467,26 @@ resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf" integrity sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A== +"@tsconfig/node10@^1.0.7": + version "1.0.9" + resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.9.tgz#df4907fc07a886922637b15e02d4cebc4c0021b2" + integrity sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA== + +"@tsconfig/node12@^1.0.7": + version "1.0.11" + resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.11.tgz#ee3def1f27d9ed66dac6e46a295cffb0152e058d" + integrity sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag== + +"@tsconfig/node14@^1.0.0": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.3.tgz#e4386316284f00b98435bf40f72f75a09dabf6c1" + integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow== + +"@tsconfig/node16@^1.0.2": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.3.tgz#472eaab5f15c1ffdd7f8628bd4c4f753995ec79e" + integrity sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ== + "@types/body-parser@*": version "1.19.0" resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.0.tgz#0685b3c47eb3006ffed117cdd55164b61f80538f" @@ -722,11 +767,21 @@ acorn-jsx@^5.3.1: resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== +acorn-walk@^8.1.1: + version "8.2.0" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" + integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== + acorn@^7.4.0: version "7.4.1" resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== +acorn@^8.4.1: + version "8.8.2" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.2.tgz#1b2f25db02af965399b9776b0c2c391276d37c4a" + integrity sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw== + agent-base@6: version "6.0.2" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" @@ -2920,11 +2975,6 @@ minimist@^1.2.0, minimist@^1.2.6: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.7.tgz#daa1c4d91f507390437c6a8bc01078e7000c4d18" integrity sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g== -minimist@^1.2.5: - version "1.2.6" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" - integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== - mkdirp@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" @@ -3643,7 +3693,7 @@ sonic-boom@^1.0.2: atomic-sleep "^1.0.0" flatstr "^1.0.12" -source-map-support@^0.5.12, source-map-support@^0.5.17: +source-map-support@^0.5.12: version "0.5.19" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61" integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw== @@ -3878,32 +3928,39 @@ tree-kill@^1.2.2: resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc" integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A== -ts-node-dev@^1.1.8: - version "1.1.8" - resolved "https://registry.yarnpkg.com/ts-node-dev/-/ts-node-dev-1.1.8.tgz#95520d8ab9d45fffa854d6668e2f8f9286241066" - integrity sha512-Q/m3vEwzYwLZKmV6/0VlFxcZzVV/xcgOt+Tx/VjaaRHyiBcFlV0541yrT09QjzzCxlDZ34OzKjrFAynlmtflEg== +ts-node-dev@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ts-node-dev/-/ts-node-dev-2.0.0.tgz#bdd53e17ab3b5d822ef519928dc6b4a7e0f13065" + integrity sha512-ywMrhCfH6M75yftYvrvNarLEY+SUXtUvU8/0Z6llrHQVBx12GiFk5sStF8UdfE/yfzk9IAq7O5EEbTQsxlBI8w== dependencies: chokidar "^3.5.1" dynamic-dedupe "^0.3.0" - minimist "^1.2.5" + minimist "^1.2.6" mkdirp "^1.0.4" resolve "^1.0.0" rimraf "^2.6.1" source-map-support "^0.5.12" tree-kill "^1.2.2" - ts-node "^9.0.0" + ts-node "^10.4.0" tsconfig "^7.0.0" -ts-node@^9.0.0: - version "9.1.1" - resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-9.1.1.tgz#51a9a450a3e959401bda5f004a72d54b936d376d" - integrity sha512-hPlt7ZACERQGf03M253ytLY3dHbGNGrAq9qIHWUY9XHYl1z7wYngSr3OQ5xmui8o2AaxsONxIzjafLUiWBo1Fg== - dependencies: +ts-node@^10.4.0: + version "10.9.1" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.1.tgz#e73de9102958af9e1f0b168a6ff320e25adcff4b" + integrity sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw== + dependencies: + "@cspotcode/source-map-support" "^0.8.0" + "@tsconfig/node10" "^1.0.7" + "@tsconfig/node12" "^1.0.7" + "@tsconfig/node14" "^1.0.0" + "@tsconfig/node16" "^1.0.2" + acorn "^8.4.1" + acorn-walk "^8.1.1" arg "^4.1.0" create-require "^1.1.0" diff "^4.0.1" make-error "^1.1.1" - source-map-support "^0.5.17" + v8-compile-cache-lib "^3.0.1" yn "3.1.1" tsconfig-paths@^3.14.1: @@ -3994,10 +4051,10 @@ typedarray-to-buffer@^4.0.0: resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-4.0.0.tgz#cdd2933c61dd3f5f02eda5d012d441f95bfeb50a" integrity sha512-6dOYeZfS3O9RtRD1caom0sMxgK59b27+IwoNy8RDPsmslSGOyU+mpTamlaIW7aNKi90ZQZ9DFaZL3YRoiSCULQ== -typescript@^4.2.4: - version "4.2.4" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.2.4.tgz#8610b59747de028fda898a8aef0e103f156d0961" - integrity sha512-V+evlYHZnQkaz8TRBuxTA92yZBPotr5H+WhQ7bD3hZUndx5tGOa1fuCgeSjxAzM1RiN5IzvadIXTVefuuwZCRg== +typescript@^4.9.5: + version "4.9.5" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a" + integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g== unbox-primitive@^1.0.0: version "1.0.1" @@ -4053,6 +4110,11 @@ uuid@^8.0.0: resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== +v8-compile-cache-lib@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" + integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== + v8-compile-cache@^2.0.3: version "2.3.0" resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" From 4b08653c3c23fdd8e5f9f6e3a3e480b3beafd97d Mon Sep 17 00:00:00 2001 From: Inokentii Mazhara Date: Mon, 6 Mar 2023 16:43:04 +0200 Subject: [PATCH 04/13] Fix 'yarn ts' errors --- package.json | 8 +- yarn.lock | 237 +++++++++++++++++++++++++++++++++------------------ 2 files changed, 158 insertions(+), 87 deletions(-) diff --git a/package.json b/package.json index 3b1d7bb..64eaf9f 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "cors": "^2.8.5", "cross-fetch": "^3.1.5", "dotenv": "^9.0.2", - "express": "^4.17.3", + "express": "^4.18.2", "firebase-admin": "^10.0.2", "memoizee": "^0.4.15", "pino": "^6.11.2", @@ -35,9 +35,11 @@ "clean": "rimraf dist/" }, "devDependencies": { - "@types/express": "^4.17.11", + "@types/express": "^4.17.17", + "@types/express-jwt": "^7.4.2", + "@types/express-unless": "^2.0.1", "@types/memoizee": "^0.4.5", - "@types/node": "^15.3.0", + "@types/node": "^18.14.6", "@types/pino": "^6.3.8", "@types/pino-http": "^5.4.1", "@types/semaphore": "^1.1.1", diff --git a/yarn.lock b/yarn.lock index 59af121..7388480 100644 --- a/yarn.lock +++ b/yarn.lock @@ -517,6 +517,13 @@ "@types/express" "*" "@types/express-unless" "*" +"@types/express-jwt@^7.4.2": + version "7.4.2" + resolved "https://registry.yarnpkg.com/@types/express-jwt/-/express-jwt-7.4.2.tgz#3367f86c856437774a1f3d73acc2597aa0cc9ab9" + integrity sha512-wb3wbD/XaWdBu9366M07Ugb3fuIiW7B2oUdnXfvUchI892eLGZ5eQqgfnVw2oNfN0dF9L2Px859DpZKPDtxzlA== + dependencies: + express-jwt "*" + "@types/express-serve-static-core@^4.17.18": version "4.17.19" resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.19.tgz#00acfc1632e729acac4f1530e9e16f6dd1508a1d" @@ -526,6 +533,15 @@ "@types/qs" "*" "@types/range-parser" "*" +"@types/express-serve-static-core@^4.17.33": + version "4.17.33" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.33.tgz#de35d30a9d637dc1450ad18dd583d75d5733d543" + integrity sha512-TPBqmR/HRYI3eC2E5hmiivIzv+bidAfXofM+sbonAGvyDhySGw9/PQZFt2BLOrjUUR++4eJVpx6KnLQK1Fk9tA== + dependencies: + "@types/node" "*" + "@types/qs" "*" + "@types/range-parser" "*" + "@types/express-unless@*": version "0.5.3" resolved "https://registry.yarnpkg.com/@types/express-unless/-/express-unless-0.5.3.tgz#271f8603617445568ed0d6efe25a7d2f338544c1" @@ -533,6 +549,13 @@ dependencies: "@types/express" "*" +"@types/express-unless@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@types/express-unless/-/express-unless-2.0.1.tgz#7d5728315caf95a315a6bcc19ac99f6a8becbe49" + integrity sha512-PJLiNw03EjkWDkQbhNjIXXDLObC3eMQhFASDV+WakFbT8eL7YdjlbV6MXd3Av5Lejq499d6pFuV1jyK+EHyG3Q== + dependencies: + express-unless "*" + "@types/express@*": version "4.17.13" resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.13.tgz#a76e2995728999bab51a33fabce1d705a3709034" @@ -543,13 +566,13 @@ "@types/qs" "*" "@types/serve-static" "*" -"@types/express@^4.17.11": - version "4.17.11" - resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.11.tgz#debe3caa6f8e5fcda96b47bd54e2f40c4ee59545" - integrity sha512-no+R6rW60JEc59977wIxreQVsIEOAYwgCqldrA/vkpCnbD7MqTefO97lmoBe4WE0F156bC4uLSP1XHDOySnChg== +"@types/express@^4.17.17": + version "4.17.17" + resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.17.tgz#01d5437f6ef9cfa8668e616e13c2f2ac9a491ae4" + integrity sha512-Q4FmmuLGBG58btUnfS1c1r/NQdlp3DMfGDGig8WhfpA2YRUtEkxAjkZb0yvplJGYdF1fsQ81iMDcH24sSCNC/Q== dependencies: "@types/body-parser" "*" - "@types/express-serve-static-core" "^4.17.18" + "@types/express-serve-static-core" "^4.17.33" "@types/qs" "*" "@types/serve-static" "*" @@ -563,6 +586,13 @@ resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== +"@types/jsonwebtoken@^9": + version "9.0.1" + resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-9.0.1.tgz#29b1369c4774200d6d6f63135bf3d1ba3ef997a4" + integrity sha512-c5ltxazpWabia/4UzhIoaDcIza4KViOQhdbjRlfcIGVnsE3c3brkz9Z+F/EeJIECOQP7W7US2hNE930cWWkPiw== + dependencies: + "@types/node" "*" + "@types/long@^4.0.0", "@types/long@^4.0.1": version "4.0.1" resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.1.tgz#459c65fa1867dafe6a8f322c4c51695663cc55e9" @@ -578,7 +608,7 @@ resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.2.tgz#93e25bf9ee75fe0fd80b594bc4feb0e862111b5a" integrity sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw== -"@types/node@*", "@types/node@^15.3.0": +"@types/node@*": version "15.3.0" resolved "https://registry.yarnpkg.com/@types/node/-/node-15.3.0.tgz#d6fed7d6bc6854306da3dea1af9f874b00783e26" integrity sha512-8/bnjSZD86ZfpBsDlCIkNXIvm+h6wi9g7IqL+kmFkQ+Wvu3JrasgLElfiPgoo8V8vVfnEi0QVS12gbl94h9YsQ== @@ -588,6 +618,11 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.23.tgz#3b41a6e643589ac6442bdbd7a4a3ded62f33f7da" integrity sha512-UxDxWn7dl97rKVeVS61vErvw086aCYhDLyvRQZ5Rk65rZKepaFdm53GeqXaKBuOhED4e9uWq34IC3TdSdJJ2Gw== +"@types/node@^18.14.6": + version "18.14.6" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.14.6.tgz#ae1973dd2b1eeb1825695bb11ebfb746d27e3e93" + integrity sha512-93+VvleD3mXwlLI/xASjw0FzKcwzl3OdTCzm1LaRfqgS21gfFtK3zDXM5Op9TeeMsJVOaJ2VRDpT9q4Y3d0AvA== + "@types/pino-http@^5.4.1": version "5.4.1" resolved "https://registry.yarnpkg.com/@types/pino-http/-/pino-http-5.4.1.tgz#eec5f7b1857ab188eca13d7451bb50e83cdcf020" @@ -983,21 +1018,23 @@ bn.js@^4.11.9: resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA== -body-parser@1.19.2: - version "1.19.2" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.2.tgz#4714ccd9c157d44797b8b5607d72c0b89952f26e" - integrity sha512-SAAwOxgoCKMGs9uUAUFHygfLAyaniaoun6I8mFY9pRAJL9+Kec34aU+oIjDhTycub1jozEfEwx1W1IuOYxVSFw== +body-parser@1.20.1: + version "1.20.1" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.1.tgz#b1812a8912c195cd371a3ee5e66faa2338a5c668" + integrity sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw== dependencies: bytes "3.1.2" content-type "~1.0.4" debug "2.6.9" - depd "~1.1.2" - http-errors "1.8.1" + depd "2.0.0" + destroy "1.2.0" + http-errors "2.0.0" iconv-lite "0.4.24" - on-finished "~2.3.0" - qs "6.9.7" - raw-body "2.4.3" + on-finished "2.4.1" + qs "6.11.0" + raw-body "2.5.1" type-is "~1.6.18" + unpipe "1.0.0" brace-expansion@^1.1.7: version "1.1.11" @@ -1197,10 +1234,10 @@ cookie-signature@1.0.6: resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw= -cookie@0.4.2: - version "0.4.2" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.2.tgz#0e41f24de5ecf317947c82fc789e06a884824432" - integrity sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA== +cookie@0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b" + integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw== cors@^2.8.5: version "2.8.5" @@ -1334,15 +1371,15 @@ delayed-stream@~1.0.0: resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= -depd@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" - integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= +depd@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" + integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== -destroy@~1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" - integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA= +destroy@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" + integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== dicer@^0.3.0: version "0.3.1" @@ -1809,38 +1846,53 @@ event-target-shim@^5.0.0: resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== -express@^4.17.3: - version "4.17.3" - resolved "https://registry.yarnpkg.com/express/-/express-4.17.3.tgz#f6c7302194a4fb54271b73a1fe7a06478c8f85a1" - integrity sha512-yuSQpz5I+Ch7gFrPCk4/c+dIBKlQUxtgwqzph132bsT6qhuzss6I8cLJQz7B3rFblzd6wtcI0ZbGltH/C4LjUg== +express-jwt@*: + version "8.4.1" + resolved "https://registry.yarnpkg.com/express-jwt/-/express-jwt-8.4.1.tgz#ba817c1ced7c6f1f7017fc2e6deac207011e8acb" + integrity sha512-IZoZiDv2yZJAb3QrbaSATVtTCYT11OcqgFGoTN4iKVyN6NBkBkhtVIixww5fmakF0Upt5HfOxJuS6ZmJVeOtTQ== + dependencies: + "@types/jsonwebtoken" "^9" + express-unless "^2.1.3" + jsonwebtoken "^9.0.0" + +express-unless@*, express-unless@^2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/express-unless/-/express-unless-2.1.3.tgz#f951c6cca52a24da3de32d42cfd4db57bc0f9a2e" + integrity sha512-wj4tLMyCVYuIIKHGt0FhCtIViBcwzWejX0EjNxveAa6dG+0XBCQhMbx+PnkLkFCxLC69qoFrxds4pIyL88inaQ== + +express@^4.18.2: + version "4.18.2" + resolved "https://registry.yarnpkg.com/express/-/express-4.18.2.tgz#3fabe08296e930c796c19e3c516979386ba9fd59" + integrity sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ== dependencies: accepts "~1.3.8" array-flatten "1.1.1" - body-parser "1.19.2" + body-parser "1.20.1" content-disposition "0.5.4" content-type "~1.0.4" - cookie "0.4.2" + cookie "0.5.0" cookie-signature "1.0.6" debug "2.6.9" - depd "~1.1.2" + depd "2.0.0" encodeurl "~1.0.2" escape-html "~1.0.3" etag "~1.8.1" - finalhandler "~1.1.2" + finalhandler "1.2.0" fresh "0.5.2" + http-errors "2.0.0" merge-descriptors "1.0.1" methods "~1.1.2" - on-finished "~2.3.0" + on-finished "2.4.1" parseurl "~1.3.3" path-to-regexp "0.1.7" proxy-addr "~2.0.7" - qs "6.9.7" + qs "6.11.0" range-parser "~1.2.1" safe-buffer "5.2.1" - send "0.17.2" - serve-static "1.14.2" + send "0.18.0" + serve-static "1.15.0" setprototypeof "1.2.0" - statuses "~1.5.0" + statuses "2.0.1" type-is "~1.6.18" utils-merge "1.0.1" vary "~1.1.2" @@ -1938,17 +1990,17 @@ fill-range@^7.0.1: dependencies: to-regex-range "^5.0.1" -finalhandler@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d" - integrity sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA== +finalhandler@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.2.0.tgz#7d23fe5731b207b4640e4fcd00aec1f9207a7b32" + integrity sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg== dependencies: debug "2.6.9" encodeurl "~1.0.2" escape-html "~1.0.3" - on-finished "~2.3.0" + on-finished "2.4.1" parseurl "~1.3.3" - statuses "~1.5.0" + statuses "2.0.1" unpipe "~1.0.0" firebase-admin@^10.0.2: @@ -2317,15 +2369,15 @@ hosted-git-info@^2.1.4: resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9" integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw== -http-errors@1.8.1: - version "1.8.1" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.8.1.tgz#7c3f28577cbc8a207388455dbd62295ed07bd68c" - integrity sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g== +http-errors@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3" + integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ== dependencies: - depd "~1.1.2" + depd "2.0.0" inherits "2.0.4" setprototypeof "1.2.0" - statuses ">= 1.5.0 < 2" + statuses "2.0.1" toidentifier "1.0.1" http-parser-js@>=0.5.1: @@ -2688,6 +2740,16 @@ jsonwebtoken@^8.5.1: ms "^2.1.1" semver "^5.6.0" +jsonwebtoken@^9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz#d0faf9ba1cc3a56255fe49c0961a67e520c1926d" + integrity sha512-tuGfYXxkQGDPnLJ7SibiQgVgeDgfbPq2k2ICcbgqW8WxWLBAxKQM/ZCu/IT8SOSwmaYl4dpTFCW5xZv7YbbWUw== + dependencies: + jws "^3.2.2" + lodash "^4.17.21" + ms "^2.1.1" + semver "^7.3.8" + jwa@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a" @@ -2816,6 +2878,11 @@ lodash.truncate@^4.4.2: resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193" integrity sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw== +lodash@^4.17.21: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + long@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28" @@ -3126,10 +3193,10 @@ object.values@^1.1.5: define-properties "^1.1.4" es-abstract "^1.20.4" -on-finished@~2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" - integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc= +on-finished@2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" + integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== dependencies: ee-first "1.1.1" @@ -3373,10 +3440,12 @@ punycode@^2.1.0: resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== -qs@6.9.7: - version "6.9.7" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.7.tgz#4610846871485e1e048f44ae3b94033f0e675afe" - integrity sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw== +qs@6.11.0: + version "6.11.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a" + integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q== + dependencies: + side-channel "^1.0.4" qs@^6.10.3: version "6.10.3" @@ -3400,13 +3469,13 @@ range-parser@~1.2.1: resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== -raw-body@2.4.3: - version "2.4.3" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.3.tgz#8f80305d11c2a0a545c2d9d89d7a0286fcead43c" - integrity sha512-UlTNLIcu0uzb4D2f4WltY6cVjLi+/jEN4lgEUj3E04tpMDpUlkBo/eSn6zou9hum2VMNpCCUone0O0WeJim07g== +raw-body@2.5.1: + version "2.5.1" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.1.tgz#fe1b1628b181b700215e5fd42389f98b71392857" + integrity sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig== dependencies: bytes "3.1.2" - http-errors "1.8.1" + http-errors "2.0.0" iconv-lite "0.4.24" unpipe "1.0.0" @@ -3574,41 +3643,41 @@ semver@^6.0.0: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== -semver@^7.2.1, semver@^7.3.7: +semver@^7.2.1, semver@^7.3.7, semver@^7.3.8: version "7.3.8" resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.8.tgz#07a78feafb3f7b32347d725e33de7e2a2df67798" integrity sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A== dependencies: lru-cache "^6.0.0" -send@0.17.2: - version "0.17.2" - resolved "https://registry.yarnpkg.com/send/-/send-0.17.2.tgz#926622f76601c41808012c8bf1688fe3906f7820" - integrity sha512-UJYB6wFSJE3G00nEivR5rgWp8c2xXvJ3OPWPhmuteU0IKj8nKbG3DrjiOmLwpnHGYWAVwA69zmTm++YG0Hmwww== +send@0.18.0: + version "0.18.0" + resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be" + integrity sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg== dependencies: debug "2.6.9" - depd "~1.1.2" - destroy "~1.0.4" + depd "2.0.0" + destroy "1.2.0" encodeurl "~1.0.2" escape-html "~1.0.3" etag "~1.8.1" fresh "0.5.2" - http-errors "1.8.1" + http-errors "2.0.0" mime "1.6.0" ms "2.1.3" - on-finished "~2.3.0" + on-finished "2.4.1" range-parser "~1.2.1" - statuses "~1.5.0" + statuses "2.0.1" -serve-static@1.14.2: - version "1.14.2" - resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.2.tgz#722d6294b1d62626d41b43a013ece4598d292bfa" - integrity sha512-+TMNA9AFxUEGuC0z2mevogSnn9MXKb4fa7ngeRMJaaGv8vTwnIEkKi+QGvPt33HSnf8pRS+WGM0EbMtCJLKMBQ== +serve-static@1.15.0: + version "1.15.0" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.15.0.tgz#faaef08cffe0a1a62f60cad0c4e513cff0ac9540" + integrity sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g== dependencies: encodeurl "~1.0.2" escape-html "~1.0.3" parseurl "~1.3.3" - send "0.17.2" + send "0.18.0" setprototypeof@1.2.0: version "1.2.0" @@ -3744,10 +3813,10 @@ sprintf-js@~1.0.2: resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== -"statuses@>= 1.5.0 < 2", statuses@~1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" - integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= +statuses@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" + integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== stream-events@^1.0.4, stream-events@^1.0.5: version "1.0.5" From 0aee44ff3dd66cbeeb3865c53ba5c141d75e9423 Mon Sep 17 00:00:00 2001 From: Inokentii Mazhara Date: Mon, 6 Mar 2023 18:46:16 +0200 Subject: [PATCH 05/13] Refactoring according to comments --- src/index.ts | 26 ++++++++++++++--------- src/utils/helpers.ts | 39 +++++++++++++++++++++++++++-------- src/utils/makeBuildQueryFn.ts | 21 ++----------------- 3 files changed, 48 insertions(+), 38 deletions(-) diff --git a/src/index.ts b/src/index.ts index 9c9f90c..f059f92 100644 --- a/src/index.ts +++ b/src/index.ts @@ -18,6 +18,7 @@ import { estimateAliceBobOutput } from './utils/alice-bob/estimate-alice-bob-out import { getAliceBobOrderInfo } from './utils/alice-bob/get-alice-bob-order-info'; import { getAliceBobPairInfo } from './utils/alice-bob/get-alice-bob-pair-info'; import { coinGeckoTokens } from './utils/gecko-tokens'; +import { getExternalApiErrorPayload } from './utils/helpers'; import logger from './utils/logger'; import { getSignedMoonPayUrl } from './utils/moonpay/get-signed-moonpay-url'; import SingleQueryDataProvider from './utils/SingleQueryDataProvider'; @@ -173,8 +174,9 @@ app.post('/api/alice-bob/create-order', async (_req, res) => { const orderInfo = await createAliceBobOrder(booleanIsWithdraw, exchangeInfo); res.status(200).send({ orderInfo }); - } catch (error: any) { - res.status(error.response.status).send(error.response.data); + } catch (error) { + const { status, data } = getExternalApiErrorPayload(error as Error); + res.status(status).send(data); } }); @@ -185,8 +187,9 @@ app.post('/api/alice-bob/cancel-order', async (_req, res) => { await cancelAliceBobOrder({ id: String(orderId) }); res.status(200); - } catch (error: any) { - res.status(error.response.status).send(error.response.data); + } catch (error) { + const { status, data } = getExternalApiErrorPayload(error as Error); + res.status(status).send(data); } }); @@ -197,8 +200,9 @@ app.get('/api/alice-bob/get-pair-info', async (_req, res) => { const pairInfo = await getAliceBobPairInfo(isWithdraw === 'true'); res.status(200).send({ pairInfo }); - } catch (error: any) { - res.status(error.response.status).send({ error: error.response.data }); + } catch (error) { + const { status, data } = getExternalApiErrorPayload(error as Error); + res.status(status).send(data); } }); @@ -209,8 +213,9 @@ app.get('/api/alice-bob/check-order', async (_req, res) => { const orderInfo = await getAliceBobOrderInfo(String(orderId)); res.status(200).send({ orderInfo }); - } catch (error: any) { - res.status(error.response.status).send({ error: error.response.data }); + } catch (error) { + const { status, data } = getExternalApiErrorPayload(error as Error); + res.status(status).send({ error: data }); } }); @@ -227,8 +232,9 @@ app.post('/api/alice-bob/estimate-amount', async (_req, res) => { const outputAmount = await estimateAliceBobOutput(exchangeInfo); res.status(200).send({ outputAmount }); - } catch (error: any) { - res.status(error.response.status).send({ error: error.response.data }); + } catch (error) { + const { status, data } = getExternalApiErrorPayload(error as Error); + res.status(status).send({ error: data }); } }); diff --git a/src/utils/helpers.ts b/src/utils/helpers.ts index 89dbdfe..23cafc8 100644 --- a/src/utils/helpers.ts +++ b/src/utils/helpers.ts @@ -1,22 +1,43 @@ +import { AxiosError } from 'axios'; import { BigNumber } from 'bignumber.js'; -export function range(start: number, end: number, step = 1) { - return Array(Math.ceil((end - start) / step)) +export const range = (start: number, end: number, step = 1) => + Array(Math.ceil((end - start) / step)) .fill(0) .map((_x, index) => start + step * index); -} -export function rangeBn(start: number, end: number, step = 1): Array { - return Array(Math.ceil((end - start) / step)) +export const rangeBn = (start: number, end: number, step = 1) => + Array(Math.ceil((end - start) / step)) .fill(0) .map((_x, index) => new BigNumber(start + step * index)); -} + +export const pick = (obj: T, keys: U[]) => { + const newObj: Partial = {}; + keys.forEach(key => { + if (key in obj) { + newObj[key] = obj[key]; + } + }); + + return newObj as Pick; +}; + +export const isAbsoluteURL = (url: string) => { + // A URL is considered absolute if it begins with "://" or "//" (protocol-relative URL). + // RFC 3986 defines scheme name as a sequence of characters beginning with a letter and followed + // by any combination of letters, digits, plus, period, or hyphen. + return /^([a-z][a-z\d\+\-\.]*:)?\/\//i.test(url); +}; // eslint-disable-next-line @typescript-eslint/no-empty-function export const emptyFn = () => {}; -export function isDefined(value: T | undefined | null): value is T { - return value !== undefined && value !== null; -} +export const isDefined = (value: T | undefined | null): value is T => value !== undefined && value !== null; export const sleep = (ms: number) => new Promise(resolve => setTimeout(() => resolve('wake'), ms)); + +export const getExternalApiErrorPayload = (error: Error) => { + const response = error instanceof AxiosError ? error.response : undefined; + + return { status: response?.status ?? 500, data: response?.data ?? { error: error.message } }; +}; diff --git a/src/utils/makeBuildQueryFn.ts b/src/utils/makeBuildQueryFn.ts index f0ad05d..2cb508d 100644 --- a/src/utils/makeBuildQueryFn.ts +++ b/src/utils/makeBuildQueryFn.ts @@ -1,28 +1,11 @@ import axios, { AxiosRequestConfig } from 'axios'; import { stringify } from 'qs'; +import { isAbsoluteURL, pick } from './helpers'; import logger from './logger'; import PromisifiedSemaphore from './PromisifiedSemaphore'; -function pick | object, U extends keyof T>(obj: T, keys: U[]) { - const newObj: Partial = {}; - keys.forEach(key => { - if (key in obj) { - newObj[key] = obj[key]; - } - }); - - return newObj as Pick; -} - -function isAbsoluteURL(url) { - // A URL is considered absolute if it begins with "://" or "//" (protocol-relative URL). - // RFC 3986 defines scheme name as a sequence of characters beginning with a letter and followed - // by any combination of letters, digits, plus, period, or hyphen. - return /^([a-z][a-z\d\+\-\.]*:)?\/\//i.test(url); -} - -export default function makeBuildQueryFn

| object, R>( +export default function makeBuildQueryFn

( baseUrl: string, maxConcurrentQueries?: number, defaultConfig: Omit = {} From affab2b23a0cee315afb569730b92e86c6b79c68 Mon Sep 17 00:00:00 2001 From: Inokentii Mazhara Date: Thu, 9 Mar 2023 13:21:58 +0200 Subject: [PATCH 06/13] TW-514 Improve handling errors from external APIs --- src/index.ts | 10 +++++----- src/utils/helpers.ts | 6 ++++-- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/index.ts b/src/index.ts index f059f92..9e2eefe 100644 --- a/src/index.ts +++ b/src/index.ts @@ -175,7 +175,7 @@ app.post('/api/alice-bob/create-order', async (_req, res) => { res.status(200).send({ orderInfo }); } catch (error) { - const { status, data } = getExternalApiErrorPayload(error as Error); + const { status, data } = getExternalApiErrorPayload(error); res.status(status).send(data); } }); @@ -188,7 +188,7 @@ app.post('/api/alice-bob/cancel-order', async (_req, res) => { res.status(200); } catch (error) { - const { status, data } = getExternalApiErrorPayload(error as Error); + const { status, data } = getExternalApiErrorPayload(error); res.status(status).send(data); } }); @@ -201,7 +201,7 @@ app.get('/api/alice-bob/get-pair-info', async (_req, res) => { res.status(200).send({ pairInfo }); } catch (error) { - const { status, data } = getExternalApiErrorPayload(error as Error); + const { status, data } = getExternalApiErrorPayload(error); res.status(status).send(data); } }); @@ -214,7 +214,7 @@ app.get('/api/alice-bob/check-order', async (_req, res) => { res.status(200).send({ orderInfo }); } catch (error) { - const { status, data } = getExternalApiErrorPayload(error as Error); + const { status, data } = getExternalApiErrorPayload(error); res.status(status).send({ error: data }); } }); @@ -233,7 +233,7 @@ app.post('/api/alice-bob/estimate-amount', async (_req, res) => { res.status(200).send({ outputAmount }); } catch (error) { - const { status, data } = getExternalApiErrorPayload(error as Error); + const { status, data } = getExternalApiErrorPayload(error); res.status(status).send({ error: data }); } }); diff --git a/src/utils/helpers.ts b/src/utils/helpers.ts index 23cafc8..c7f612a 100644 --- a/src/utils/helpers.ts +++ b/src/utils/helpers.ts @@ -36,8 +36,10 @@ export const isDefined = (value: T | undefined | null): value is T => value ! export const sleep = (ms: number) => new Promise(resolve => setTimeout(() => resolve('wake'), ms)); -export const getExternalApiErrorPayload = (error: Error) => { +export const getExternalApiErrorPayload = (error: unknown) => { const response = error instanceof AxiosError ? error.response : undefined; + const status = response?.status ?? 500; + const data = response?.data ?? { error: error instanceof Error ? error.message : error }; - return { status: response?.status ?? 500, data: response?.data ?? { error: error.message } }; + return { status, data }; }; From 1d13a959e4dfade9bb33ec0aa541c8ff64d7cbe1 Mon Sep 17 00:00:00 2001 From: Inokentii Mazhara Date: Thu, 9 Mar 2023 18:24:21 +0200 Subject: [PATCH 07/13] TW-514 Remove a default export --- src/utils/coingecko.ts | 2 +- src/utils/makeBuildQueryFn.ts | 2 +- src/utils/three-route.ts | 2 +- src/utils/tzkt.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/utils/coingecko.ts b/src/utils/coingecko.ts index 0193ce5..b4abf9c 100644 --- a/src/utils/coingecko.ts +++ b/src/utils/coingecko.ts @@ -1,5 +1,5 @@ import { range } from './helpers'; -import makeBuildQueryFn from './makeBuildQueryFn'; +import { makeBuildQueryFn } from './makeBuildQueryFn'; import SingleQueryDataProvider from './SingleQueryDataProvider'; type CoinsListParams = { diff --git a/src/utils/makeBuildQueryFn.ts b/src/utils/makeBuildQueryFn.ts index 2cb508d..116a183 100644 --- a/src/utils/makeBuildQueryFn.ts +++ b/src/utils/makeBuildQueryFn.ts @@ -5,7 +5,7 @@ import { isAbsoluteURL, pick } from './helpers'; import logger from './logger'; import PromisifiedSemaphore from './PromisifiedSemaphore'; -export default function makeBuildQueryFn

( +export function makeBuildQueryFn

( baseUrl: string, maxConcurrentQueries?: number, defaultConfig: Omit = {} diff --git a/src/utils/three-route.ts b/src/utils/three-route.ts index e7720d3..19c2615 100644 --- a/src/utils/three-route.ts +++ b/src/utils/three-route.ts @@ -1,5 +1,5 @@ import { THREE_ROUTE_API_AUTH_TOKEN, THREE_ROUTE_API_URL } from '../config'; -import makeBuildQueryFn from './makeBuildQueryFn'; +import { makeBuildQueryFn } from './makeBuildQueryFn'; interface SwapQueryParams { inputTokenSymbol: string; diff --git a/src/utils/tzkt.ts b/src/utils/tzkt.ts index 7e9edd6..996d7fa 100644 --- a/src/utils/tzkt.ts +++ b/src/utils/tzkt.ts @@ -1,5 +1,5 @@ import DataProvider from './DataProvider'; -import makeBuildQueryFn from './makeBuildQueryFn'; +import { makeBuildQueryFn } from './makeBuildQueryFn'; export type BcdTokenData = { network: string; From 313c5fc672161ef5cd3e7f21779c5460c6c1f045 Mon Sep 17 00:00:00 2001 From: Inokentii Mazhara Date: Fri, 26 May 2023 13:02:25 +0300 Subject: [PATCH 08/13] Fix blocked exchange rates --- src/utils/tokens.ts | 79 +++++++++++++++++++++++++++------------------ 1 file changed, 48 insertions(+), 31 deletions(-) diff --git a/src/utils/tokens.ts b/src/utils/tokens.ts index 072f8cd..695db93 100644 --- a/src/utils/tokens.ts +++ b/src/utils/tokens.ts @@ -52,6 +52,8 @@ const probeSwapsProvider = new DataProvider(Infinity, async (outputTokenSymbol: return { directSwap, invertedSwap }; }); +export class TimeoutError extends Error {} + export type TokenExchangeRateEntry = { tokenAddress: string; tokenId?: number; @@ -72,49 +74,64 @@ const getTokensExchangeRates = async (): Promise => { throw tokensError ?? tezExchangeRateError; } - const exchangeRates = await Promise.all( + const exchangeRatesWithHoles = await Promise.all( tokens .filter( (token): token is ThreeRouteFa12Token | ThreeRouteFa2Token => token.standard !== ThreeRouteStandardEnum.xtz ) - .map(async (token): Promise => { + .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); - const { data: probeSwaps, error: swapError } = await probeSwapsProvider.get(token.symbol); - await tokensMetadataProvider.subscribe(contract, tokenId); - const { data: metadata } = await tokensMetadataProvider.get(contract, tokenId); - - if (swapError) { - logger.error(`Failed to get exchange rate for token ${token.symbol}`); - throw swapError; + try { + const { data: probeSwaps, error: swapError } = await Promise.race([ + probeSwapsProvider.get(token.symbol), + new Promise((_, rej) => setTimeout(() => rej(new TimeoutError()), 10000)) + ]); + await tokensMetadataProvider.subscribe(contract, tokenId); + const { data: metadata } = await tokensMetadataProvider.get(contract, tokenId); + + if (swapError) { + logger.error(`Failed to get exchange rate for token ${token.symbol}`); + 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 exchangeRate = + toTezExchangeRatesVersions.length === 0 + ? new BigNumber(0) + : BigNumber.sum(...toTezExchangeRatesVersions) + .div(toTezExchangeRatesVersions.length) + .times(tezExchangeRate); + + return { + tokenAddress: contract, + tokenId, + exchangeRate, + metadata: mapTzktTokenDataToBcdTokenData(metadata?.[0]) + }; + } catch (e) { + if (e instanceof TimeoutError) { + console.error('Timeout error while getting exchange rate for token', token.symbol); + + return undefined; + } + + throw e; } - - 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 exchangeRate = - toTezExchangeRatesVersions.length === 0 - ? new BigNumber(0) - : BigNumber.sum(...toTezExchangeRatesVersions) - .div(toTezExchangeRatesVersions.length) - .times(tezExchangeRate); - - return { - tokenAddress: contract, - tokenId, - exchangeRate, - metadata: mapTzktTokenDataToBcdTokenData(metadata?.[0]) - }; }) ); + const exchangeRates = exchangeRatesWithHoles.filter(isDefined); + if (!exchangeRates.some(({ tokenAddress }) => tokenAddress === ASPENCOIN_ADDRESS)) { logger.info('Getting exchange rate for Aspencoin'); try { From f433c694756eebb106f84928172ba0ce805ee8f6 Mon Sep 17 00:00:00 2001 From: Inokentii Mazhara Date: Fri, 26 May 2023 13:10:29 +0300 Subject: [PATCH 09/13] Fix logging --- src/utils/tokens.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/tokens.ts b/src/utils/tokens.ts index 695db93..fea40bb 100644 --- a/src/utils/tokens.ts +++ b/src/utils/tokens.ts @@ -120,7 +120,7 @@ const getTokensExchangeRates = async (): Promise => { }; } catch (e) { if (e instanceof TimeoutError) { - console.error('Timeout error while getting exchange rate for token', token.symbol); + logger.error('Timeout error while getting exchange rate for token', token.symbol); return undefined; } From ad0696bd6c6064398dd45ed4459acd1c508c358f Mon Sep 17 00:00:00 2001 From: Inokentii Mazhara Date: Wed, 28 Jun 2023 13:18:09 +0300 Subject: [PATCH 10/13] TW-740 Implement preventing blockFinder callback freezing --- src/utils/tokens.ts | 48 ++++++++++++++++++++++++++++----------------- 1 file changed, 30 insertions(+), 18 deletions(-) diff --git a/src/utils/tokens.ts b/src/utils/tokens.ts index fea40bb..da9a7ad 100644 --- a/src/utils/tokens.ts +++ b/src/utils/tokens.ts @@ -153,7 +153,10 @@ const getTokensExchangeRates = async (): Promise => { exchangeRate: tokenPrice, metadata: mapTzktTokenDataToBcdTokenData(aspencoinMetadata?.[0]) }); - } catch (e) {} + } catch (e) { + logger.error('Failed to get exchange rate for Aspencoin'); + logger.error(e as Error); + } } logger.info('Successfully got tokens exchange rates'); @@ -181,28 +184,37 @@ blockFinder(EMPTY_BLOCK, async block => return false; } - await probeSwapsProvider.subscribe(token.symbol); - const { data: probeSwaps, error: swapError } = await probeSwapsProvider.get(token.symbol); + try { + 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)) + ]); - if (swapError) { - throw swapError; - } + if (swapError) { + throw swapError; + } - const { directSwap, invertedSwap } = probeSwaps; - const dexesAddresses = directSwap.chains - .concat(invertedSwap.chains) - .map(chain => chain.hops.map(hop => dexes.find(dex => dex.id === hop.dex)?.contract).filter(isDefined)) - .flat(); + const { directSwap, invertedSwap } = probeSwaps; + const dexesAddresses = directSwap.chains + .concat(invertedSwap.chains) + .map(chain => chain.hops.map(hop => dexes.find(dex => dex.id === hop.dex)?.contract).filter(isDefined)) + .flat(); - const firstUpdatedDexAddress = dexesAddresses.find(dexAddress => recentDestinations.includes(dexAddress)); - if (isDefined(firstUpdatedDexAddress)) { - logger.info(`updating swap output for token ${token.symbol} because of dex ${firstUpdatedDexAddress}`); - await probeSwapsProvider.refetchInSubscription(token.symbol); + const firstUpdatedDexAddress = dexesAddresses.find(dexAddress => recentDestinations.includes(dexAddress)); + if (isDefined(firstUpdatedDexAddress)) { + logger.info(`updating swap output for token ${token.symbol} because of dex ${firstUpdatedDexAddress}`); + await probeSwapsProvider.refetchInSubscription(token.symbol); - return true; - } + return true; + } - return false; + return false; + } catch (e) { + logger.error(e as Error); + + return false; + } }) ); if (outputsUpdatesFlags.some(flag => flag)) { From 46a0a068c671a40d3aa241b525f98309e6d3edea Mon Sep 17 00:00:00 2001 From: Inokentii Mazhara Date: Wed, 28 Jun 2023 15:53:23 +0300 Subject: [PATCH 11/13] TW-740 Improve console output --- src/utils/tokens.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/tokens.ts b/src/utils/tokens.ts index da9a7ad..f061532 100644 --- a/src/utils/tokens.ts +++ b/src/utils/tokens.ts @@ -120,7 +120,7 @@ const getTokensExchangeRates = async (): Promise => { }; } catch (e) { if (e instanceof TimeoutError) { - logger.error('Timeout error while getting exchange rate for token', token.symbol); + logger.error(`Timeout error while getting exchange rate for token ${token.symbol}`); return undefined; } From 51cc5cbd74daf873b697997783b1d8140177c754 Mon Sep 17 00:00:00 2001 From: Inokentii Mazhara Date: Mon, 3 Jul 2023 17:05:24 +0300 Subject: [PATCH 12/13] TW-740 Fix exchange rate calculation for SIRS token --- src/utils/three-route.ts | 30 +++++++++++++++++++++++++----- src/utils/tokens.ts | 18 ++++++++++++++---- 2 files changed, 39 insertions(+), 9 deletions(-) diff --git a/src/utils/three-route.ts b/src/utils/three-route.ts index 19c2615..e89fcc7 100644 --- a/src/utils/three-route.ts +++ b/src/utils/three-route.ts @@ -31,6 +31,13 @@ export interface ThreeRouteSwapResponse { chains: ThreeRouteChain[]; } +export interface ThreeRouteSirsSwapResponse { + input: number; + output: number; + tzbtcChain: ThreeRouteSwapResponse; + xtzChain: ThreeRouteSwapResponse; +} + interface ThreeRouteTokenCommon { id: number; symbol: string; @@ -90,7 +97,13 @@ export interface ThreeRouteDex { } type ThreeRouteQueryParams = object | SwapQueryParams; -type ThreeRouteQueryResponse = ThreeRouteSwapResponse | ThreeRouteDex[] | ThreeRouteToken[]; +type ThreeRouteQueryResponse = + | ThreeRouteSwapResponse + | ThreeRouteSirsSwapResponse + | ThreeRouteDex[] + | ThreeRouteToken[]; + +export const THREE_ROUTE_SIRS_SYMBOL = 'SIRS'; const threeRouteBuildQueryFn = makeBuildQueryFn( THREE_ROUTE_API_URL, @@ -98,11 +111,18 @@ const threeRouteBuildQueryFn = makeBuildQueryFn( - ({ inputTokenSymbol, outputTokenSymbol, realAmount }) => - `/swap/${inputTokenSymbol}/${outputTokenSymbol}/${realAmount}` -); +export const getThreeRouteSwap = threeRouteBuildQueryFn< + SwapQueryParams, + ThreeRouteSwapResponse | ThreeRouteSirsSwapResponse +>(({ inputTokenSymbol, outputTokenSymbol, realAmount }) => { + const isSirsSwap = inputTokenSymbol === THREE_ROUTE_SIRS_SYMBOL || outputTokenSymbol === THREE_ROUTE_SIRS_SYMBOL; + + return `/${isSirsSwap ? 'swap-sirs' : 'swap'}/${inputTokenSymbol}/${outputTokenSymbol}/${realAmount}`; +}); export const getThreeRouteDexes = threeRouteBuildQueryFn('/dexes', []); export const getThreeRouteTokens = threeRouteBuildQueryFn('/tokens', []); + +export const getChains = (response: ThreeRouteSwapResponse | ThreeRouteSirsSwapResponse) => + 'chains' in response ? response.chains : [...response.xtzChain.chains, ...response.tzbtcChain.chains]; diff --git a/src/utils/tokens.ts b/src/utils/tokens.ts index f061532..864d0f3 100644 --- a/src/utils/tokens.ts +++ b/src/utils/tokens.ts @@ -16,7 +16,9 @@ import { getThreeRouteTokens, ThreeRouteStandardEnum, ThreeRouteFa12Token, - ThreeRouteFa2Token + ThreeRouteFa2Token, + getChains, + THREE_ROUTE_SIRS_SYMBOL } from './three-route'; import { BcdTokenData, mapTzktTokenDataToBcdTokenData, tokensMetadataProvider } from './tzkt'; @@ -80,7 +82,7 @@ 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}`); + logger.info(token.symbol); const { contract, tokenId: rawTokenId } = token; const tokenId = isDefined(rawTokenId) ? Number(rawTokenId) : undefined; await probeSwapsProvider.subscribe(token.symbol); @@ -184,6 +186,12 @@ blockFinder(EMPTY_BLOCK, async block => return false; } + if (token.symbol === THREE_ROUTE_SIRS_SYMBOL) { + logger.info('swap output for SIRS should be updated each block because of baking subsidy'); + + return true; + } + try { await probeSwapsProvider.subscribe(token.symbol); const { data: probeSwaps, error: swapError } = await Promise.race([ @@ -196,8 +204,10 @@ blockFinder(EMPTY_BLOCK, async block => } const { directSwap, invertedSwap } = probeSwaps; - const dexesAddresses = directSwap.chains - .concat(invertedSwap.chains) + const directSwapChains = getChains(directSwap); + const invertedSwapChains = getChains(invertedSwap); + const dexesAddresses = directSwapChains + .concat(invertedSwapChains) .map(chain => chain.hops.map(hop => dexes.find(dex => dex.id === hop.dex)?.contract).filter(isDefined)) .flat(); From 9964568c27a05015345656e2aeea4ccc3718b580 Mon Sep 17 00:00:00 2001 From: Inokentii Mazhara Date: Mon, 3 Jul 2023 17:22:53 +0300 Subject: [PATCH 13/13] TW-740 Change logging according to comments --- src/utils/tokens.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/utils/tokens.ts b/src/utils/tokens.ts index 864d0f3..70a6a53 100644 --- a/src/utils/tokens.ts +++ b/src/utils/tokens.ts @@ -82,7 +82,7 @@ const getTokensExchangeRates = async (): Promise => { (token): token is ThreeRouteFa12Token | ThreeRouteFa2Token => token.standard !== ThreeRouteStandardEnum.xtz ) .map(async (token): Promise => { - logger.info(token.symbol); + 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); @@ -187,8 +187,7 @@ blockFinder(EMPTY_BLOCK, async block => } if (token.symbol === THREE_ROUTE_SIRS_SYMBOL) { - logger.info('swap output for SIRS should be updated each block because of baking subsidy'); - + // Swap output for SIRS should be updated each block because of baking subsidy return true; }