Render every console page with the brand of the host serving it
Hanzo CI/CD / cicd (push) Failing after 1h10m29s
CI/CD / cicd (push) Failing after 2h37m43s

A static export has no host when it prerenders, so BrandProvider renders the
tree only once the browser has resolved the host, pages that show the brand
are client components, the static head names no brand, and the title and
favicon come from the host. The chain page keeps its static params and
metadata on the server and renders its body on the client, with RPC endpoints
on the brand's API origin at /v1/rpc/{chain}/{network}.

Hanzo's API origin is web3.hanzo.ai itself. The node fleet view, its landing
strip and its nav link belong to the brand that operates a fleet, which is
Lux alone. llms.txt, one file for every host, names no brand and points at
each host's own /docs/api.

Marketing copy states no certification the company does not hold and no
unmeasured uptime, latency, volume or response-time figure. Every internal
link points at a page that exists, every catalog chain is prerendered, and
the routes test holds page bodies to zero dead links.

Co-authored-by: Hanzo Dev <[email protected]>
This commit is contained in:
zooqueen
2026-09-15 12:01:27 -07:00
co-authored by Hanzo Dev
parent ab187aa2be
commit df50d16787
41 changed files with 633 additions and 852 deletions
+10 -36
View File
@@ -1,5 +1,6 @@
"use client"
import Link from "next/link"
import type { Metadata } from "next"
import { Navbar } from "@/components/navbar"
import { Footer } from "@/components/footer"
import { Button } from "@/components/ui/button"
@@ -18,12 +19,6 @@ import { getBrand } from "@/lib/brand"
const brand = getBrand()
export const metadata: Metadata = {
title: "About",
description:
`${brand.name} is built by Hanzo AI, a Techstars '17 company. Our mission is to make blockchain infrastructure accessible to every developer.`,
}
const values = [
{
icon: <Zap className="h-6 w-6" />,
@@ -35,7 +30,7 @@ const values = [
icon: <Shield className="h-6 w-6" />,
title: "Reliability Above All",
description:
"Production applications depend on us. We engineer for 99.999% uptime with automatic failover, redundant infrastructure, and zero-downtime deployments.",
"Production applications depend on us. We engineer for redundant infrastructure and zero-downtime deployments.",
},
{
icon: <Globe className="h-6 w-6" />,
@@ -51,13 +46,6 @@ const values = [
},
]
const stats = [
{ label: "Blockchains Supported", value: "100+" },
{ label: "API Requests / Day", value: "10B+" },
{ label: "Uptime SLA", value: "99.999%" },
{ label: "Global Edge Locations", value: "50+" },
]
const milestones = [
{
year: "2017",
@@ -81,7 +69,7 @@ const milestones = [
year: "2023",
title: `${brand.name} Launched`,
description:
`${brand.name} launched publicly, offering multi-chain RPC, Token APIs, NFT APIs, and Webhooks. The platform quickly grew to support thousands of developers and billions of monthly API calls.`,
`${brand.name} launched publicly, offering multi-chain RPC, Token APIs, NFT APIs, and Webhooks.`,
},
{
year: "2024",
@@ -93,7 +81,7 @@ const milestones = [
year: "2025",
title: "100+ Chains",
description:
`${brand.name} reached support for over 100 blockchains with sub-100ms response times across all networks, solidifying its position as the most comprehensive blockchain infrastructure platform available.`,
`${brand.name} reached support for over 100 blockchains through one unified API.`,
},
]
@@ -126,20 +114,6 @@ export default function AboutPage() {
</div>
</section>
{/* Stats */}
<section className="border-b py-12">
<div className="container">
<div className="grid grid-cols-2 gap-8 md:grid-cols-4">
{stats.map((stat) => (
<div key={stat.label} className="text-center">
<p className="text-3xl font-bold">{stat.value}</p>
<p className="mt-1 text-sm text-muted-foreground">{stat.label}</p>
</div>
))}
</div>
</div>
</section>
{/* Mission */}
<section className="border-b py-16">
<div className="container">
@@ -160,8 +134,8 @@ export default function AboutPage() {
</p>
<p>
We do this by combining deep blockchain expertise with enterprise-grade
infrastructure engineering. Our platform handles billions of API requests daily
with sub-100ms response times and 99.999% uptime, so developers can focus on
infrastructure engineering. Our platform runs the nodes, indexing and caching,
so developers can focus on
building their applications instead of managing infrastructure.
</p>
</div>
@@ -236,8 +210,8 @@ export default function AboutPage() {
<h3 className="font-semibold text-foreground">Infrastructure & DevOps</h3>
</div>
<p className="mt-2 text-sm">
Veterans of high-scale infrastructure at companies handling billions of daily
requests, with expertise in distributed systems, Kubernetes, and edge computing.
Engineers who have run large-scale infrastructure, with expertise in
distributed systems, Kubernetes, and edge computing.
</p>
</div>
<div className="rounded-lg border bg-card p-4">
@@ -247,7 +221,7 @@ export default function AboutPage() {
</div>
<p className="mt-2 text-sm">
Product engineers obsessed with developer experience, having built SDKs, CLIs,
and documentation platforms used by hundreds of thousands of developers.
and documentation platforms for developers.
</p>
</div>
</div>
+2 -7
View File
@@ -1,5 +1,6 @@
"use client"
import Link from "next/link"
import type { Metadata } from "next"
import { Navbar } from "@/components/navbar"
import { Footer } from "@/components/footer"
import { Button } from "@/components/ui/button"
@@ -19,12 +20,6 @@ import {
} from "@luxfi/ui/icons"
import { getBrand } from "@/lib/brand"
export const metadata: Metadata = {
title: "Agents",
description:
"Crypto for AI agents. Pay, trade, build apps — in 1 prompt. Agents can sign up and pay autonomously.",
}
const apis = [
{
number: "01",
+2 -2
View File
@@ -40,9 +40,9 @@ export default function BlogPage() {
},
{
slug: "scaling-rpc-infrastructure",
title: "How We Scaled to 10 Billion Daily RPC Requests",
title: "How We Built Our RPC Infrastructure",
summary:
`Behind every blockchain application is an RPC layer handling the heavy lifting. We share the architecture and engineering decisions that allow ${brand.name} to serve over 10 billion API requests per day across 100+ chains with sub-100ms latency and 99.999% uptime. Topics covered include our edge caching strategy, global load balancing, automatic failover, and how we monitor the health of thousands of blockchain nodes.`,
`Behind every blockchain application is an RPC layer handling the heavy lifting. We share the architecture and engineering decisions behind ${brand.name}'s RPC layer across 100+ chains. Topics covered include our caching strategy, load balancing, failover, and how we monitor the health of the blockchain nodes behind it.`,
date: "November 8, 2025",
readTime: "15 min read",
category: "Engineering",
+6 -11
View File
@@ -1,5 +1,6 @@
"use client"
import Link from "next/link"
import type { Metadata } from "next"
import { Navbar } from "@/components/navbar"
import { Footer } from "@/components/footer"
import { Button } from "@/components/ui/button"
@@ -18,12 +19,6 @@ import { getBrand } from "@/lib/brand"
const brand = getBrand()
export const metadata: Metadata = {
title: "Careers",
description:
`Join ${brand.name} and help build the infrastructure powering the next generation of blockchain applications. We are hiring engineers, DevRel, and more.`,
}
const positions = [
{
title: "Senior Backend Engineer",
@@ -31,7 +26,7 @@ const positions = [
location: "Remote (US/EU)",
type: "Full-time",
description:
`Design and build the core infrastructure that powers ${brand.name}'s multi-chain RPC platform, data indexing pipelines, and webhook delivery system. You will work on high-throughput distributed systems handling billions of daily requests across 100+ blockchain networks.`,
`Design and build the core infrastructure that powers ${brand.name}'s multi-chain RPC platform, data indexing pipelines, and webhook delivery system. You will work on high-throughput distributed systems serving 100+ blockchain networks.`,
requirements: [
"5+ years of experience in Go or Python with a focus on backend systems",
"Deep understanding of distributed systems, databases, and message queues",
@@ -47,7 +42,7 @@ const positions = [
location: "Remote (US/EU)",
type: "Full-time",
description:
`Build the ${brand.name} dashboard, developer portal, and documentation platform. You will create the interfaces that thousands of developers use daily to manage API keys, monitor usage, configure webhooks, and deploy smart wallets.`,
`Build the ${brand.name} dashboard, developer portal, and documentation platform. You will create the interfaces developers use to manage API keys, monitor usage, configure webhooks, and deploy smart wallets.`,
requirements: [
"5+ years of experience with React and TypeScript",
"Strong experience with Next.js, server components, and modern React patterns",
@@ -150,8 +145,8 @@ export default function CareersPage() {
</span>
</h1>
<p className="mt-6 text-lg text-muted-foreground">
Join a small, high-impact team building blockchain infrastructure used by
thousands of developers worldwide. We are remote-first, engineering-driven,
Join a small, high-impact team building blockchain infrastructure for
developers worldwide. We are remote-first, engineering-driven,
and backed by Techstars.
</p>
</div>
+384
View File
@@ -0,0 +1,384 @@
"use client"
import Link from "next/link"
import { Navbar } from "@/components/navbar"
import { Footer } from "@/components/footer"
import { Button } from "@/components/ui/button"
import { Badge } from "@/components/ui/badge"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Separator } from "@/components/ui/separator"
import { CHAINS, getChainBySlug } from "@/lib/chains"
import { useBrand } from "@/components/brand-provider"
import {
ArrowLeft,
ArrowRight,
BookOpen,
Check,
ExternalLink,
Globe,
Network,
Zap,
} from "@luxfi/ui/icons"
const CHAIN_EXPLORERS: Record<string, string> = {
ethereum: "https://etherscan.io",
solana: "https://explorer.solana.com",
base: "https://basescan.org",
arbitrum: "https://arbiscan.io",
polygon: "https://polygonscan.com",
optimism: "https://optimistic.etherscan.io",
avalanche: "https://snowtrace.io",
bnb: "https://bscscan.com",
lux: "https://explore.lux.network",
}
const CHAIN_APIS: Record<string, string[]> = {
ethereum: ["JSON-RPC", "WebSocket", "Beacon API", "Token API", "NFT API", "Transfers API", "Webhooks", "Debug & Trace", "Archive Nodes"],
solana: ["JSON-RPC", "WebSocket", "DAS API", "Token API", "NFT API", "Transfers API", "Webhooks"],
base: ["JSON-RPC", "WebSocket", "Token API", "NFT API", "Transfers API", "Webhooks", "Debug & Trace", "Flashblocks API"],
arbitrum: ["JSON-RPC", "WebSocket", "Token API", "NFT API", "Transfers API", "Webhooks", "Debug & Trace", "Stylus Support"],
polygon: ["JSON-RPC", "WebSocket", "Token API", "NFT API", "Transfers API", "Webhooks", "Debug & Trace"],
optimism: ["JSON-RPC", "WebSocket", "Token API", "NFT API", "Transfers API", "Webhooks", "Debug & Trace", "Flashblocks API"],
avalanche: ["JSON-RPC", "WebSocket", "Token API", "NFT API", "Transfers API", "Webhooks", "Subnet Support"],
bnb: ["JSON-RPC", "WebSocket", "Token API", "NFT API", "Transfers API", "Webhooks", "Debug & Trace"],
lux: ["JSON-RPC", "WebSocket", "Token API", "NFT API", "Transfers API", "Webhooks", "Multi-Consensus API", "Post-Quantum Signing"],
}
// The API's registry names BNB Smart Chain `bsc`; every other chain here uses its catalog id.
const API_CHAIN: Record<string, string> = { bnb: "bsc" }
export function ChainDetail({ slug }: { slug: string }) {
const brand = useBrand()
const chain = getChainBySlug(slug)
if (!chain) return null
const explorer = CHAIN_EXPLORERS[chain.id] || "#"
const apis = CHAIN_APIS[chain.id] || ["JSON-RPC", "WebSocket", "Token API", "NFT API", "Transfers API", "Webhooks"]
const mainnet = chain.networks.find((n) => !n.isTestnet)
const testnets = chain.networks.filter((n) => n.isTestnet)
// The JSON-RPC proxy answers at POST /v1/rpc/{chain}/{network} on the brand's API origin.
const rpcChain = API_CHAIN[chain.id] ?? chain.id
const rpcEndpoint = `${brand.apiUrl}/v1/rpc/${rpcChain}/mainnet`
const curlExample = chain.type === "svm"
? `curl -X POST ${rpcEndpoint} \\
-H "Content-Type: application/json" \\
-H "Authorization: Bearer YOUR_API_KEY" \\
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "getSlot",
"params": []
}'`
: `curl -X POST ${rpcEndpoint} \\
-H "Content-Type: application/json" \\
-H "Authorization: Bearer YOUR_API_KEY" \\
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "eth_blockNumber",
"params": []
}'`
return (
<div className="flex min-h-screen flex-col">
<Navbar />
{/* Hero */}
<section className="border-b bg-gradient-to-b from-background to-muted/20">
<div className="container py-16 md:py-20">
<Link
href="/chains"
className="mb-6 inline-flex items-center text-sm text-muted-foreground hover:text-foreground"
>
<ArrowLeft className="mr-1 h-4 w-4" />
All Chains
</Link>
<div className="flex flex-col gap-6 md:flex-row md:items-start md:justify-between">
<div className="flex items-start gap-4">
<div
className="flex h-16 w-16 items-center justify-center rounded-2xl text-2xl font-bold text-white"
style={{ backgroundColor: chain.color || "#6366f1" }}
>
{chain.name.charAt(0)}
</div>
<div>
<div className="flex items-center gap-3">
<h1 className="text-3xl font-bold tracking-tight md:text-4xl">
{chain.name}
</h1>
<Badge
variant={chain.type === "evm" ? "secondary" : "outline"}
className="uppercase"
>
{chain.type}
</Badge>
</div>
<p className="mt-2 max-w-xl text-lg text-muted-foreground">
{chain.description}
</p>
{mainnet?.chainId && (
<p className="mt-1 text-sm text-muted-foreground">
Chain ID: {mainnet.chainId}
</p>
)}
</div>
</div>
<div className="flex gap-3">
<Button asChild>
<Link href="/dashboard">
Get API Key
<ArrowRight className="ml-2 h-4 w-4" />
</Link>
</Button>
<Button variant="outline" asChild>
<Link href="/docs/quickstart">
<BookOpen className="mr-2 h-4 w-4" />
Documentation
</Link>
</Button>
</div>
</div>
</div>
</section>
{/* Networks */}
<section className="border-b py-16">
<div className="container">
<h2 className="text-2xl font-bold tracking-tight">Networks</h2>
<p className="mt-2 text-muted-foreground">
Available networks for {chain.name}. Use mainnet for production and testnets for development.
</p>
<div className="mt-8 grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
{mainnet && (
<Card className="border-primary/30">
<CardHeader className="pb-3">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<Network className="h-5 w-5 text-primary" />
<CardTitle className="text-base">{mainnet.name}</CardTitle>
</div>
<Badge variant="success">Production</Badge>
</div>
</CardHeader>
<CardContent>
{mainnet.chainId && (
<p className="text-sm text-muted-foreground">
Chain ID: <code className="rounded bg-muted px-1 font-mono text-xs">{mainnet.chainId}</code>
</p>
)}
<p className="mt-2 text-sm text-muted-foreground">
RPC: <code className="rounded bg-muted px-1 font-mono text-xs break-all">{rpcEndpoint}</code>
</p>
<div className="mt-3 flex gap-2">
{explorer !== "#" && (
<Button variant="outline" size="sm" asChild>
<a href={explorer} target="_blank" rel="noopener noreferrer">
Explorer
<ExternalLink className="ml-1 h-3 w-3" />
</a>
</Button>
)}
</div>
</CardContent>
</Card>
)}
{testnets.map((testnet) => (
<Card key={testnet.id}>
<CardHeader className="pb-3">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<Network className="h-5 w-5 text-muted-foreground" />
<CardTitle className="text-base">{testnet.name}</CardTitle>
</div>
<Badge variant="warning">Testnet</Badge>
</div>
</CardHeader>
<CardContent>
{testnet.chainId && (
<p className="text-sm text-muted-foreground">
Chain ID: <code className="rounded bg-muted px-1 font-mono text-xs">{testnet.chainId}</code>
</p>
)}
<p className="mt-2 text-sm text-muted-foreground">
RPC: <code className="rounded bg-muted px-1 font-mono text-xs break-all">
{`${brand.apiUrl}/v1/rpc/${rpcChain}/${testnet.id}`}
</code>
</p>
</CardContent>
</Card>
))}
</div>
</div>
</section>
{/* Features & APIs */}
<section className="border-b bg-muted/30 py-16">
<div className="container">
<div className="grid gap-12 lg:grid-cols-2">
<div>
<h2 className="text-2xl font-bold tracking-tight">Supported APIs</h2>
<p className="mt-2 text-muted-foreground">
Everything you need to build on {chain.name}, available through a single API key.
</p>
<div className="mt-8 grid gap-3 sm:grid-cols-2">
{apis.map((api) => (
<div key={api} className="flex items-center gap-2">
<Check className="h-4 w-4 text-green-500" />
<span className="text-sm">{api}</span>
</div>
))}
</div>
</div>
<div>
<h2 className="text-2xl font-bold tracking-tight">Chain Features</h2>
<p className="mt-2 text-muted-foreground">
Key capabilities and characteristics of {chain.name}.
</p>
<div className="mt-8 space-y-3">
{chain.features.map((feature) => (
<div
key={feature}
className="flex items-center gap-3 rounded-lg border bg-card p-3"
>
<Zap className="h-4 w-4 text-primary" />
<span className="text-sm font-medium">{feature}</span>
</div>
))}
</div>
</div>
</div>
</div>
</section>
{/* Quick Start */}
<section className="border-b py-16">
<div className="container">
<h2 className="text-2xl font-bold tracking-tight">Quick Start</h2>
<p className="mt-2 text-muted-foreground">
Send your first request to {chain.name} in under a minute.
</p>
<div className="mt-8">
<div className="rounded-lg border bg-card">
<div className="flex items-center justify-between border-b px-4 py-3">
<div className="flex items-center gap-2">
<Globe className="h-4 w-4 text-muted-foreground" />
<span className="text-sm font-medium">cURL</span>
</div>
<Badge variant="secondary" className="font-mono text-xs">
{chain.type === "svm" ? "getSlot" : "eth_blockNumber"}
</Badge>
</div>
<pre className="overflow-x-auto p-4 font-mono text-sm text-muted-foreground">
<code>{curlExample}</code>
</pre>
</div>
</div>
<div className="mt-6 flex gap-3">
<Button asChild>
<Link href="/dashboard">
Get Your API Key
<ArrowRight className="ml-2 h-4 w-4" />
</Link>
</Button>
<Button variant="outline" asChild>
<Link href="/docs/quickstart">View Full Guide</Link>
</Button>
</div>
</div>
</section>
{/* Pricing */}
<section className="border-b bg-muted/30 py-16">
<div className="container">
<h2 className="text-2xl font-bold tracking-tight">Pricing</h2>
<p className="mt-2 text-muted-foreground">
{chain.name} is included in all {brand.name} plans. Start free and scale as you grow.
</p>
<div className="mt-8 grid gap-4 sm:grid-cols-3">
<Card>
<CardHeader>
<CardTitle className="text-base">Free</CardTitle>
</CardHeader>
<CardContent>
<p className="text-2xl font-bold">$0<span className="text-sm font-normal text-muted-foreground">/mo</span></p>
<p className="mt-2 text-sm text-muted-foreground">100M compute units per month. Perfect for getting started.</p>
</CardContent>
</Card>
<Card className="border-primary/30">
<CardHeader>
<div className="flex items-center justify-between">
<CardTitle className="text-base">Growth</CardTitle>
<Badge>Popular</Badge>
</div>
</CardHeader>
<CardContent>
<p className="text-2xl font-bold">$99<span className="text-sm font-normal text-muted-foreground">/mo</span></p>
<p className="mt-2 text-sm text-muted-foreground">1B compute units per month. All chains and APIs included.</p>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle className="text-base">Enterprise</CardTitle>
</CardHeader>
<CardContent>
<p className="text-2xl font-bold">Custom</p>
<p className="mt-2 text-sm text-muted-foreground">Dedicated infrastructure, custom limits and direct support.</p>
</CardContent>
</Card>
</div>
<div className="mt-6 text-center">
<Button variant="outline" asChild>
<Link href="/pricing">
View Full Pricing
<ArrowRight className="ml-2 h-4 w-4" />
</Link>
</Button>
</div>
</div>
</section>
<Separator />
{/* Other chains */}
<section className="py-16">
<div className="container">
<h2 className="text-2xl font-bold tracking-tight">Explore More Chains</h2>
<p className="mt-2 text-muted-foreground">
{brand.name} supports 100+ blockchains. Here are some popular ones.
</p>
<div className="mt-8 grid grid-cols-3 gap-3 sm:grid-cols-5 md:grid-cols-9">
{CHAINS.filter((c) => !c.deprecated && c.id !== chain.id)
.slice(0, 9)
.map((c) => (
<Link
key={c.id}
href={`/chains/${c.slug}`}
className="flex flex-col items-center gap-2 rounded-lg border bg-card p-3 transition-colors hover:border-foreground/20"
>
<div
className="flex h-8 w-8 items-center justify-center rounded-full text-sm font-bold text-white"
style={{ backgroundColor: c.color || "#6366f1" }}
>
{c.name.charAt(0)}
</div>
<span className="text-center text-xs font-medium">{c.name}</span>
</Link>
))}
</div>
<div className="mt-8 text-center">
<Button variant="outline" asChild>
<Link href="/chains">
View All Chains
<ArrowRight className="ml-2 h-4 w-4" />
</Link>
</Button>
</div>
</div>
</section>
<Footer />
</div>
)
}
+6 -395
View File
@@ -1,59 +1,10 @@
import Link from "next/link"
import { notFound } from "next/navigation"
import { Navbar } from "@/components/navbar"
import { Footer } from "@/components/footer"
import { Button } from "@/components/ui/button"
import { Badge } from "@/components/ui/badge"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Separator } from "@/components/ui/separator"
import { CHAINS, getChainBySlug } from "@/lib/chains"
import {
ArrowLeft,
ArrowRight,
BookOpen,
Check,
ExternalLink,
Globe,
Network,
Zap,
} from "@luxfi/ui/icons"
const CHAIN_EXPLORERS: Record<string, string> = {
ethereum: "https://etherscan.io",
solana: "https://explorer.solana.com",
base: "https://basescan.org",
arbitrum: "https://arbiscan.io",
polygon: "https://polygonscan.com",
optimism: "https://optimistic.etherscan.io",
avalanche: "https://snowtrace.io",
bnb: "https://bscscan.com",
lux: "https://explore.lux.network",
}
const CHAIN_APIS: Record<string, string[]> = {
ethereum: ["JSON-RPC", "WebSocket", "Beacon API", "Token API", "NFT API", "Transfers API", "Webhooks", "Debug & Trace", "Archive Nodes"],
solana: ["JSON-RPC", "WebSocket", "DAS API", "Token API", "NFT API", "Transfers API", "Webhooks"],
base: ["JSON-RPC", "WebSocket", "Token API", "NFT API", "Transfers API", "Webhooks", "Debug & Trace", "Flashblocks API"],
arbitrum: ["JSON-RPC", "WebSocket", "Token API", "NFT API", "Transfers API", "Webhooks", "Debug & Trace", "Stylus Support"],
polygon: ["JSON-RPC", "WebSocket", "Token API", "NFT API", "Transfers API", "Webhooks", "Debug & Trace"],
optimism: ["JSON-RPC", "WebSocket", "Token API", "NFT API", "Transfers API", "Webhooks", "Debug & Trace", "Flashblocks API"],
avalanche: ["JSON-RPC", "WebSocket", "Token API", "NFT API", "Transfers API", "Webhooks", "Subnet Support"],
bnb: ["JSON-RPC", "WebSocket", "Token API", "NFT API", "Transfers API", "Webhooks", "Debug & Trace"],
lux: ["JSON-RPC", "WebSocket", "Token API", "NFT API", "Transfers API", "Webhooks", "Multi-Consensus API", "Post-Quantum Signing"],
}
import { ChainDetail } from "./chain-detail"
// Every chain in the catalog is a page, so every card on /chains links to one.
export function generateStaticParams() {
return [
{ slug: "ethereum" },
{ slug: "solana" },
{ slug: "base" },
{ slug: "arbitrum" },
{ slug: "polygon" },
{ slug: "optimism" },
{ slug: "avalanche" },
{ slug: "bnb-smart-chain" },
{ slug: "lux" },
]
return CHAINS.map((c) => ({ slug: c.slug }))
}
export async function generateMetadata({ params }: { params: Promise<{ slug: string }> }) {
@@ -62,7 +13,7 @@ export async function generateMetadata({ params }: { params: Promise<{ slug: str
if (!chain) return { title: "Chain Not Found" }
return {
title: `${chain.name} - RPC & APIs`,
description: `Access ${chain.name} with Bootnode's high-performance RPC endpoints, Token APIs, NFT APIs, WebSockets, and more. 99.999% uptime guaranteed.`,
description: `${chain.name} RPC endpoints, token and NFT APIs, WebSockets and webhooks.`,
}
}
@@ -72,346 +23,6 @@ export default async function ChainDetailPage({
params: Promise<{ slug: string }>
}) {
const { slug } = await params
const chain = getChainBySlug(slug)
if (!chain) {
notFound()
}
const explorer = CHAIN_EXPLORERS[chain.id] || "#"
const apis = CHAIN_APIS[chain.id] || ["JSON-RPC", "WebSocket", "Token API", "NFT API", "Transfers API", "Webhooks"]
const mainnet = chain.networks.find((n) => !n.isTestnet)
const testnets = chain.networks.filter((n) => n.isTestnet)
const rpcDomain = "rpc.web3.hanzo.ai"
const rpcEndpoint = chain.type === "svm"
? `https://${chain.slug}.mainnet.${rpcDomain}`
: `https://${chain.slug}-mainnet.${rpcDomain}`
const curlExample = chain.type === "svm"
? `curl -X POST ${rpcEndpoint} \\
-H "Content-Type: application/json" \\
-H "Authorization: Bearer YOUR_API_KEY" \\
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "getSlot",
"params": []
}'`
: `curl -X POST ${rpcEndpoint} \\
-H "Content-Type: application/json" \\
-H "Authorization: Bearer YOUR_API_KEY" \\
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "eth_blockNumber",
"params": []
}'`
return (
<div className="flex min-h-screen flex-col">
<Navbar />
{/* Hero */}
<section className="border-b bg-gradient-to-b from-background to-muted/20">
<div className="container py-16 md:py-20">
<Link
href="/chains"
className="mb-6 inline-flex items-center text-sm text-muted-foreground hover:text-foreground"
>
<ArrowLeft className="mr-1 h-4 w-4" />
All Chains
</Link>
<div className="flex flex-col gap-6 md:flex-row md:items-start md:justify-between">
<div className="flex items-start gap-4">
<div
className="flex h-16 w-16 items-center justify-center rounded-2xl text-2xl font-bold text-white"
style={{ backgroundColor: chain.color || "#6366f1" }}
>
{chain.name.charAt(0)}
</div>
<div>
<div className="flex items-center gap-3">
<h1 className="text-3xl font-bold tracking-tight md:text-4xl">
{chain.name}
</h1>
<Badge
variant={chain.type === "evm" ? "secondary" : "outline"}
className="uppercase"
>
{chain.type}
</Badge>
</div>
<p className="mt-2 max-w-xl text-lg text-muted-foreground">
{chain.description}
</p>
{mainnet?.chainId && (
<p className="mt-1 text-sm text-muted-foreground">
Chain ID: {mainnet.chainId}
</p>
)}
</div>
</div>
<div className="flex gap-3">
<Button asChild>
<Link href="/dashboard">
Get API Key
<ArrowRight className="ml-2 h-4 w-4" />
</Link>
</Button>
{chain.docs?.quickstart && (
<Button variant="outline" asChild>
<Link href={chain.docs.quickstart}>
<BookOpen className="mr-2 h-4 w-4" />
Documentation
</Link>
</Button>
)}
</div>
</div>
</div>
</section>
{/* Networks */}
<section className="border-b py-16">
<div className="container">
<h2 className="text-2xl font-bold tracking-tight">Networks</h2>
<p className="mt-2 text-muted-foreground">
Available networks for {chain.name}. Use mainnet for production and testnets for development.
</p>
<div className="mt-8 grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
{mainnet && (
<Card className="border-primary/30">
<CardHeader className="pb-3">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<Network className="h-5 w-5 text-primary" />
<CardTitle className="text-base">{mainnet.name}</CardTitle>
</div>
<Badge variant="success">Production</Badge>
</div>
</CardHeader>
<CardContent>
{mainnet.chainId && (
<p className="text-sm text-muted-foreground">
Chain ID: <code className="rounded bg-muted px-1 font-mono text-xs">{mainnet.chainId}</code>
</p>
)}
<p className="mt-2 text-sm text-muted-foreground">
RPC: <code className="rounded bg-muted px-1 font-mono text-xs break-all">{rpcEndpoint}</code>
</p>
<div className="mt-3 flex gap-2">
{explorer !== "#" && (
<Button variant="outline" size="sm" asChild>
<a href={explorer} target="_blank" rel="noopener noreferrer">
Explorer
<ExternalLink className="ml-1 h-3 w-3" />
</a>
</Button>
)}
</div>
</CardContent>
</Card>
)}
{testnets.map((testnet) => (
<Card key={testnet.id}>
<CardHeader className="pb-3">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<Network className="h-5 w-5 text-muted-foreground" />
<CardTitle className="text-base">{testnet.name}</CardTitle>
</div>
<Badge variant="warning">Testnet</Badge>
</div>
</CardHeader>
<CardContent>
{testnet.chainId && (
<p className="text-sm text-muted-foreground">
Chain ID: <code className="rounded bg-muted px-1 font-mono text-xs">{testnet.chainId}</code>
</p>
)}
<p className="mt-2 text-sm text-muted-foreground">
RPC: <code className="rounded bg-muted px-1 font-mono text-xs break-all">
{chain.type === "svm"
? `https://${chain.slug}.devnet.${rpcDomain}`
: `https://${chain.slug}-${testnet.id}.${rpcDomain}`}
</code>
</p>
</CardContent>
</Card>
))}
</div>
</div>
</section>
{/* Features & APIs */}
<section className="border-b bg-muted/30 py-16">
<div className="container">
<div className="grid gap-12 lg:grid-cols-2">
<div>
<h2 className="text-2xl font-bold tracking-tight">Supported APIs</h2>
<p className="mt-2 text-muted-foreground">
Everything you need to build on {chain.name}, available through a single API key.
</p>
<div className="mt-8 grid gap-3 sm:grid-cols-2">
{apis.map((api) => (
<div key={api} className="flex items-center gap-2">
<Check className="h-4 w-4 text-green-500" />
<span className="text-sm">{api}</span>
</div>
))}
</div>
</div>
<div>
<h2 className="text-2xl font-bold tracking-tight">Chain Features</h2>
<p className="mt-2 text-muted-foreground">
Key capabilities and characteristics of {chain.name}.
</p>
<div className="mt-8 space-y-3">
{chain.features.map((feature) => (
<div
key={feature}
className="flex items-center gap-3 rounded-lg border bg-card p-3"
>
<Zap className="h-4 w-4 text-primary" />
<span className="text-sm font-medium">{feature}</span>
</div>
))}
</div>
</div>
</div>
</div>
</section>
{/* Quick Start */}
<section className="border-b py-16">
<div className="container">
<h2 className="text-2xl font-bold tracking-tight">Quick Start</h2>
<p className="mt-2 text-muted-foreground">
Send your first request to {chain.name} in under a minute.
</p>
<div className="mt-8">
<div className="rounded-lg border bg-card">
<div className="flex items-center justify-between border-b px-4 py-3">
<div className="flex items-center gap-2">
<Globe className="h-4 w-4 text-muted-foreground" />
<span className="text-sm font-medium">cURL</span>
</div>
<Badge variant="secondary" className="font-mono text-xs">
{chain.type === "svm" ? "getSlot" : "eth_blockNumber"}
</Badge>
</div>
<pre className="overflow-x-auto p-4 font-mono text-sm text-muted-foreground">
<code>{curlExample}</code>
</pre>
</div>
</div>
<div className="mt-6 flex gap-3">
<Button asChild>
<Link href="/dashboard">
Get Your API Key
<ArrowRight className="ml-2 h-4 w-4" />
</Link>
</Button>
{chain.docs?.quickstart && (
<Button variant="outline" asChild>
<Link href={chain.docs.quickstart}>View Full Guide</Link>
</Button>
)}
</div>
</div>
</section>
{/* Pricing */}
<section className="border-b bg-muted/30 py-16">
<div className="container">
<h2 className="text-2xl font-bold tracking-tight">Pricing</h2>
<p className="mt-2 text-muted-foreground">
{chain.name} is included in all Bootnode plans. Start free and scale as you grow.
</p>
<div className="mt-8 grid gap-4 sm:grid-cols-3">
<Card>
<CardHeader>
<CardTitle className="text-base">Free</CardTitle>
</CardHeader>
<CardContent>
<p className="text-2xl font-bold">$0<span className="text-sm font-normal text-muted-foreground">/mo</span></p>
<p className="mt-2 text-sm text-muted-foreground">100M compute units per month. Perfect for getting started.</p>
</CardContent>
</Card>
<Card className="border-primary/30">
<CardHeader>
<div className="flex items-center justify-between">
<CardTitle className="text-base">Growth</CardTitle>
<Badge>Popular</Badge>
</div>
</CardHeader>
<CardContent>
<p className="text-2xl font-bold">$99<span className="text-sm font-normal text-muted-foreground">/mo</span></p>
<p className="mt-2 text-sm text-muted-foreground">1B compute units per month. All chains and APIs included.</p>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle className="text-base">Enterprise</CardTitle>
</CardHeader>
<CardContent>
<p className="text-2xl font-bold">Custom</p>
<p className="mt-2 text-sm text-muted-foreground">Dedicated infrastructure with 99.999% SLA and 24/7 support.</p>
</CardContent>
</Card>
</div>
<div className="mt-6 text-center">
<Button variant="outline" asChild>
<Link href="/pricing">
View Full Pricing
<ArrowRight className="ml-2 h-4 w-4" />
</Link>
</Button>
</div>
</div>
</section>
<Separator />
{/* Other chains */}
<section className="py-16">
<div className="container">
<h2 className="text-2xl font-bold tracking-tight">Explore More Chains</h2>
<p className="mt-2 text-muted-foreground">
Bootnode supports 100+ blockchains. Here are some popular ones.
</p>
<div className="mt-8 grid grid-cols-3 gap-3 sm:grid-cols-5 md:grid-cols-9">
{CHAINS.filter((c) => !c.deprecated && c.id !== chain.id)
.slice(0, 9)
.map((c) => (
<Link
key={c.id}
href={`/chains/${c.slug}`}
className="flex flex-col items-center gap-2 rounded-lg border bg-card p-3 transition-colors hover:border-foreground/20"
>
<div
className="flex h-8 w-8 items-center justify-center rounded-full text-sm font-bold text-white"
style={{ backgroundColor: c.color || "#6366f1" }}
>
{c.name.charAt(0)}
</div>
<span className="text-center text-xs font-medium">{c.name}</span>
</Link>
))}
</div>
<div className="mt-8 text-center">
<Button variant="outline" asChild>
<Link href="/chains">
View All Chains
<ArrowRight className="ml-2 h-4 w-4" />
</Link>
</Button>
</div>
</div>
</section>
<Footer />
</div>
)
if (!getChainBySlug(slug)) notFound()
return <ChainDetail slug={slug} />
}
+1 -1
View File
@@ -244,7 +244,7 @@ export default function StakingPage() {
</p>
<div className="flex gap-2 mt-2">
<Button variant="outline" size="sm" asChild>
<Link href="/dashboard/settings/keys">
<Link href="/dashboard/api-keys">
<Key className="mr-2 h-4 w-4" />
Configure KMS
</Link>
+2 -5
View File
@@ -1,3 +1,5 @@
"use client"
import Link from "next/link"
import { Badge } from "@/components/ui/badge"
import { Card, CardContent } from "@/components/ui/card"
@@ -5,11 +7,6 @@ import { DocsLayout } from "@/components/docs-layout"
import { ArrowRight } from "@luxfi/ui/icons"
import { docsConfig } from "@/lib/docs-config"
export const metadata = {
title: "Account Abstraction",
description: `Build with ERC-4337 account abstraction on ${docsConfig.brandName}.`,
}
export default function AAQuickstartPage() {
return (
<DocsLayout>
+2 -5
View File
@@ -1,13 +1,10 @@
"use client"
import { Badge } from "@/components/ui/badge"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { DocsLayout } from "@/components/docs-layout"
import { docsConfig } from "@/lib/docs-config"
export const metadata = {
title: "API Reference",
description: `Complete API reference for all ${docsConfig.brandName} endpoints.`,
}
function Endpoint({
method,
path,
+2 -5
View File
@@ -1,3 +1,5 @@
"use client"
import Link from "next/link"
import { Badge } from "@/components/ui/badge"
import { Card, CardContent } from "@/components/ui/card"
@@ -5,11 +7,6 @@ import { DocsLayout } from "@/components/docs-layout"
import { ArrowRight } from "@luxfi/ui/icons"
import { docsConfig } from "@/lib/docs-config"
export const metadata = {
title: "Authentication",
description: `Learn how to authenticate with the ${docsConfig.brandName} API.`,
}
export default function AuthQuickstartPage() {
return (
<DocsLayout>
+3 -6
View File
@@ -1,13 +1,10 @@
"use client"
import { Badge } from "@/components/ui/badge"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { DocsLayout } from "@/components/docs-layout"
import { docsConfig } from "@/lib/docs-config"
export const metadata = {
title: "Changelog",
description: `${docsConfig.brandName} platform changelog and release notes.`,
}
export default function ChangelogPage() {
return (
<DocsLayout>
@@ -41,7 +38,7 @@ export default function ChangelogPage() {
<div>
<h3 className="font-semibold mb-3">Multi-Chain RPC (Node)</h3>
<ul className="list-disc list-inside text-muted-foreground space-y-1.5">
<li>Support for 100+ blockchain networks with 99.999% uptime SLA</li>
<li>Support for 100+ blockchain networks</li>
<li>Ethereum, Solana, Base, Arbitrum, Polygon, Optimism, Avalanche, BNB Smart Chain, Lux Network, and more</li>
<li>Full EVM JSON-RPC support including archive data, debug/trace methods, and eth_subscribe</li>
<li>Solana JSON-RPC with getAccountInfo, getTransaction, and all standard methods</li>
+2 -5
View File
@@ -1,3 +1,5 @@
"use client"
import Link from "next/link"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Badge } from "@/components/ui/badge"
@@ -5,11 +7,6 @@ import { DocsLayout } from "@/components/docs-layout"
import { ArrowRight } from "@luxfi/ui/icons"
import { docsConfig } from "@/lib/docs-config"
export const metadata = {
title: "Examples",
description: `Common ${docsConfig.brandName} API usage examples with full code snippets.`,
}
export default function ExamplesPage() {
return (
<DocsLayout>
+27 -27
View File
@@ -23,9 +23,9 @@ const sections = [
description: "Quick start guides and tutorials",
icon: Zap,
items: [
{ name: "Introduction", href: "/docs/intro" },
{ name: "Authentication", href: "/docs/auth" },
{ name: "Making Requests", href: "/docs/requests" },
{ name: "Introduction", href: "/docs/quickstart" },
{ name: "Authentication", href: "/docs/auth/quickstart" },
{ name: "Making Requests", href: "/docs/api" },
{ name: "ZAP Protocol", href: "/docs/zap/quickstart" },
],
},
@@ -34,10 +34,10 @@ const sections = [
description: "JSON-RPC and REST endpoints for all chains",
icon: Globe,
items: [
{ name: "Overview", href: "/docs/node" },
{ name: "Ethereum", href: "/docs/node/ethereum" },
{ name: "Solana", href: "/docs/node/solana" },
{ name: "All Chains", href: "/docs/node/chains" },
{ name: "Overview", href: "/docs/api" },
{ name: "Ethereum", href: "/docs/api" },
{ name: "Solana", href: "/docs/api" },
{ name: "All Chains", href: "/docs/api" },
],
},
{
@@ -45,10 +45,10 @@ const sections = [
description: "Indexed blockchain data for analytics",
icon: FileCode,
items: [
{ name: "Blocks", href: "/docs/data/blocks" },
{ name: "Transactions", href: "/docs/data/transactions" },
{ name: "Tokens", href: "/docs/data/tokens" },
{ name: "NFTs", href: "/docs/data/nfts" },
{ name: "Blocks", href: "/docs/api" },
{ name: "Transactions", href: "/docs/api" },
{ name: "Tokens", href: "/docs/api" },
{ name: "NFTs", href: "/docs/api" },
],
},
{
@@ -56,10 +56,10 @@ const sections = [
description: "Real-time event notifications",
icon: Webhook,
items: [
{ name: "Setup", href: "/docs/webhooks/setup" },
{ name: "Events", href: "/docs/webhooks/events" },
{ name: "Signatures", href: "/docs/webhooks/signatures" },
{ name: "Retries", href: "/docs/webhooks/retries" },
{ name: "Setup", href: "/docs/webhooks/quickstart" },
{ name: "Events", href: "/docs/webhooks/quickstart" },
{ name: "Signatures", href: "/docs/webhooks/quickstart" },
{ name: "Retries", href: "/docs/webhooks/quickstart" },
],
},
{
@@ -67,10 +67,10 @@ const sections = [
description: "ERC-4337 account abstraction",
icon: Wallet,
items: [
{ name: "Overview", href: "/docs/wallets" },
{ name: "Creating Wallets", href: "/docs/wallets/create" },
{ name: "User Operations", href: "/docs/wallets/userops" },
{ name: "Session Keys", href: "/docs/wallets/sessions" },
{ name: "Overview", href: "/docs/wallets/quickstart" },
{ name: "Creating Wallets", href: "/docs/wallets/quickstart" },
{ name: "User Operations", href: "/docs/wallets/quickstart" },
{ name: "Session Keys", href: "/docs/wallets/quickstart" },
],
},
{
@@ -78,10 +78,10 @@ const sections = [
description: "Gas sponsorship and paymasters",
icon: Fuel,
items: [
{ name: "Policies", href: "/docs/gas/policies" },
{ name: "Paymasters", href: "/docs/gas/paymasters" },
{ name: "Sponsorship", href: "/docs/gas/sponsorship" },
{ name: "Limits", href: "/docs/gas/limits" },
{ name: "Policies", href: "/docs/aa/quickstart" },
{ name: "Paymasters", href: "/docs/aa/quickstart" },
{ name: "Sponsorship", href: "/docs/aa/quickstart" },
{ name: "Limits", href: "/docs/aa/quickstart" },
],
},
{
@@ -98,10 +98,10 @@ const sections = [
]
const sdks = [
{ name: "TypeScript", badge: "v2.0", href: "/docs/sdk/typescript" },
{ name: "Python", badge: "v2.0", href: "/docs/sdk/python" },
{ name: "Go", badge: "v1.0", href: "/docs/sdk/go" },
{ name: "Rust", badge: "v0.9", href: "/docs/sdk/rust" },
{ name: "TypeScript", badge: "v2.0", href: "/docs/sdks" },
{ name: "Python", badge: "v2.0", href: "/docs/sdks" },
{ name: "Go", badge: "v1.0", href: "/docs/sdks" },
{ name: "Rust", badge: "v0.9", href: "/docs/sdks" },
]
export default function DocsPage() {
+2 -5
View File
@@ -1,3 +1,5 @@
"use client"
import Link from "next/link"
import { Badge } from "@/components/ui/badge"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
@@ -5,11 +7,6 @@ import { DocsLayout } from "@/components/docs-layout"
import { ArrowRight } from "@luxfi/ui/icons"
import { docsConfig } from "@/lib/docs-config"
export const metadata = {
title: "Quickstart",
description: `Get started with ${docsConfig.brandName} in under 5 minutes.`,
}
export default function QuickstartPage() {
return (
<DocsLayout>
+2 -5
View File
@@ -1,3 +1,5 @@
"use client"
import Link from "next/link"
import { Badge } from "@/components/ui/badge"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
@@ -5,11 +7,6 @@ import { DocsLayout } from "@/components/docs-layout"
import { ArrowRight } from "@luxfi/ui/icons"
import { docsConfig } from "@/lib/docs-config"
export const metadata = {
title: "SDKs",
description: `Official ${docsConfig.brandName} client libraries for TypeScript, Python, Go, and Rust.`,
}
export default function SDKsPage() {
return (
<DocsLayout>
+2 -5
View File
@@ -1,3 +1,5 @@
"use client"
import Link from "next/link"
import { Badge } from "@/components/ui/badge"
import { Card, CardContent } from "@/components/ui/card"
@@ -5,11 +7,6 @@ import { DocsLayout } from "@/components/docs-layout"
import { ArrowRight } from "@luxfi/ui/icons"
import { docsConfig } from "@/lib/docs-config"
export const metadata = {
title: "Smart Wallets Quickstart",
description: `Create and use ERC-4337 smart wallets with ${docsConfig.brandName}.`,
}
export default function WalletsQuickstartPage() {
return (
<DocsLayout>
@@ -1,3 +1,5 @@
"use client"
import Link from "next/link"
import { Badge } from "@/components/ui/badge"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
@@ -5,11 +7,6 @@ import { DocsLayout } from "@/components/docs-layout"
import { ArrowRight } from "@luxfi/ui/icons"
import { docsConfig } from "@/lib/docs-config"
export const metadata = {
title: "Webhooks Quickstart",
description: `Set up real-time blockchain event notifications with ${docsConfig.brandName} Webhooks.`,
}
export default function WebhooksQuickstartPage() {
return (
<DocsLayout>
@@ -1,3 +1,5 @@
"use client"
import Link from "next/link"
import { Badge } from "@/components/ui/badge"
import { Card, CardContent } from "@/components/ui/card"
@@ -5,11 +7,6 @@ import { DocsLayout } from "@/components/docs-layout"
import { ArrowRight } from "@luxfi/ui/icons"
import { docsConfig } from "@/lib/docs-config"
export const metadata = {
title: "WebSockets Quickstart",
description: `Subscribe to real-time blockchain data with ${docsConfig.brandName} WebSockets.`,
}
export default function WebSocketsQuickstartPage() {
return (
<DocsLayout>
+2 -5
View File
@@ -1,3 +1,5 @@
"use client"
import Link from "next/link"
import { Badge } from "@/components/ui/badge"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
@@ -5,11 +7,6 @@ import { DocsLayout } from "@/components/docs-layout"
import { ArrowRight } from "@luxfi/ui/icons"
import { docsConfig } from "@/lib/docs-config"
export const metadata = {
title: "ZAP Protocol",
description: `Native ZAP (Zero-Copy App Proto) support for high-performance AI agent access to ${docsConfig.brandName}.`,
}
export default function ZapQuickstartPage() {
return (
<DocsLayout>
+17 -10
View File
@@ -1,17 +1,24 @@
import type { Metadata } from "next"
"use client"
import { useEffect } from "react"
import { useRouter } from "next/navigation"
import { Navbar } from "@/components/navbar"
import { Footer } from "@/components/footer"
import { FleetDashboard } from "@/components/lux/fleet-dashboard"
import { getBrand } from "@/lib/brand"
export const metadata: Metadata = {
title: "Node Fleet — Lux Cloud",
description:
"Live status of the managed luxd validator fleet across devnet, testnet and mainnet: health, peers, per-chain block heights, version, plugin set and DEX.",
}
import { useBrand } from "@/components/brand-provider"
// The fleet view is the brand's own node fleet. A brand that operates none has
// no page here, so the route sends its visitors to the landing.
export default function FleetPage() {
const brand = getBrand()
const brand = useBrand()
const router = useRouter()
useEffect(() => {
if (!brand.fleet) router.replace("/")
}, [brand.fleet, router])
if (!brand.fleet) return null
return (
<div className="flex min-h-screen flex-col">
<Navbar />
@@ -30,7 +37,7 @@ export default function FleetPage() {
</h1>
<p className="mt-3 text-muted-foreground">
Real-time view of the managed luxd node fleet. Health, peer counts,
per-chain block heights, software version and the native DEX — read
per-chain block heights, software version and the native DEX, read
live from each node, across every network.
</p>
</div>
+7 -27
View File
@@ -4,39 +4,19 @@ import { AuthProvider } from "@/lib/auth"
import { Providers } from "@/app/providers"
import { Toaster } from "@/components/ui/sonner"
import { BrandProvider } from "@/components/brand-provider"
import { getBrand } from "@/lib/brand"
import "@hanzo/font/css"
import "@/app/globals.css"
// `output: "export"` prerenders this at build time, where there is no request
// and therefore no Host header — so the static <head> can only carry the
// default brand, and the real one is resolved from the hostname after
// hydration by BrandProvider (which sets document.title and the favicon).
//
// This is the one honest way to white-label a static export. Baking a brand in
// at build time via NEXT_PUBLIC_BRAND is the alternative, and it is what this
// app used to do: it produced four near-identical images, three of which were
// wrong for whichever host they happened to be serving.
const fallback = getBrand("")
// and therefore no host to name a brand from. The static <head> carries no title
// and an empty icon, so it names no brand and the browser asks for no
// /favicon.ico; BrandProvider writes the host's title and favicon in the browser,
// and with no title element of the layout's own there is nothing to overwrite it.
export const metadata: Metadata = {
title: { default: `${fallback.name} — ${fallback.tagline}`, template: `%s | ${fallback.name}` },
description: fallback.description,
icons: { icon: "data:," },
description:
"Multi-chain RPC, token and NFT APIs, smart wallets, webhooks and account abstraction.",
keywords: ["blockchain", "RPC", "API", "Web3", "NFT", "DeFi", "smart contracts", "developer tools"],
openGraph: {
type: "website",
locale: "en_US",
url: `https://${fallback.domain}`,
siteName: fallback.name,
title: `${fallback.name} — ${fallback.tagline}`,
description: fallback.description,
},
twitter: {
card: "summary_large_image",
title: `${fallback.name} — ${fallback.tagline}`,
description: fallback.description,
},
icons: { icon: fallback.favicon, apple: fallback.favicon, shortcut: fallback.favicon },
}
export default function RootLayout({ children }: { children: React.ReactNode }) {
+8 -12
View File
@@ -1,19 +1,15 @@
import { getBrand } from "@/lib/brand"
export const dynamic = "force-static"
// One static file serves every white-label host, and a static export has no
// host to name a brand from. So this file names none: the API origin is the
// one each host's own API reference states, at /docs/api.
export async function GET() {
const brand = getBrand()
const apiDomain = brand.name === "Hanzo" ? "api.hanzo.ai" : `api.${brand.domain}`
const docsDomain = brand.name === "Hanzo" ? "web3.hanzo.ai" : brand.domain
const content = `# Blockchain infrastructure API
const content = `# ${brand.name}
## ${brand.tagline}
> ${brand.description}
> Multi-chain RPC, token and NFT APIs, smart wallets, webhooks and account abstraction.
## Products
- Node: Multi-chain RPC with 99.999% uptime across 100+ chains
- Node: Multi-chain JSON-RPC across 100+ chains
- Data: Token, NFT, and Transfer APIs
- Wallets: ERC-4337 Smart Wallets with Account Abstraction
- Webhooks: Real-time onchain event notifications
@@ -22,7 +18,7 @@ export async function GET() {
- Rollups: Launch custom rollups
## API Base URL
https://${apiDomain}/v1
The API origin for this host is listed at /docs/api. Every path below is relative to it.
## Authentication
X-API-Key: <your-api-key>
@@ -51,7 +47,7 @@ X-API-Key: <your-api-key>
Ethereum, Solana, Base, Arbitrum, Polygon, Optimism, Avalanche, BNB Smart Chain, Lux Network, and 90+ more.
## Documentation
https://${docsDomain}/docs
/docs on this host
`
return new Response(content, {
headers: { "Content-Type": "text/plain; charset=utf-8" },
+9 -7
View File
@@ -1,3 +1,5 @@
"use client"
import Link from "next/link"
import { Button } from "@/components/ui/button"
import { Badge } from "@/components/ui/badge"
@@ -85,7 +87,7 @@ export default function HomePage() {
<ProductCard
icon={<Globe className="h-6 w-6" />}
title="Node"
description="Build and scale your app on the most powerful web3 development platform with 99.999% uptime."
description="Build and scale your app on one multi-chain JSON-RPC endpoint."
href="/products/node"
/>
<ProductCard
@@ -115,7 +117,7 @@ export default function HomePage() {
<ProductCard
icon={<Layers className="h-6 w-6" />}
title="Rollups"
description="Launch a custom rollup with native developer tools and scale to millions of users."
description="Launch a custom rollup with native developer tools."
href="/products/rollups"
/>
</div>
@@ -137,13 +139,13 @@ export default function HomePage() {
<div className="mt-8 space-y-4">
<FeatureItem
icon={<Zap className="h-5 w-5 text-yellow-500" />}
title="Lightning Fast"
description="Sub-100ms response times with global edge caching"
title="Cached reads"
description="Repeat reads are served from a per-method response cache"
/>
<FeatureItem
icon={<Shield className="h-5 w-5 text-green-500" />}
title="99.999% Uptime"
description="Enterprise-grade reliability with automatic failover"
title="One API key"
description="RPC, token, NFT and transfer data behind a single key"
/>
<FeatureItem
icon={<Globe className="h-5 w-5 text-blue-500" />}
@@ -317,7 +319,7 @@ export default function HomePage() {
Ready to build the future?
</h2>
<p className="mt-4 text-lg text-white/80">
Join thousands of developers building on {brand.name}. Get started for
Start building on {brand.name}. Get started for
free with 100M compute units per month.
</p>
<div className="mt-10 flex flex-col justify-center gap-4 sm:flex-row">
+3 -8
View File
@@ -1,5 +1,6 @@
"use client"
import Link from "next/link"
import type { Metadata } from "next"
import { Navbar } from "@/components/navbar"
import { Footer } from "@/components/footer"
import { Button } from "@/components/ui/button"
@@ -23,12 +24,6 @@ import {
} from "@luxfi/ui/icons"
import { getBrand } from "@/lib/brand"
export const metadata: Metadata = {
title: "Pricing",
description:
"Simple, transparent pricing for blockchain infrastructure. Start free with 30M compute units per month.",
}
const tiers = [
{
name: "Free",
@@ -187,7 +182,7 @@ const faqs = [
{
question: "How can I upgrade to Enterprise?",
answer:
"Contact our sales team to discuss your requirements. Enterprise plans include custom throughput limits, volume discounts, SLAs, dedicated support with 5-minute response times, and priority access to new features. We will work with you to create a plan that fits your needs.",
"Contact our sales team to discuss your requirements. Enterprise plans include custom throughput limits, volume discounts, SLAs, dedicated support, and priority access to new features. We will work with you to create a plan that fits your needs.",
},
]
+2 -7
View File
@@ -1,5 +1,6 @@
"use client"
import Link from "next/link"
import type { Metadata } from "next"
import { Navbar } from "@/components/navbar"
import { Footer } from "@/components/footer"
import { Badge } from "@/components/ui/badge"
@@ -8,12 +9,6 @@ import { getBrand } from "@/lib/brand"
const brand = getBrand()
export const metadata: Metadata = {
title: "Privacy Policy",
description:
`${brand.name} Privacy Policy. Learn how we collect, use, and protect your personal information.`,
}
export default function PrivacyPage() {
return (
<div className="flex min-h-screen flex-col">
+2 -2
View File
@@ -58,7 +58,7 @@ export default function DataProductPage() {
</Link>
</Button>
<Button size="xl" variant="outline" asChild>
<Link href="/docs/data">API Reference</Link>
<Link href="/docs/api">API Reference</Link>
</Button>
</div>
</div>
@@ -351,7 +351,7 @@ export default function DataProductPage() {
className="border-white/30 bg-transparent text-white hover:bg-white/10"
asChild
>
<Link href="/docs/data">Explore the Docs</Link>
<Link href="/docs/api">Explore the Docs</Link>
</Button>
</div>
</div>
+4 -25
View File
@@ -58,25 +58,13 @@ export default function ExplorerProductPage() {
</Link>
</Button>
<Button size="xl" variant="outline" asChild>
<Link href="/docs/explorer">Read the Docs</Link>
<Link href="/docs/api">Read the Docs</Link>
</Button>
</div>
</div>
</div>
</section>
{/* Stats */}
<section className="border-b bg-muted/30 py-12">
<div className="container">
<div className="grid grid-cols-2 gap-8 md:grid-cols-4">
<StatItem label="Explorers Deployed" value="120+" />
<StatItem label="Indexed Blocks" value="5B+" />
<StatItem label="Verified Contracts" value="800K+" />
<StatItem label="Time to Deploy" value="<5 min" />
</div>
</div>
</section>
{/* Why Your Own Explorer */}
<section className="border-b py-24">
<div className="container">
@@ -106,7 +94,7 @@ export default function ExplorerProductPage() {
},
{
title: "Works with any EVM chain",
desc: "Ethereum L1, OP Stack rollups, Arbitrum Orbit, Lux subnets, or any EVM-compatible network.",
desc: "Ethereum L1, OP Stack rollups, Arbitrum Orbit, Lux chains, or any EVM-compatible network.",
},
].map((item) => (
<li key={item.title} className="flex items-start gap-3">
@@ -271,7 +259,7 @@ export default function ExplorerProductPage() {
</p>
<div className="mt-8">
<Button asChild>
<Link href="/docs/explorer/api">
<Link href="/docs/api">
API Reference
<ArrowRight className="ml-2 h-4 w-4" />
</Link>
@@ -341,7 +329,7 @@ curl "https://explorer.yourchain.com/api\\
className="border-white/30 bg-transparent text-white hover:bg-white/10"
asChild
>
<Link href="/docs/explorer">Read the Docs</Link>
<Link href="/docs/api">Read the Docs</Link>
</Button>
</div>
</div>
@@ -354,15 +342,6 @@ curl "https://explorer.yourchain.com/api\\
)
}
function StatItem({ label, value }: { label: string; value: string }) {
return (
<div className="text-center">
<div className="text-2xl font-bold md:text-3xl">{value}</div>
<div className="mt-1 text-sm text-muted-foreground">{label}</div>
</div>
)
}
function FeatureCard({
icon,
title,
+8 -29
View File
@@ -47,8 +47,8 @@ export default function MPCProductPage() {
</h1>
<p className="mt-6 max-w-2xl text-lg text-muted-foreground md:text-xl">
Launch a distributed MPC node cluster for institutional-grade key
management. Threshold signatures across 15+ chains—no single node
ever holds the full private key. Powered by Lux MPC.
management. Threshold signatures across 15+ chains, and no single
node ever holds the full private key.
</p>
<div className="mt-10 flex flex-col gap-4 sm:flex-row">
<Button size="xl" asChild>
@@ -58,25 +58,13 @@ export default function MPCProductPage() {
</Link>
</Button>
<Button size="xl" variant="outline" asChild>
<Link href="/docs/mpc">Read the Docs</Link>
<Link href="/docs/api">Read the Docs</Link>
</Button>
</div>
</div>
</div>
</section>
{/* Stats */}
<section className="border-b bg-muted/30 py-12">
<div className="container">
<div className="grid grid-cols-2 gap-8 md:grid-cols-4">
<StatItem label="Supported Chains" value="15+" />
<StatItem label="Signing Latency" value="<500ms" />
<StatItem label="Key Schemes" value="3" />
<StatItem label="Uptime SLA" value="99.99%" />
</div>
</div>
</section>
{/* Why MPC */}
<section className="border-b py-24">
<div className="container">
@@ -261,7 +249,7 @@ export default function MPCProductPage() {
<FeatureCard
icon={<Users className="h-6 w-6" />}
title="JWT & OIDC Authentication"
description="REST API secured with JWT tokens, OIDC login via Lux ID / Hanzo ID, role-based API keys, MFA support, and full audit logging."
description={`REST API secured with JWT tokens, OIDC login via ${brand.iam.idLabel}, role-based API keys, MFA support, and full audit logging.`}
/>
<FeatureCard
icon={<Globe className="h-6 w-6" />}
@@ -313,7 +301,7 @@ export default function MPCProductPage() {
<StepCard
step="4"
title="Sign"
description="Submit signing requests via REST API. Any t-of-n nodes produce a valid ECDSA or EdDSA signature in under 500ms."
description="Submit signing requests via REST API. Any t-of-n nodes produce a valid ECDSA or EdDSA signature."
/>
</div>
</div>
@@ -333,7 +321,7 @@ export default function MPCProductPage() {
</p>
<div className="mt-8">
<Button asChild>
<Link href="/docs/mpc/api">
<Link href="/docs/api">
API Reference
<ArrowRight className="ml-2 h-4 w-4" />
</Link>
@@ -404,7 +392,7 @@ curl -X POST https://mpc.yourorg.com/api/v1/sign \\
/>
<UseCaseCard
title="Custodial Wallets"
description="Offer institutional custody with distributed key management. SOC 2 and regulatory compliance built in."
description="Offer institutional custody with distributed key management."
/>
<UseCaseCard
title="DeFi Protocols"
@@ -453,7 +441,7 @@ curl -X POST https://mpc.yourorg.com/api/v1/sign \\
className="border-white/30 bg-transparent text-white hover:bg-white/10"
asChild
>
<Link href="/docs/mpc">Read the Docs</Link>
<Link href="/docs/api">Read the Docs</Link>
</Button>
</div>
</div>
@@ -466,15 +454,6 @@ curl -X POST https://mpc.yourorg.com/api/v1/sign \\
)
}
function StatItem({ label, value }: { label: string; value: string }) {
return (
<div className="text-center">
<div className="text-2xl font-bold md:text-3xl">{value}</div>
<div className="mt-1 text-sm text-muted-foreground">{label}</div>
</div>
)
}
function FeatureCard({
icon,
title,
+6 -27
View File
@@ -45,9 +45,9 @@ export default function NodeProductPage() {
</span>
</h1>
<p className="mt-6 max-w-2xl text-lg text-muted-foreground md:text-xl">
Access 100+ blockchain networks through a single, reliable RPC
endpoint. Sub-100ms latency, 99.999% uptime, and zero
configuration. Just plug in your API key and start building.
Access 100+ blockchain networks through a single RPC endpoint
with zero configuration. Just plug in your API key and start
building.
</p>
<div className="mt-10 flex flex-col gap-4 sm:flex-row">
<Button size="xl" asChild>
@@ -57,25 +57,13 @@ export default function NodeProductPage() {
</Link>
</Button>
<Button size="xl" variant="outline" asChild>
<Link href="/docs/rpc">Read the Docs</Link>
<Link href="/docs/api">Read the Docs</Link>
</Button>
</div>
</div>
</div>
</section>
{/* Stats Bar */}
<section className="border-b bg-muted/30 py-8">
<div className="container">
<div className="grid grid-cols-2 gap-8 md:grid-cols-4">
<StatItem label="Chains Supported" value="100+" />
<StatItem label="Uptime SLA" value="99.999%" />
<StatItem label="Avg. Latency" value="<80ms" />
<StatItem label="Requests / Day" value="10B+" />
</div>
</div>
</section>
{/* Features Grid */}
<section className="border-b py-24">
<div className="container">
@@ -91,8 +79,8 @@ export default function NodeProductPage() {
<div className="mt-16 grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
<FeatureCard
icon={<Zap className="h-6 w-6" />}
title="Sub-100ms Latency"
description="Responses served from the nearest edge location in our global network. Intelligent request routing ensures every call takes the fastest path to the blockchain."
title="Response caching"
description="Read methods are cached per method, so repeated calls return without another round trip to the node."
/>
<FeatureCard
icon={<Shield className="h-6 w-6" />}
@@ -295,15 +283,6 @@ export default function NodeProductPage() {
)
}
function StatItem({ label, value }: { label: string; value: string }) {
return (
<div className="text-center">
<div className="text-2xl font-bold md:text-3xl">{value}</div>
<div className="mt-1 text-sm text-muted-foreground">{label}</div>
</div>
)
}
function FeatureCard({
icon,
title,
+5 -26
View File
@@ -60,25 +60,13 @@ export default function RollupsProductPage() {
</Link>
</Button>
<Button size="xl" variant="outline" asChild>
<Link href="/docs/rollups">Read the Docs</Link>
<Link href="/docs">Read the Docs</Link>
</Button>
</div>
</div>
</div>
</section>
{/* Stats */}
<section className="border-b bg-muted/30 py-12">
<div className="container">
<div className="grid grid-cols-2 gap-8 md:grid-cols-4">
<StatItem label="Rollups Deployed" value="50+" />
<StatItem label="Transactions / sec" value="2,000+" />
<StatItem label="Gas Cost vs L1" value="~95% less" />
<StatItem label="Time to Launch" value="<1 day" />
</div>
</div>
</section>
{/* Why a Rollup */}
<section className="border-b py-24">
<div className="container">
@@ -108,8 +96,8 @@ export default function RollupsProductPage() {
desc: "All state roots are posted to Ethereum L1. Your rollup inherits the full security guarantees of Ethereum consensus.",
},
{
title: "Scale to millions",
desc: "Process 2,000+ transactions per second with sub-second finality. Scale with demand, not with gas wars.",
title: "Scale with demand",
desc: "Throughput grows with your users, not with gas wars.",
},
].map((item) => (
<li key={item.title} className="flex items-start gap-3">
@@ -179,7 +167,7 @@ export default function RollupsProductPage() {
<FeatureCard
icon={<Rocket className="h-6 w-6" />}
title="Managed Sequencer"
description={`High-availability sequencer with sub-second block times. ${brand.name} runs and monitors the sequencer so your chain stays live 24/7 with 99.99% uptime SLA.`}
description={`High-availability sequencer. ${brand.name} runs and monitors the sequencer so your chain stays live.`}
/>
<FeatureCard
icon={<Network className="h-6 w-6" />}
@@ -292,7 +280,7 @@ export default function RollupsProductPage() {
className="border-white/30 bg-transparent text-white hover:bg-white/10"
asChild
>
<Link href="/docs/rollups">Read the Docs</Link>
<Link href="/docs">Read the Docs</Link>
</Button>
</div>
</div>
@@ -305,15 +293,6 @@ export default function RollupsProductPage() {
)
}
function StatItem({ label, value }: { label: string; value: string }) {
return (
<div className="text-center">
<div className="text-2xl font-bold md:text-3xl">{value}</div>
<div className="mt-1 text-sm text-muted-foreground">{label}</div>
</div>
)
}
function FeatureCard({
icon,
title,
+1 -22
View File
@@ -60,25 +60,13 @@ export default function WalletsProductPage() {
</Link>
</Button>
<Button size="xl" variant="outline" asChild>
<Link href="/docs/wallets">Read the Docs</Link>
<Link href="/docs/wallets/quickstart">Read the Docs</Link>
</Button>
</div>
</div>
</div>
</section>
{/* Key Benefits */}
<section className="border-b bg-muted/30 py-12">
<div className="container">
<div className="grid grid-cols-2 gap-8 md:grid-cols-4">
<StatItem label="Wallets Created" value="2M+" />
<StatItem label="Gas Sponsored" value="$4.2M" />
<StatItem label="Avg. Onboarding" value="<3 sec" />
<StatItem label="Chains Supported" value="30+" />
</div>
</div>
</section>
{/* Features Grid */}
<section className="border-b py-24">
<div className="container">
@@ -287,15 +275,6 @@ console.log(txHash);
)
}
function StatItem({ label, value }: { label: string; value: string }) {
return (
<div className="text-center">
<div className="text-2xl font-bold md:text-3xl">{value}</div>
<div className="mt-1 text-sm text-muted-foreground">{label}</div>
</div>
)
}
function FeatureCard({
icon,
title,
+2 -23
View File
@@ -60,25 +60,13 @@ export default function WebhooksProductPage() {
</Link>
</Button>
<Button size="xl" variant="outline" asChild>
<Link href="/docs/webhooks">Read the Docs</Link>
<Link href="/docs/webhooks/quickstart">Read the Docs</Link>
</Button>
</div>
</div>
</div>
</section>
{/* Stats */}
<section className="border-b bg-muted/30 py-12">
<div className="container">
<div className="grid grid-cols-2 gap-8 md:grid-cols-4">
<StatItem label="Events Delivered / Day" value="500M+" />
<StatItem label="Avg. Delivery" value="<200ms" />
<StatItem label="Delivery Success" value="99.98%" />
<StatItem label="Event Types" value="7" />
</div>
</div>
</section>
{/* Event Types */}
<section className="border-b py-24">
<div className="container">
@@ -282,7 +270,7 @@ export default function WebhooksProductPage() {
className="border-white/30 bg-transparent text-white hover:bg-white/10"
asChild
>
<Link href="/docs/webhooks">View Full Docs</Link>
<Link href="/docs/webhooks/quickstart">View Full Docs</Link>
</Button>
</div>
</div>
@@ -295,15 +283,6 @@ export default function WebhooksProductPage() {
)
}
function StatItem({ label, value }: { label: string; value: string }) {
return (
<div className="text-center">
<div className="text-2xl font-bold md:text-3xl">{value}</div>
<div className="mt-1 text-sm text-muted-foreground">{label}</div>
</div>
)
}
function EventTypeCard({
name,
description,
+21 -35
View File
@@ -1,5 +1,6 @@
"use client"
import Link from "next/link"
import type { Metadata } from "next"
import { Navbar } from "@/components/navbar"
import { Footer } from "@/components/footer"
import { Button } from "@/components/ui/button"
@@ -18,19 +19,7 @@ import { getBrand } from "@/lib/brand"
const brand = getBrand()
export const metadata: Metadata = {
title: "Security",
description:
`${brand.name}'s security practices: SOC 2 compliance, data encryption, API key hashing, HMAC webhook signing, rate limiting, and responsible disclosure.`,
}
const practices = [
{
icon: <ShieldCheck className="h-6 w-6" />,
title: "SOC 2 Type II Compliance",
description:
`${brand.name} has completed SOC 2 Type II certification, independently verifying that our systems and processes meet the highest standards for security, availability, processing integrity, confidentiality, and privacy. Our SOC 2 report is available to enterprise customers under NDA.`,
},
{
icon: <Lock className="h-6 w-6" />,
title: "Data Encryption",
@@ -53,13 +42,13 @@ const practices = [
icon: <Network className="h-6 w-6" />,
title: "Rate Limiting & DDoS Protection",
description:
"All API endpoints are protected by multi-layer rate limiting at the edge, application, and per-key levels. Our infrastructure is fronted by enterprise-grade DDoS protection capable of absorbing volumetric attacks exceeding 1 Tbps. Adaptive rate limiting automatically adjusts to abnormal traffic patterns.",
"All API endpoints are protected by multi-layer rate limiting at the edge, application, and per-key levels. Our infrastructure is fronted by DDoS protection at the edge.",
},
{
icon: <Shield className="h-6 w-6" />,
title: "Infrastructure Security",
description:
"Our infrastructure runs on hardened, purpose-built systems with minimal attack surface. All systems are patched within 24 hours of critical CVE disclosure. We use network segmentation, least-privilege IAM policies, and immutable infrastructure patterns. No engineer has standing access to production systems -- all access requires just-in-time approval with full audit logging.",
"Our infrastructure runs on hardened, purpose-built systems with minimal attack surface. We use network segmentation, least-privilege IAM policies, and immutable infrastructure patterns. No engineer has standing access to production systems -- all access requires just-in-time approval with full audit logging.",
},
]
@@ -80,8 +69,8 @@ export default function SecurityPage() {
Security at {brand.name}
</h1>
<p className="mt-6 text-lg text-muted-foreground">
We take security seriously. {brand.name} is SOC 2 Type II certified and
implements defense-in-depth across every layer of our infrastructure.
We take security seriously. {brand.name} implements defense-in-depth
across every layer of our infrastructure.
Your API keys, data, and webhook payloads are protected by
industry-leading security practices.
</p>
@@ -126,17 +115,14 @@ export default function SecurityPage() {
<h3 className="font-semibold">Penetration Testing</h3>
<p className="mt-2 text-sm text-muted-foreground leading-relaxed">
We conduct annual third-party penetration tests covering our API
endpoints, dashboard application, and infrastructure. Critical findings
are remediated within 48 hours. Pentest reports are available to
enterprise customers under NDA.
endpoints, dashboard application, and infrastructure.
</p>
</div>
<div className="rounded-lg border bg-card p-6">
<h3 className="font-semibold">Dependency Management</h3>
<p className="mt-2 text-sm text-muted-foreground leading-relaxed">
Automated dependency scanning runs on every pull request using
Dependabot and Snyk. Critical vulnerabilities in dependencies are
patched within 24 hours. We maintain a software bill of materials (SBOM)
Dependabot and Snyk. We maintain a software bill of materials (SBOM)
for all production services.
</p>
</div>
@@ -154,8 +140,8 @@ export default function SecurityPage() {
<p className="mt-2 text-sm text-muted-foreground leading-relaxed">
We maintain a documented incident response plan with defined severity
levels, escalation paths, and communication procedures. All security
incidents are reviewed in post-incident reviews. Critical incidents are
communicated to affected customers within 24 hours.
incidents are reviewed in post-incident reviews, and affected customers
are told about critical incidents.
</p>
</div>
<div className="rounded-lg border bg-card p-6">
@@ -170,8 +156,8 @@ export default function SecurityPage() {
<div className="rounded-lg border bg-card p-6">
<h3 className="font-semibold">Compliance</h3>
<p className="mt-2 text-sm text-muted-foreground leading-relaxed">
In addition to SOC 2 Type II, we align our practices with the OWASP
Top 10, CIS Benchmarks, and NIST Cybersecurity Framework. GDPR data
We align our practices with the OWASP Top 10, CIS Benchmarks, and
NIST Cybersecurity Framework. GDPR data
processing agreements are available for customers operating in the EU.
</p>
</div>
@@ -195,15 +181,15 @@ export default function SecurityPage() {
<div className="space-y-4 text-sm text-muted-foreground leading-relaxed">
<p>
We value the security research community and welcome responsible
disclosure of vulnerabilities. If you discover a security issue in
Hanzo Web3, please report it to us privately so we can address it before
disclosure of vulnerabilities. If you discover a security issue
in {brand.name}, please report it to us privately so we can address it before
public disclosure.
</p>
<h4 className="font-semibold text-foreground">Scope</h4>
<ul className="ml-4 list-disc space-y-1">
<li>Hanzo API endpoints (api.hanzo.ai)</li>
<li>Hanzo dashboard (web3.hanzo.ai/dashboard)</li>
<li>Hanzo documentation site (web3.hanzo.ai/docs)</li>
<li>{brand.name} API endpoints ({brand.apiUrl})</li>
<li>{brand.name} dashboard ({brand.domain}/dashboard)</li>
<li>{brand.name} documentation site ({brand.domain}/docs)</li>
<li>Authentication and authorization systems</li>
<li>Webhook delivery and signature verification</li>
</ul>
@@ -225,8 +211,8 @@ export default function SecurityPage() {
security@hanzo.ai
</a>
. Include a detailed description, steps to reproduce, and any proof-of-concept
code. We will acknowledge receipt within 24 hours and provide an initial
assessment within 72 hours.
code. We will acknowledge receipt and follow up with an initial
assessment.
</p>
<p>
We do not currently offer monetary bounties but will credit researchers
@@ -247,8 +233,8 @@ export default function SecurityPage() {
Questions about security?
</h2>
<p className="mt-4 text-muted-foreground">
Our team is happy to discuss our security practices, provide SOC 2 reports,
or answer compliance questions.
Our team is happy to discuss our security practices or answer compliance
questions.
</p>
<div className="mt-8 flex flex-col justify-center gap-4 sm:flex-row">
<Button size="lg" asChild>
+2 -7
View File
@@ -1,5 +1,6 @@
"use client"
import Link from "next/link"
import type { Metadata } from "next"
import { Navbar } from "@/components/navbar"
import { Footer } from "@/components/footer"
import { Badge } from "@/components/ui/badge"
@@ -8,12 +9,6 @@ import { getBrand } from "@/lib/brand"
const brand = getBrand()
export const metadata: Metadata = {
title: "Terms of Service",
description:
`${brand.name} Terms of Service. The terms and conditions governing your use of the ${brand.name} platform.`,
}
export default function TermsPage() {
return (
<div className="flex min-h-screen flex-col">
+26 -16
View File
@@ -2,12 +2,16 @@
// BrandProvider — publishes the hostname-resolved brand to the tree.
//
// `getBrand()` is already a pure function of the hostname, so this provider
// loads nothing: no /config.json fetch, no window.__BRAND_CATALOG__. It exists
// for exactly one reason — `output: "export"` prerenders every page with NO
// host, so the static HTML is necessarily the default brand. One
// post-hydration re-resolve swaps in the real one and re-renders every
// consumer. That is the whole job.
// `getBrand()` is a pure function of the hostname, and a static export has no
// hostname when it prerenders. So the tree renders only in the browser, once
// the host is known: the exported HTML carries no brand, and every client
// component that reads the brand renders with the brand of the host it is
// served on. Nothing renders twice with two different brands, so there is no
// hydration mismatch to repair.
//
// A server component is rendered at build time, where the brand is always the
// default one, so a page that shows the brand is a client component, and
// server-authored markup names the brand through <BrandName />.
//
// It also owns the two brand-derived document facts a static <head> cannot
// know: the title and the favicon.
@@ -18,25 +22,31 @@ import { getBrand, type BrandConfig } from "@/lib/brand"
const BrandContext = createContext<BrandConfig>(getBrand(""))
export function BrandProvider({ children }: { children: React.ReactNode }) {
// The first render must match the server's markup, so it uses the same
// host-less default; the effect below corrects it immediately after mount.
const [brand, setBrand] = useState<BrandConfig>(() => getBrand(""))
const [brand, setBrand] = useState<BrandConfig | null>(null)
useEffect(() => {
const resolved = getBrand()
setBrand(resolved)
document.title = `${resolved.name} — ${resolved.tagline}`
document.querySelector<HTMLLinkElement>('link[rel="icon"]')?.setAttribute("href", resolved.favicon)
document.title = `${resolved.name} | ${resolved.tagline}`
let icon = document.querySelector<HTMLLinkElement>('link[rel="icon"]')
if (!icon) {
icon = document.createElement("link")
icon.rel = "icon"
document.head.appendChild(icon)
}
icon.href = resolved.favicon
}, [])
if (!brand) return null
return <BrandContext.Provider value={brand}>{children}</BrandContext.Provider>
}
/**
* The active brand, context-backed so every consumer re-renders when the
* hostname resolve lands. Components must use this rather than calling
* `getBrand()` directly, or they keep rendering the prerendered default.
*/
/** The active brand. Every consumer renders after the host has resolved it. */
export function useBrand(): BrandConfig {
return useContext(BrandContext)
}
/** The brand's name as text, for pages whose markup is authored on the server. */
export function BrandName() {
return <>{useBrand().name}</>
}
+3 -5
View File
@@ -5,7 +5,7 @@ import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
import { Badge } from "@/components/ui/badge"
import { Check, Copy, Play } from "@luxfi/ui/icons"
import { getBrand } from "@/lib/brand"
import { useBrand } from "@/components/brand-provider"
const CHAINS = ["Ethereum", "Solana", "Base", "Arbitrum", "Polygon"]
const METHODS = [
@@ -22,10 +22,8 @@ export function CodeDemo() {
const [response, setResponse] = useState<string | null>(null)
const [loading, setLoading] = useState(false)
const brand = getBrand()
// Use api.web3.hanzo.ai for Hanzo branding (brand.name is "Hanzo Web3")
const apiDomain = brand.name.includes("Hanzo") ? "api.web3.hanzo.ai" : `api.${brand.domain}`
const code = `curl -X POST https://${apiDomain}/v1/rpc/${selectedChain.toLowerCase()}/mainnet \\
const brand = useBrand()
const code = `curl -X POST ${brand.apiUrl}/v1/rpc/${selectedChain.toLowerCase()}/mainnet \\
-H "Content-Type: application/json" \\
-H "X-API-Key: your_api_key" \\
-d '{
+5 -2
View File
@@ -3,6 +3,7 @@
import { useEffect, useState } from "react"
import Link from "next/link"
import { ArrowRight, Circle } from "@luxfi/ui/icons"
import { useBrand } from "@/components/brand-provider"
import {
NETWORK_LABEL,
fetchFleetOverview,
@@ -18,9 +19,11 @@ const DOT: Record<string, string> = {
/** Compact live fleet status strip for the landing hero. Public, read-only. */
export function FleetStrip() {
const { fleet } = useBrand()
const [ov, setOv] = useState<LuxFleetOverview | null>(null)
useEffect(() => {
if (!fleet) return
let alive = true
const ctrl = new AbortController()
const load = () =>
@@ -34,9 +37,9 @@ export function FleetStrip() {
ctrl.abort()
clearInterval(t)
}
}, [])
}, [fleet])
if (!ov) return null
if (!fleet || !ov) return null
const totals = ov.totals ?? {}
+5 -3
View File
@@ -100,7 +100,7 @@ function ProductsContent({ closeMenu }: { closeMenu: () => void }) {
<ArrowRight className="h-3.5 w-3.5 text-muted-foreground/40 group-hover:text-muted-foreground transition-colors" />
</div>
<p className="text-xs text-muted-foreground leading-snug mt-0.5">
Multi-chain JSON-RPC with 99.999% uptime
Multi-chain JSON-RPC behind one API key
</p>
</div>
</Link>
@@ -352,12 +352,14 @@ function SolutionsContent({ closeMenu }: { closeMenu: () => void }) {
// the previous header wrote them twice and the two copies had already drifted
// (Chains was in the sheet only).
const PRIMARY_LINKS = [
{ name: "Fleet", href: "/fleet" },
{ name: "Chains", href: "/chains" },
{ name: "Docs", href: "/docs" },
{ name: "Pricing", href: "/pricing" },
]
// The fleet view belongs to the brand that operates a fleet (lib/brand.ts).
const FLEET_LINK = { name: "Fleet", href: "/fleet" }
// The product pages, for the phone sheet — which has no room for the desktop
// mega-menu. Every href here is a route the build actually emits. Note there
// is deliberately no bare `/products`: it is a DIRECTORY of pages with no
@@ -466,7 +468,7 @@ export function Navbar() {
</Link>
))}
</span>
{PRIMARY_LINKS.map((link) => (
{(brand.fleet ? [FLEET_LINK, ...PRIMARY_LINKS] : PRIMARY_LINKS).map((link) => (
<Link
key={link.href}
href={link.href}
+16
View File
@@ -38,6 +38,22 @@ describe("white-label by hostname", () => {
})
}
it("serves the Hanzo API from the console's own origin", () => {
// web3.hanzo.ai routes /v1 to the bootnode service, so a Hanzo page never
// calls a bootno.de address.
const hanzo = getBrand("web3.hanzo.ai")
assert.equal(hanzo.apiUrl, "https://web3.hanzo.ai")
assert.equal(hanzo.wsUrl, "wss://web3.hanzo.ai")
assert.equal(getBrand("bootno.de").apiUrl, "https://api.bootno.de")
})
it("shows a node fleet only on the brand that operates one", () => {
assert.equal(getBrand("lux.cloud").fleet, "lux")
for (const host of ["bootno.de", "web3.hanzo.ai", "zoo.cloud", "cloud.pars.network"]) {
assert.equal(getBrand(host).fleet, null, host)
}
})
it("does not regress the one login that works today", () => {
// lux.cloud + lux-cloud + https://lux.cloud/auth/callback is the triple
// that answers 302 on lux.id and lands a real user on /dashboard. If this
+15 -9
View File
@@ -55,6 +55,7 @@ export type BrandConfig = {
statusUrl: string
networkName: string // e.g. "Lux Network" — header/footer chrome
networkUrl: string
fleet: "lux" | null // the node fleet this brand operates, shown on the landing and at /fleet
skillsOrg: string // GitHub org for the skills repo
defaultTheme: "light" | "dark"
colors: {
@@ -81,13 +82,13 @@ export type BrandConfig = {
}[]
}
// The bootnode control plane. api.bootno.de, api.web3.hanzo.ai and
// api.cloud.lux.network are the SAME service behind three names — verified
// 2026-07-26, /v1/chains byte-identical across all three
// (sha256 2aa472c11e47d3f67e53be17fb24230fccc94630b485e6f3864d51fa2a4e1400).
// api.cloud.zoo.network and api.cloud.pars.network do NOT resolve, so Zoo and
// Pars point at the one origin that answers rather than at a derived name that
// would fail. Per-brand vanity origins can be added the day they exist.
// The bootnode control plane. Every brand without an origin of its own reaches
// the one service at api.bootno.de.
//
// Hanzo is served from its own host: web3.hanzo.ai routes /v1 to the same
// service, so the console calls its API on the origin it was loaded from. A
// second-level name such as api.web3.hanzo.ai has no edge certificate, and a
// Hanzo page has no reason to reach a bootno.de address.
const API = "https://api.bootno.de"
const WS = "wss://ws.bootno.de"
@@ -113,6 +114,7 @@ const BRANDS: Record<BrandKey, Omit<BrandConfig, "iam">> = {
statusUrl: "https://status.bootno.de",
networkName: "Bootnode",
networkUrl: "https://bootno.de",
fleet: null,
skillsOrg: "bootnode",
defaultTheme: "dark",
colors: { primary: "#f5f5f5", primaryForeground: "#000000", accent: "#a8a8ff", accentEnd: "#8bffd1" },
@@ -139,6 +141,7 @@ const BRANDS: Record<BrandKey, Omit<BrandConfig, "iam">> = {
statusUrl: "https://status.lux.network",
networkName: "Lux Network",
networkUrl: "https://lux.network",
fleet: "lux",
skillsOrg: "luxfi",
defaultTheme: "dark",
colors: { primary: "#f5f5f5", primaryForeground: "#000000", accent: "#a8a8ff", accentEnd: "#8bffd1" },
@@ -169,6 +172,7 @@ const BRANDS: Record<BrandKey, Omit<BrandConfig, "iam">> = {
statusUrl: "https://status.zoo.network",
networkName: "Zoo Network",
networkUrl: "https://zoo.network",
fleet: null,
skillsOrg: "zooai",
defaultTheme: "dark",
colors: { primary: "#f5f5f5", primaryForeground: "#000000", accent: "#8bffd1", accentEnd: "#a8a8ff" },
@@ -190,11 +194,12 @@ const BRANDS: Record<BrandKey, Omit<BrandConfig, "iam">> = {
favicon: "/logo/hanzo-icon.svg",
monoMark: false,
domain: "web3.hanzo.ai",
apiUrl: API,
wsUrl: WS,
apiUrl: "https://web3.hanzo.ai",
wsUrl: "wss://web3.hanzo.ai",
statusUrl: "https://status.hanzo.ai",
networkName: "Hanzo Network",
networkUrl: "https://hanzo.ai",
fleet: null,
skillsOrg: "hanzoai",
defaultTheme: "dark",
colors: { primary: "#f5f5f5", primaryForeground: "#000000", accent: "#a8a8ff", accentEnd: "#ff8bc0" },
@@ -221,6 +226,7 @@ const BRANDS: Record<BrandKey, Omit<BrandConfig, "iam">> = {
statusUrl: "https://status.pars.network",
networkName: "Pars Network",
networkUrl: "https://pars.network",
fleet: null,
skillsOrg: "parsdao",
defaultTheme: "dark",
colors: { primary: "#f5f5f5", primaryForeground: "#000000", accent: "#ffd18b", accentEnd: "#ff8bc0" },
+5 -15
View File
@@ -103,20 +103,10 @@ describe("internal links", () => {
assert.deepEqual([...new Set(dead)].sort(), [], `dead links in the shell:\n${dead.join("\n")}`)
})
it("does not add dead links to page bodies", () => {
// 36 dead links predate this work — the docs index and the product pages
// link to a documentation tree (/docs/node/*, /docs/wallets/*, /docs/sdk/*
// …) that was never built. Fixing them means either writing those pages or
// pointing them elsewhere: a content decision, not a shell one.
//
// So this is a RATCHET, not a clean-zero assertion. The debt is counted and
// may only go down. A test that fails on the day it is written gets
// deleted; one that stops the bleeding gets kept.
const KNOWN_DEAD = 36
const dead = [...new Set(internalLinks().filter((l) => !SHELL.includes(l.file) && isDead(l.href)).map((l) => l.href))]
assert.ok(
dead.length <= KNOWN_DEAD,
`dead internal links rose from ${KNOWN_DEAD} to ${dead.length}. New:\n${dead.join("\n")}`
)
it("has zero dead links in page bodies", () => {
const dead = internalLinks()
.filter((l) => !SHELL.includes(l.file) && isDead(l.href))
.map((l) => `${l.href} (${l.file})`)
assert.deepEqual([...new Set(dead)].sort(), [], `dead links in page bodies:\n${dead.join("\n")}`)
})
})