Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
95f3440
Add landing page, market pages, and header toolbar to frontend
xbtmatt Apr 2, 2026
d8e3940
Format
xbtmatt Apr 2, 2026
9686402
wip
xbtmatt Apr 3, 2026
b7b7971
wip2
xbtmatt Apr 3, 2026
ce00e20
Add price libs and unit tests
xbtmatt Apr 3, 2026
541b10c
Remove `workspaces` entry from package.json
xbtmatt Apr 3, 2026
405c337
Add real market data for everything but daily volume on the market la…
xbtmatt Apr 3, 2026
a062924
Add proper env setup with RPC URLs with private/public split
xbtmatt Apr 4, 2026
22fef6a
Handle slug properly
xbtmatt Apr 4, 2026
8862687
Add icon.svg to pubilc/ and add it to layout.tsx
xbtmatt Apr 4, 2026
d9a16de
Conditionally render react query dev tools
xbtmatt Apr 4, 2026
2a8b4c1
sort + neightbor comparison for slug prefixes
xbtmatt Apr 4, 2026
b8c83a2
Memoize prefix map
xbtmatt Apr 4, 2026
f2c1171
Format and add `useSortedClasses` import rule
xbtmatt Apr 7, 2026
e12a4a0
Add a `lint:ts:fix:unsafe` option
xbtmatt Apr 7, 2026
d069759
Only return random volume values in development
xbtmatt Apr 7, 2026
1cd009d
Don't early truncate bigints in format number
xbtmatt Apr 7, 2026
7771b89
Fix typo
xbtmatt Apr 7, 2026
d95a573
do BigInt(Number.MAX_SAFE_INTEGER) instead of Number.isSafeInteger(n)…
xbtmatt Apr 7, 2026
acea3e6
Dynamic import the react query dev tools
xbtmatt Apr 7, 2026
6c70c44
Use stable rpcClient value
xbtmatt Apr 7, 2026
ef598d9
Fix buildPrefixMap types
xbtmatt Apr 7, 2026
8862814
Remove useless ternary in use-all-markets
xbtmatt Apr 7, 2026
de5fe29
Remove TODO comment since it's done now
xbtmatt Apr 7, 2026
2bfe6db
Add e2e tests for single dropset market account + view path
xbtmatt Apr 7, 2026
ebd6410
Add couldn't load market error display
xbtmatt Apr 7, 2026
cc6bf8f
Fix address type
xbtmatt Apr 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion biome.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@
"noDuplicateArgumentNames": "error",
"noDuplicatedSpreadProps": "error",
"noFloatingPromises": "error",
"noShadow": "error"
"noShadow": "error",
"useSortedClasses": "error"
}
},
"domains": {
Expand Down
10 changes: 10 additions & 0 deletions frontend/example.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Required: which Solana cluster to connect to.
# Valid values: localnet, devnet, testnet, mainnet
NEXT_PUBLIC_CLUSTER=localnet

# Optional: private/paid RPC URLs (server-side only, never exposed to the browser).
# Falls back to the public RPC URL for the selected cluster if not set.
# SERVER_LOCALNET_RPC_URL=http://localhost:8899
# SERVER_DEVNET_RPC_URL=https://your-paid-rpc.example.com
# SERVER_TESTNET_RPC_URL=https://your-paid-rpc.example.com
# SERVER_MAINNET_RPC_URL=https://your-paid-rpc.example.com
4 changes: 3 additions & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@
"build": "next build",
"check": "tsc --noEmit",
"clean": "rm -rf .next",
"lint": "biome check .",
"lint": "biome check",
"lint:fix": "pnpm lint --write",
"format": "pnpm _format --write",
"start": "next start"
},
"dependencies": {
"@base-ui/react": "^1.3.0",
"@solana/addresses": "catalog:",
"@solana/rpc-types": "catalog:",
"@tanstack/react-query": "^5.95.2",
"@tanstack/react-query-devtools": "^5.95.2",
"class-variance-authority": "^0.7.1",
Expand Down
47 changes: 47 additions & 0 deletions frontend/public/icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
17 changes: 16 additions & 1 deletion frontend/src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,21 @@
:root {
--background: #ffffff;
--foreground: #171717;
--muted: #f5f5f5;
--muted-fg: #737373;
--border: #e5e5e5;
--accent: #3b82f6;
--accent-hover: #2563eb;
}

@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-muted: var(--muted);
--color-muted-fg: var(--muted-fg);
--color-border: var(--border);
--color-accent: var(--accent);
--color-accent-hover: var(--accent-hover);
--font-sans: var(--font-geist-sans);
--font-mono: var(--font-geist-mono);
}
Expand All @@ -16,11 +26,16 @@
:root {
--background: #0a0a0a;
--foreground: #ededed;
--muted: #1a1a1a;
--muted-fg: #a3a3a3;
--border: #262626;
--accent: #60a5fa;
--accent-hover: #93bbfd;
}
}

