Skip to content

Commit

Permalink
Peter/feat pools trading fee apr (#1389)
Browse files Browse the repository at this point in the history
* chore: update monetary to latest 0.7.3

* feat(pools): add trading fee APR

* refactor: clean-up naming
  • Loading branch information
peterslany authored Jul 3, 2023
1 parent 81337c5 commit 7765f97
Show file tree
Hide file tree
Showing 5 changed files with 236 additions and 6 deletions.
8 changes: 5 additions & 3 deletions src/components/PoolsTable/PoolsTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { calculateAccountLiquidityUSD, calculateTotalLiquidityUSD } from '@/page
import { getCoinIconProps } from '@/utils/helpers/coin-icon';
import { getFarmingApr } from '@/utils/helpers/pools';
import { DateRangeVolume, useGetDexVolumes } from '@/utils/hooks/api/use-get-dex-volume';
import { useGetPoolsTradingApr } from '@/utils/hooks/api/use-get-pools-trading-apr';
import { useGetPrices } from '@/utils/hooks/api/use-get-prices';

import { AssetCell, BalanceCell, Cell, Table, TableProps } from '../DataGrid';
Expand Down Expand Up @@ -42,6 +43,7 @@ const PoolsTable = ({ variant, pools, onRowAction, title }: PoolsTableProps): JS
const prices = useGetPrices();
const titleId = useId();
const { getDexTotalVolumeUSD } = useGetDexVolumes(DateRangeVolume.D7);
const { getTradingAprOfPool } = useGetPoolsTradingApr();

const isAccountPools = variant === 'account-pools';

Expand Down Expand Up @@ -71,8 +73,8 @@ const PoolsTable = ({ variant, pools, onRowAction, title }: PoolsTableProps): JS
const totalLiquidityUSD = calculateTotalLiquidityUSD(pooledCurrencies, prices);

const farmingApr = getFarmingApr(rewardAmountsYearly, totalSupply, totalLiquidityUSD, prices);
// TODO: add also APR from trading volume based on squid data
const aprAmount = farmingApr;
const tradingApr = getTradingAprOfPool(data);
const aprAmount = farmingApr.add(tradingApr);
const apr = <Cell label={isEmpty ? 'N/A' : formatPercentage(aprAmount.toNumber())} />;

// TODO: revert alignItems prop when `sevenDayVolume` is adressed
Expand Down Expand Up @@ -103,7 +105,7 @@ const PoolsTable = ({ variant, pools, onRowAction, title }: PoolsTableProps): JS
accountLiquidity
};
}),
[getDexTotalVolumeUSD, isAccountPools, pools, prices, variant]
[getDexTotalVolumeUSD, isAccountPools, pools, prices, variant, getTradingAprOfPool]
);

