hoodquidity/apps/web/src/screens/Markets.tsx
Денис Зефиров dba2811447 Hoodquidity: liquidity campaigns for Robinhood Chain stock token markets
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>
2026-07-11 08:32:28 +04:00

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