diff --git a/packages/stores/src/queries/pool-incentives/incentivized-pools.ts b/packages/stores/src/queries/pool-incentives/incentivized-pools.ts index b3bdf6ff9b..a41ae5b18b 100644 --- a/packages/stores/src/queries/pool-incentives/incentivized-pools.ts +++ b/packages/stores/src/queries/pool-incentives/incentivized-pools.ts @@ -11,7 +11,7 @@ import { ObservableQueryEpochProvisions, ObservableQueryMintParmas, } from "../mint"; -import { ObservableQueryPools } from "../pools"; +import { ObservableQueryPools, ExternalGauge } from "../pools"; import { IPriceStore } from "../../price"; import { ObservableQueryDistrInfo } from "./distr-info"; import { ObservableQueryLockableDurations } from "./lockable-durations"; @@ -118,6 +118,97 @@ export class ObservableQueryIncentivizedPools extends ObservableChainQuery { + if (!allowedGauges.length) { + return new RatePretty(new Dec(0)); + } + + const externalGauge = allowedGauges.find((externalGauge) => { + return ( + duration.asMilliseconds() === externalGauge.duration.asMilliseconds() + ); + }); + + if (!externalGauge?.rewardAmount) { + return new RatePretty(new Dec(0)); + } + + const pool = this.queryPools.getPool(poolId); + + if (!pool) { + return new RatePretty(new Dec(0)); + } + + const mintDenom = this.queryMintParmas.mintDenom; + const epochIdentifier = this.queryMintParmas.epochIdentifier; + + if (!mintDenom || !epochIdentifier) { + return new RatePretty(new Dec(0)); + } + + const epoch = this.queryEpochs.getEpoch(epochIdentifier); + + const chainInfo = this.chainGetter.getChain(this.chainId); + + const mintCurrency = chainInfo.findCurrency( + externalGauge.rewardAmount.currency.coinMinimalDenom + ); + + if (!mintCurrency?.coinGeckoId || !epoch.duration) { + return new RatePretty(new Dec(0)); + } + + const mintPrice = priceStore.getPrice( + mintCurrency.coinGeckoId, + fiatCurrency.currency + ); + + const poolTVL = pool.computeTotalValueLocked(priceStore); + + if (!mintPrice || !poolTVL.toDec().gt(new Dec(0))) { + return new RatePretty(new Dec(0)); + } + + const epochProvision = this.queryEpochProvision.epochProvisions; + + if (!epochProvision) { + return new RatePretty(new Dec(0)); + } + + const numEpochPerYear = + dayjs + .duration({ + years: 1, + }) + .asMilliseconds() / epoch.duration.asMilliseconds(); + + const externalIncentivePrice = new Dec(mintPrice.toString()).mul( + externalGauge.rewardAmount.toDec() + ); + + const yearProvision = new Dec(numEpochPerYear.toString()).quo( + new Dec(externalGauge.remainingEpochs) + ); + + // coins = (X coin's price in USD * remaining incentives in X tokens * (365 / remaining days in gauge)) + // apr = coins / TVL of pool + + return new RatePretty( + externalIncentivePrice.mul(yearProvision).quo(poolTVL.toDec()) + ); + } + ); + /** * 리워드를 받을 수 있는 풀의 연당 이익률을 반환한다. * 리워드를 받을 수 없는 풀일 경우 0를 리턴한다. @@ -157,6 +248,7 @@ export class ObservableQueryIncentivizedPools extends ObservableChainQuery RatePretty; + /** + * Computes external incentive APY for the given duration + */ + readonly computeExternalIncentiveAPYForSpecificDuration: (poolId: string, duration: Duration, priceStore: IPriceStore, fiatCurrency: FiatCurrency, allowedGauges: ExternalGauge[]) => RatePretty; /** * 리워드를 받을 수 있는 풀의 연당 이익률을 반환한다. * 리워드를 받을 수 없는 풀일 경우 0를 리턴한다. diff --git a/packages/stores/types/queries/pools/pool-details.d.ts b/packages/stores/types/queries/pools/pool-details.d.ts index 617e275f5f..853daa8b27 100644 --- a/packages/stores/types/queries/pools/pool-details.d.ts +++ b/packages/stores/types/queries/pools/pool-details.d.ts @@ -7,13 +7,7 @@ import { ObservableQueryIncentivizedPools, ObservableQueryLockableDurations, Obs import { ObservableQueryGuage } from "../incentives"; import { ObservableQueryAccountLocked, ObservableQueryAccountLockedCoins, ObservableQueryAccountUnlockingCoins } from "../lockup"; import { ObservableQueryPool } from "./pool"; -/** Non OSMO gauge. */ -export declare type ExternalGauge = { - id: string; - duration: Duration; - rewardAmount?: CoinPretty; - remainingEpochs: number; -}; +import { ExternalGauge } from "./types"; /** Convenience store for getting common details of a pool via many other query stores. */ export declare class ObservableQueryPoolDetails { protected readonly fiatCurrency: FiatCurrency; diff --git a/packages/stores/types/queries/pools/types.d.ts b/packages/stores/types/queries/pools/types.d.ts index 2ca0208b66..a64f7d09fb 100644 --- a/packages/stores/types/queries/pools/types.d.ts +++ b/packages/stores/types/queries/pools/types.d.ts @@ -1,7 +1,16 @@ import { WeightedPoolRaw } from "@osmosis-labs/pools"; +import { Duration } from "dayjs/plugin/duration"; +import { CoinPretty } from "@keplr-wallet/unit"; export declare type Pools = { pools: WeightedPoolRaw[]; }; export declare type NumPools = { num_pools: string; }; +/** Non OSMO gauge. */ +export declare type ExternalGauge = { + id: string; + duration: Duration; + rewardAmount?: CoinPretty; + remainingEpochs: number; +}; diff --git a/packages/web/pages/pool/[id].tsx b/packages/web/pages/pool/[id].tsx index bc7af24eec..d990596bbf 100644 --- a/packages/web/pages/pool/[id].tsx +++ b/packages/web/pages/pool/[id].tsx @@ -193,6 +193,32 @@ const Pool: FunctionComponent = observer(() => { gaugeDurationMap.set(gauge.duration.asSeconds(), gauge); }); + // Compute combined APR (internal gauge + white-listed external gauge) + gaugeDurationMap.forEach((gauge) => { + const baseApy = queryOsmosis.queryIncentivizedPools.computeAPY( + pool?.id ?? "", + gauge.duration, + priceStore, + fiat + ); + + const externalApy = + queryOsmosis.queryIncentivizedPools.computeExternalIncentiveAPYForSpecificDuration( + pool?.id ?? "", + gauge.duration, + priceStore, + fiat, + allowedGauges + ); + + const totalApr = baseApy.add(externalApy); + + gaugeDurationMap.set(gauge.duration.asSeconds(), { + ...gauge, + apr: totalApr, + }); + }); + return Array.from(gaugeDurationMap.values()).sort( (a, b) => a.duration.asSeconds() - b.duration.asSeconds() ); @@ -825,14 +851,11 @@ const Pool: FunctionComponent = observer(() => { )} {allowedLockupGauges && pool && (
- {allowedLockupGauges.map(({ duration, superfluidApr }) => ( + {allowedLockupGauges.map(({ duration, apr, superfluidApr }) => (