From 9d98dd56fe7defdbdc217dafc3058af093d98d33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Slan=C3=BD?= <47864599+peterslany@users.noreply.github.com> Date: Wed, 22 Nov 2023 15:44:55 +0100 Subject: [PATCH] Peter/fix dex volumes query (#1608) * chore: update monetary to latest 0.7.3 * fix(pools): update hook to return data on per pool basis --- src/components/PoolsTable/PoolsTable.tsx | 6 +- src/hooks/api/use-get-dex-volume.tsx | 167 ++++++------------ src/hooks/api/use-get-pools-trading-apr.tsx | 69 +------- .../SwapLiquidity/SwapLiquidity.tsx | 4 +- src/services/queries/pools.ts | 61 +++++++ 5 files changed, 126 insertions(+), 181 deletions(-) create mode 100644 src/services/queries/pools.ts diff --git a/src/components/PoolsTable/PoolsTable.tsx b/src/components/PoolsTable/PoolsTable.tsx index 01bc23d149..d35b6c5390 100644 --- a/src/components/PoolsTable/PoolsTable.tsx +++ b/src/components/PoolsTable/PoolsTable.tsx @@ -41,7 +41,7 @@ const PoolsTable = ({ variant, pools, onRowAction, title }: PoolsTableProps): JS const { t } = useTranslation(); const prices = useGetPrices(); const titleId = useId(); - const { getDexTotalVolumeUSD } = useGetDexVolumes(DateRangeVolume.D7); + const { getDexVolumeByPool } = useGetDexVolumes(DateRangeVolume.D7); const isAccountPools = variant === 'account-pools'; @@ -82,7 +82,7 @@ const PoolsTable = ({ variant, pools, onRowAction, title }: PoolsTableProps): JS // TODO: revert alignItems prop when `sevenDayVolume` is adressed const totalLiquidity = ; - const total7DayVolumeUSD = getDexTotalVolumeUSD(pooledCurrencies.map((pooled) => pooled.currency.ticker)); + const total7DayVolumeUSD = getDexVolumeByPool(data); const total7DayVolumeLabel = formatUSD(total7DayVolumeUSD, { compact: true }); const sevenDayVolume = ( @@ -107,7 +107,7 @@ const PoolsTable = ({ variant, pools, onRowAction, title }: PoolsTableProps): JS accountLiquidity }; }), - [getDexTotalVolumeUSD, isAccountPools, onRowAction, pools, prices, variant] + [getDexVolumeByPool, isAccountPools, onRowAction, pools, prices, variant] ); return ( diff --git a/src/hooks/api/use-get-dex-volume.tsx b/src/hooks/api/use-get-dex-volume.tsx index 4d494d20f3..ef70121559 100644 --- a/src/hooks/api/use-get-dex-volume.tsx +++ b/src/hooks/api/use-get-dex-volume.tsx @@ -1,13 +1,16 @@ -import { CurrencyExt, newMonetaryAmount } from '@interlay/interbtc-api'; +import { CurrencyExt, LiquidityPool, newMonetaryAmount } from '@interlay/interbtc-api'; import { MonetaryAmount } from '@interlay/monetary-js'; +import Big from 'big.js'; import { subDays } from 'date-fns'; -import { gql, GraphQLClient } from 'graphql-request'; +import { GraphQLClient } from 'graphql-request'; import { useCallback } from 'react'; import { useErrorHandler } from 'react-error-boundary'; import { useQuery, UseQueryResult } from 'react-query'; -import { convertMonetaryAmountToValueInUSD } from '@/common/utils/utils'; +import { convertMonetaryAmountToBigUSD } from '@/common/utils/utils'; import { SQUID_URL } from '@/constants'; +import { getPoolDataId, getPoolsVolumesQuery } from '@/services/queries/pools'; +import { CurrencySquidFormat } from '@/types/currency'; import { REFETCH_INTERVAL } from '@/utils/constants/api'; import { getTokenPrice } from '@/utils/helpers/prices'; @@ -21,133 +24,78 @@ const graphQLClient = new GraphQLClient(SQUID_URL, { } }); -// TODO: add this to a dedicated schemas folder -const AMOUNT_FIELDS = gql` - fragment AmountFields on PooledAmount { - amount - amountHuman - token { - ... on NativeToken { - __typename - token - } - ... on ForeignAsset { - __typename - asset - } - ... on StableLpToken { - __typename - poolId - } - } - } -`; - -// TODO: add this to a dedicated schemas folder -const GET_DEX_VOLUMES = gql` - ${AMOUNT_FIELDS} - query poolVolumes($start: DateTime, $end: DateTime) { - startVolumes: cumulativeDexTradingVolumes( - limit: 1 - orderBy: tillTimestamp_ASC - where: { tillTimestamp_gte: $start } - ) { - tillTimestamp - amounts { - ...AmountFields - } - } - endVolumes: cumulativeDexTradingVolumes( - limit: 1 - orderBy: tillTimestamp_DESC - where: { tillTimestamp_lte: $end, tillTimestamp_gte: $start } - ) { - tillTimestamp - amounts { - ...AmountFields - } - } - } -`; - enum DateRangeVolume { H24, D7 } -type DexCurrencyVolume = { - amount: MonetaryAmount; - usd: number; +type DexVolumesData = Record; + +type UseGetCurrenciesResult = UseQueryResult & { + getDexVolumeByPool: (pool: LiquidityPool | undefined) => number; }; -type DexVolumesData = Record; +const getVolumes = ( + volumes: any, + dataId: string, + getCurrencyFromSquidFormat: (currencySquid: CurrencySquidFormat) => CurrencyExt +): Array> => { + const startVolumes = volumes[`${dataId}__startVolumes`]; + const endVolumes = volumes[`${dataId}__endVolumes`]; + if (startVolumes.length === 0 || endVolumes.length === 0) { + return []; + } -type UseGetCurrenciesResult = UseQueryResult & { - getDexVolumeByTicker: (ticker: string) => DexCurrencyVolume | undefined; - getDexTotalVolumeUSD: (tickers: string[]) => number; + return startVolumes[0].amounts.map((amount: any, index: number) => { + const currency = getCurrencyFromSquidFormat(amount.token); + const endAmount = Big(endVolumes[0].amounts[index].amount); + const amountDelta = endAmount.sub(Big(amount.amount)); + + return newMonetaryAmount(amountDelta, currency); + }); }; const useGetDexVolumes = (range: DateRangeVolume): UseGetCurrenciesResult => { - const { getCurrencyFromTicker, getForeignCurrencyFromId } = useGetCurrencies(true); - const { getStableLiquidityPoolById } = useGetLiquidityPools(); + const { getCurrencyFromSquidFormat } = useGetCurrencies(true); + const { data: pools } = useGetLiquidityPools(); const prices = useGetPrices(); const getDexVolumes = useCallback( async (range: DateRangeVolume): Promise => { + if (!pools) { + return {}; + } + const start = subDays(new Date(), range === DateRangeVolume.D7 ? 7 : 1); const end = new Date(); - const data = await graphQLClient.request(GET_DEX_VOLUMES, { start, end }); + const query = getPoolsVolumesQuery(pools); - if (!data.startVolumes.length || !data.endVolumes.length) { - return {}; - } + const data = await graphQLClient.request(query, { start, end }); - const [startVolumes] = data.startVolumes; - const [endVolumes] = data.endVolumes; - - return startVolumes.amounts.reduce((acc: DexVolumesData, item: any) => { - let currency: CurrencyExt; - let endVolume; - - switch (item.token.__typename) { - case 'NativeToken': { - const { token } = item.token; - currency = getCurrencyFromTicker(token); - endVolume = endVolumes.amounts.find((endAmount: any) => endAmount.token.token === token); - break; - } - case 'ForeignAsset': { - const { asset } = item.token; - currency = getForeignCurrencyFromId(asset); - endVolume = endVolumes.amounts.find((endAmount: any) => endAmount.token.asset === asset); - break; - } - case 'StableLpToken': { - const { poolId } = item.token; - currency = getStableLiquidityPoolById(poolId).lpToken; - endVolume = endVolumes.amounts.find((endAmount: any) => endAmount.token.poolId === poolId); - break; - } - default: - return acc; - } + const result = pools.map((pool: LiquidityPool) => { + const dataId = getPoolDataId(pool); - if (!endVolume) { - return acc; + const volumes = getVolumes(data, dataId, getCurrencyFromSquidFormat); + if (volumes.length === 0) { + return { [dataId]: Big(0) }; } - const volumeAmount = newMonetaryAmount(endVolume.amount - item.amount, currency); + const totalVolumeInUSD = volumes + .reduce( + (total, amount) => + total.add(convertMonetaryAmountToBigUSD(amount, getTokenPrice(prices, amount.currency.ticker)?.usd)), + Big(0) + ) + // Divide by amount of pooled currencies. + .div(pool.pooledCurrencies.length); - const volume: DexCurrencyVolume = { - amount: volumeAmount, - usd: convertMonetaryAmountToValueInUSD(volumeAmount, getTokenPrice(prices, currency.ticker)?.usd) || 0 - }; + return { [dataId]: totalVolumeInUSD }; + }); - return { ...acc, [currency.ticker]: volume }; - }, {}); + return result.reduce((result, pool) => ({ ...result, ...pool })); }, - [getCurrencyFromTicker, getForeignCurrencyFromId, getStableLiquidityPoolById, prices] + [pools, getCurrencyFromSquidFormat, prices] ); const queryResult = useQuery({ @@ -156,16 +104,15 @@ const useGetDexVolumes = (range: DateRangeVolume): UseGetCurrenciesResult => { refetchInterval: REFETCH_INTERVAL.MINUTE }); - const getDexVolumeByTicker = useCallback((ticker: string) => queryResult.data?.[ticker], [queryResult.data]); - - const getDexTotalVolumeUSD = useCallback( - (tickers: string[]) => tickers.reduce((sum, ticker) => sum + (getDexVolumeByTicker(ticker)?.usd || 0), 0), - [getDexVolumeByTicker] + const getDexVolumeByPool = useCallback( + (pool: LiquidityPool | undefined) => + queryResult.data && pool ? queryResult.data[getPoolDataId(pool)].toNumber() : 0, + [queryResult] ); useErrorHandler(queryResult.error); - return { ...queryResult, getDexTotalVolumeUSD, getDexVolumeByTicker }; + return { ...queryResult, getDexVolumeByPool }; }; export { DateRangeVolume, useGetDexVolumes }; diff --git a/src/hooks/api/use-get-pools-trading-apr.tsx b/src/hooks/api/use-get-pools-trading-apr.tsx index ba73ace329..80486652c8 100644 --- a/src/hooks/api/use-get-pools-trading-apr.tsx +++ b/src/hooks/api/use-get-pools-trading-apr.tsx @@ -1,19 +1,13 @@ -import { - CurrencyExt, - isForeignAsset, - LiquidityPool, - newMonetaryAmount, - PooledCurrencies, - TickerToData -} from '@interlay/interbtc-api'; +import { CurrencyExt, LiquidityPool, newMonetaryAmount, TickerToData } from '@interlay/interbtc-api'; import { MonetaryAmount } from '@interlay/monetary-js'; import Big from 'big.js'; -import { gql, GraphQLClient } from 'graphql-request'; +import { GraphQLClient } from 'graphql-request'; import { useCallback } from 'react'; import { useQuery } from 'react-query'; import { convertMonetaryAmountToBigUSD } from '@/common/utils/utils'; import { SQUID_URL } from '@/constants'; +import { getPoolDataId, getPoolsVolumesQuery } from '@/services/queries/pools'; import { CurrencySquidFormat } from '@/types/currency'; import { MILLISECONDS_PER_DAY } from '@/utils/constants/date-time'; import { calculateTotalLiquidityUSD } from '@/utils/helpers/pool'; @@ -29,63 +23,6 @@ const graphQLClient = new GraphQLClient(SQUID_URL, { } }); -const getPoolDataId = (pool: LiquidityPool): string => - `${pool.type}_${pool.pooledCurrencies.map(({ currency }) => currency.ticker).join('_')}`; - -const getPooledCurrenciesCondition = (pooledCurrencies: PooledCurrencies) => - `${pooledCurrencies - .map(({ currency }) => { - const currencyId = isForeignAsset(currency) ? currency.foreignAsset.id.toString() : currency.ticker; - return `AND: {poolId_contains: "${currencyId}"`; - }) - .join()}${pooledCurrencies.map((_) => '}').join('')}`; - -const getPoolsVolumesQuery = (pools: Array): string => gql` - fragment AmountFields on PooledAmount { - amount - amountHuman - token { - ... on NativeToken { - __typename - token - } - ... on ForeignAsset { - __typename - asset - } - ... on StableLpToken { - __typename - poolId - } - } - } - - fragment PoolVolumeFields on CumulativeDexTradingVolumePerPool { - poolId - poolType - tillTimestamp - amounts { - ...AmountFields - } - } - - query poolVolumes($start: DateTime, $end: DateTime) { - ${pools - .map((pool: LiquidityPool) => { - const poolDataId = getPoolDataId(pool); - const pooledCurrenciesCondition = getPooledCurrenciesCondition(pool.pooledCurrencies); - return `${poolDataId}__startVolumes: cumulativeDexTradingVolumePerPools(limit: 1, orderBy: tillTimestamp_ASC, where: {tillTimestamp_gte: $start, ${pooledCurrenciesCondition}}) { - ...PoolVolumeFields - } - ${poolDataId}__endVolumes:cumulativeDexTradingVolumePerPools(limit: 1, orderBy: tillTimestamp_DESC, where: {tillTimestamp_lte: $end, ${pooledCurrenciesCondition}}) { - ...PoolVolumeFields - } - `; - }) - .join('\n')} - } -`; - const getYearlyVolume = ( volumes: any, dataId: string, diff --git a/src/pages/Swap/components/SwapLiquidity/SwapLiquidity.tsx b/src/pages/Swap/components/SwapLiquidity/SwapLiquidity.tsx index c34b6fab91..eac147c7f6 100644 --- a/src/pages/Swap/components/SwapLiquidity/SwapLiquidity.tsx +++ b/src/pages/Swap/components/SwapLiquidity/SwapLiquidity.tsx @@ -18,9 +18,9 @@ type SwapLiquidityProps = Props & InheritAttrs; const SwapLiquidity = ({ input, output, liquidityPool, ...props }: SwapLiquidityProps): JSX.Element | null => { const prices = useGetPrices(); - const { getDexTotalVolumeUSD } = useGetDexVolumes(DateRangeVolume.H24); + const { getDexVolumeByPool } = useGetDexVolumes(DateRangeVolume.H24); - const h24Volume = getDexTotalVolumeUSD([input.ticker, output.ticker]); + const h24Volume = getDexVolumeByPool(liquidityPool); const h24VolumeLabel = formatUSD(h24Volume, { compact: true }); const liquidity = liquidityPool && calculateTotalLiquidityUSD(liquidityPool.pooledCurrencies, prices); diff --git a/src/services/queries/pools.ts b/src/services/queries/pools.ts new file mode 100644 index 0000000000..fbff466b23 --- /dev/null +++ b/src/services/queries/pools.ts @@ -0,0 +1,61 @@ +import { isForeignAsset, LiquidityPool, PooledCurrencies } from '@interlay/interbtc-api'; +import { gql } from 'graphql-request'; + +const getPoolDataId = (pool: LiquidityPool): string => + `${pool.type}_${pool.pooledCurrencies.map(({ currency }) => currency.ticker).join('_')}`; + +const getPooledCurrenciesCondition = (pooledCurrencies: PooledCurrencies) => + `${pooledCurrencies + .map(({ currency }) => { + const currencyId = isForeignAsset(currency) ? currency.foreignAsset.id.toString() : currency.ticker; + return `AND: {poolId_contains: "${currencyId}"`; + }) + .join()}${pooledCurrencies.map((_) => '}').join('')}`; + +const getPoolsVolumesQuery = (pools: Array): string => gql` + fragment AmountFields on PooledAmount { + amount + amountHuman + token { + ... on NativeToken { + __typename + token + } + ... on ForeignAsset { + __typename + asset + } + ... on StableLpToken { + __typename + poolId + } + } + } + + fragment PoolVolumeFields on CumulativeDexTradingVolumePerPool { + poolId + poolType + tillTimestamp + amounts { + ...AmountFields + } + } + + query poolVolumes($start: DateTime, $end: DateTime) { + ${pools + .map((pool: LiquidityPool) => { + const poolDataId = getPoolDataId(pool); + const pooledCurrenciesCondition = getPooledCurrenciesCondition(pool.pooledCurrencies); + return `${poolDataId}__startVolumes: cumulativeDexTradingVolumePerPools(limit: 1, orderBy: tillTimestamp_ASC, where: {tillTimestamp_gte: $start, ${pooledCurrenciesCondition}}) { + ...PoolVolumeFields + } + ${poolDataId}__endVolumes:cumulativeDexTradingVolumePerPools(limit: 1, orderBy: tillTimestamp_DESC, where: {tillTimestamp_lte: $end, ${pooledCurrenciesCondition}}) { + ...PoolVolumeFields + } + `; + }) + .join('\n')} + } +`; + +export { getPoolDataId, getPoolsVolumesQuery };