body {
background: var(--background);
color: var(--foreground);
font-family: var(--font-geist-sans);
font-family: var(--font-geist-sans), Arial, Helvetica, sans-serif;
}
10 changes: 10 additions & 0 deletions frontend/src/app/info/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export default function InfoPage() {
return (
<div className="mx-auto max-w-6xl px-6 py-8">
<div className="mb-8">
<h1 className="font-semibold text-2xl tracking-tight">Info</h1>
<p className="mt-1 text-muted-fg">Coming soon.</p>
</div>
</div>
);
}
16 changes: 13 additions & 3 deletions frontend/src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import { Header } from "@/components/header";
import { Providers } from "@/lib/providers";
import "./globals.css";

const geistSans = Geist({
Expand All @@ -13,8 +15,11 @@ const geistMono = Geist_Mono({
});

export const metadata: Metadata = {
title: "Dropset",
description: "Dropset alpha prototype",
title: "dropset",
description: "dropset alpha prototype",
icons: {
icon: "/icon.svg",
},
};

export default function RootLayout({
Expand All @@ -27,7 +32,12 @@ export default function RootLayout({
lang="en"
className={`${geistSans.variable} ${geistMono.variable} h-full antialiased`}
>
<body className="min-h-full flex flex-col">{children}</body>
<body className="flex min-h-full flex-col">
<Providers>
<Header />
<main className="flex-1">{children}</main>
</Providers>
</body>
</html>
);
}
51 changes: 51 additions & 0 deletions frontend/src/app/market/[slug]/market-view.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
"use client";

import type { Address } from "@solana/addresses";
import { useMarket } from "@/lib/hooks/use-market";

export function MarketView({ address }: { address: Address }) {
const { data: market, isLoading } = useMarket(address);

return (
<div className="mx-auto max-w-6xl px-6 py-8">
<div className="mb-8">
<h1 className="font-semibold text-2xl tracking-tight">Market</h1>
<p className="mt-1 break-all font-mono text-muted-fg text-sm">
{address}
</p>
</div>

{isLoading && <p className="text-zinc-500">Loading…</p>}
{!isLoading && !market && <p className="text-red-500">Couldn't load market</p>}

{market && (
<div className="grid grid-cols-[repeat(auto-fill,minmax(180px,1fr))] gap-4">
<div className="flex flex-col gap-1 rounded-lg border border-border p-4">
<span className="text-muted-fg text-xs uppercase tracking-[0.05em]">
Traders
</span>
<span className="font-semibold text-xl tabular-nums">
{market.traders}
</span>
</div>
<div className="flex flex-col gap-1 rounded-lg border border-border p-4">
<span className="text-muted-fg text-xs uppercase tracking-[0.05em]">
Liquidity
</span>
<span className="font-semibold text-xl tabular-nums">
${market.liquidity.toLocaleString()}
</span>
</div>
<div className="flex flex-col gap-1 rounded-lg border border-border p-4">
<span className="text-muted-fg text-xs uppercase tracking-[0.05em]">
24h Volume
</span>
<span className="font-semibold text-xl tabular-nums">
${market.volume24h.toLocaleString()}
</span>
</div>
</div>
)}
</div>
);
}
37 changes: 37 additions & 0 deletions frontend/src/app/market/[slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import type { Metadata } from "next";
import { notFound } from "next/navigation";
import { fetchAllMarkets } from "@/lib/queries/fetch-all-markets";
import { resolveSlug } from "@/lib/slug";
import { MarketView } from "./market-view";

type Props = {
params: Promise<{ slug: string }>;
};

export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { slug } = await params;
const markets = await fetchAllMarkets();
const address =
resolveSlug(
slug,
markets.map((m) => m.address),
) ?? slug;

return {
title: `dropset – market ${address}`,
description: `A dropset alpha market at address ${address}`,
};
}

export default async function MarketPage({ params }: Props) {
const { slug } = await params;
const markets = await fetchAllMarkets();
const address = resolveSlug(
slug,
markets.map((m) => m.address),
);

if (!address) notFound();

return <MarketView address={address} />;
}
94 changes: 91 additions & 3 deletions frontend/src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,96 @@
"use client";

import Link from "next/link";
import { useMemo } from "react";
import { useAllMarkets } from "@/lib/hooks/use-all-markets";
import { buildPrefixMap } from "@/lib/slug";
import { ensureU64 } from "@/ts-sdk/rust-types";

function formatNumber(input: number | bigint): string {
const n = ensureU64(input);
if (n <= BigInt(Number.MAX_SAFE_INTEGER)) {
const val = Number(n);
if (val >= 1_000_000) return `${(val / 1_000_000).toFixed(1)}M`;
if (val >= 1_000) return `${(val / 1_000).toFixed(1)}K`;
} else {
if (n >= 1_000_000n) return `${(Number(n / 1_000_000n)).toFixed(1)}M`;
if (n >= 1_000n) return `${(Number(n / 1_000n)).toFixed(1)}K`;
}
return n.toString();
}

export default function Home() {
const { data: markets, isLoading, error } = useAllMarkets();

const addresses = useMemo(
() => markets?.map((m) => m.address) ?? [],
[markets],
);

const prefixMap = useMemo(() => buildPrefixMap(addresses), [addresses]);

return (
<div className="flex flex-1 flex-col items-center justify-center gap-4 font-sans">
<h1 className="text-3xl font-semibold tracking-tight">Dropset</h1>
<p className="text-zinc-500 dark:text-zinc-400">Alpha prototype</p>
<div className="mx-auto max-w-6xl px-6 py-8">
<div className="mb-8">
<h1 className="font-semibold text-2xl tracking-tight">Markets</h1>
<p className="mt-1 text-muted-fg">Browse active Dropset markets</p>
</div>

{isLoading && <p className="text-zinc-500">Loading markets…</p>}
{error && <p className="text-red-500">Failed to load markets.</p>}

{markets && (
<div className="overflow-x-auto rounded-lg border border-border">
<table className="w-full border-collapse text-sm">
<thead className="bg-muted">
<tr>
<th className="px-4 py-2.5 text-left font-medium text-muted-fg text-xs uppercase tracking-[0.05em]">
Address
</th>
<th className="px-4 py-2.5 text-right font-medium font-mono text-muted-fg text-xs uppercase tabular-nums tracking-[0.05em]">
Traders
</th>
<th className="px-4 py-2.5 text-right font-medium font-mono text-muted-fg text-xs uppercase tabular-nums tracking-[0.05em]">
Liquidity
</th>
<th className="px-4 py-2.5 text-right font-medium font-mono text-muted-fg text-xs uppercase tabular-nums tracking-[0.05em]">
24h Volume
</th>
</tr>
</thead>
<tbody>
{markets.map((market) => {
const shortSlug =
prefixMap.get(market.address) ?? market.address;
return (
<tr key={market.address} className="hover:bg-muted">
<td className="border-border border-t px-4 py-3">
<Link
href={`/market/${shortSlug}`}
className="text-accent no-underline transition-colors hover:text-accent-hover hover:underline"
title={market.address}
>
<code className="font-mono text-[0.8125rem]">
{market.address}
</code>
</Link>
</td>
<td className="border-border border-t px-4 py-3 text-right font-mono tabular-nums">
{market.traders}
</td>
<td className="border-border border-t px-4 py-3 text-right font-mono tabular-nums">
${formatNumber(market.liquidity)}
</td>
<td className="border-border border-t px-4 py-3 text-right font-mono tabular-nums">
${formatNumber(market.volume24h)}
</td>
</tr>
);
})}
</tbody>
</table>
</div>
)}
</div>
);
}
10 changes: 10 additions & 0 deletions frontend/src/app/repo/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export default function RepoPage() {
return (
<div className="mx-auto max-w-6xl px-6 py-8">
<div className="mb-8">
<h1 className="font-semibold text-2xl tracking-tight">Repo</h1>
<p className="mt-1 text-muted-fg">Coming soon.</p>
</div>
</div>
);
}
10 changes: 10 additions & 0 deletions frontend/src/app/team/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export default function TeamPage() {
return (
<div className="mx-auto max-w-6xl px-6 py-8">
<div className="mb-8">
<h1 className="font-semibold text-2xl tracking-tight">Team</h1>
<p className="mt-1 text-muted-fg">Coming soon.</p>
</div>
</div>
);
}
Loading