Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ pnpm-debug.log*
lerna-debug.log*

node_modules
*.tsbuildinfo
dist
dist-ssr
*.local
Expand Down
Binary file added public/pro/block-position-diagram.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
20 changes: 11 additions & 9 deletions src/app/api/early-access/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,16 +53,18 @@ export async function POST(request: NextRequest) {
if (!isAddress(wallet_address.trim())) {
return NextResponse.json({ error: "Invalid wallet address" }, { status: 400 })
}
if (!x_handle?.trim()) {
return NextResponse.json({ error: "X handle is required" }, { status: 400 })
}
if (!discord_handle?.trim()) {
return NextResponse.json({ error: "Discord handle is required" }, { status: 400 })
}
if (!email?.trim()) {
return NextResponse.json({ error: "Email is required" }, { status: 400 })
// At least one contact method required
const hasContact =
x_handle?.trim() ||
discord_handle?.trim() ||
(email?.trim() && EMAIL_REGEX.test(email.trim()))
if (!hasContact) {
return NextResponse.json(
{ error: "At least one contact method is required (X, Discord, or email)" },
{ status: 400 }
)
}
if (!EMAIL_REGEX.test(email.trim())) {
if (email?.trim() && !EMAIL_REGEX.test(email.trim())) {
return NextResponse.json({ error: "Invalid email address" }, { status: 400 })
}

Expand Down
Binary file modified src/app/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
62 changes: 62 additions & 0 deletions src/app/pro/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import type { Metadata } from "next"
import { Sora } from "next/font/google"
import { SITE_URL } from "@/lib/site-config"

const sora = Sora({
subsets: ["latin"],
weight: ["400", "500", "600", "700", "800"],
variable: "--font-sora",
display: "swap",
})

export const metadata: Metadata = {
title: "Best Execution Ethereum Swaps — Preconfirmed in Under 500ms | Fast Protocol",
description:
"Compare Fast Protocol execution quality vs Uniswap and 1inch on real Ethereum mainnet swaps. Top-of-block preconfirmations, sub-500ms confirmation, and tokenized mev rewards.",
alternates: { canonical: `${SITE_URL}/pro` },
openGraph: {
title: "Fast Protocol — Better Execution on Ethereum, Verified",
description:
"Top-of-block preconfirmed swaps vs Uniswap and aggregators. See live execution quality comparisons on real mainnet trades.",
url: `${SITE_URL}/pro`,
type: "website",
},
twitter: {
card: "summary_large_image",
title: "Fast Protocol — Better Execution on Ethereum, Verified",
description:
"Top-of-block preconfirmed swaps vs Uniswap and aggregators. Sub-500ms confirmation with mev rewards.",
},
}

const proJsonLd = {
"@context": "https://schema.org",
"@type": "WebPage",
name: "Fast Protocol Execution Quality Comparison",
description:
"Compare Fast Protocol swap execution vs Uniswap and aggregators on Ethereum mainnet",
url: `${SITE_URL}/pro`,
isPartOf: {
"@type": "WebSite",
name: "Fast Protocol",
url: SITE_URL,
},
about: {
"@type": "SoftwareApplication",
name: "Fast Protocol",
applicationCategory: "DeFi",
operatingSystem: "Web",
},
}

export default function ProLayout({ children }: { children: React.ReactNode }) {
return (
<div className={sora.variable}>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(proJsonLd) }}
/>
{children}
</div>
)
}
25 changes: 25 additions & 0 deletions src/app/pro/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import ProHeader from "@/components/pro/ProHeader"
import Hero from "@/components/pro/Hero"
import LiveMetrics from "@/components/pro/LiveMetrics"
import Comparison from "@/components/pro/Comparison"
import BlockPosition from "@/components/pro/BlockPosition"
import WhyItWorks from "@/components/pro/WhyItWorks"
import Earnings from "@/components/pro/Earnings"
import Speed from "@/components/pro/Speed"
import FinalCTA from "@/components/pro/FinalCTA"

export default function ProPage() {
return (
<main className="min-h-screen bg-background text-foreground overflow-x-hidden">
<ProHeader />
<Hero />
<LiveMetrics />
<Comparison />
<BlockPosition />
<WhyItWorks />
<Earnings />
<Speed />
<FinalCTA />
</main>
)
}
46 changes: 0 additions & 46 deletions src/app/share/preconfirm/route.ts

This file was deleted.

6 changes: 6 additions & 0 deletions src/app/sitemap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ export default async function sitemap(): Promise<MetadataRoute.Sitemap> {

return [
{ url: baseUrl, lastModified: new Date(), changeFrequency: "weekly", priority: 1 },
{
url: `${baseUrl}/pro`,
lastModified: new Date(),
changeFrequency: "weekly",
priority: 0.9,
},
{
url: `${baseUrl}/early-access`,
lastModified: new Date(),
Expand Down
6 changes: 3 additions & 3 deletions src/components/dashboard/ReferralsCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ export const ReferralsCard = ({

const handleShareOnX = () => {
const tweetVariants = [
`Just minted my Fast Genesis SBT. @Fast_Protocol\n\nNext step: stack Fast Miles by routing swaps through Fast RPC.\n\nCome mint yours →\n${referralLink}`,
`I just claimed the Fast Genesis SBT @Fast_Protocol.\n\nNow, I'm running Fast RPC for faster sends and earning Fast Miles on top.\n\nMint yours →\n${referralLink}`,
`Just finished minting Fast Genesis SBT @Fast_Protocol.\n\nSwitch your send path to Fast RPC: faster execution on mainnet, better execution quality, and earn Fast Miles on top.\n\nBacked by a16z CSX, HashKey, Figment.\n\nMint →\n${referralLink}`,
`Sub-second swaps on Ethereum L1. No bridge. No L2.\n\n@Fast_Protocol turns mev into Miles that reward you for every swap.\n\nEarly access is live →\n${referralLink}`,
`Just swapped on @Fast_Protocol — confirmed in under a second on Ethereum mainnet.\n\nMiles are stacking. Leaderboard is live. Early access filling up.\n\nGet in →\n${referralLink}`,
`Ethereum swaps don't have to take 12 seconds.\n\n@Fast_Protocol preconfirms your trade before the block lands. ~90% of mev flows back to you as Miles.\n\nJoin early access →\n${referralLink}`,
]
const randomText = tweetVariants[Math.floor(Math.random() * tweetVariants.length)]
const url = `https://twitter.com/intent/tweet?text=${encodeURIComponent(randomText)}`
Expand Down
27 changes: 18 additions & 9 deletions src/components/landing/EarlyAccessForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,11 @@ export function EarlyAccessForm({ initialWalletAddress }: EarlyAccessFormProps)
const next: Record<string, string> = {}
if (!walletAddress?.trim()) next.walletAddress = "Wallet address is required"
else if (!isAddress(walletAddress.trim())) next.walletAddress = "Invalid wallet address"
if (!xHandle?.trim()) next.xHandle = "X handle is required"
if (!discordHandle?.trim()) next.discordHandle = "Discord handle is required"
if (!email?.trim()) next.email = "Email is required"
else if (!EMAIL_REGEX.test(email.trim())) next.email = "Invalid email address"
if (email?.trim() && !EMAIL_REGEX.test(email.trim())) next.email = "Invalid email address"
// At least one contact method
const hasContact =
xHandle?.trim() || discordHandle?.trim() || (email?.trim() && EMAIL_REGEX.test(email.trim()))
if (!hasContact) next.contact = "Provide at least one: X handle, Discord, or email"
setErrors(next)
return Object.keys(next).length === 0
}
Expand Down Expand Up @@ -157,12 +158,16 @@ export function EarlyAccessForm({ initialWalletAddress }: EarlyAccessFormProps)
</div>

<p className="text-sm text-muted-foreground rounded-lg border border-primary/15 bg-primary/5 px-3 py-2">
Use your real X and Discord profiles—if you&apos;re approved, we&apos;ll reach out to
you there.
Provide at least one way to reach you we&apos;ll use it to let you know when
you&apos;re in.
</p>

{errors.contact && <p className="text-sm text-destructive">{errors.contact}</p>}

<div className="space-y-2">
<Label htmlFor="x">X handle</Label>
<Label htmlFor="x">
X handle <span className="text-muted-foreground font-normal">(optional)</span>
</Label>
<Input
id="x"
placeholder="@username"
Expand All @@ -175,7 +180,9 @@ export function EarlyAccessForm({ initialWalletAddress }: EarlyAccessFormProps)
</div>

<div className="space-y-2">
<Label htmlFor="discord">Discord handle</Label>
<Label htmlFor="discord">
Discord handle <span className="text-muted-foreground font-normal">(optional)</span>
</Label>
<Input
id="discord"
placeholder="username#1234 or username"
Expand All @@ -190,7 +197,9 @@ export function EarlyAccessForm({ initialWalletAddress }: EarlyAccessFormProps)
</div>

<div className="space-y-2">
<Label htmlFor="email">Email</Label>
<Label htmlFor="email">
Email <span className="text-muted-foreground font-normal">(optional)</span>
</Label>
<Input
id="email"
type="email"
Expand Down
64 changes: 64 additions & 0 deletions src/components/pro/BlockPosition.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
"use client"

import Image from "next/image"
import { useRef, useState, useEffect } from "react"

const BlockPosition = () => {
const sectionRef = useRef<HTMLDivElement>(null)
const [visible, setVisible] = useState(false)

useEffect(() => {
if (!sectionRef.current) return
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
setVisible(true)
observer.disconnect()
}
},
{ threshold: 0.15 }
)
observer.observe(sectionRef.current)
return () => observer.disconnect()
}, [])

return (
<section ref={sectionRef} className="px-4 py-20 md:py-28 max-w-[1100px] mx-auto">
<div
className={`transition-all duration-500 ease-out ${
visible ? "opacity-100 translate-y-0" : "opacity-0 translate-y-4"
}`}
>
<h2 className="font-sora font-bold text-2xl sm:text-3xl md:text-4xl text-center mb-3">
Why block position determines swap price
</h2>
<p className="text-sm text-muted-foreground/70 text-center mb-6 sm:mb-10">
Better position in the block = better price
</p>

<Image
src="/pro/block-position-diagram.png"
alt="Diagram showing how top-of-block execution with Fast Protocol yields better prices compared to lower block positions"
width={960}
height={540}
loading="lazy"
className="w-full max-w-[960px] h-auto mx-auto rounded-xl"
/>

<p className="text-xs text-muted-foreground/60 text-center mt-8 max-w-lg mx-auto">
Most traders lose value due to where their transaction lands in the block. Fast secures
top-of-block execution to minimize that loss.
</p>

<div className="max-w-lg mx-auto mt-4 px-4 py-3 rounded-lg bg-primary/[0.06] border border-primary/10 text-center">
<p className="text-xs text-foreground/70">
Execution quality isn&apos;t just routing — it&apos;s where your trade lands in the
block.
</p>
</div>
</div>
</section>
)
}

export default BlockPosition
Loading
Loading