diff --git a/app/components/SearchPopup.tsx b/app/components/SearchPopup.tsx index f0ae641..5d1397a 100644 --- a/app/components/SearchPopup.tsx +++ b/app/components/SearchPopup.tsx @@ -219,7 +219,7 @@ const SearchPopup = ({ onClose }: SearchProps) => {

Volume(24h)

-

APR

+

APY

TVL

diff --git a/app/graphql/pair.queries.ts b/app/graphql/pair.queries.ts index eb3e5f7..acb6039 100644 --- a/app/graphql/pair.queries.ts +++ b/app/graphql/pair.queries.ts @@ -27,6 +27,8 @@ export const PAIR_FRAGMENT = gql` reserveUSD totalSupply txCount + volume0 + volume1 volumeUSD lpFee protocolFee @@ -34,7 +36,12 @@ export const PAIR_FRAGMENT = gql` royaltiesBeneficiary totalFee dayData(first: 7, orderBy: date, orderDirection: desc) { + date + reserve0 + reserve1 reserveUSD + volume0 + volume1 volumeUSD txCount } diff --git a/app/lib/pools.server.ts b/app/lib/pools.server.ts index 7953156..9b53707 100644 --- a/app/lib/pools.server.ts +++ b/app/lib/pools.server.ts @@ -8,15 +8,6 @@ import type { TroveTokenMapping, } from "~/types"; -const getPoolAPY = (volume1w: number, reserveUSD: number) => { - if (reserveUSD === 0) { - return 0; - } - - const apr = ((volume1w / 7) * 365 * 0.0025) / reserveUSD; - return ((1 + apr / 100 / 3650) ** 3650 - 1) * 100; -}; - export const createPoolFromPair = ( pair: Pair, collectionMapping: TroveCollectionMapping, @@ -38,12 +29,12 @@ export const createPoolFromPair = ( parseUnits(pair.reserve1 as NumberString, Number(pair.token1.decimals)) ).toString(), }; + const reserveUSD = Number(pair.reserveUSD); - const volume24h = Number(pair.dayData[0]?.volumeUSD ?? 0); - const volume1w = pair.dayData.reduce( - (total, { volumeUSD }) => total + Number(volumeUSD), - 0 - ); + const dayTime = Math.floor(Date.now() / 1000) - 60 * 60 * 24; + const dayData = pair.dayData.find(({ date }) => Number(date) >= dayTime); + const weekTime = Math.floor(Date.now() / 1000) - 60 * 60 * 24 * 7; + const weekData = pair.dayData.filter(({ date }) => Number(date) >= weekTime); return { ...pair, name: `${token0.symbol} / ${token1.symbol}`, @@ -52,11 +43,24 @@ export const createPoolFromPair = ( hasNFT: token0.isNFT || token1.isNFT, isNFTNFT: token0.isNFT && token1.isNFT, reserveUSD, - volume24h, - volume1w, - apy: getPoolAPY(volume1w, reserveUSD), - feesUSD: Number(pair.volumeUSD) * Number(pair.lpFee), - fees24h: volume24h * Number(pair.lpFee), + volume0: Number(pair.volume0), + volume1: Number(pair.volume1), + volumeUSD: Number(pair.volumeUSD), + volume24h0: Number(dayData?.volume0 ?? 0), + volume24h1: Number(dayData?.volume1 ?? 0), + volume24hUSD: Number(dayData?.volumeUSD ?? 0), + volume1w0: weekData.reduce( + (total, { volume0 }) => total + Number(volume0), + 0 + ), + volume1w1: weekData.reduce( + (total, { volume1 }) => total + Number(volume1), + 0 + ), + volume1wUSD: weekData.reduce( + (total, { volumeUSD }) => total + Number(volumeUSD), + 0 + ), }; }; diff --git a/app/lib/pools.ts b/app/lib/pools.ts index b31883c..1b17d92 100644 --- a/app/lib/pools.ts +++ b/app/lib/pools.ts @@ -6,9 +6,10 @@ import { } from "@sushiswap/tines"; import { parseUnits } from "viem"; +import { formatAmount, formatTokenAmount, formatUSD } from "./currency"; import type { Pool } from "./pools.server"; import { tokenToRToken } from "./tokens"; -import type { AddressString, NumberString, PoolToken } from "~/types"; +import type { AddressString, PoolToken } from "~/types"; export const quote = (amountA: bigint, reserveA: bigint, reserveB: bigint) => reserveA > 0 ? (amountA * reserveB) / reserveA : 0n; @@ -51,8 +52,8 @@ export const createSwapRoute = ( tokenToRToken(token0), tokenToRToken(token1), Number(totalFee ?? 0), - parseUnits(reserve0 as NumberString, token0.decimals), - parseUnits(reserve1 as NumberString, token0.decimals) + parseUnits(reserve0.toString(), token0.decimals), + parseUnits(reserve1.toString(), token0.decimals) ); } ); @@ -80,3 +81,73 @@ export const createSwapRoute = ( return findMultiRouteExactIn(rTokenIn, rTokenOut, amount, rPools, networks); }; + +export const getPoolAPY = (pool: Pool) => { + const volume1w = pool.volume1wUSD + ? pool.volume1wUSD + : pool.isNFTNFT || !pool.token0.isNFT + ? pool.volume1w0 + : pool.volume1w1; + const reserve = pool.reserveUSD + ? pool.reserveUSD + : pool.isNFTNFT || !pool.token0.isNFT + ? Number(pool.token0.reserve) + : Number(pool.token1.reserve); + + if (reserve === 0) { + return 0; + } + + const apr = ((volume1w / 7) * 365 * 0.0025) / reserve; + return ((1 + apr / 100 / 3650) ** 3650 - 1) * 100; +}; + +export const getPoolVolume24hDisplay = (pool: Pool) => { + if (!pool.volume24hUSD) { + if (pool.isNFTNFT || !pool.token0.isNFT) { + return `${formatAmount(pool.volume24h0)} ${pool.token0.symbol}`; + } + + return `${formatAmount(pool.volume24h1)} ${pool.token1.symbol}`; + } + + return formatUSD(pool.volume24hUSD); +}; + +export const getPoolReserveDisplay = (pool: Pool) => { + if (!pool.reserveUSD) { + if (pool.isNFTNFT || !pool.token0.isNFT) { + return `${formatTokenAmount(BigInt(pool.token0.reserve))} ${pool.token0.symbol}`; + } + + return `${formatTokenAmount(BigInt(pool.token1.reserve))} ${pool.token1.symbol}`; + } + + return formatUSD(pool.reserveUSD); +}; + +export const getPoolFeesDisplay = (pool: Pool) => { + const fee = Number(pool.lpFee); + if (!pool.volumeUSD) { + if (pool.isNFTNFT || !pool.token0.isNFT) { + return `${formatAmount(pool.volume0 * fee)} ${pool.token0.symbol}`; + } + + return `${formatAmount(pool.volume0 * fee)}`; + } + + return formatUSD(pool.volumeUSD * fee); +}; + +export const getPoolFees24hDisplay = (pool: Pool) => { + const fee = Number(pool.lpFee); + if (!pool.volume24hUSD) { + if (pool.isNFTNFT || !pool.token0.isNFT) { + return `${formatAmount(pool.volume24h0 * fee)} ${pool.token0.symbol}`; + } + + return `${formatAmount(pool.volume24h1 * fee)}`; + } + + return formatUSD(pool.volume24hUSD * fee); +}; diff --git a/app/routes/pools.tsx b/app/routes/pools.tsx index 02f0fdc..29d4a07 100644 --- a/app/routes/pools.tsx +++ b/app/routes/pools.tsx @@ -13,8 +13,13 @@ import { Tabs } from "~/components/Tabs"; import { PoolImage } from "~/components/pools/PoolImage"; import { Button } from "~/components/ui/Button"; import { useAccount } from "~/contexts/account"; -import { formatUSD } from "~/lib/currency"; import { formatPercent } from "~/lib/number"; +import { + getPoolAPY, + getPoolFeesDisplay, + getPoolReserveDisplay, + getPoolVolume24hDisplay, +} from "~/lib/pools"; import type { Pool } from "~/lib/pools.server"; import { generateTitle, getSocialMetas, getUrl } from "~/lib/seo"; import type { RootLoader } from "~/root"; @@ -101,16 +106,16 @@ const PoolsTable = ({ pools }: { pools: Pool[] }) => { - {formatUSD(pool.volume24h)} + {getPoolVolume24hDisplay(pool)} - {formatPercent(pool.apy)} + {formatPercent(getPoolAPY(pool))} - {formatUSD(pool.reserveUSD)} + {getPoolReserveDisplay(pool)} - {formatUSD(pool.feesUSD)} + {getPoolFeesDisplay(pool)} ))} diff --git a/app/routes/pools_.$id.tsx b/app/routes/pools_.$id.tsx index 013a001..c49b5a2 100644 --- a/app/routes/pools_.$id.tsx +++ b/app/routes/pools_.$id.tsx @@ -64,6 +64,11 @@ import { truncateEthAddress } from "~/lib/address"; import { sumArray } from "~/lib/array"; import { formatAmount, formatTokenAmount, formatUSD } from "~/lib/currency"; import { bigIntToNumber, formatNumber, formatPercent } from "~/lib/number"; +import { + getPoolAPY, + getPoolFees24hDisplay, + getPoolVolume24hDisplay, +} from "~/lib/pools"; import type { Pool } from "~/lib/pools.server"; import { generateTitle, getSocialMetas, getUrl } from "~/lib/seo"; import { cn } from "~/lib/utils"; @@ -388,19 +393,19 @@ export default function PoolDetailsPage() {

Volume (24h)

- {formatUSD(pool.volume24h)} + {getPoolVolume24hDisplay(pool)}

-

APR

+

APY

- {formatPercent(pool.apy)} + {formatPercent(getPoolAPY(pool))}

Fees (24h)

- {formatUSD(pool.fees24h)} + {getPoolFees24hDisplay(pool)}

diff --git a/app/routes/pools_.$id[.]png.tsx b/app/routes/pools_.$id[.]png.tsx index 92e7270..cd9b6b8 100644 --- a/app/routes/pools_.$id[.]png.tsx +++ b/app/routes/pools_.$id[.]png.tsx @@ -3,7 +3,7 @@ import { MagicSwapLogoFull } from "@treasure-project/branding"; import invariant from "tiny-invariant"; import { fetchPool } from "~/api/pools.server"; -import { formatAmount, formatTokenAmount, formatUSD } from "~/lib/currency"; +import { formatAmount, formatTokenAmount } from "~/lib/currency"; import { bigIntToNumber, formatPercent } from "~/lib/number"; import { NIGHT_100, @@ -11,6 +11,7 @@ import { TokenDisplay, generateOgImage, } from "~/lib/og.server"; +import { getPoolAPY, getPoolReserveDisplay } from "~/lib/pools"; const PILL_BG = "rgba(64, 70, 82, 0.6)"; @@ -89,7 +90,7 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => { color: NIGHT_100, }} > - {formatUSD(pool?.reserveUSD || 0)} + {pool ? getPoolReserveDisplay(pool) : 0}
{ color: NIGHT_100, }} > - {formatPercent(pool?.apy || 0)} + {formatPercent(pool ? getPoolAPY(pool) : 0)}