return (
Expand Down
16 changes: 15 additions & 1 deletion src/types/currency.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,19 @@ enum ForeignAssetIdLiteral {
BTC = 'BTC'
}

export type { BTCToCollateralTokenRate };
type CurrencySquidFormat =
| {
__typename: 'NativeToken';
token: string;
}
| {
__typename: 'ForeignAsset';
asset: number;
}
| {
__typename: 'LendToken';
lendTokenId: number;
};

export type { BTCToCollateralTokenRate, CurrencySquidFormat };
export { ForeignAssetIdLiteral };
4 changes: 3 additions & 1 deletion src/utils/constants/date-time.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
const YEAR_MONTH_DAY_PATTERN = 'dd/MM/yy';

export { YEAR_MONTH_DAY_PATTERN };
const MILLISECONDS_PER_DAY = 1000 * 60 * 60 * 24;

export { MILLISECONDS_PER_DAY, YEAR_MONTH_DAY_PATTERN };
26 changes: 25 additions & 1 deletion src/utils/hooks/api/use-get-currencies.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { CurrencyExt, InterbtcPrimitivesCurrencyId, tokenSymbolToCurrency } from
import { useCallback } from 'react';
import { useQuery, UseQueryResult } from 'react-query';

import { CurrencySquidFormat } from '@/types/currency';
import { NATIVE_CURRENCIES } from '@/utils/constants/currency';

import { FeatureFlags, useFeatureFlag } from '../use-feature-flag';
Expand All @@ -10,6 +11,7 @@ type UseGetCurrenciesResult = UseQueryResult<Array<CurrencyExt>> & {
getCurrencyFromTicker: (ticker: string) => CurrencyExt;
getForeignCurrencyFromId: (id: number) => CurrencyExt;
getCurrencyFromIdPrimitive: (currencyPrimitive: InterbtcPrimitivesCurrencyId) => CurrencyExt;
getCurrencyFromSquidFormat: (currencySquidFormat: CurrencySquidFormat) => CurrencyExt;
};

const getCurrencies = async (featureFlags: { lending: boolean; amm: boolean }): Promise<Array<CurrencyExt>> => {
Expand Down Expand Up @@ -105,7 +107,29 @@ const useGetCurrencies = (bridgeLoaded: boolean): UseGetCurrenciesResult => {
[getForeignCurrencyFromId, getLendCurrencyFromId]
);

return { ...queryResult, getCurrencyFromTicker, getForeignCurrencyFromId, getCurrencyFromIdPrimitive };
const getCurrencyFromSquidFormat = useCallback(
(currencySquidFormat: CurrencySquidFormat) => {
switch (currencySquidFormat.__typename) {
case 'NativeToken':
return getCurrencyFromTicker(currencySquidFormat.token);
case 'ForeignAsset':
return getForeignCurrencyFromId(currencySquidFormat.asset);
case 'LendToken':
return getLendCurrencyFromId(currencySquidFormat.lendTokenId);
default:
throw new Error(`No handling implemented for currency format of ${currencySquidFormat}`);
}
},
[getCurrencyFromTicker, getForeignCurrencyFromId, getLendCurrencyFromId]
);

return {
...queryResult,
getCurrencyFromTicker,
getForeignCurrencyFromId,
getCurrencyFromIdPrimitive,
getCurrencyFromSquidFormat
};
};

export { useGetCurrencies };
188 changes: 188 additions & 0 deletions src/utils/hooks/api/use-get-pools-trading-apr.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
import {
CurrencyExt,
isForeignAsset,
LiquidityPool,
newMonetaryAmount,
PooledCurrencies,
TickerToData
} from '@interlay/interbtc-api';
import { MonetaryAmount } from '@interlay/monetary-js';
import Big from 'big.js';
import { gql, 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 { calculateTotalLiquidityUSD } from '@/pages/AMM/shared/utils';
import { CurrencySquidFormat } from '@/types/currency';
import { MILLISECONDS_PER_DAY } from '@/utils/constants/date-time';
import { getTokenPrice } from '@/utils/helpers/prices';

import { useGetLiquidityPools } from './amm/use-get-liquidity-pools';
import { useGetCurrencies } from './use-get-currencies';
import { useGetPrices } from './use-get-prices';

const graphQLClient = new GraphQLClient(SQUID_URL, {
headers: {
'Content-Type': 'application/json'
}
});

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<LiquidityPool>): 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,
getCurrencyFromSquidFormat: (currencySquid: CurrencySquidFormat) => CurrencyExt
): Array<MonetaryAmount<CurrencyExt>> => {
const startVolumes = volumes[`${dataId}__startVolumes`];
const endVolumes = volumes[`${dataId}__endVolumes`];
if (startVolumes.length === 0 || endVolumes.length === 0) {
return [];
}

const startDate = new Date(startVolumes[0].tillTimestamp);
const endDate = new Date(endVolumes[0].tillTimestamp);
const daysDelta = (endDate.getTime() - startDate.getTime()) / MILLISECONDS_PER_DAY;
if (daysDelta < 1) {
return [];
}
// Extrapolate to yearly volume amount.
const toYearFactor = 365 / daysDelta;

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));

const yearlyVolumeAmount = amountDelta.mul(toYearFactor);

return newMonetaryAmount(yearlyVolumeAmount, currency);
});
};

interface UseGetPoolsTradingAPR {
isLoading: boolean;
getTradingAprOfPool: (pool: LiquidityPool) => number;
}

const useGetPoolsTradingApr = (): UseGetPoolsTradingAPR => {
const { data: pools } = useGetLiquidityPools();
const { isLoading: isLoadingCurrencies, getCurrencyFromSquidFormat } = useGetCurrencies(true);
const prices = useGetPrices();

const getPoolsTradingAPR = useCallback(async (): Promise<TickerToData<number>> => {
if (!pools || !prices || isLoadingCurrencies) {
return {};
}

const query = getPoolsVolumesQuery(pools);
const endDate = new Date();
const startDate = new Date();
startDate.setMonth(endDate.getMonth() - 1);

const volumes = await graphQLClient.request(query, { start: startDate.toISOString(), end: endDate.toISOString() });

const result = pools.map((pool: LiquidityPool) => {
const dataId = getPoolDataId(pool);

const yearlyVolumes = getYearlyVolume(volumes, dataId, getCurrencyFromSquidFormat);
if (yearlyVolumes.length === 0) {
return { [dataId]: 0 };
}

const totalVolumeInUSD = yearlyVolumes
.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 totalLiquidityUSD = calculateTotalLiquidityUSD(pool.pooledCurrencies, prices);

const yearlyAPR = totalVolumeInUSD.mul(pool.tradingFee).div(totalLiquidityUSD).mul(100).toNumber();
return { [dataId]: yearlyAPR };
});

return result.reduce((result, pool) => ({ ...result, ...pool }));
}, [pools, prices, isLoadingCurrencies, getCurrencyFromSquidFormat]);

const { isLoading, data } = useQuery({
queryKey: 'amm-pools-trading-apr',
queryFn: getPoolsTradingAPR,
enabled: !!pools || !!prices || !isLoadingCurrencies
});

const getTradingAprOfPool = useCallback(
(pool: LiquidityPool): number => {
if (!data) {
return 0;
}
const poolDataId = getPoolDataId(pool);
return data[poolDataId] || 0;
},
[data]
);

return { isLoading, getTradingAprOfPool };
};

export { useGetPoolsTradingApr };

2 comments on commit 7765f97

@vercel
Copy link

@vercel vercel bot commented on 7765f97 Jul 3, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vercel
Copy link

@vercel vercel bot commented on 7765f97 Jul 3, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.