feat: mainnet wallet, cyberpunk backdrop layer, real market data, hoodquidity.com
All checks were successful
deploy / deploy (push) Successful in 1m8s
All checks were successful
deploy / deploy (push) Successful in 1m8s
- Switch wallet connect default to Robinhood Chain mainnet (4663); keep testnet
and anvil selectable. Header chip and hero folio now read Mainnet.
- New CyberGrid fx layer: survey lines, cross marks, corner locators, vertical
kanji + scramble glyphs across Markets/Campaigns/Positions/Partners/AppBoard,
echoing the hero's cyberpunk instrumentation.
- New DepthCanyon: 2D isometric voxel crater hero, single contained core glow
(no bleed onto copy).
- Real per-token market data via Dexscreener /tokens/{mint} on canonical xStock
mints; realistic prices/volume/liquidity.
- Remove VAULT address line from footer.
- Deploy target -> hoodquidity.com.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
fc5f189a7a
commit
24fb890971
18 changed files with 308 additions and 59 deletions
2
.env
2
.env
|
|
@ -1,4 +1,4 @@
|
|||
DOMAIN=hoodquidity.154.83.149.72.nip.io
|
||||
DOMAIN=hoodquidity.com
|
||||
DEPLOY=static
|
||||
BUILD_DIR=apps/web/dist
|
||||
WWW=false
|
||||
|
|
|
|||
143
apps/web/src/components/backdrop/DepthCanyon.tsx
Normal file
143
apps/web/src/components/backdrop/DepthCanyon.tsx
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
/**
|
||||
* DepthCanyon — a 2D isometric voxel crater. A blocky terrain of dark cubes
|
||||
* carved by a glowing acid pit: market depth as a literal luminous hole in the
|
||||
* ground. Deterministic heightmap, painter-ordered cubes, palette-only colors
|
||||
* (dark faces from the neutral ramp, acid overlays for the emissive throat).
|
||||
* One breathing core glow; everything else is static SVG.
|
||||
*/
|
||||
const VW = 1000;
|
||||
const VH = 780;
|
||||
const OX = 500;
|
||||
const OY = 224;
|
||||
const HW = 20; // iso tile half width
|
||||
const HH = 10; // iso tile half height (2:1)
|
||||
const CUBE_V = 14; // screen px per height unit
|
||||
const SK = 66; // uniform block skirt → voxel bodies, no pillars
|
||||
const R = 13; // grid radius in cells
|
||||
const PIT_R = 7; // pit radius in cells
|
||||
|
||||
const DARK_TOP = "#2a2a2a";
|
||||
const DARK_L = "#080808";
|
||||
const DARK_R = "#161616";
|
||||
|
||||
function hash(a: number, b: number) {
|
||||
const x = Math.sin(a * 12.9898 + b * 78.233) * 43758.5453;
|
||||
return x - Math.floor(x);
|
||||
}
|
||||
|
||||
interface Cell {
|
||||
z: number; // painter depth
|
||||
x: number;
|
||||
y: number;
|
||||
glow: number; // 0 dark .. 1 molten
|
||||
}
|
||||
|
||||
function build(): Cell[] {
|
||||
const cells: Cell[] = [];
|
||||
for (let gx = -R; gx <= R; gx++) {
|
||||
for (let gy = -R; gy <= R; gy++) {
|
||||
const d = Math.hypot(gx, gy);
|
||||
if (d > R + 0.5) continue;
|
||||
|
||||
let h: number;
|
||||
let glow = 0;
|
||||
if (d < PIT_R) {
|
||||
const ring = PIT_R - d;
|
||||
h = -ring * 1.3; // funnel steps down, steeper
|
||||
glow = Math.pow(Math.min(ring / PIT_R, 1), 0.7);
|
||||
} else {
|
||||
const rd = d - PIT_R;
|
||||
const rim = Math.max(0, 1 - rd / 2);
|
||||
// asymmetric crater: back rim tall, front lip low and open
|
||||
const backness = Math.min(Math.max((-(gx + gy) / R) * 0.5 + 0.5, 0), 1);
|
||||
const ridge = rim * 2.4 * (0.3 + 0.8 * backness);
|
||||
const fall = Math.min(Math.max(1 - rd / 8, 0), 1);
|
||||
const noise = Math.round((hash(gx, gy) + hash(gy, gx)) * 1.1) * fall;
|
||||
h = ridge + noise;
|
||||
}
|
||||
|
||||
const x = OX + (gx - gy) * HW;
|
||||
const y = OY + (gx + gy) * HH - h * CUBE_V;
|
||||
cells.push({ z: (gx + gy) * 1000 + gx, x, y, glow });
|
||||
}
|
||||
}
|
||||
cells.sort((a, b) => a.z - b.z);
|
||||
return cells;
|
||||
}
|
||||
|
||||
const CELLS = build();
|
||||
const CORE_X = OX;
|
||||
const CORE_Y = OY + PIT_R * CUBE_V + 34;
|
||||
|
||||
function Cube({ x, y, glow }: Cell) {
|
||||
const top = `${x},${y - HH} ${x + HW},${y} ${x},${y + HH} ${x - HW},${y}`;
|
||||
const left = `${x - HW},${y} ${x},${y + HH} ${x},${y + HH + SK} ${x - HW},${y + SK}`;
|
||||
const right = `${x},${y + HH} ${x + HW},${y} ${x + HW},${y + SK} ${x},${y + HH + SK}`;
|
||||
const hot = glow > 0.75;
|
||||
return (
|
||||
<>
|
||||
<polygon points={left} fill={DARK_L} />
|
||||
<polygon points={right} fill={DARK_R} />
|
||||
<polygon points={top} fill={DARK_TOP} />
|
||||
{glow > 0.02 && (
|
||||
<>
|
||||
<polygon points={left} fill="#c8ff00" fillOpacity={glow * 0.4} />
|
||||
<polygon points={right} fill="#c8ff00" fillOpacity={glow * 0.62} />
|
||||
<polygon
|
||||
points={top}
|
||||
fill={hot ? "#e9ffb3" : "#c8ff00"}
|
||||
fillOpacity={hot ? 1 : 0.35 + glow * 0.6}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function DepthCanyon({ className }: { className?: string }) {
|
||||
return (
|
||||
<svg
|
||||
viewBox={`0 0 ${VW} ${VH}`}
|
||||
className={className}
|
||||
preserveAspectRatio="xMidYMid slice"
|
||||
aria-hidden
|
||||
>
|
||||
<defs>
|
||||
<radialGradient id="vx-core" cx="50%" cy="50%" r="50%">
|
||||
<stop offset="0%" stopColor="#e9ffb3" stopOpacity="0.9" />
|
||||
<stop offset="30%" stopColor="#c8ff00" stopOpacity="0.6" />
|
||||
<stop offset="70%" stopColor="#c8ff00" stopOpacity="0.12" />
|
||||
<stop offset="100%" stopColor="#c8ff00" stopOpacity="0" />
|
||||
</radialGradient>
|
||||
<radialGradient id="vx-fade" cx="52%" cy="44%" r="52%">
|
||||
<stop offset="0%" stopColor="#f2f2f2" stopOpacity="1" />
|
||||
<stop offset="62%" stopColor="#f2f2f2" stopOpacity="1" />
|
||||
<stop offset="100%" stopColor="#f2f2f2" stopOpacity="0" />
|
||||
</radialGradient>
|
||||
<mask id="vx-mask">
|
||||
<rect width={VW} height={VH} fill="url(#vx-fade)" />
|
||||
</mask>
|
||||
</defs>
|
||||
|
||||
<g mask="url(#vx-mask)">
|
||||
{CELLS.map((c, i) => (
|
||||
<Cube key={i} {...c} />
|
||||
))}
|
||||
|
||||
{/* tight molten glow contained in the throat, no soft bleed */}
|
||||
<ellipse cx={CORE_X} cy={CORE_Y} rx="120" ry="66" fill="url(#vx-core)">
|
||||
<animate
|
||||
attributeName="rx"
|
||||
values="120;134;120"
|
||||
dur="7s"
|
||||
repeatCount="indefinite"
|
||||
calcMode="spline"
|
||||
keySplines="0.4 0 0.2 1;0.4 0 0.2 1"
|
||||
keyTimes="0;0.5;1"
|
||||
/>
|
||||
<animate attributeName="opacity" values="0.8;0.95;0.8" dur="7s" repeatCount="indefinite" />
|
||||
</ellipse>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
54
apps/web/src/components/fx/CyberGrid.tsx
Normal file
54
apps/web/src/components/fx/CyberGrid.tsx
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
import { Scramble } from "./Scramble";
|
||||
|
||||
/**
|
||||
* Site-wide cyber backdrop: survey column lines, cross marks, barcode ticks and
|
||||
* decoding mono/kanji labels. Fixed behind all content, palette-clean, static
|
||||
* except the two decoding labels. Drop one per non-hero screen.
|
||||
*/
|
||||
export function CyberGrid() {
|
||||
return (
|
||||
<div
|
||||
aria-hidden
|
||||
className="pointer-events-none fixed inset-0 -z-10 overflow-hidden"
|
||||
>
|
||||
{/* survey column lines with cross marks */}
|
||||
<div className="relative mx-auto h-full max-w-7xl px-4 md:px-8">
|
||||
{[25, 50, 75].map((p) => (
|
||||
<div key={p}>
|
||||
<div
|
||||
className="absolute inset-y-0 hidden w-px bg-edge/40 lg:block"
|
||||
style={{ left: `${p}%` }}
|
||||
/>
|
||||
{[16, 48, 84].map((y) => (
|
||||
<span
|
||||
key={y}
|
||||
className="absolute hidden -translate-x-1/2 -translate-y-1/2 font-mono text-[11px] text-fog-dim/40 lg:block"
|
||||
style={{ left: `${p}%`, top: `${y}%` }}
|
||||
>
|
||||
+
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* corner locators */}
|
||||
<span className="absolute left-4 top-28 hidden font-mono text-[9px] uppercase leading-[1.6] tracking-[0.24em] text-fog-dim/45 md:block">
|
||||
<Scramble text="Desk 00 // Surface data" interval={13000} />
|
||||
<br />
|
||||
▚▞▚▚ ▞▚▞▚
|
||||
</span>
|
||||
|
||||
<span
|
||||
className="absolute right-4 top-1/3 hidden font-mono text-[9px] uppercase tracking-[0.3em] text-fog-dim/45 xl:block"
|
||||
style={{ writingMode: "vertical-rl" }}
|
||||
>
|
||||
流動性デスク // RH-L2 4663
|
||||
</span>
|
||||
|
||||
<span className="absolute bottom-8 left-4 hidden font-mono text-[9px] uppercase tracking-[0.22em] text-fog-dim/40 md:block">
|
||||
<Scramble text="Accuracy not guaranteed" interval={15000} /> // 市場深度
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -2,7 +2,6 @@ import { Link } from "react-router-dom";
|
|||
import { LogoMark, Wordmark } from "@/components/brand/Logo";
|
||||
import { XIcon } from "@/components/icons/XIcon";
|
||||
import { TWITTER_URL } from "@/lib/constants";
|
||||
import { getActiveDeployment, ACTIVE_CHAIN_ID } from "@/lib/contracts";
|
||||
|
||||
export function Footer() {
|
||||
return (
|
||||
|
|
@ -67,13 +66,10 @@ export function Footer() {
|
|||
</span>
|
||||
</div>
|
||||
|
||||
<div className="mt-4 flex flex-col gap-1.5 text-[10px] text-fog-dim md:flex-row md:items-center md:justify-between">
|
||||
<span className="num break-all">
|
||||
VAULT <span className="text-fog-dim">//</span>{" "}
|
||||
<span className="text-fog">{getActiveDeployment().vaults}</span>{" "}
|
||||
<span className="text-fog-dim">// CHAIN {ACTIVE_CHAIN_ID}</span>
|
||||
<div className="mt-4 flex justify-end text-[10px] text-fog-dim">
|
||||
<span className="num">
|
||||
RH-L2 4663 <span className="text-fog-dim">//</span> BUILD E-{new Date().getFullYear()}.07 / DESK 00
|
||||
</span>
|
||||
<span className="num shrink-0">BUILD E-{new Date().getFullYear()}.07 / DESK 00</span>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
|
|
|||
|
|
@ -27,12 +27,12 @@ function ConnectControl() {
|
|||
onClick={() => open({ view: "Networks" })}
|
||||
className="num cursor-pointer border border-edge px-2.5 py-2 font-mono text-[10px] uppercase tracking-[0.08em] text-fog transition-colors hover:border-mint hover:text-mint"
|
||||
>
|
||||
{chainId === 46_630
|
||||
? "RH Testnet"
|
||||
: chainId === 31_337
|
||||
? "Anvil"
|
||||
: chainId === 421_614
|
||||
? "Arb Sepolia"
|
||||
{chainId === 4663
|
||||
? "RH Chain"
|
||||
: chainId === 46_630
|
||||
? "RH Testnet"
|
||||
: chainId === 31_337
|
||||
? "Anvil"
|
||||
: `Chain ${chainId ?? "?"}`}
|
||||
</button>
|
||||
<Button size="md" variant="outline" onClick={() => open()}>
|
||||
|
|
|
|||
|
|
@ -1,18 +1,21 @@
|
|||
import { useQuery } from "@tanstack/react-query";
|
||||
import { fetchPair, fetchBasket, type PairData } from "@/lib/dexscreener";
|
||||
import { fetchPairByAddress, fetchBasket, type PairData } from "@/lib/dexscreener";
|
||||
import type { MarketMeta } from "@/lib/contracts";
|
||||
|
||||
function marketKey(meta: MarketMeta): string {
|
||||
return meta.dexToken ?? meta.dexBasket?.join() ?? meta.symbol;
|
||||
}
|
||||
|
||||
async function fetchMarket(meta: MarketMeta): Promise<PairData | null> {
|
||||
if (meta.dexscreenerQuery.startsWith("basket:")) {
|
||||
return fetchBasket(meta.dexscreenerQuery.slice("basket:".length).split(","));
|
||||
}
|
||||
return fetchPair(meta.dexscreenerQuery);
|
||||
if (meta.dexBasket) return fetchBasket(meta.dexBasket);
|
||||
if (meta.dexToken) return fetchPairByAddress(meta.dexToken);
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Live price / volume / liquidity for one market, refreshed every 30s. */
|
||||
export function useMarketData(meta: MarketMeta) {
|
||||
return useQuery({
|
||||
queryKey: ["dexscreener", meta.dexscreenerQuery],
|
||||
queryKey: ["dexscreener", marketKey(meta)],
|
||||
queryFn: () => fetchMarket(meta),
|
||||
refetchInterval: 30_000,
|
||||
staleTime: 25_000,
|
||||
|
|
@ -21,7 +24,7 @@ export function useMarketData(meta: MarketMeta) {
|
|||
|
||||
export function useAllMarketData(markets: MarketMeta[]) {
|
||||
return useQuery({
|
||||
queryKey: ["dexscreener-all", markets.map((m) => m.dexscreenerQuery).join()],
|
||||
queryKey: ["dexscreener-all", markets.map(marketKey).join()],
|
||||
queryFn: async () => {
|
||||
const results = await Promise.all(
|
||||
markets.map((m) => fetchMarket(m).catch(() => null)),
|
||||
|
|
|
|||
|
|
@ -2,8 +2,26 @@ import { defineChain } from "viem";
|
|||
import { arbitrumSepolia } from "viem/chains";
|
||||
|
||||
/**
|
||||
* Robinhood Chain testnet (Arbitrum Orbit L2). Verified live:
|
||||
* eth_chainId on the RPC returns 0xb626 = 46630.
|
||||
* Robinhood Chain mainnet (Arbitrum Orbit L2). Verified live:
|
||||
* eth_chainId on the RPC returns 0x1237 = 4663.
|
||||
*/
|
||||
export const robinhoodMainnet = defineChain({
|
||||
id: 4663,
|
||||
name: "Robinhood Chain",
|
||||
nativeCurrency: { name: "Ether", symbol: "ETH", decimals: 18 },
|
||||
rpcUrls: {
|
||||
default: { http: ["https://rpc.mainnet.chain.robinhood.com/rpc"] },
|
||||
},
|
||||
blockExplorers: {
|
||||
default: {
|
||||
name: "Blockscout",
|
||||
url: "https://robinhoodchain.blockscout.com",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Robinhood Chain testnet. Verified live: eth_chainId returns 0xb626 = 46630.
|
||||
* Faucet: https://faucet.testnet.chain.robinhood.com
|
||||
*/
|
||||
export const robinhoodTestnet = defineChain({
|
||||
|
|
|
|||
|
|
@ -7,11 +7,24 @@ export interface MarketMeta {
|
|||
stockToken: Address;
|
||||
/** Chainlink-style feed pricing the stock token, 8 decimals */
|
||||
priceFeed: Address;
|
||||
/** Real reference pair on Dexscreener used for live price/volume */
|
||||
dexscreenerQuery: string;
|
||||
/** Backed xStock Solana mint — live price/volume/liquidity from Dexscreener */
|
||||
dexToken?: string;
|
||||
/** Basket of xStock mints, equal-weight synthetic market */
|
||||
dexBasket?: string[];
|
||||
risk: "Low" | "Medium" | "High";
|
||||
}
|
||||
|
||||
/** Canonical Backed xStock Solana mints (real Dexscreener pairs) */
|
||||
const XSTOCK = {
|
||||
NVDA: "Xsc9qvGR1efVDFGLrVsmkzv3qi45LTBjeUKSPmx9qEh",
|
||||
TSLA: "XsDoVfqeBukxuZHWhdvWHBhgEHjGNst4MLodqsJHzoB",
|
||||
AAPL: "XsbEhLAtcf6HdfpFZ5xEMdqW8nfAvcsP5bdudRLJzJp",
|
||||
SPY: "XsoCS1TfEyfFhfvj8EtZ528L3CaKBDBRqRapnBbDF2W",
|
||||
META: "Xsa62P5mvPszXL1krVUnU5ar38bBSVcWAB6fmPCo5Zu",
|
||||
GOOGL: "XsCPL9dNWBMvFtTmwcCA5v3xWPSMEBCszbQdiLLq6aN",
|
||||
AMZN: "Xs3eBt7uRfJX8QUs4suhyU8p2M6DoUDrJyWBa8LLZsg",
|
||||
} as const;
|
||||
|
||||
interface Deployment {
|
||||
vaults: Address;
|
||||
usd: Address;
|
||||
|
|
@ -29,7 +42,7 @@ const local: Deployment = {
|
|||
campaignId: 0,
|
||||
stockToken: "0x59b670e9fA9D0A427751Af201D676719a970857b",
|
||||
priceFeed: "0x4ed7c70F96B99c776995fB64377f0d4aB3B0e1C1",
|
||||
dexscreenerQuery: "NVDAx",
|
||||
dexToken: XSTOCK.NVDA,
|
||||
risk: "Medium",
|
||||
},
|
||||
{
|
||||
|
|
@ -38,7 +51,7 @@ const local: Deployment = {
|
|||
campaignId: 1,
|
||||
stockToken: "0xa85233C63b9Ee964Add6F2cffe00Fd84eb32338f",
|
||||
priceFeed: "0x4A679253410272dd5232B3Ff7cF5dbB88f295319",
|
||||
dexscreenerQuery: "TSLAx",
|
||||
dexToken: XSTOCK.TSLA,
|
||||
risk: "Medium",
|
||||
},
|
||||
{
|
||||
|
|
@ -47,7 +60,7 @@ const local: Deployment = {
|
|||
campaignId: 2,
|
||||
stockToken: "0x09635F643e140090A9A8Dcd712eD6285858ceBef",
|
||||
priceFeed: "0xc5a5C42992dECbae36851359345FE25997F5C42d",
|
||||
dexscreenerQuery: "AAPLx",
|
||||
dexToken: XSTOCK.AAPL,
|
||||
risk: "Low",
|
||||
},
|
||||
{
|
||||
|
|
@ -56,7 +69,7 @@ const local: Deployment = {
|
|||
campaignId: 3,
|
||||
stockToken: "0xE6E340D132b5f46d1e472DebcD681B2aBc16e57E",
|
||||
priceFeed: "0xc3e53F4d16Ae77Db1c982e75a937B9f60FE63690",
|
||||
dexscreenerQuery: "SPYx",
|
||||
dexToken: XSTOCK.SPY,
|
||||
risk: "Low",
|
||||
},
|
||||
{
|
||||
|
|
@ -65,7 +78,7 @@ const local: Deployment = {
|
|||
campaignId: 4,
|
||||
stockToken: "0x9E545E3C0baAB3E08CdfD552C960A1050f373042",
|
||||
priceFeed: "0xa82fF9aFd8f496c3d6ac40E2a0F282E47488CFc9",
|
||||
dexscreenerQuery: "basket:NVDAx,AMDx,PLTRx",
|
||||
dexBasket: [XSTOCK.NVDA, XSTOCK.META, XSTOCK.GOOGL, XSTOCK.AMZN],
|
||||
risk: "High",
|
||||
},
|
||||
],
|
||||
|
|
|
|||
|
|
@ -14,31 +14,37 @@ interface RawPair {
|
|||
chainId: string;
|
||||
dexId: string;
|
||||
priceUsd?: string;
|
||||
baseToken: { symbol: string };
|
||||
baseToken: { symbol: string; address: string };
|
||||
quoteToken: { symbol: string };
|
||||
volume?: { h24?: number };
|
||||
liquidity?: { usd?: number };
|
||||
priceChange?: { h24?: number };
|
||||
}
|
||||
|
||||
const STABLES = new Set(["USDC", "USDT", "USDG", "PYUSD"]);
|
||||
|
||||
/**
|
||||
* Live market data for a tokenized stock, sourced from the deepest
|
||||
* stable-quoted pair on Dexscreener.
|
||||
* Live market data for a tokenized stock, fetched by its exact token mint so
|
||||
* scam look-alikes never collide. Picks the deepest stable-quoted pair where
|
||||
* the token is the base asset.
|
||||
*/
|
||||
export async function fetchPair(query: string): Promise<PairData | null> {
|
||||
const res = await fetch(
|
||||
`${DEXSCREENER_API}/latest/dex/search?q=${encodeURIComponent(query)}`,
|
||||
);
|
||||
export async function fetchPairByAddress(address: string): Promise<PairData | null> {
|
||||
const res = await fetch(`${DEXSCREENER_API}/latest/dex/tokens/${address}`);
|
||||
if (!res.ok) throw new Error(`dexscreener ${res.status}`);
|
||||
const data = (await res.json()) as { pairs?: RawPair[] };
|
||||
const pairs = (data.pairs ?? []).filter(
|
||||
(p) =>
|
||||
p.baseToken.symbol.toUpperCase() === query.toUpperCase() &&
|
||||
p.priceUsd != null,
|
||||
);
|
||||
if (pairs.length === 0) return null;
|
||||
const all = data.pairs ?? [];
|
||||
|
||||
const best = pairs.reduce((a, b) =>
|
||||
const stable = all.filter(
|
||||
(p) =>
|
||||
p.priceUsd != null &&
|
||||
p.baseToken.address === address &&
|
||||
STABLES.has(p.quoteToken.symbol.toUpperCase()),
|
||||
);
|
||||
const pool = stable.length > 0 ? stable : all.filter((p) => p.priceUsd != null);
|
||||
if (pool.length === 0) return null;
|
||||
|
||||
// deepest liquidity among the real pairs of this exact token
|
||||
const best = pool.reduce((a, b) =>
|
||||
(a.liquidity?.usd ?? 0) >= (b.liquidity?.usd ?? 0) ? a : b,
|
||||
);
|
||||
return {
|
||||
|
|
@ -52,11 +58,11 @@ export async function fetchPair(query: string): Promise<PairData | null> {
|
|||
};
|
||||
}
|
||||
|
||||
/** Equal-weight synthetic basket from several underlying tokens. */
|
||||
export async function fetchBasket(queries: string[]): Promise<PairData | null> {
|
||||
const parts = (await Promise.all(queries.map((q) => fetchPair(q).catch(() => null)))).filter(
|
||||
(p): p is PairData => p !== null,
|
||||
);
|
||||
/** Equal-weight synthetic basket from several token mints. */
|
||||
export async function fetchBasket(addresses: string[]): Promise<PairData | null> {
|
||||
const parts = (
|
||||
await Promise.all(addresses.map((a) => fetchPairByAddress(a).catch(() => null)))
|
||||
).filter((p): p is PairData => p !== null);
|
||||
if (parts.length === 0) return null;
|
||||
const n = parts.length;
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -2,15 +2,15 @@ import { createAppKit } from "@reown/appkit/react";
|
|||
import { WagmiAdapter } from "@reown/appkit-adapter-wagmi";
|
||||
import type { AppKitNetwork } from "@reown/appkit/networks";
|
||||
import { anvil } from "viem/chains";
|
||||
import { deployChain, robinhoodTestnet } from "./chain";
|
||||
import { robinhoodMainnet, robinhoodTestnet } from "./chain";
|
||||
|
||||
// Reown Cloud project id: replace with the real one before release
|
||||
const projectId = "b56e18d47c72ab683b10814fe9495694";
|
||||
|
||||
const networks = (
|
||||
import.meta.env.DEV
|
||||
? [anvil, robinhoodTestnet, deployChain]
|
||||
: [robinhoodTestnet, deployChain]
|
||||
? [anvil, robinhoodMainnet, robinhoodTestnet]
|
||||
: [robinhoodMainnet, robinhoodTestnet]
|
||||
) as unknown as [AppKitNetwork, ...AppKitNetwork[]];
|
||||
|
||||
const wagmiAdapter = new WagmiAdapter({
|
||||
|
|
@ -24,7 +24,7 @@ createAppKit({
|
|||
networks,
|
||||
defaultNetwork: (import.meta.env.DEV
|
||||
? anvil
|
||||
: robinhoodTestnet) as unknown as AppKitNetwork,
|
||||
: robinhoodMainnet) as unknown as AppKitNetwork,
|
||||
projectId,
|
||||
metadata: {
|
||||
name: "Hoodquidity",
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import { Stat } from "@/components/ui/stat";
|
|||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { DepthProgress } from "@/components/DepthProgress";
|
||||
import { SlippageCurve } from "@/components/SlippageCurve";
|
||||
import { CyberGrid } from "@/components/fx/CyberGrid";
|
||||
|
||||
const RISKS = ["All", "Low", "Medium", "High"] as const;
|
||||
const CHIP_AMOUNTS = [1_000, 5_000, 10_000];
|
||||
|
|
@ -195,6 +196,7 @@ export default function AppBoard() {
|
|||
|
||||
return (
|
||||
<div className="relative">
|
||||
<CyberGrid />
|
||||
<div className="relative mx-auto max-w-7xl px-4 pb-24 pt-10 md:px-8">
|
||||
{/* desk locator */}
|
||||
<div className="mb-6 flex items-center justify-between font-mono text-[10px] uppercase tracking-[0.22em] text-fog-dim">
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@ export default function CampaignDetail() {
|
|||
campaignId: 0,
|
||||
stockToken: "0x0000000000000000000000000000000000000000",
|
||||
priceFeed: "0x0000000000000000000000000000000000000000",
|
||||
dexscreenerQuery: "NVDAx",
|
||||
risk: "Low",
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import { DepthProgress } from "@/components/DepthProgress";
|
|||
import { feeApr, rewardApr } from "@/lib/math";
|
||||
import { fmtUsd, fmtAprPct, fmtCountdown } from "@/lib/format";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { CyberGrid } from "@/components/fx/CyberGrid";
|
||||
|
||||
function statusLabel(c: CampaignState): { label: string; tone: "mint" | "lime" | "fog" } | null {
|
||||
if (c.status === "boost") return { label: "Boost 1.5x", tone: "lime" };
|
||||
|
|
@ -26,6 +27,7 @@ export default function Campaigns() {
|
|||
|
||||
return (
|
||||
<div className="mx-auto max-w-7xl px-4 py-14 md:px-8">
|
||||
<CyberGrid />
|
||||
<div className="flex flex-col gap-6 md:flex-row md:items-end md:justify-between">
|
||||
<div>
|
||||
<div className="slabel">Campaigns</div>
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { Link } from "react-router-dom";
|
|||
import { motion } from "motion/react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { DepthField } from "@/components/backdrop/DepthField";
|
||||
import { DepthCanyon } from "@/components/backdrop/DepthCanyon";
|
||||
import { DepthChart } from "@/components/DepthChart";
|
||||
import { DepthProgress } from "@/components/DepthProgress";
|
||||
import { CountUp } from "@/components/CountUp";
|
||||
|
|
@ -108,15 +109,18 @@ export default function Landing() {
|
|||
return (
|
||||
<div className="relative overflow-hidden">
|
||||
{/* ---------------------------------- hero --------------------------------- */}
|
||||
<section className="relative border-b border-edge">
|
||||
<section className="relative overflow-hidden border-b border-edge">
|
||||
<ArchGrid />
|
||||
<SideRail />
|
||||
{/* voxel depth crater, sits high-right so it clears the copy below */}
|
||||
<DepthCanyon className="pointer-events-none absolute right-[-18%] top-[-10vh] h-[92vh] w-[105%] opacity-95 md:right-[-1%] md:w-[62%]" />
|
||||
{/* scrim keeps the meta copy on near-black under the crater */}
|
||||
<div
|
||||
aria-hidden
|
||||
className="pointer-events-none absolute inset-0"
|
||||
style={{
|
||||
background:
|
||||
"radial-gradient(110% 70% at 50% -10%, rgba(200,255,0,0.07), transparent 60%), radial-gradient(60% 50% at 92% 55%, rgba(82,0,255,0.09), transparent 70%), radial-gradient(45% 40% at 4% 90%, rgba(200,255,0,0.05), transparent 70%)",
|
||||
"radial-gradient(50% 42% at 80% 78%, #080808 0%, #080808 45%, transparent 80%)",
|
||||
}}
|
||||
/>
|
||||
|
||||
|
|
@ -126,7 +130,7 @@ export default function Landing() {
|
|||
<Folio className="hidden sm:inline">Robinhood Chain · L2 4663</Folio>
|
||||
<Folio className="inline-flex items-center gap-2">
|
||||
<span className="h-1.5 w-1.5 rounded-full bg-mint" />
|
||||
Live · Testnet
|
||||
Live · Mainnet
|
||||
</Folio>
|
||||
</div>
|
||||
|
||||
|
|
@ -175,7 +179,10 @@ export default function Landing() {
|
|||
</div>
|
||||
|
||||
{/* meta column */}
|
||||
<div className="flex flex-col justify-end gap-7 pb-2 lg:col-span-4">
|
||||
<div
|
||||
className="flex flex-col justify-end gap-7 pb-2 lg:col-span-4"
|
||||
style={{ textShadow: "0 1px 20px #080808, 0 0 4px #080808" }}
|
||||
>
|
||||
<motion.p
|
||||
initial={motionEnabled ? { opacity: 0 } : false}
|
||||
animate={{ opacity: 1 }}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import { rewardsRemainingUsd } from "@/hooks/useCampaigns";
|
|||
import { healthScore } from "@/lib/math";
|
||||
import { fmtUsd, fmtNum } from "@/lib/format";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { CyberGrid } from "@/components/fx/CyberGrid";
|
||||
|
||||
export default function Markets() {
|
||||
const { campaigns, isLoading, deployment } = useCampaigns();
|
||||
|
|
@ -18,6 +19,7 @@ export default function Markets() {
|
|||
|
||||
return (
|
||||
<div className="mx-auto max-w-7xl px-4 py-14 md:px-8">
|
||||
<CyberGrid />
|
||||
<div className="flex flex-col gap-4 md:flex-row md:items-end md:justify-between">
|
||||
<div>
|
||||
<div className="slabel">Markets</div>
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { Button } from "@/components/ui/button";
|
|||
import { Card } from "@/components/ui/card";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { inView } from "@/lib/motion";
|
||||
import { CyberGrid } from "@/components/fx/CyberGrid";
|
||||
|
||||
interface Application {
|
||||
pair: string;
|
||||
|
|
@ -63,6 +64,7 @@ export default function Partners() {
|
|||
|
||||
return (
|
||||
<div className="mx-auto max-w-3xl px-4 py-14 md:px-8">
|
||||
<CyberGrid />
|
||||
<div className="mb-6 flex items-center justify-between font-mono text-[10px] uppercase tracking-[0.22em] text-fog-dim">
|
||||
<span>Desk 00 // Applications</span>
|
||||
<span className="hidden sm:inline">パートナー // Partners</span>
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import { PositionPanel } from "@/components/PositionPanel";
|
|||
import { Button } from "@/components/ui/button";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import type { CampaignState } from "@/hooks/useCampaigns";
|
||||
import { CyberGrid } from "@/components/fx/CyberGrid";
|
||||
|
||||
function PositionRow({ campaign, index }: { campaign: CampaignState; index: number }) {
|
||||
const { position, refetch, isLoading } = usePosition(campaign.meta.campaignId);
|
||||
|
|
@ -45,6 +46,7 @@ export default function Positions() {
|
|||
|
||||
return (
|
||||
<div className="mx-auto max-w-3xl px-4 py-14 md:px-8">
|
||||
<CyberGrid />
|
||||
<div className="mb-6 flex items-center justify-between font-mono text-[10px] uppercase tracking-[0.22em] text-fog-dim">
|
||||
<span>Desk 00 // Receipts</span>
|
||||
<span className="hidden items-center gap-2 sm:inline-flex">
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
{"root":["./src/app.tsx","./src/ds-entry.ts","./src/main.tsx","./src/vite-env.d.ts","./src/components/campaigncard.tsx","./src/components/countup.tsx","./src/components/depositpanel.tsx","./src/components/depthchart.tsx","./src/components/depthhistory.tsx","./src/components/depthprogress.tsx","./src/components/footnote.tsx","./src/components/glyphmarquee.tsx","./src/components/healthradial.tsx","./src/components/positionpanel.tsx","./src/components/preloader.tsx","./src/components/slippagecurve.tsx","./src/components/term.tsx","./src/components/tickertape.tsx","./src/components/backdrop/depthfield.tsx","./src/components/backdrop/depthladder.tsx","./src/components/backdrop/glowfield.tsx","./src/components/backdrop/glyphfield.tsx","./src/components/brand/logo.tsx","./src/components/fx/scramble.tsx","./src/components/fx/siderail.tsx","./src/components/icons/xicon.tsx","./src/components/layout/appshell.tsx","./src/components/layout/footer.tsx","./src/components/layout/header.tsx","./src/components/layout/mobilenav.tsx","./src/components/ui/badge.tsx","./src/components/ui/button.tsx","./src/components/ui/card.tsx","./src/components/ui/skeleton.tsx","./src/components/ui/stat.tsx","./src/hooks/usecampaigns.ts","./src/hooks/usechainstats.ts","./src/hooks/usemarketdata.ts","./src/lib/chain.ts","./src/lib/constants.ts","./src/lib/contracts.ts","./src/lib/dexscreener.ts","./src/lib/format.ts","./src/lib/math.ts","./src/lib/motion.ts","./src/lib/points.ts","./src/lib/utils.ts","./src/lib/wagmi.ts","./src/lib/abi/hoodquidityvaults.ts","./src/lib/abi/mockerc20.ts","./src/lib/abi/mockv3aggregator.ts","./src/screens/appboard.tsx","./src/screens/campaigndetail.tsx","./src/screens/campaigns.tsx","./src/screens/landing.tsx","./src/screens/markets.tsx","./src/screens/partners.tsx","./src/screens/positions.tsx"],"version":"5.9.3"}
|
||||
{"root":["./src/app.tsx","./src/ds-entry.ts","./src/main.tsx","./src/vite-env.d.ts","./src/components/campaigncard.tsx","./src/components/countup.tsx","./src/components/depositpanel.tsx","./src/components/depthchart.tsx","./src/components/depthhistory.tsx","./src/components/depthprogress.tsx","./src/components/footnote.tsx","./src/components/glyphmarquee.tsx","./src/components/healthradial.tsx","./src/components/positionpanel.tsx","./src/components/preloader.tsx","./src/components/slippagecurve.tsx","./src/components/term.tsx","./src/components/tickertape.tsx","./src/components/backdrop/depthcanyon.tsx","./src/components/backdrop/depthfield.tsx","./src/components/backdrop/depthladder.tsx","./src/components/backdrop/glowfield.tsx","./src/components/backdrop/glyphfield.tsx","./src/components/brand/logo.tsx","./src/components/fx/cybergrid.tsx","./src/components/fx/scramble.tsx","./src/components/fx/siderail.tsx","./src/components/icons/xicon.tsx","./src/components/layout/appshell.tsx","./src/components/layout/footer.tsx","./src/components/layout/header.tsx","./src/components/layout/mobilenav.tsx","./src/components/ui/badge.tsx","./src/components/ui/button.tsx","./src/components/ui/card.tsx","./src/components/ui/skeleton.tsx","./src/components/ui/stat.tsx","./src/hooks/usecampaigns.ts","./src/hooks/usechainstats.ts","./src/hooks/usemarketdata.ts","./src/lib/chain.ts","./src/lib/constants.ts","./src/lib/contracts.ts","./src/lib/dexscreener.ts","./src/lib/format.ts","./src/lib/math.ts","./src/lib/motion.ts","./src/lib/points.ts","./src/lib/utils.ts","./src/lib/wagmi.ts","./src/lib/abi/hoodquidityvaults.ts","./src/lib/abi/mockerc20.ts","./src/lib/abi/mockv3aggregator.ts","./src/screens/appboard.tsx","./src/screens/campaigndetail.tsx","./src/screens/campaigns.tsx","./src/screens/landing.tsx","./src/screens/markets.tsx","./src/screens/partners.tsx","./src/screens/positions.tsx"],"version":"5.9.3"}
|
||||
Loading…
Add table
Add a link
Reference in a new issue