Files
assetx/webapp/components/alp/ALPStatsCards.tsx
default 2ee4553b71 init: 初始化 AssetX 项目仓库
包含 webapp(Next.js 用户端)、webapp-back(Go 后端)、
antdesign(管理后台)、landingpage(营销落地页)、
数据库 SQL 和配置文件。
2026-03-27 11:26:43 +00:00

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