Skip to content

Commit

Permalink
Add sorting to pools list (#1501)
Browse files Browse the repository at this point in the history
* Add sorting to pools list

* Don't mutate array in place

* Fix type errors

* Remove unused arg

* Remove comment

* Address rest of PR comments
  • Loading branch information
ryangoree authored Sep 19, 2024
1 parent d6d61f0 commit d61538f
Show file tree
Hide file tree
Showing 10 changed files with 324 additions and 191 deletions.
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"delvtech",
"dnum",
"fixedpointmath",
"hyperdrive",
"hyperwasm",
"ihyperdrive",
"mintable",
Expand Down
14 changes: 10 additions & 4 deletions apps/hyperdrive-trading/src/base/formatRate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,18 @@ export function formatRate(
decimals = 18,
includePercentSign = true,
): string {
const formatted = fixed(rate, decimals).format({
let formatted = fixed(rate, decimals).format({
percent: true,
decimals: 2,
});
if (includePercentSign) {
return formatted;

if (formatted === "-0.00%") {
formatted = "0.00%";
}

if (!includePercentSign) {
formatted = formatted.split("%")[0];
}
return formatted.split("%")[0];

return formatted;
}
76 changes: 76 additions & 0 deletions apps/hyperdrive-trading/src/hyperdrive/getLpApy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { Block, ReadHyperdrive } from "@delvtech/hyperdrive-viem";
import { appConfig, HyperdriveConfig } from "@hyperdrive/appconfig";
import { convertMillisecondsToDays } from "src/base/convertMillisecondsToDays";
import { isForkChain } from "src/chains/isForkChain";

export type LpApyResult = {
ratePeriodDays: number;
} & (
| {
lpApy: bigint;
isNew: false;
}
| {
lpApy: undefined;
isNew: true;
}
);

export async function getLpApy({
readHyperdrive,
hyperdrive,
}: {
readHyperdrive: ReadHyperdrive;
hyperdrive: HyperdriveConfig;
}): Promise<LpApyResult> {
const currentBlock = (await readHyperdrive.network.getBlock()) as Block;
const currentBlockNumber = currentBlock.blockNumber!;

// Appconfig tells us how many days to look back for historical rates
const numBlocksForHistoricalRate = isForkChain(hyperdrive.chainId)
? 1000n // roughly 3 hours for cloudchain
: appConfig.chains[hyperdrive.chainId].dailyAverageBlocks *
BigInt(
appConfig.yieldSources[hyperdrive.yieldSource].historicalRatePeriod,
);
const targetFromBlock = currentBlockNumber - numBlocksForHistoricalRate;

let lpApy: bigint | undefined;

const blocksSinceInitialization =
(currentBlockNumber || 0n) - hyperdrive.initializationBlock;
const isYoungerThanOneDay =
blocksSinceInitialization <
appConfig.chains[hyperdrive.chainId].dailyAverageBlocks;

// if the pool was deployed less than one day ago, it's new.
if (!isYoungerThanOneDay) {
const lpApyResult = await readHyperdrive.getLpApy({
fromBlock: [31337].includes(hyperdrive.chainId)
? // local devnets don't have a lot of blocks, so start from the beginning
1n
: targetFromBlock,
});
lpApy = lpApyResult.lpApy;
}

// Figure out if the pool is younger than 1 rate period
const isPoolYoungerThanOneRatePeriod =
hyperdrive.initializationBlock > targetFromBlock;

// If we don't have enough blocks to go back 1 full historical period, then
// grab the all-time rate instead.
let ratePeriodDays =
appConfig.yieldSources[hyperdrive.yieldSource].historicalRatePeriod;
if (isPoolYoungerThanOneRatePeriod) {
ratePeriodDays = convertMillisecondsToDays(
Date.now() - Number(hyperdrive.initializationTimestamp * 1000n),
);
}

return {
lpApy,
ratePeriodDays,
isNew: lpApy === undefined,
} as LpApyResult;
}
32 changes: 32 additions & 0 deletions apps/hyperdrive-trading/src/token/getTokenFiatPrice.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { parseFixed } from "@delvtech/fixed-point-wasm";
import { ETH_MAGIC_NUMBER } from "src/token/ETH_MAGIC_NUMBER";
import { gnosis, linea, mainnet } from "viem/chains";

export async function getTokenFiatPrice({
tokenAddress,
chainId,
}: {
tokenAddress: string;
chainId: number;
}): Promise<bigint | undefined> {
// Always use mainnet ETH as the reference for native ETH price, regardless of
// the current chain.
let defiLlamaTokenId = `${defiLlamaChainNameIdentifier[chainId]}:${tokenAddress}`;
if (tokenAddress === ETH_MAGIC_NUMBER) {
defiLlamaTokenId = `ethereum:${ETH_MAGIC_NUMBER}`;
}

const response = await fetch(
`https://coins.llama.fi/prices/current/${defiLlamaTokenId}`,
);
const data = await response.json();
const { price } = data.coins[defiLlamaTokenId];
return parseFixed(price).bigint;
}

// NOTE: DefiLlama chain name identifier must be lower case.
export const defiLlamaChainNameIdentifier: Record<number, string> = {
[mainnet.id]: "ethereum",
[gnosis.id]: "gnosis",
[linea.id]: "linea",
};
53 changes: 7 additions & 46 deletions apps/hyperdrive-trading/src/ui/hyperdrive/hooks/useLpApy.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
import { Block } from "@delvtech/hyperdrive-viem";
import { appConfig, findHyperdriveConfig } from "@hyperdrive/appconfig";
import { useQuery } from "@tanstack/react-query";
import { convertMillisecondsToDays } from "src/base/convertMillisecondsToDays";
import { formatRate } from "src/base/formatRate";
import { makeQueryKey } from "src/base/makeQueryKey";
import { isForkChain } from "src/chains/isForkChain";
import { getLpApy, LpApyResult } from "src/hyperdrive/getLpApy";
import { usePoolInfo } from "src/ui/hyperdrive/hooks/usePoolInfo";
import { useReadHyperdrive } from "src/ui/hyperdrive/hooks/useReadHyperdrive";
import { Address } from "viem";
Expand All @@ -17,9 +14,7 @@ export function useLpApy({
hyperdriveAddress: Address;
chainId: number;
}): {
lpApy:
| { lpApy: bigint; formatted: string; ratePeriodDays: number }
| undefined;
lpApy: LpApyResult | undefined;
lpApyStatus: "error" | "success" | "loading";
} {
const { poolInfo: currentPoolInfo } = usePoolInfo({
Expand All @@ -45,45 +40,11 @@ export function useLpApy({
hyperdrive: hyperdriveAddress,
}),
queryFn: queryEnabled
? async () => {
const numBlocksForHistoricalRate = isForkChain(chainId)
? 1000n // roughly 3 hours for cloudchain
: appConfig.chains[hyperdrive.chainId].dailyAverageBlocks *
BigInt(
appConfig.yieldSources[hyperdrive.yieldSource]
.historicalRatePeriod,
);
const { lpApy } = await readHyperdrive.getLpApy({
fromBlock: [31337].includes(chainId)
? // local devnets don't have a lot of blocks, so start from the beginning
1n
: // Appconfig tells us how many days to look back for historical rates
blockNumber - numBlocksForHistoricalRate,
});

// Figure out if the pool is younger than 1 rate period
const currentBlock =
(await readHyperdrive.network.getBlock()) as Block;
const isPoolYoungerThanOneRatePeriod =
hyperdrive.initializationBlock >
currentBlock.blockNumber! - numBlocksForHistoricalRate;

// If we don't have enough blocks to go back 1 full historical period, then
// grab the all-time rate instead.
let ratePeriodDays =
appConfig.yieldSources[hyperdrive.yieldSource].historicalRatePeriod;
if (isPoolYoungerThanOneRatePeriod) {
ratePeriodDays = convertMillisecondsToDays(
Date.now() - Number(hyperdrive.initializationTimestamp * 1000n),
);
}

return {
lpApy,
ratePeriodDays,
formatted: formatRate(lpApy),
};
}
? async () =>
getLpApy({
readHyperdrive,
hyperdrive,
})
: undefined,
enabled: queryEnabled,
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import classNames from "classnames";
import { MouseEvent, ReactElement } from "react";
import Skeleton from "react-loading-skeleton";
import { calculateRatio } from "src/base/calculateRatio";
import { formatRate } from "src/base/formatRate";
import { isTestnetChain } from "src/chains/isTestnetChain";
import { getHasEnoughAllowance } from "src/token/getHasEnoughAllowance";
import { getHasEnoughBalance } from "src/token/getHasEnoughBalance";
Expand All @@ -20,7 +21,6 @@ import { PrimaryStat } from "src/ui/base/components/PrimaryStat";
import { formatBalance } from "src/ui/base/formatting/formatBalance";
import { useNumericInput } from "src/ui/base/hooks/useNumericInput";
import { TransactionView } from "src/ui/hyperdrive/TransactionView";
import { useIsNewPool } from "src/ui/hyperdrive/hooks/useIsNewPool";
import { useLpApy } from "src/ui/hyperdrive/hooks/useLpApy";
import { usePoolInfo } from "src/ui/hyperdrive/hooks/usePoolInfo";
import { useFixedRate } from "src/ui/hyperdrive/longs/hooks/useFixedRate";
Expand Down Expand Up @@ -217,7 +217,6 @@ export function AddLiquidityForm({
status: addLiquidityPreviewStatus,
previewAddLiquidityError,
} = usePreviewAddLiquidity(addLiquidityParams);
const isNewPool = useIsNewPool({ hyperdrive });

const { lpSharesTotalSupply } = useLpSharesTotalSupply({
chainId: hyperdrive.chainId,
Expand All @@ -227,7 +226,6 @@ export function AddLiquidityForm({
lpSharesBalanceOf,
lpSharesOut,
lpSharesTotalSupply,
hyperdrive,
baseToken,
});
const { fiatPrice: activeTokenPrice } = useTokenFiatPrice({
Expand Down Expand Up @@ -360,10 +358,10 @@ export function AddLiquidityForm({
<PrimaryStat
label="LP APY"
value={
isNewPool || lpApy === undefined ? (
lpApy === undefined || lpApy.isNew ? (
<div className="flex gap-2">✨New✨</div>
) : (
`${lpApy.formatted === "-0.00" ? "0.00" : lpApy.formatted}`
`${formatRate(lpApy.lpApy)}`
)
}
tooltipContent="The annual percentage yield projection for providing liquidity."
Expand Down Expand Up @@ -469,13 +467,11 @@ function calculatePoolShare({
lpSharesBalanceOf,
lpSharesOut,
lpSharesTotalSupply,
hyperdrive,
baseToken,
}: {
lpSharesBalanceOf: bigint | undefined;
lpSharesOut: bigint | undefined;
lpSharesTotalSupply: bigint | undefined;
hyperdrive: HyperdriveConfig;
baseToken: TokenConfig;
}) {
if (!lpSharesOut || !lpSharesTotalSupply || lpSharesBalanceOf === undefined) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import classNames from "classnames";
import { MouseEvent, ReactElement } from "react";
import Skeleton from "react-loading-skeleton";
import { calculateRatio } from "src/base/calculateRatio";
import { formatRate } from "src/base/formatRate";
import { isTestnetChain } from "src/chains/isTestnetChain";
import { getHasEnoughAllowance } from "src/token/getHasEnoughAllowance";
import { getHasEnoughBalance } from "src/token/getHasEnoughBalance";
Expand Down Expand Up @@ -503,11 +504,11 @@ function LpApyStat({ hyperdrive }: { hyperdrive: HyperdriveConfig }) {
if (showSkeleton) {
return <Skeleton />;
}
if (isNewPool) {
if (lpApy === undefined || lpApy.isNew) {
return <div className="flex gap-2">✨New✨</div>;
}

return `${lpApy?.formatted === "-0.00" ? "0.00" : lpApy?.formatted}`;
return `${formatRate(lpApy?.lpApy)}`;
})()}
tooltipContent="The annual percentage yield projection for providing liquidity."
tooltipPosition="left"
Expand Down
Loading

0 comments on commit d61538f

Please sign in to comment.