All checks were successful
CI / lint-and-test (push) Successful in 9m52s
Co-authored-by: Cursor <cursoragent@cursor.com>
127 lines
5.0 KiB
TypeScript
127 lines
5.0 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useRef } from "react";
|
|
import { gsap } from "gsap";
|
|
import { CONTRACT_ADDRESSES } from "@/lib/web3/contracts";
|
|
import { CHAIN138_TOKENS, formatTokenAmount } from "@/lib/tokens";
|
|
import { useTokenBalances } from "@/lib/hooks/useTokenBalances";
|
|
import { formatUsd, tokenUsdValue, useUsdPrices } from "@/lib/prices";
|
|
import { formatAddress } from "@/lib/utils";
|
|
import { CHAIN138_ID } from "@/lib/constants/chain138";
|
|
|
|
export function BalanceDisplay() {
|
|
const treasuryAddress = CONTRACT_ADDRESSES.TreasuryWallet as `0x${string}` | undefined;
|
|
const displayRef = useRef<HTMLDivElement>(null);
|
|
|
|
const { ethUsd, isLoading: pricesLoading } = useUsdPrices();
|
|
const {
|
|
nativeBalance,
|
|
cusdtRaw,
|
|
cusdcRaw,
|
|
erc20Tokens,
|
|
isLoading: balancesLoading,
|
|
} = useTokenBalances(treasuryAddress);
|
|
|
|
const loading = balancesLoading || pricesLoading;
|
|
|
|
useEffect(() => {
|
|
if (displayRef.current && nativeBalance) {
|
|
gsap.from(displayRef.current, {
|
|
opacity: 0,
|
|
y: 20,
|
|
duration: 0.8,
|
|
ease: "power3.out",
|
|
});
|
|
}
|
|
}, [nativeBalance]);
|
|
|
|
if (loading && !nativeBalance && cusdtRaw === undefined) {
|
|
return (
|
|
<div className="bg-gray-900 rounded-xl p-8 h-64 flex items-center justify-center">
|
|
<p className="text-gray-400">Loading treasury balance…</p>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (!treasuryAddress) {
|
|
return (
|
|
<div className="bg-gray-900 rounded-xl p-8 h-64 flex items-center justify-center">
|
|
<p className="text-gray-400">Treasury wallet not configured</p>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const ethRaw = nativeBalance?.value;
|
|
const ethUsdVal = tokenUsdValue(ethRaw, 18, "ETH", ethUsd);
|
|
const cusdtUsd = tokenUsdValue(cusdtRaw, erc20Tokens[0].decimals, "cUSDT", ethUsd);
|
|
const cusdcUsd = tokenUsdValue(cusdcRaw, erc20Tokens[1].decimals, "cUSDC", ethUsd);
|
|
const totalUsd =
|
|
[ethUsdVal, cusdtUsd, cusdcUsd].reduce<number | null>((sum, v) => {
|
|
if (v == null) return sum;
|
|
return (sum ?? 0) + v;
|
|
}, null) ?? 0;
|
|
|
|
const formattedNative = nativeBalance?.formatted ?? "0";
|
|
|
|
return (
|
|
<div ref={displayRef} className="relative bg-gray-900 rounded-xl p-8 overflow-hidden border border-gray-800">
|
|
<div className="absolute inset-0 bg-gradient-to-br from-blue-500/10 to-purple-500/10" />
|
|
<div className="absolute inset-0 bg-[radial-gradient(circle_at_50%_50%,rgba(59,130,246,0.1),transparent_50%)]" />
|
|
<div className="relative z-10">
|
|
<div className="flex flex-wrap items-start justify-between gap-3 mb-4">
|
|
<div>
|
|
<h2 className="text-2xl font-semibold bg-gradient-to-r from-blue-400 to-purple-400 bg-clip-text text-transparent">
|
|
Treasury Balance
|
|
</h2>
|
|
<p className="text-xs text-gray-500 mt-1 font-mono">
|
|
Multisig · {formatAddress(treasuryAddress)} · Chain {CHAIN138_ID}
|
|
</p>
|
|
</div>
|
|
<div className="text-right">
|
|
<p className="text-xs text-gray-500">Estimated total</p>
|
|
<p className="text-xl font-bold text-white tabular-nums">{formatUsd(totalUsd)}</p>
|
|
</div>
|
|
</div>
|
|
<div className="flex items-center gap-8">
|
|
<div className="flex-1 space-y-4">
|
|
<div className="flex items-end justify-between gap-4">
|
|
<div>
|
|
<p className="text-5xl font-bold mb-1 bg-gradient-to-r from-white to-gray-300 bg-clip-text text-transparent">
|
|
{formattedNative}
|
|
</p>
|
|
<p className="text-gray-400">{nativeBalance?.symbol ?? "ETH"}</p>
|
|
</div>
|
|
<p className="text-lg text-emerald-400 tabular-nums pb-1">{formatUsd(ethUsdVal)}</p>
|
|
</div>
|
|
{cusdtRaw !== undefined && (
|
|
<div className="flex items-center justify-between gap-4 text-sm">
|
|
<span className="text-gray-400">
|
|
{formatTokenAmount(cusdtRaw.toString(), erc20Tokens[0])}
|
|
</span>
|
|
<span className="text-emerald-400 tabular-nums">{formatUsd(cusdtUsd)}</span>
|
|
</div>
|
|
)}
|
|
{cusdcRaw !== undefined && (
|
|
<div className="flex items-center justify-between gap-4 text-sm">
|
|
<span className="text-gray-400">
|
|
{formatTokenAmount(cusdcRaw.toString(), erc20Tokens[1])}
|
|
</span>
|
|
<span className="text-emerald-400 tabular-nums">{formatUsd(cusdcUsd)}</span>
|
|
</div>
|
|
)}
|
|
</div>
|
|
<div className="w-64 h-64 hidden sm:flex items-center justify-center">
|
|
<div
|
|
className="relative w-40 h-40 rounded-full border-2 border-blue-500/40 shadow-[0_0_40px_rgba(59,130,246,0.25)] animate-[spin_24s_linear_infinite]"
|
|
aria-hidden
|
|
>
|
|
<div className="absolute inset-3 rounded-full border border-purple-500/30" />
|
|
<div className="absolute inset-8 rounded-full bg-gradient-to-br from-blue-500/20 to-purple-500/20" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|