Full build: Vite/React acid-terminal frontend (BEARER design system, Panchang/Sentient/Fragment Mono, neon layer, palette audit script), HoodquidityVaults Foundry contracts (single+dual deposits via Chainlink-style feeds, hybrid exit, 10% platform fee, boost, Genesis LP), Reown AppKit wallet connect with Robinhood Chain testnet (46630), live data from Dexscreener and onchain reads, partner portal, design-sync setup for claude.ai/design. Deploy: static via Forgejo Actions platform, BUILD_DIR=apps/web/dist (.env). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
153 lines
7.3 KiB
TypeScript
153 lines
7.3 KiB
TypeScript
import { Link } from "react-router-dom";
|
|
import { useCampaigns } from "@/hooks/useCampaigns";
|
|
import { useAllMarketData } from "@/hooks/useMarketData";
|
|
import { useChainStats } from "@/hooks/useChainStats";
|
|
import { Skeleton } from "@/components/ui/skeleton";
|
|
import { HealthRadial } from "@/components/HealthRadial";
|
|
import { SlippageCurve } from "@/components/SlippageCurve";
|
|
import { DepthHistory } from "@/components/DepthHistory";
|
|
import { rewardsRemainingUsd } from "@/hooks/useCampaigns";
|
|
import { healthScore } from "@/lib/math";
|
|
import { fmtUsd, fmtNum } from "@/lib/format";
|
|
import { cn } from "@/lib/utils";
|
|
|
|
export default function Markets() {
|
|
const { campaigns, isLoading, deployment } = useCampaigns();
|
|
const { data: pairs } = useAllMarketData(deployment.markets);
|
|
const { data: stats } = useChainStats();
|
|
|
|
return (
|
|
<div className="mx-auto max-w-7xl px-4 py-14 md:px-8">
|
|
<div className="flex flex-col gap-4 md:flex-row md:items-end md:justify-between">
|
|
<div>
|
|
<div className="slabel">Markets</div>
|
|
<h1 className="mt-3 text-4xl md:text-6xl">Market depth dashboard</h1>
|
|
<p className="mt-3 max-w-xl text-fog">
|
|
Live depth, volume and slippage across supported Stock Token
|
|
markets, sourced from public market data.
|
|
</p>
|
|
</div>
|
|
{stats && (
|
|
<div className="num flex shrink-0 items-center gap-2 whitespace-nowrap font-mono text-[10px] uppercase tracking-[0.14em] text-fog-dim">
|
|
<span className="h-1.5 w-1.5 rounded-full bg-mint" />
|
|
L1 {fmtNum(stats.blockNumber)} // Gas {stats.gasGwei.toFixed(2)} gwei
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* quiet terminal: one frame, hairline sections, no boxes */}
|
|
<div className="mt-10 border border-edge bg-card/60">
|
|
{isLoading || !campaigns
|
|
? Array.from({ length: 3 }, (_, i) => <Skeleton key={i} className="m-3 h-44" />)
|
|
: campaigns.map((c, i) => {
|
|
const pair = pairs?.[c.meta.symbol];
|
|
const depth = (pair?.liquidityUsd ?? 0) + c.tvlUsd;
|
|
const score = healthScore({
|
|
liquidityUsd: depth,
|
|
volume24h: pair?.volume24h ?? 0,
|
|
tvlUsd: c.tvlUsd,
|
|
targetDepthUsd: c.targetDepthUsd,
|
|
});
|
|
return (
|
|
<section
|
|
key={c.meta.campaignId}
|
|
className="border-b border-edge last:border-b-0"
|
|
>
|
|
{/* market strip */}
|
|
<div className="flex flex-wrap items-center justify-between gap-3 border-b border-edge/60 px-5 py-3 md:px-7">
|
|
<div className="flex items-baseline gap-3">
|
|
<span className="num font-mono text-[10px] text-fog-dim">
|
|
0{i + 1}
|
|
</span>
|
|
<span className="font-display text-xl uppercase leading-none">
|
|
{c.meta.symbol}{" "}
|
|
<span className="font-mono text-[14px] normal-case tracking-normal text-fog-dim">
|
|
/ hUSD
|
|
</span>
|
|
</span>
|
|
<span className="hidden text-[11px] text-fog-dim sm:inline">
|
|
{c.meta.name}
|
|
</span>
|
|
<span
|
|
className={cn(
|
|
"font-mono text-[9px] uppercase tracking-[0.12em]",
|
|
c.meta.risk === "High" ? "text-danger" : "text-fog-dim",
|
|
)}
|
|
>
|
|
{c.meta.risk === "Medium" ? "Med" : c.meta.risk} risk
|
|
</span>
|
|
</div>
|
|
<Link
|
|
to={`/campaigns/${c.meta.campaignId}`}
|
|
className="font-mono text-[11px] uppercase tracking-[0.1em] text-fog transition-colors hover:text-mint"
|
|
>
|
|
Deepen this market →
|
|
</Link>
|
|
</div>
|
|
|
|
<div className="grid gap-6 px-5 py-5 md:grid-cols-[1fr_1.25fr_auto] md:px-7">
|
|
{pair ? (
|
|
<div className="space-y-2 text-sm">
|
|
<div className="leader">
|
|
<span className="text-fog">Price</span>
|
|
<span className="num font-semibold">${pair.priceUsd.toFixed(2)}</span>
|
|
</div>
|
|
<div className="leader">
|
|
<span className="text-fog">24h change</span>
|
|
<span className={cn("num font-semibold", pair.change24h >= 0 ? "text-mint" : "text-danger")}>
|
|
{pair.change24h >= 0 ? "+" : ""}
|
|
{pair.change24h.toFixed(2)}%
|
|
</span>
|
|
</div>
|
|
<div className="leader">
|
|
<span className="text-fog">24h volume</span>
|
|
<span className="num font-semibold">{fmtUsd(pair.volume24h)}</span>
|
|
</div>
|
|
<div className="leader">
|
|
<span className="text-fog">Total depth</span>
|
|
<span className="num font-semibold text-mint">{fmtUsd(depth)}</span>
|
|
</div>
|
|
<div className="leader">
|
|
<span className="text-fog">Active LPs</span>
|
|
<span className="num font-semibold">{c.activeLPs}</span>
|
|
</div>
|
|
<div className="leader">
|
|
<span className="text-fog">Rewards remaining</span>
|
|
<span className="num font-semibold text-lime">
|
|
{fmtUsd(rewardsRemainingUsd(c, Math.floor(Date.now() / 1000)))}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
) : (
|
|
<Skeleton className="h-32 w-full" />
|
|
)}
|
|
|
|
<div className="space-y-3 md:border-l md:border-edge md:pl-6">
|
|
<div className="fig">Fig. 01 // Slippage by trade size</div>
|
|
<SlippageCurve liquidityUsd={depth} />
|
|
<div className="pt-1.5">
|
|
<div className="fig">Fig. 02 // Depth 30d // Estimated</div>
|
|
<DepthHistory currentUsd={depth} seedKey={c.meta.symbol} />
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex flex-row items-center gap-4 md:flex-col md:items-end md:justify-start md:border-l md:border-edge md:pl-6">
|
|
<HealthRadial score={score} />
|
|
<span className="fig md:text-right">
|
|
Fig. 03
|
|
<br className="hidden md:block" /> Health
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
);
|
|
})}
|
|
</div>
|
|
|
|
<p className="mt-4 font-mono text-[10px] leading-relaxed tracking-[0.05em] text-fog-dim">
|
|
Depth history is estimated. Slippage figures derive from live pool
|
|
liquidity and are not execution quotes
|
|
</p>
|
|
</div>
|
|
);
|
|
}
|