106 lines
3.3 KiB
TypeScript
106 lines
3.3 KiB
TypeScript
|
|
"use client";
|
||
|
|
|
||
|
|
import { useReadContracts } from 'wagmi';
|
||
|
|
import { useAccount } from 'wagmi';
|
||
|
|
import { formatUnits } from 'viem';
|
||
|
|
import { useQuery } from '@tanstack/react-query';
|
||
|
|
import { useApp } from "@/contexts/AppContext";
|
||
|
|
import { getContractAddress, abis } from '@/lib/contracts';
|
||
|
|
|
||
|
|
function formatUSD(v: number): string {
|
||
|
|
if (v >= 1_000_000) return `$${(v / 1_000_000).toFixed(2)}M`;
|
||
|
|
if (v >= 1_000) return `$${(v / 1_000).toFixed(2)}K`;
|
||
|
|
return `$${v.toFixed(2)}`;
|
||
|
|
}
|
||
|
|
|
||
|
|
export default function ALPStatsCards() {
|
||
|
|
const { t } = useApp();
|
||
|
|
const { chainId } = useAccount();
|
||
|
|
|
||
|
|
const poolManagerAddress = chainId ? getContractAddress('YTPoolManager', chainId) : undefined;
|
||
|
|
const lpTokenAddress = chainId ? getContractAddress('YTLPToken', chainId) : undefined;
|
||
|
|
|
||
|
|
const { data: tvlData, isLoading } = useReadContracts({
|
||
|
|
contracts: [
|
||
|
|
{
|
||
|
|
address: poolManagerAddress,
|
||
|
|
abi: abis.YTPoolManager as any,
|
||
|
|
functionName: 'getAumInUsdy',
|
||
|
|
args: [true],
|
||
|
|
chainId: chainId,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
address: lpTokenAddress,
|
||
|
|
abi: abis.YTLPToken as any,
|
||
|
|
functionName: 'totalSupply',
|
||
|
|
args: [],
|
||
|
|
chainId: chainId,
|
||
|
|
},
|
||
|
|
],
|
||
|
|
query: { refetchInterval: 30000, enabled: !!poolManagerAddress && !!lpTokenAddress && !!chainId },
|
||
|
|
});
|
||
|
|
|
||
|
|
const aumRaw = tvlData?.[0]?.result as bigint | undefined;
|
||
|
|
const supplyRaw = tvlData?.[1]?.result as bigint | undefined;
|
||
|
|
const tvlValue = aumRaw ? formatUSD(parseFloat(formatUnits(aumRaw, 18))) : (isLoading ? '...' : '--');
|
||
|
|
const alpPrice = aumRaw && supplyRaw && supplyRaw > 0n
|
||
|
|
? `$${(parseFloat(formatUnits(aumRaw, 18)) / parseFloat(formatUnits(supplyRaw, 18))).toFixed(4)}`
|
||
|
|
: (isLoading ? '...' : '--');
|
||
|
|
|
||
|
|
// Fetch APR from backend (requires historical snapshots)
|
||
|
|
const { data: aprData } = useQuery({
|
||
|
|
queryKey: ['alp-stats'],
|
||
|
|
queryFn: async () => {
|
||
|
|
const res = await fetch('/api/alp/stats');
|
||
|
|
const json = await res.json();
|
||
|
|
return json.data as { poolAPR: number; rewardAPR: number };
|
||
|
|
},
|
||
|
|
refetchInterval: 5 * 60 * 1000, // refresh every 5 min
|
||
|
|
});
|
||
|
|
|
||
|
|
const poolAPR = aprData?.poolAPR != null
|
||
|
|
? `${aprData.poolAPR.toFixed(4)}%`
|
||
|
|
: '--';
|
||
|
|
|
||
|
|
const stats = [
|
||
|
|
{
|
||
|
|
label: t("stats.totalValueLocked"),
|
||
|
|
value: tvlValue,
|
||
|
|
isGreen: false,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
label: t("alp.price"),
|
||
|
|
value: alpPrice,
|
||
|
|
isGreen: false,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
label: t("alp.poolAPR"),
|
||
|
|
value: poolAPR,
|
||
|
|
isGreen: aprData?.poolAPR != null && aprData.poolAPR > 0,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
label: t("alp.rewardAPR"),
|
||
|
|
value: "--",
|
||
|
|
isGreen: true,
|
||
|
|
},
|
||
|
|
];
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-3 md:gap-4">
|
||
|
|
{stats.map((stat, index) => (
|
||
|
|
<div
|
||
|
|
key={index}
|
||
|
|
className="bg-bg-subtle dark:bg-gray-800 rounded-2xl border border-border-gray dark:border-gray-700 px-4 md:px-6 py-4 flex flex-col gap-2"
|
||
|
|
>
|
||
|
|
<div className="text-caption-tiny font-bold text-text-tertiary dark:text-gray-400">
|
||
|
|
{stat.label}
|
||
|
|
</div>
|
||
|
|
<div className={`text-xl md:text-[32px] font-bold leading-[130%] tracking-[-0.01em] ${stat.isGreen ? 'text-[#10b981]' : 'text-text-primary dark:text-white'}`}>
|
||
|
|
{stat.value}
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
))}
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|