A React + TypeScript SPA for the Crypto Liquidity Audit product: the Split Cockpit token report plus comparison, methodology, learn, about, and case studies pages. Styling is inline styles driven by a shared token module — no CSS framework.
cd Liquidity-Audit/website
npm run test # Vitest unit + component tests
npm run test:watch # Vitest watch modecd Liquidity-Audit/website
npm install
npm run sync:data # copy ../data/analysis/ → public/data/analysis/
npm run dev # http://localhost:5173sync:data copies CLI output from ../data/analysis/ into
public/data/analysis/. Run it after each local cli.py analysis run. The copied
data is gitignored.
Local dev uses /data/analysis by default (bundled via public/). Optional override:
VITE_ANALYSIS_DATA_BASE=/custom/path npm run devProduction JSON is served from a public Cloudflare R2 bucket, not bundled in
the static site. A GitHub Actions workflow runs cli.py every 12 hours and syncs
data/analysis/ to R2; pushes to Liquidity-Audit master build and deploy the SPA behind a
Cloudflare Worker (static assets + pair-route OG injection).
Worker name: crypto-liquidity-audit (set in wrangler.jsonc).
Production URL: https://crypto-liquidity-audit.<your-subdomain>.workers.dev
Production builds require VITE_SITE_URL (public site origin, no trailing slash),
VITE_ANALYSIS_DATA_BASE (public R2 URL, no trailing slash), VITE_POSTHOG_KEY, and
VITE_SENTRY_DSN:
cp .env.production.example .env.production
# edit VITE_SITE_URL, VITE_ANALYSIS_DATA_BASE, VITE_POSTHOG_KEY, and VITE_SENTRY_DSN
npm run build
npm run deployvite.config.ts fails the build if any required variable is unset in production mode.
npm run build also writes dist/sitemap.xml and dist/robots.txt.
- Static shell:
index.htmlships default description, Open Graph, Twitter Card, andWebSiteJSON-LD (URLs filled at build fromVITE_SITE_URL). - Client routes:
usePageMetaupdates title, description, canonical, OG/Twitter tags, and per-route JSON-LD (WebPage,BreadcrumbList, pair reports). - Shared pair links: the Cloudflare Worker (
worker/index.ts) injects OG + JSON-LD for/pairs/*document requests (run_worker_firstinwrangler.jsonc). Runtime varsSITE_URLandANALYSIS_DATA_BASEare set at deploy (same values as the Vite env vars).SITE_URLmust match the public origin (e.g.https://crypto-liquidity-audit.octobot.workers.dev); if unset, the Worker falls back to the request origin.
| Route | JSON-LD |
|---|---|
/ |
WebSite + SearchAction |
| Static pages | WebPage + BreadcrumbList |
/pairs/:exchange/:slug |
WebPage + BreadcrumbList (verdict as description) |
Assets: public/favicon.svg, public/og-default.png, public/site.webmanifest.
Pair report OG title and description are derived from analysis JSON (score, grade, failed issues, verdict). The default preview image is og-default.png.
Production-like local preview (Wrangler, port 8787):
npm run preview:workerOn the public analysis bucket, allow GET/HEAD from your Worker and custom domains:
[
{
"AllowedOrigins": [
"https://crypto-liquidity-audit.<your-subdomain>.workers.dev",
"https://<your-custom-domain>"
],
"AllowedMethods": ["GET", "HEAD"],
"AllowedHeaders": ["*"],
"MaxAgeSeconds": 3600
}
]Without CORS, browser fetch() from the Worker to R2 will fail.
| Secret | Purpose |
|---|---|
VITE_SITE_URL |
Public site origin for canonical URLs, OG, sitemap, and Worker vars |
VITE_ANALYSIS_DATA_BASE |
Public R2 base URL for JSON |
VITE_POSTHOG_KEY |
PostHog EU Cloud project token for production analytics |
VITE_SENTRY_DSN |
Sentry DSN for production error reporting |
CLOUDFLARE_API_TOKEN |
Worker deploy via Wrangler (Workers Scripts: Edit) |
CLOUDFLARE_ACCOUNT_ID |
Cloudflare account |
R2_ACCESS_KEY_ID / R2_SECRET_ACCESS_KEY |
Cron upload to R2 |
R2_ANALYSIS_BUCKET / R2_STATE_BUCKET |
Public + private bucket names |
MATTERMOST_WEBHOOK_URL |
Runtime env for cli.py Mattermost notifications |
Worker name is configured in wrangler.jsonc, not via a GitHub secret.
- PostHog (EU Cloud): autocapture, SPA pageviews, pageleave. Ingest
https://eu.i.posthog.com; setVITE_POSTHOG_KEYat build time. - Sentry (DE ingest, errors only): uncaught exceptions and
AppErrorBoundaryreports. SetVITE_SENTRY_DSNat build time. No performance tracing or session replay.
Both SDKs no-op during Vitest (MODE=test). In production builds, both env vars must be set.
| Path | Page |
|---|---|
/ |
Comparison (home) — rankings, search, exchange filter |
/pairs/:exchange/:symbolSlug |
Token report (e.g. /pairs/mexc/SOL_USDT) |
/methodology |
Metric formulas and scoring |
/about |
Principles and contact |
/learn |
Glossary |
/case-studies |
Before/after narratives |
wrangler.jsonc Worker config (static assets, SPA routing)
public/data/analysis/ Local dev JSON only (gitignored)
scripts/ sync-analysis-data.ts
src/
lib/
data/ Fetch, adapt, static options, samples
loader.ts fetchRankings, fetchPairAnalysis, slug helpers
adapter.ts pairAnalysisToToken, rankingsToViewModel
implementationOptions.ts
exchanges.ts
samples.ts Reference view models (tests/dev only)
usePageMeta.ts Document title, description, OG, canonical, JSON-LD
siteMeta.ts Shared SEO copy, JSON-LD builders, worker meta injection
monitoring/ PostHog + Sentry init (env-gated)
routes/ Route wrappers with loading/error states
components/ Nav, Footer, Screen, ScoreDial, IssueChips, …
pages/ One component per screen
types.ts TokenViewModel + section interfaces (Layer 3)
format.ts fmtUsd / fmtVolShort / …
theme.ts Color + font tokens
router.tsx Route table + page layout
main.tsx BrowserRouter entry
Local dev:
CLI → Liquidity-Audit/data/analysis/
→ npm run sync:data
→ public/data/analysis/
→ fetch (loader.ts)
→ adapter.ts → TokenViewModel / RankingsViewModel
→ pages
Production:
CLI (GH cron) → R2 analysis bucket
Worker SPA → fetch VITE_ANALYSIS_DATA_BASE/rankings|pairs/...
→ adapter.ts → pages
See ../../Projects-Finder/design_specs/03-data-contract.md for the full backend →
view-model field mapping.
<TokenReport> omits each detail section when its backing data is empty/null:
| Section | Hidden when |
|---|---|
| Health dashboard | healthDashboard empty |
| Peer comparison | peers empty |
| Investor simulator | investorSimulator empty |
| Lost opportunity | lostOpportunity is null |
| Root cause | rootCauses empty |
| Improvement potential | improvements empty |
| Improvement roadmap | roadmap empty |
| Implementation options | implementationOptions empty |
| Delisting notice + chip | delistingRisk empty |
Implementation options appear when score < 60 or the roadmap is non-empty.