From df8d4ec7d10e0efbd7f6582c1d10675c316bf888 Mon Sep 17 00:00:00 2001 From: 0xFirekeeper <0xFirekeeper@gmail.com> Date: Tue, 2 Dec 2025 06:27:10 +0700 Subject: [PATCH 1/5] [WIP] Dedicated Relayer --- .../components/ProjectSidebarLayout.tsx | 4 + .../components/active-state.tsx | 245 ++++++++++++++++++ .../components/empty-state.tsx | 103 ++++++++ .../dedicated-relayer/components/index.ts | 5 + .../components/page-client.tsx | 121 +++++++++ .../components/pending-state.tsx | 173 +++++++++++++ .../components/tier-selection.tsx | 160 ++++++++++++ .../wallets/dedicated-relayer/layout.tsx | 61 +++++ .../wallets/dedicated-relayer/lib/api.ts | 91 +++++++ .../wallets/dedicated-relayer/lib/hooks.ts | 21 ++ .../wallets/dedicated-relayer/page.tsx | 51 ++++ .../wallets/dedicated-relayer/types.ts | 44 ++++ 12 files changed, 1079 insertions(+) create mode 100644 apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/active-state.tsx create mode 100644 apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/empty-state.tsx create mode 100644 apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/index.ts create mode 100644 apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/page-client.tsx create mode 100644 apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/pending-state.tsx create mode 100644 apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/tier-selection.tsx create mode 100644 apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/layout.tsx create mode 100644 apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/lib/api.ts create mode 100644 apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/lib/hooks.ts create mode 100644 apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/page.tsx create mode 100644 apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/types.ts diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/components/ProjectSidebarLayout.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/components/ProjectSidebarLayout.tsx index e0eefd78b12..85bd449bdee 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/components/ProjectSidebarLayout.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/components/ProjectSidebarLayout.tsx @@ -51,6 +51,10 @@ export function ProjectSidebarLayout(props: { href: `${props.layoutPath}/wallets/sponsored-gas`, label: "Gas Sponsorship", }, + { + href: `${props.layoutPath}/wallets/dedicated-relayer`, + label: "Dedicated Relayer", + }, ], }, ...(props.showContracts diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/active-state.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/active-state.tsx new file mode 100644 index 00000000000..4151918bc34 --- /dev/null +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/active-state.tsx @@ -0,0 +1,245 @@ +"use client"; + +import { + ActivityIcon, + CheckCircle2Icon, + CoinsIcon, + WalletIcon, +} from "lucide-react"; +import { useState } from "react"; +import type { ThirdwebClient } from "thirdweb"; +import { toEther } from "thirdweb/utils"; +import type { Project } from "@/api/project/projects"; +import { + DateRangeSelector, + getLastNDaysRange, +} from "@/components/analytics/date-range-selector"; +import { StatCard } from "@/components/analytics/stat"; +import { Button } from "@/components/ui/button"; +import { CopyAddressButton } from "@/components/ui/CopyAddressButton"; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table"; +import { defineDashboardChain } from "@/lib/defineDashboardChain"; +import { normalizeTimeISOString } from "@/lib/time"; +import { useFleetAnalytics } from "../lib/hooks"; +import type { Fleet } from "../types"; + +type DedicatedRelayerActiveStateProps = { + fleet: Fleet; + project: Project; + authToken: string; + teamSlug: string; + client: ThirdwebClient; +}; + +/** + * Active state shown when fleet is fully set up with executors. + * Shows executor addresses, analytics, and transaction stats. + */ +export function DedicatedRelayerActiveState( + props: DedicatedRelayerActiveStateProps, +) { + const { fleet, project } = props; + + const [range, setRange] = useState(() => getLastNDaysRange("last-30")); + + const normalizedFrom = normalizeTimeISOString(range.from); + const normalizedTo = normalizeTimeISOString(range.to); + + const analyticsQuery = useFleetAnalytics({ + teamId: project.teamId, + projectId: project.id, + authToken: props.authToken, + startDate: normalizedFrom, + endDate: normalizedTo, + }); + + // Group executors by chain + const executorsByChain = fleet.executors.reduce( + (acc, executor) => { + if (!acc[executor.chainId]) { + acc[executor.chainId] = []; + } + acc[executor.chainId]?.push(executor.address); + return acc; + }, + {} as Record, + ); + + return ( +
+ {/* Status Banner */} +
+ +
+

+ Dedicated relayer is active +

+

+ Your transactions are being relayed through dedicated executors. +

+
+
+ + {/* Date Range Selector */} +
+ +
+ + {/* Summary Stats */} + + + {/* Executor Wallets */} +
+
+

+ Executor Wallets +

+

+ Dedicated wallets relaying your transactions +

+
+ +
+ + + + Chain + Executor Address + Actions + + + + {Object.entries(executorsByChain).map(([chainId, addresses]) => + addresses.map((address, idx) => { + const chain = defineDashboardChain( + Number(chainId), + undefined, + ); + return ( + + + + {chain.name || `Chain ${chainId}`} + + {idx === 0 && addresses.length > 1 && ( + + ({addresses.length} executors) + + )} + + + + + + + + + ); + }), + )} + +
+
+
+ + {/* Supported Chains */} +
+
+

+ Supported Chains +

+
+ +
+
+ {fleet.chainIds.map((chainId) => { + const chain = defineDashboardChain(chainId, undefined); + return ( + + {chain.name || `Chain ${chainId}`} + + ); + })} +
+
+
+
+ ); +} + +function FleetAnalyticsSummary(props: { + data: + | { + totalTransactions: number; + totalGasSpentWei: string; + remainingBalanceWei: string; + } + | null + | undefined; + isPending: boolean; +}) { + const formatWeiToEth = (weiString: string): number => { + try { + if (weiString === "0") return 0; + const weiBigInt = BigInt(weiString); + return Number.parseFloat(toEther(weiBigInt)); + } catch { + return 0; + } + }; + + return ( +
+ + `${v.toFixed(6)} ETH`} + icon={CoinsIcon} + isPending={props.isPending} + label="Total Gas Spent" + value={formatWeiToEth(props.data?.totalGasSpentWei ?? "0")} + /> + `${v.toFixed(6)} ETH`} + icon={WalletIcon} + isPending={props.isPending} + label="Remaining Balance" + value={formatWeiToEth(props.data?.remainingBalanceWei ?? "0")} + /> +
+ ); +} diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/empty-state.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/empty-state.tsx new file mode 100644 index 00000000000..dde35446cce --- /dev/null +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/empty-state.tsx @@ -0,0 +1,103 @@ +"use client"; + +import { ExternalLinkIcon, ZapIcon } from "lucide-react"; +import Link from "next/link"; +import { useState } from "react"; +import { Button } from "@/components/ui/button"; +import { type RelayerTier, TierSelection } from "./tier-selection"; + +type DedicatedRelayerEmptyStateProps = { + teamSlug: string; + projectSlug: string; + onPurchaseTier: (tier: RelayerTier) => Promise; +}; + +/** + * Empty state shown when user hasn't purchased a dedicated relayer fleet. + * Shows tier selection for purchasing. + */ +export function DedicatedRelayerEmptyState( + props: DedicatedRelayerEmptyStateProps, +) { + const [isLoading, setIsLoading] = useState(false); + const [selectedTier, setSelectedTier] = useState(null); + + const handleSelectTier = async (tier: RelayerTier) => { + setSelectedTier(tier); + setIsLoading(true); + try { + await props.onPurchaseTier(tier); + } finally { + setIsLoading(false); + setSelectedTier(null); + } + }; + + return ( +
+ {/* Hero Section */} +
+
+
+ +
+ +
+

+ Dedicated Relayer Fleet +

+

+ Your own dedicated executor wallets that automatically relay + transactions on your chosen chains. No manual gas management, no + shared infrastructure. +

+
+ +
+ + + +
+ + +
+
+ + {/* Tier Selection */} +
+ +
+
+ ); +} + +function FeatureCard(props: { title: string; description: string }) { + return ( +
+

{props.title}

+

{props.description}

+
+ ); +} diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/index.ts b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/index.ts new file mode 100644 index 00000000000..1a0d128f412 --- /dev/null +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/index.ts @@ -0,0 +1,5 @@ +export { DedicatedRelayerActiveState } from "./active-state"; +export { DedicatedRelayerEmptyState } from "./empty-state"; +export { DedicatedRelayerPageClient } from "./page-client"; +export { DedicatedRelayerPendingState } from "./pending-state"; +export { TierSelection } from "./tier-selection"; diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/page-client.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/page-client.tsx new file mode 100644 index 00000000000..c699354b6e1 --- /dev/null +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/page-client.tsx @@ -0,0 +1,121 @@ +"use client"; + +import { useState } from "react"; +import type { ThirdwebClient } from "thirdweb"; +import type { Project } from "@/api/project/projects"; +import type { Fleet, FleetExecutor, FleetStatus } from "../types"; +import { DedicatedRelayerActiveState } from "./active-state"; +import { DedicatedRelayerEmptyState } from "./empty-state"; +import { DedicatedRelayerPendingState } from "./pending-state"; +import type { RelayerTier } from "./tier-selection"; + +// Mock executor address for demo +const MOCK_EXECUTOR_ADDRESS = "0xE0F28D9d95143858Be492BDf3abBCA746d0d2272"; + +// Chain IDs +const BASE_MAINNET = 8453; +const BASE_SEPOLIA = 84532; + +type DedicatedRelayerPageClientProps = { + project: Project; + authToken: string; + teamSlug: string; + projectSlug: string; + client: ThirdwebClient; + initialFleet: Fleet | null; +}; + +export function DedicatedRelayerPageClient( + props: DedicatedRelayerPageClientProps, +) { + const [fleet, setFleet] = useState(props.initialFleet); + const [fleetStatus, setFleetStatus] = useState(() => + getInitialStatus(props.initialFleet), + ); + + const handlePurchaseTier = async (tier: RelayerTier) => { + // Simulate API call delay + await new Promise((resolve) => setTimeout(resolve, 1500)); + + // Create mock fleet based on tier (pending state - no executors yet) + const mockFleet: Fleet = { + id: `fleet-${Date.now()}`, + tier, + chainIds: [BASE_MAINNET, BASE_SEPOLIA], + executors: [], // Empty initially - pending setup + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + }; + + setFleet(mockFleet); + setFleetStatus("pending-setup"); + }; + + const handleSkipSetup = () => { + if (!fleet) return; + + const executorCount = fleet.tier === "starter" ? 1 : 10; + const executors: FleetExecutor[] = []; + + for (let i = 0; i < executorCount; i++) { + executors.push({ + address: MOCK_EXECUTOR_ADDRESS, + chainId: BASE_MAINNET, + }); + executors.push({ + address: MOCK_EXECUTOR_ADDRESS, + chainId: BASE_SEPOLIA, + }); + } + + setFleet((prev) => + prev + ? { + ...prev, + executors, + updatedAt: new Date().toISOString(), + } + : null, + ); + setFleetStatus("active"); + }; + + return ( +
+ {fleetStatus === "not-purchased" && ( + + )} + + {fleetStatus === "pending-setup" && fleet && ( + + )} + + {fleetStatus === "active" && fleet && ( + + )} +
+ ); +} + +function getInitialStatus(fleet: Fleet | null): FleetStatus { + if (!fleet) { + return "not-purchased"; + } + if (fleet.executors.length === 0) { + return "pending-setup"; + } + return "active"; +} diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/pending-state.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/pending-state.tsx new file mode 100644 index 00000000000..bfd67b5ee04 --- /dev/null +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/pending-state.tsx @@ -0,0 +1,173 @@ +"use client"; + +import { ClockIcon, Loader2Icon } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { defineDashboardChain } from "@/lib/defineDashboardChain"; +import type { Fleet } from "../types"; + +type DedicatedRelayerPendingStateProps = { + fleet: Fleet; + onSkipSetup?: () => void; +}; + +/** + * Pending state shown when fleet is purchased but executors are not yet set up. + * Shows the requested chain IDs and a "setting up" status. + */ +export function DedicatedRelayerPendingState( + props: DedicatedRelayerPendingStateProps, +) { + const { fleet } = props; + + return ( +
+ {/* Status Banner */} +
+ +
+

+ Setting up your dedicated relayer +

+

+ Your relayer is being provisioned. This usually takes a few minutes. +

+
+
+ + {/* Fleet Details Preview */} +
+
+

+ Fleet Configuration +

+
+ +
+
+ {/* Requested Chains */} +
+

+ Requested Chains +

+
+ {fleet.chainIds.map((chainId) => ( + + ))} +
+
+ + {/* Status */} +
+

+ Status +

+
+ + Awaiting executor deployment +
+
+
+
+
+ + {/* What to Expect */} +
+
+

+ What to Expect +

+
+ +
+
    + + + + +
+
+
+ + {/* Dev Skip Button */} + {props.onSkipSetup && ( +
+
+
+

๐Ÿงช Dev Mode

+

+ Skip the setup wait and go straight to active state +

+
+ +
+
+ )} +
+ ); +} + +function ChainBadge(props: { chainId: number }) { + const chain = defineDashboardChain(props.chainId, undefined); + return ( + + {chain.name || `Chain ${props.chainId}`} + + ); +} + +function SetupStep(props: { + number: number; + title: string; + completed: boolean; + inProgress?: boolean; +}) { + return ( +
  • +
    + {props.inProgress ? ( + + ) : ( + props.number + )} +
    + + {props.title} + +
  • + ); +} diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/tier-selection.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/tier-selection.tsx new file mode 100644 index 00000000000..d9e42c0d23a --- /dev/null +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/tier-selection.tsx @@ -0,0 +1,160 @@ +"use client"; + +import { CheckIcon, ExternalLinkIcon, UsersIcon } from "lucide-react"; +import Link from "next/link"; +import { Button } from "@/components/ui/button"; +import { Spinner } from "@/components/ui/Spinner"; +import { cn } from "@/lib/utils"; + +export type RelayerTier = "starter" | "growth" | "enterprise"; + +type TierConfig = { + id: RelayerTier; + name: string; + description: string; + price: string; + priceSubtext?: string; + features: string[]; + maxChains: number; + executors: number; + cta: string; + highlighted?: boolean; +}; + +const TIERS: TierConfig[] = [ + { + id: "starter", + name: "Single Executor", + description: "One dedicated executor for your project", + price: "$99", + priceSubtext: "/month", + features: ["1 dedicated executor wallet", "Up to 2 chains"], + maxChains: 2, + executors: 1, + cta: "Subscribe", + }, + { + id: "growth", + name: "Executor Fleet", + description: "10x the throughput with parallel execution", + price: "$299", + priceSubtext: "/month", + features: ["10 dedicated executor wallets", "Up to 4 chains"], + maxChains: 4, + executors: 10, + cta: "Subscribe", + highlighted: true, + }, + { + id: "enterprise", + name: "Executor Army", + description: "Custom executor count for maximum throughput", + price: "Custom", + features: ["Custom executor count", "Unlimited chains"], + maxChains: -1, + executors: -1, + cta: "Contact Sales", + }, +]; + +type TierSelectionProps = { + onSelectTier: (tier: RelayerTier) => void; + isLoading?: boolean; + selectedTier?: RelayerTier | null; +}; + +export function TierSelection(props: TierSelectionProps) { + return ( +
    +
    + {TIERS.map((tier) => ( + props.onSelectTier(tier.id)} + /> + ))} +
    +
    + ); +} + +function TierCard(props: { + tier: TierConfig; + onSelect: () => void; + isLoading?: boolean; + isDisabled?: boolean; +}) { + const { tier } = props; + const isEnterprise = tier.id === "enterprise"; + + return ( +
    + {tier.highlighted && ( +
    + + Most Popular + +
    + )} + +
    +

    {tier.name}

    +

    {tier.description}

    +
    + +
    + {tier.price} + {tier.priceSubtext && ( + {tier.priceSubtext} + )} +
    + +
      + {tier.features.map((feature) => ( +
    • + + {feature} +
    • + ))} +
    + + {isEnterprise ? ( + + ) : ( + + )} +
    + ); +} diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/layout.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/layout.tsx new file mode 100644 index 00000000000..26e68783596 --- /dev/null +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/layout.tsx @@ -0,0 +1,61 @@ +import { redirect } from "next/navigation"; +import { getAuthToken } from "@/api/auth-token"; +import { getProject } from "@/api/project/projects"; +import { ProjectPage } from "@/components/blocks/project-page/project-page"; +import { getClientThirdwebClient } from "@/constants/thirdweb-client.client"; +import { WalletProductIcon } from "@/icons/WalletProductIcon"; +import { loginRedirect } from "@/utils/redirects"; + +export default async function Layout(props: { + children: React.ReactNode; + params: Promise<{ team_slug: string; project_slug: string }>; +}) { + const params = await props.params; + const basePath = `/team/${params.team_slug}/${params.project_slug}/wallets/dedicated-relayer`; + + const [authToken, project] = await Promise.all([ + getAuthToken(), + getProject(params.team_slug, params.project_slug), + ]); + + if (!authToken) { + loginRedirect(basePath); + } + + if (!project) { + redirect(`/team/${params.team_slug}`); + } + + const client = getClientThirdwebClient({ + jwt: authToken, + teamId: project.teamId, + }); + + return ( + + {props.children} + + ); +} diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/lib/api.ts b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/lib/api.ts new file mode 100644 index 00000000000..9608badfb11 --- /dev/null +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/lib/api.ts @@ -0,0 +1,91 @@ +import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "@/constants/public-envs"; +import type { Fleet, FleetAnalytics } from "../types"; + +type GetFleetParams = { + teamId: string; + projectId: string; + authToken: string; +}; + +/** + * Fetches the fleet data for a project. + * Returns null if no fleet exists (dev hasn't purchased). + */ +export async function getFleet(params: GetFleetParams): Promise { + try { + const response = await fetch( + `${NEXT_PUBLIC_THIRDWEB_API_HOST}/v1/teams/${params.teamId}/projects/${params.projectId}/fleet`, + { + headers: { + Authorization: `Bearer ${params.authToken}`, + "Content-Type": "application/json", + }, + method: "GET", + }, + ); + + if (response.status === 404) { + // Fleet not purchased yet + return null; + } + + if (!response.ok) { + console.error("Error fetching fleet:", response.status); + return null; + } + + const data = await response.json(); + return data.result as Fleet; + } catch (error) { + console.error("Error fetching fleet:", error); + return null; + } +} + +export type GetFleetAnalyticsParams = { + teamId: string; + projectId: string; + authToken: string; + startDate?: string; + endDate?: string; +}; + +/** + * Fetches analytics for a fleet. + * Only call this when fleet has executors (active state). + */ +export async function getFleetAnalytics( + params: GetFleetAnalyticsParams, +): Promise { + try { + const url = new URL( + `${NEXT_PUBLIC_THIRDWEB_API_HOST}/v1/teams/${params.teamId}/projects/${params.projectId}/fleet/analytics`, + ); + + if (params.startDate) { + url.searchParams.set("startDate", params.startDate); + } + if (params.endDate) { + url.searchParams.set("endDate", params.endDate); + } + + const response = await fetch(url.toString(), { + headers: { + Authorization: `Bearer ${params.authToken}`, + "Content-Type": "application/json", + }, + method: "GET", + }); + + if (!response.ok) { + console.error("Error fetching fleet analytics:", response.status); + return null; + } + + const data = await response.json(); + return data.result as FleetAnalytics; + } catch (error) { + console.error("Error fetching fleet analytics:", error); + return null; + } +} diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/lib/hooks.ts b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/lib/hooks.ts new file mode 100644 index 00000000000..0fe32f6f5b9 --- /dev/null +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/lib/hooks.ts @@ -0,0 +1,21 @@ +"use client"; + +import { useQuery } from "@tanstack/react-query"; +import { type GetFleetAnalyticsParams, getFleetAnalytics } from "./api"; + +export function useFleetAnalytics( + params: GetFleetAnalyticsParams & { enabled?: boolean }, +) { + return useQuery({ + queryKey: [ + "fleet-analytics", + params.teamId, + params.projectId, + params.startDate, + params.endDate, + ], + queryFn: () => getFleetAnalytics(params), + refetchOnWindowFocus: false, + enabled: params.enabled ?? true, + }); +} diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/page.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/page.tsx new file mode 100644 index 00000000000..ced8cdc1b9f --- /dev/null +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/page.tsx @@ -0,0 +1,51 @@ +import { redirect } from "next/navigation"; +import { getAuthToken } from "@/api/auth-token"; +import { getProject } from "@/api/project/projects"; +import { getClientThirdwebClient } from "@/constants/thirdweb-client.client"; +import { loginRedirect } from "@/utils/redirects"; +import { DedicatedRelayerPageClient } from "./components/page-client"; +import { getFleet } from "./lib/api"; + +export const dynamic = "force-dynamic"; + +export default async function DedicatedRelayerPage(props: { + params: Promise<{ team_slug: string; project_slug: string }>; +}) { + const params = await props.params; + const basePath = `/team/${params.team_slug}/${params.project_slug}/wallets/dedicated-relayer`; + + const authToken = await getAuthToken(); + + if (!authToken) { + loginRedirect(basePath); + } + + const project = await getProject(params.team_slug, params.project_slug); + + if (!project) { + redirect(`/team/${params.team_slug}`); + } + + // Fetch fleet data from the bundler service on the project + const fleet = await getFleet({ + teamId: project.teamId, + projectId: project.id, + authToken, + }); + + const client = getClientThirdwebClient({ + jwt: authToken, + teamId: project.teamId, + }); + + return ( + + ); +} diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/types.ts b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/types.ts new file mode 100644 index 00000000000..4135cd18b29 --- /dev/null +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/types.ts @@ -0,0 +1,44 @@ +/** + * Types for Dedicated Relayer (Fleet) feature. + * + * Fleet lifecycle: + * - Not returned: empty state, dev hasn't purchased + * - Returned with empty executors: pending setup state + * - Returned with executors: fully active state + */ + +export type FleetExecutor = { + address: string; + chainId: number; +}; + +export type FleetTier = "starter" | "growth" | "enterprise"; + +export type Fleet = { + id: string; + tier?: FleetTier; + chainIds: number[]; + executors: FleetExecutor[]; + createdAt: string; + updatedAt: string; +}; + +export type FleetAnalytics = { + totalTransactions: number; + totalGasSpentWei: string; + remainingBalanceWei: string; +}; + +export type FleetStatus = "not-purchased" | "pending-setup" | "active"; + +export function getFleetStatus(fleet: Fleet | null | undefined): FleetStatus { + if (!fleet) { + return "not-purchased"; + } + + if (fleet.executors.length === 0) { + return "pending-setup"; + } + + return "active"; +} From d71c070f1d2af6ed32c5994bfcac40cc55a43a5f Mon Sep 17 00:00:00 2001 From: 0xFirekeeper <0xFirekeeper@gmail.com> Date: Thu, 4 Dec 2025 04:58:42 +0700 Subject: [PATCH 2/5] Refactor dedicated relayer to use new fleet analytics API Replaces legacy analytics and fleet API calls with new endpoints for fleet transactions and summary. Updates the active state UI to show paginated transaction tables, summary stats, and chain filtering. Refactors types and hooks to match new API responses, removes unused code, and updates documentation links and UI copy for clarity. --- .../components/active-state.tsx | 496 +++++++++++------- .../components/empty-state.tsx | 2 +- .../dedicated-relayer/components/index.ts | 1 - .../components/page-client.tsx | 86 ++- .../components/pending-state.tsx | 2 +- .../wallets/dedicated-relayer/layout.tsx | 12 +- .../wallets/dedicated-relayer/lib/api.ts | 130 ++--- .../wallets/dedicated-relayer/lib/hooks.ts | 51 +- .../wallets/dedicated-relayer/page.tsx | 66 ++- .../wallets/dedicated-relayer/types.ts | 71 ++- packages/service-utils/src/core/api.ts | 4 + 11 files changed, 544 insertions(+), 377 deletions(-) diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/active-state.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/active-state.tsx index 4151918bc34..c0ef9eabaa6 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/active-state.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/active-state.tsx @@ -1,244 +1,376 @@ "use client"; -import { - ActivityIcon, - CheckCircle2Icon, - CoinsIcon, - WalletIcon, -} from "lucide-react"; +import { format, formatDistance } from "date-fns"; +import { ExternalLinkIcon, XIcon } from "lucide-react"; +import Link from "next/link"; import { useState } from "react"; import type { ThirdwebClient } from "thirdweb"; -import { toEther } from "thirdweb/utils"; -import type { Project } from "@/api/project/projects"; -import { - DateRangeSelector, - getLastNDaysRange, -} from "@/components/analytics/date-range-selector"; -import { StatCard } from "@/components/analytics/stat"; +import { SingleNetworkSelector } from "@/components/blocks/NetworkSelectors"; +import { PaginationButtons } from "@/components/blocks/pagination-buttons"; +import { WalletAddress } from "@/components/blocks/wallet-address"; import { Button } from "@/components/ui/button"; -import { CopyAddressButton } from "@/components/ui/CopyAddressButton"; +import { CopyTextButton } from "@/components/ui/CopyTextButton"; +import { Skeleton } from "@/components/ui/skeleton"; import { Table, TableBody, TableCell, + TableContainer, TableHead, TableHeader, TableRow, } from "@/components/ui/table"; -import { defineDashboardChain } from "@/lib/defineDashboardChain"; -import { normalizeTimeISOString } from "@/lib/time"; -import { useFleetAnalytics } from "../lib/hooks"; -import type { Fleet } from "../types"; +import { ToolTipLabel } from "@/components/ui/tooltip"; +import { useAllChainsData } from "@/hooks/chains/allChains"; +import { ChainIconClient } from "@/icons/ChainIcon"; +import { + useFleetTransactions, + useFleetTransactionsSummary, +} from "../lib/hooks"; +import type { Fleet, FleetTransaction } from "../types"; type DedicatedRelayerActiveStateProps = { fleet: Fleet; - project: Project; - authToken: string; - teamSlug: string; + teamId: string; + fleetId: string; client: ThirdwebClient; + from: string; + to: string; }; -/** - * Active state shown when fleet is fully set up with executors. - * Shows executor addresses, analytics, and transaction stats. - */ export function DedicatedRelayerActiveState( props: DedicatedRelayerActiveStateProps, ) { - const { fleet, project } = props; + const { fleet, teamId, fleetId, client, from, to } = props; - const [range, setRange] = useState(() => getLastNDaysRange("last-30")); + const pageSize = 10; + const [page, setPage] = useState(1); + const [chainIdFilter, setChainIdFilter] = useState(); - const normalizedFrom = normalizeTimeISOString(range.from); - const normalizedTo = normalizeTimeISOString(range.to); + const summaryQuery = useFleetTransactionsSummary({ + teamId, + fleetId, + from, + to, + }); - const analyticsQuery = useFleetAnalytics({ - teamId: project.teamId, - projectId: project.id, - authToken: props.authToken, - startDate: normalizedFrom, - endDate: normalizedTo, + const transactionsQuery = useFleetTransactions({ + teamId, + fleetId, + from, + to, + limit: pageSize, + offset: (page - 1) * pageSize, + chainId: chainIdFilter, }); - // Group executors by chain - const executorsByChain = fleet.executors.reduce( - (acc, executor) => { - if (!acc[executor.chainId]) { - acc[executor.chainId] = []; - } - acc[executor.chainId]?.push(executor.address); - return acc; - }, - {} as Record, - ); + const totalPages = transactionsQuery.data + ? Math.ceil(transactionsQuery.data.meta.total / pageSize) + : 0; return (
    - {/* Status Banner */} -
    - -
    -

    - Dedicated relayer is active -

    -

    - Your transactions are being relayed through dedicated executors. -

    -
    -
    - - {/* Date Range Selector */} -
    - -
    - {/* Summary Stats */} - +
    + + + +
    - {/* Executor Wallets */} + {/* Executors Info */}

    - Executor Wallets + Fleet Executors

    -

    - Dedicated wallets relaying your transactions -

    -
    +
    + {fleet.executors.map((address) => ( +
    + +
    + ))} +
    +
    +
    + + {/* Transactions Table */} +
    +
    +

    Fleet Transactions

    +
    + { + setChainIdFilter(chainId); + setPage(1); + }} + client={client} + /> +
    +
    + + + Transaction Hash Chain - Executor Address - Actions + Wallet + Executor + Timestamp + Fee - {Object.entries(executorsByChain).map(([chainId, addresses]) => - addresses.map((address, idx) => { - const chain = defineDashboardChain( - Number(chainId), - undefined, - ); - return ( - - - - {chain.name || `Chain ${chainId}`} - - {idx === 0 && addresses.length > 1 && ( - - ({addresses.length} executors) - - )} - - - - - - - - - ); - }), - )} + {!transactionsQuery.isPending + ? transactionsQuery.data?.data.map((tx) => ( + + )) + : Array.from({ length: pageSize }).map((_, index) => ( + // biome-ignore lint/suspicious/noArrayIndexKey: skeleton rows have no unique id + + ))}
    -
    -
    + - {/* Supported Chains */} -
    -
    -

    - Supported Chains -

    -
    + {!transactionsQuery.isPending && + (transactionsQuery.isError ? ( +
    +
    +
    + +
    +
    +

    + Failed to load transactions +

    +
    + ) : transactionsQuery.data?.data.length === 0 ? ( +
    +
    +
    + +
    +
    +

    + No transactions yet +

    +
    + ) : null)} -
    -
    - {fleet.chainIds.map((chainId) => { - const chain = defineDashboardChain(chainId, undefined); - return ( - - {chain.name || `Chain ${chainId}`} - - ); - })} + {totalPages > 1 && ( +
    +
    -
    + )}
    ); } -function FleetAnalyticsSummary(props: { - data: - | { - totalTransactions: number; - totalGasSpentWei: string; - remainingBalanceWei: string; - } - | null - | undefined; - isPending: boolean; +function StatCard(props: { + title: string; + value: string; + isLoading?: boolean; }) { - const formatWeiToEth = (weiString: string): number => { - try { - if (weiString === "0") return 0; - const weiBigInt = BigInt(weiString); - return Number.parseFloat(toEther(weiBigInt)); - } catch { - return 0; - } - }; + return ( +
    +

    {props.title}

    + {props.isLoading ? ( + + ) : ( +

    {props.value}

    + )} +
    + ); +} + +function TransactionRow(props: { + transaction: FleetTransaction; + client: ThirdwebClient; +}) { + const { transaction, client } = props; + const utcTimestamp = transaction.timestamp.endsWith("Z") + ? transaction.timestamp + : `${transaction.timestamp}Z`; return ( -
    - - `${v.toFixed(6)} ETH`} - icon={CoinsIcon} - isPending={props.isPending} - label="Total Gas Spent" - value={formatWeiToEth(props.data?.totalGasSpentWei ?? "0")} + + + + + + + + + + + + + + + + + {formatDistance(new Date(utcTimestamp), new Date(), { + addSuffix: true, + })} + + + + + + + + ); +} + +function SkeletonRow() { + return ( + + + + + + + + + + + + + + + + + + + + + ); +} + +function TransactionHashCell(props: { hash: string; chainId: string }) { + const { idToChain } = useAllChainsData(); + const chain = idToChain.get(Number(props.chainId)); + + const explorerUrl = chain?.explorers?.[0]?.url; + const txHashToShow = `${props.hash.slice(0, 6)}...${props.hash.slice(-4)}`; + + if (explorerUrl) { + return ( + + ); + } + + return ( + + ); +} + +function ChainCell(props: { chainId: string; client: ThirdwebClient }) { + const { idToChain, allChains } = useAllChainsData(); + const chain = idToChain.get(Number(props.chainId)); + + if (allChains.length === 0) { + return ; + } + + return ( +
    + - `${v.toFixed(6)} ETH`} - icon={WalletIcon} - isPending={props.isPending} - label="Remaining Balance" - value={formatWeiToEth(props.data?.remainingBalanceWei ?? "0")} + + {chain ? chain.name : `Chain #${props.chainId}`} + +
    + ); +} + +function TransactionFeeCell(props: { usdValue: number }) { + return ( + + ${props.usdValue < 0.01 ? "<0.01" : props.usdValue.toFixed(2)} + + ); +} + +function ChainFilter(props: { + chainId: number | undefined; + setChainId: (chainId: number | undefined) => void; + client: ThirdwebClient; +}) { + return ( +
    + props.setChainId(chainId)} + popoverContentClassName="z-[10001]" + align="end" + placeholder="All Chains" + className="min-w-[150px]" />
    ); diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/empty-state.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/empty-state.tsx index dde35446cce..57f148df03e 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/empty-state.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/empty-state.tsx @@ -70,7 +70,7 @@ export function DedicatedRelayerEmptyState(
    diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/pending-state.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/pending-state.tsx index bfd67b5ee04..a1885debb19 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/pending-state.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/pending-state.tsx @@ -83,7 +83,7 @@ export function DedicatedRelayerPendingState( {props.children} diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/lib/api.ts b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/lib/api.ts index 9608badfb11..c7685be319e 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/lib/api.ts +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/lib/api.ts @@ -1,91 +1,75 @@ -import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "@/constants/public-envs"; -import type { Fleet, FleetAnalytics } from "../types"; +"use server"; -type GetFleetParams = { +import { analyticsServerProxy } from "@/actions/proxies"; +import type { FleetTransaction, FleetTransactionsSummary } from "../types"; + +type GetFleetTransactionsParams = { teamId: string; - projectId: string; - authToken: string; + fleetId: string; + from: string; + to: string; + limit: number; + offset: number; + chainId?: number; }; /** - * Fetches the fleet data for a project. - * Returns null if no fleet exists (dev hasn't purchased). + * Fetches paginated fleet transactions from the analytics service. */ -export async function getFleet(params: GetFleetParams): Promise { - try { - const response = await fetch( - `${NEXT_PUBLIC_THIRDWEB_API_HOST}/v1/teams/${params.teamId}/projects/${params.projectId}/fleet`, - { - headers: { - Authorization: `Bearer ${params.authToken}`, - "Content-Type": "application/json", - }, - method: "GET", - }, - ); - - if (response.status === 404) { - // Fleet not purchased yet - return null; - } +export async function getFleetTransactions(params: GetFleetTransactionsParams) { + const res = await analyticsServerProxy<{ + data: FleetTransaction[]; + meta: { total: number }; + }>({ + method: "GET", + pathname: "/v2/bundler/fleet-transactions", + searchParams: { + teamId: params.teamId, + fleetId: params.fleetId, + from: params.from, + to: params.to, + limit: params.limit.toString(), + offset: params.offset.toString(), + ...(params.chainId && { chainId: params.chainId.toString() }), + }, + }); - if (!response.ok) { - console.error("Error fetching fleet:", response.status); - return null; - } - - const data = await response.json(); - return data.result as Fleet; - } catch (error) { - console.error("Error fetching fleet:", error); - return null; + if (!res.ok) { + throw new Error(res.error); } + + return res.data; } -export type GetFleetAnalyticsParams = { +type GetFleetTransactionsSummaryParams = { teamId: string; - projectId: string; - authToken: string; - startDate?: string; - endDate?: string; + fleetId: string; + from: string; + to: string; }; /** - * Fetches analytics for a fleet. - * Only call this when fleet has executors (active state). + * Fetches fleet transactions summary from the analytics service. */ -export async function getFleetAnalytics( - params: GetFleetAnalyticsParams, -): Promise { - try { - const url = new URL( - `${NEXT_PUBLIC_THIRDWEB_API_HOST}/v1/teams/${params.teamId}/projects/${params.projectId}/fleet/analytics`, - ); +export async function getFleetTransactionsSummary( + params: GetFleetTransactionsSummaryParams, +) { + const res = await analyticsServerProxy<{ + data: FleetTransactionsSummary; + }>({ + method: "GET", + pathname: "/v2/bundler/fleet-transactions/summary", + searchParams: { + teamId: params.teamId, + fleetId: params.fleetId, + from: params.from, + to: params.to, + }, + }); - if (params.startDate) { - url.searchParams.set("startDate", params.startDate); - } - if (params.endDate) { - url.searchParams.set("endDate", params.endDate); - } - - const response = await fetch(url.toString(), { - headers: { - Authorization: `Bearer ${params.authToken}`, - "Content-Type": "application/json", - }, - method: "GET", - }); - - if (!response.ok) { - console.error("Error fetching fleet analytics:", response.status); - return null; - } - - const data = await response.json(); - return data.result as FleetAnalytics; - } catch (error) { - console.error("Error fetching fleet analytics:", error); - return null; + if (!res.ok) { + throw new Error(res.error); } + + return res.data; } diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/lib/hooks.ts b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/lib/hooks.ts index 0fe32f6f5b9..14642eba338 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/lib/hooks.ts +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/lib/hooks.ts @@ -1,21 +1,46 @@ "use client"; -import { useQuery } from "@tanstack/react-query"; -import { type GetFleetAnalyticsParams, getFleetAnalytics } from "./api"; +import { keepPreviousData, useQuery } from "@tanstack/react-query"; +import { getFleetTransactions, getFleetTransactionsSummary } from "./api"; -export function useFleetAnalytics( - params: GetFleetAnalyticsParams & { enabled?: boolean }, +type UseFleetTransactionsParams = { + teamId: string; + fleetId: string; + from: string; + to: string; + limit: number; + offset: number; + chainId?: number; +}; + +/** + * React Query hook for fetching paginated fleet transactions. + */ +export function useFleetTransactions(params: UseFleetTransactionsParams) { + return useQuery({ + queryKey: ["fleet-transactions", params], + queryFn: () => getFleetTransactions(params), + placeholderData: keepPreviousData, + refetchOnWindowFocus: false, + }); +} + +type UseFleetTransactionsSummaryParams = { + teamId: string; + fleetId: string; + from: string; + to: string; +}; + +/** + * React Query hook for fetching fleet transactions summary. + */ +export function useFleetTransactionsSummary( + params: UseFleetTransactionsSummaryParams, ) { return useQuery({ - queryKey: [ - "fleet-analytics", - params.teamId, - params.projectId, - params.startDate, - params.endDate, - ], - queryFn: () => getFleetAnalytics(params), + queryKey: ["fleet-transactions-summary", params], + queryFn: () => getFleetTransactionsSummary(params), refetchOnWindowFocus: false, - enabled: params.enabled ?? true, }); } diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/page.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/page.tsx index ced8cdc1b9f..59d5dae1dcd 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/page.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/page.tsx @@ -1,51 +1,79 @@ import { redirect } from "next/navigation"; import { getAuthToken } from "@/api/auth-token"; import { getProject } from "@/api/project/projects"; +import { getTeamBySlug } from "@/api/team/get-team"; +import { getLastNDaysRange } from "@/components/analytics/date-range-selector"; import { getClientThirdwebClient } from "@/constants/thirdweb-client.client"; import { loginRedirect } from "@/utils/redirects"; import { DedicatedRelayerPageClient } from "./components/page-client"; -import { getFleet } from "./lib/api"; +import { buildFleetId, type Fleet } from "./types"; export const dynamic = "force-dynamic"; export default async function DedicatedRelayerPage(props: { params: Promise<{ team_slug: string; project_slug: string }>; }) { - const params = await props.params; - const basePath = `/team/${params.team_slug}/${params.project_slug}/wallets/dedicated-relayer`; - - const authToken = await getAuthToken(); + const [params, authToken] = await Promise.all([props.params, getAuthToken()]); if (!authToken) { - loginRedirect(basePath); + loginRedirect( + `/team/${params.team_slug}/${params.project_slug}/wallets/dedicated-relayer`, + ); } - const project = await getProject(params.team_slug, params.project_slug); + const [team, project] = await Promise.all([ + getTeamBySlug(params.team_slug), + getProject(params.team_slug, params.project_slug), + ]); + + if (!team) { + redirect("/team"); + } if (!project) { redirect(`/team/${params.team_slug}`); } - // Fetch fleet data from the bundler service on the project - const fleet = await getFleet({ - teamId: project.teamId, - projectId: project.id, - authToken, - }); - const client = getClientThirdwebClient({ jwt: authToken, teamId: project.teamId, }); + // Build fleet ID from team and project + const fleetId = buildFleetId(team.id, project.id); + + // Default date range: last 30 days + const range = getLastNDaysRange("last-30"); + + // Extract fleet configuration from bundler service + const bundlerService = project.services.find((s) => s.name === "bundler"); + const fleetConfig = + bundlerService && "fleet" in bundlerService ? bundlerService.fleet : null; + + // Convert fleet config to Fleet type + // If fleet is undefined/null, show empty state (not-purchased) + // If fleet.executors is empty, show pending state + // If fleet.executors has addresses, show active state + let initialFleet: Fleet | null = null; + if (fleetConfig) { + initialFleet = { + id: fleetId, + chainIds: fleetConfig.chainIds, + executors: fleetConfig.executors, + }; + } + return ( ); } diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/types.ts b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/types.ts index 4135cd18b29..fa304921a80 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/types.ts +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/types.ts @@ -1,44 +1,61 @@ /** - * Types for Dedicated Relayer (Fleet) feature. - * - * Fleet lifecycle: - * - Not returned: empty state, dev hasn't purchased - * - Returned with empty executors: pending setup state - * - Returned with executors: fully active state + * Represents a dedicated relayer fleet configuration. + * Fleet config is fetched from ProjectBundlerService.fleet (service-utils). */ - -export type FleetExecutor = { - address: string; - chainId: number; -}; - -export type FleetTier = "starter" | "growth" | "enterprise"; - export type Fleet = { id: string; - tier?: FleetTier; chainIds: number[]; - executors: FleetExecutor[]; - createdAt: string; - updatedAt: string; -}; - -export type FleetAnalytics = { - totalTransactions: number; - totalGasSpentWei: string; - remainingBalanceWei: string; + executors: string[]; // executor wallet addresses }; +/** + * Fleet status based on its state. + */ export type FleetStatus = "not-purchased" | "pending-setup" | "active"; -export function getFleetStatus(fleet: Fleet | null | undefined): FleetStatus { +/** + * Derives the fleet status from the fleet object. + */ +export function getFleetStatus(fleet: Fleet | null): FleetStatus { if (!fleet) { return "not-purchased"; } - if (fleet.executors.length === 0) { return "pending-setup"; } - return "active"; } + +/** + * A single transaction from the fleet. + */ +export type FleetTransaction = { + timestamp: string; + chainId: string; + transactionFee: number; + transactionFeeUsd: number; + walletAddress: string; + transactionHash: string; + userOpHash: string; + executorAddress: string; +}; + +/** + * Summary statistics for fleet transactions. + */ +export type FleetTransactionsSummary = { + totalTransactions: number; + totalGasSpentUsd: number; + transactionsByChain: { + chainId: string; + count: number; + gasSpentUsd: number; + }[]; +}; + +/** + * Build the fleet ID from team and project. + */ +export function buildFleetId(teamId: string, projectId: string): string { + return `fleet-${teamId}-${projectId}`; +} diff --git a/packages/service-utils/src/core/api.ts b/packages/service-utils/src/core/api.ts index 5e66043b957..2ea551281c8 100644 --- a/packages/service-utils/src/core/api.ts +++ b/packages/service-utils/src/core/api.ts @@ -195,6 +195,10 @@ export type ProjectBundlerService = { value: string; }>; } | null; + fleet?: { + chainIds: number[]; + executors: string[]; + } | null; }; export type ProjectEmbeddedWalletsService = { From 153a446a4a6800ddaa5a3ea3793b816d657eee5c Mon Sep 17 00:00:00 2001 From: Manan Tank Date: Fri, 5 Dec 2025 23:47:31 +0530 Subject: [PATCH 3/5] UI updates, Fix lint error --- .../dedicated-relayer/monitoring-dark.png | Bin 0 -> 37735 bytes .../dedicated-relayer/monitoring-light.png | Bin 0 -> 21593 bytes .../dedicated-relayer/no-config-dark.png | Bin 0 -> 15856 bytes .../dedicated-relayer/no-config-light.png | Bin 0 -> 15490 bytes .../dedicated-relayer/server-wallet-dark.png | Bin 0 -> 25001 bytes .../dedicated-relayer/server-wallet-light.png | Bin 0 -> 26007 bytes apps/dashboard/src/@/types/billing.ts | 7 + .../checkout/[team_slug]/[sku]/page.tsx | 18 +- .../src/app/(app)/(stripe)/utils/billing.ts | 15 +- .../components/empty-state.tsx | 140 +++++++------ .../dedicated-relayer/components/index.ts | 4 - .../components/page-client.tsx | 22 +- .../components/pending-state.tsx | 4 +- .../components/tier-selection.tsx | 196 ++++++++++-------- .../wallets/dedicated-relayer/layout.tsx | 2 +- .../wallets/dedicated-relayer/types.ts | 2 +- 16 files changed, 244 insertions(+), 166 deletions(-) create mode 100644 apps/dashboard/public/assets/dedicated-relayer/monitoring-dark.png create mode 100644 apps/dashboard/public/assets/dedicated-relayer/monitoring-light.png create mode 100644 apps/dashboard/public/assets/dedicated-relayer/no-config-dark.png create mode 100644 apps/dashboard/public/assets/dedicated-relayer/no-config-light.png create mode 100644 apps/dashboard/public/assets/dedicated-relayer/server-wallet-dark.png create mode 100644 apps/dashboard/public/assets/dedicated-relayer/server-wallet-light.png delete mode 100644 apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/index.ts diff --git a/apps/dashboard/public/assets/dedicated-relayer/monitoring-dark.png b/apps/dashboard/public/assets/dedicated-relayer/monitoring-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..a55074a25c4200aaa5a9a8b769b5fdcc73acdee3 GIT binary patch literal 37735 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 M?(FqTjUzGbZ}hbUfB*mh literal 0 HcmV?d00001 diff --git a/apps/dashboard/public/assets/dedicated-relayer/monitoring-light.png b/apps/dashboard/public/assets/dedicated-relayer/monitoring-light.png new file mode 100644 index 0000000000000000000000000000000000000000..f20dc8f646ab0d716c60f835a4b47dc2db0d5560 GIT binary patch literal 21593 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 UX!aiq`=@@#;J$v5p2N$30nN2YUH||9 literal 0 HcmV?d00001 diff --git a/apps/dashboard/public/assets/dedicated-relayer/no-config-dark.png b/apps/dashboard/public/assets/dedicated-relayer/no-config-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..376c199b1395bebe88c838d5ad01fb320961b1fe GIT binary patch literal 15856 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 literal 0 HcmV?d00001 diff --git a/apps/dashboard/public/assets/dedicated-relayer/server-wallet-light.png b/apps/dashboard/public/assets/dedicated-relayer/server-wallet-light.png new file mode 100644 index 0000000000000000000000000000000000000000..aec90e46796fbd478711387127d85be1c682e468 GIT binary patch literal 26007 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 WkgA1`L2k0(-RIAmoGHj literal 0 HcmV?d00001 diff --git a/apps/dashboard/src/@/types/billing.ts b/apps/dashboard/src/@/types/billing.ts index cfa6f45df3d..211c5421919 100644 --- a/apps/dashboard/src/@/types/billing.ts +++ b/apps/dashboard/src/@/types/billing.ts @@ -13,9 +13,16 @@ export type ProductSKU = | "usage:in_app_wallet" | "usage:aa_sponsorship" | "usage:aa_sponsorship_op_grant" + // dedicated relayer SKUs + | DedicatedRelayerSKU | null; export type ChainInfraSKU = | "chain:infra:rpc" | "chain:infra:insight" | "chain:infra:account_abstraction"; + +export type DedicatedRelayerSKU = + | "product:dedicated_relayer_standard" + | "product:dedicated_relayer_premium" + | "product:dedicated_relayer_enterprise"; diff --git a/apps/dashboard/src/app/(app)/(stripe)/checkout/[team_slug]/[sku]/page.tsx b/apps/dashboard/src/app/(app)/(stripe)/checkout/[team_slug]/[sku]/page.tsx index 3d684adad47..c9304aff460 100644 --- a/apps/dashboard/src/app/(app)/(stripe)/checkout/[team_slug]/[sku]/page.tsx +++ b/apps/dashboard/src/app/(app)/(stripe)/checkout/[team_slug]/[sku]/page.tsx @@ -17,15 +17,21 @@ export default async function CheckoutPage(props: { searchParams: Promise<{ amount?: string; invoice_id?: string; + project_id?: string; + chain_id?: string[]; }>; }) { - const params = await props.params; + const [params, searchParams] = await Promise.all([ + props.params, + props.searchParams, + ]); + + console.log("params", params); + console.log("searchParams", searchParams); switch (params.sku) { case "topup": { - const amountUSD = Number.parseInt( - (await props.searchParams).amount || "10", - ); + const amountUSD = Number.parseInt(searchParams.amount || "10"); if (Number.isNaN(amountUSD)) { return ; } @@ -43,7 +49,7 @@ export default async function CheckoutPage(props: { break; } case "invoice": { - const invoiceId = (await props.searchParams).invoice_id; + const invoiceId = searchParams.invoice_id; if (!invoiceId) { return ; } @@ -59,10 +65,12 @@ export default async function CheckoutPage(props: { redirect(invoice); break; } + default: { const response = await getBillingCheckoutUrl({ sku: decodeURIComponent(params.sku) as Exclude, teamSlug: params.team_slug, + params: searchParams, }); if (response.status === "error") { diff --git a/apps/dashboard/src/app/(app)/(stripe)/utils/billing.ts b/apps/dashboard/src/app/(app)/(stripe)/utils/billing.ts index 906fc538695..b52e3df597c 100644 --- a/apps/dashboard/src/app/(app)/(stripe)/utils/billing.ts +++ b/apps/dashboard/src/app/(app)/(stripe)/utils/billing.ts @@ -7,6 +7,10 @@ import { getAbsoluteUrl } from "@/utils/vercel"; export async function getBillingCheckoutUrl(options: { teamSlug: string; sku: Exclude; + params: { + projectId?: string; + chain_id?: string[]; + }; }) { const token = await getAuthToken(); @@ -16,14 +20,17 @@ export async function getBillingCheckoutUrl(options: { status: "error", } as const; } + const body = { + redirectTo: getAbsoluteStripeRedirectUrl(), + sku: options.sku, + projectId: options.params.projectId, + chainIds: options.params.chain_id?.map(Number), + }; const res = await fetch( `${NEXT_PUBLIC_THIRDWEB_API_HOST}/v1/teams/${options.teamSlug}/checkout/create-link`, { - body: JSON.stringify({ - redirectTo: getAbsoluteStripeRedirectUrl(), - sku: options.sku, - }), + body: JSON.stringify(body), headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json", diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/empty-state.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/empty-state.tsx index 57f148df03e..643dea51aa6 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/empty-state.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/empty-state.tsx @@ -1,15 +1,18 @@ "use client"; -import { ExternalLinkIcon, ZapIcon } from "lucide-react"; -import Link from "next/link"; +import { Img } from "@workspace/ui/components/img"; +import { useTheme } from "next-themes"; import { useState } from "react"; -import { Button } from "@/components/ui/button"; -import { type RelayerTier, TierSelection } from "./tier-selection"; +import type { DedicatedRelayerSKU } from "@/types/billing"; +import { PlanSection } from "./tier-selection"; type DedicatedRelayerEmptyStateProps = { teamSlug: string; projectSlug: string; - onPurchaseTier: (tier: RelayerTier) => Promise; + onPurchaseTier: ( + tier: DedicatedRelayerSKU, + chainIds: number[], + ) => Promise; }; /** @@ -20,13 +23,16 @@ export function DedicatedRelayerEmptyState( props: DedicatedRelayerEmptyStateProps, ) { const [isLoading, setIsLoading] = useState(false); - const [selectedTier, setSelectedTier] = useState(null); + const [selectedTier, setSelectedTier] = useState( + null, + ); - const handleSelectTier = async (tier: RelayerTier) => { + const handleSelectTier = async (tier: DedicatedRelayerSKU) => { setSelectedTier(tier); setIsLoading(true); try { - await props.onPurchaseTier(tier); + // TODO-FLEET: pass the actual chain ids up + await props.onPurchaseTier(tier, [84532, 421614]); } finally { setIsLoading(false); setSelectedTier(null); @@ -34,70 +40,76 @@ export function DedicatedRelayerEmptyState( }; return ( -
    - {/* Hero Section */} -
    -
    -
    - -
    +
    + + +
    + ); +} -
    -

    - Dedicated Relayer Fleet -

    -

    - Your own dedicated executor wallets that automatically relay - transactions on your chosen chains. No manual gas management, no - shared infrastructure. -

    -
    +function FeatureSection() { + return ( +
    + -
    - - - -
    + - -
    -
    - - {/* Tier Selection */} -
    - -
    +
    ); } -function FeatureCard(props: { title: string; description: string }) { +function FeatureCard(props: { + title: string; + description: string; + className?: string; + images: { + darkSrc: string; + lightSrc: string; + }; +}) { + const { resolvedTheme } = useTheme(); + const imageSrc = + resolvedTheme === "light" ? props.images.lightSrc : props.images.darkSrc; + return ( -
    -

    {props.title}

    -

    {props.description}

    +
    + +
    +

    + {props.title} +

    +

    + {props.description} +

    +
    ); } diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/index.ts b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/index.ts deleted file mode 100644 index fcdfd9e81ee..00000000000 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export { DedicatedRelayerActiveState } from "./active-state"; -export { DedicatedRelayerEmptyState } from "./empty-state"; -export { DedicatedRelayerPendingState } from "./pending-state"; -export { TierSelection } from "./tier-selection"; diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/page-client.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/page-client.tsx index 52f74c99201..3fa82cfa666 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/page-client.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/page-client.tsx @@ -2,11 +2,12 @@ import { useState } from "react"; import type { ThirdwebClient } from "thirdweb"; +import type { DedicatedRelayerSKU } from "@/types/billing"; +import { getAbsoluteUrl } from "@/utils/vercel"; import type { Fleet, FleetStatus } from "../types"; import { DedicatedRelayerActiveState } from "./active-state"; import { DedicatedRelayerEmptyState } from "./empty-state"; import { DedicatedRelayerPendingState } from "./pending-state"; -import type { RelayerTier } from "./tier-selection"; type DedicatedRelayerPageClientProps = { teamId: string; @@ -37,9 +38,22 @@ export function DedicatedRelayerPageClient( // - Call bundler service to provision executor wallets // - Update ProjectBundlerService with fleet config // 5. Refetch fleet status and update UI - const handlePurchaseTier = async (_tier: RelayerTier) => { - // TODO-FLEET: Replace with actual Stripe + API server integration - // For now, simulate purchase by transitioning to pending-setup + const handlePurchaseTier = async ( + sku: DedicatedRelayerSKU, + chainIds: number[], + ) => { + const search = new URLSearchParams(); + search.set("project_id", props.projectId); + for (const chainId of chainIds) { + search.set("chain_id", chainId.toString()); + } + + // TODO-FLEET: use the existing checkout pattern with `stripe-reditect` instead + window.open( + `${getAbsoluteUrl()}/checkout/${props.teamSlug}/${sku}?${search.toString()}`, + "_blank", + ); + const mockFleet: Fleet = { id: props.fleetId, chainIds: [], diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/pending-state.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/pending-state.tsx index a1885debb19..70e57f44126 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/pending-state.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/pending-state.tsx @@ -2,7 +2,7 @@ import { ClockIcon, Loader2Icon } from "lucide-react"; import { Button } from "@/components/ui/button"; -import { defineDashboardChain } from "@/lib/defineDashboardChain"; +import { useV5DashboardChain } from "@/hooks/chains/v5-adapter"; import type { Fleet } from "../types"; type DedicatedRelayerPendingStateProps = { @@ -126,7 +126,7 @@ export function DedicatedRelayerPendingState( } function ChainBadge(props: { chainId: number }) { - const chain = defineDashboardChain(props.chainId, undefined); + const chain = useV5DashboardChain(props.chainId); return ( {chain.name || `Chain ${props.chainId}`} diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/tier-selection.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/tier-selection.tsx index d9e42c0d23a..9b702ab9c4a 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/tier-selection.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/tier-selection.tsx @@ -1,79 +1,103 @@ "use client"; -import { CheckIcon, ExternalLinkIcon, UsersIcon } from "lucide-react"; +import { + ArrowUpRightIcon, + BoxesIcon, + FolderCogIcon, + FolderIcon, + FoldersIcon, +} from "lucide-react"; import Link from "next/link"; import { Button } from "@/components/ui/button"; import { Spinner } from "@/components/ui/Spinner"; +import { WalletProductIcon } from "@/icons/WalletProductIcon"; import { cn } from "@/lib/utils"; - -export type RelayerTier = "starter" | "growth" | "enterprise"; +import type { DedicatedRelayerSKU } from "@/types/billing"; type TierConfig = { - id: RelayerTier; + id: DedicatedRelayerSKU; + icon: React.FC<{ className?: string }>; name: string; description: string; price: string; - priceSubtext?: string; - features: string[]; - maxChains: number; - executors: number; + isPerMonth?: boolean; + features: Array<{ icon: React.FC<{ className?: string }>; label: string }>; cta: string; - highlighted?: boolean; + isRecommended?: boolean; }; const TIERS: TierConfig[] = [ { - id: "starter", - name: "Single Executor", - description: "One dedicated executor for your project", + id: "product:dedicated_relayer_standard", + icon: FolderIcon, + name: "Standard", + description: + "Most suitable for small startups and apps doing less than 10,000 transactions per day", price: "$99", - priceSubtext: "/month", - features: ["1 dedicated executor wallet", "Up to 2 chains"], - maxChains: 2, - executors: 1, - cta: "Subscribe", + isPerMonth: true, + features: [ + { icon: WalletProductIcon, label: "Single executor wallet" }, + { icon: BoxesIcon, label: "Support for up to 2 chains" }, + ], + cta: "Select", }, { - id: "growth", - name: "Executor Fleet", - description: "10x the throughput with parallel execution", + id: "product:dedicated_relayer_premium", + icon: FoldersIcon, + name: "Premium", + description: + "Best for large enterprise companies and apps doing 100,000+ transactions per day", price: "$299", - priceSubtext: "/month", - features: ["10 dedicated executor wallets", "Up to 4 chains"], - maxChains: 4, - executors: 10, - cta: "Subscribe", - highlighted: true, + isPerMonth: true, + features: [ + { + icon: WalletProductIcon, + label: "10 executor wallets (10x throughput)", + }, + { icon: BoxesIcon, label: "Support for up to 4 chains" }, + ], + cta: "Select", + isRecommended: true, }, { - id: "enterprise", - name: "Executor Army", - description: "Custom executor count for maximum throughput", + id: "product:dedicated_relayer_enterprise", + icon: FolderCogIcon, + name: "Custom", + description: + "Contact us for more throughput with custom number of chains and executor wallets", price: "Custom", - features: ["Custom executor count", "Unlimited chains"], - maxChains: -1, - executors: -1, + features: [ + { icon: WalletProductIcon, label: "Unlimited executor wallets" }, + { icon: BoxesIcon, label: "Unlimited chains" }, + ], cta: "Contact Sales", }, ]; -type TierSelectionProps = { - onSelectTier: (tier: RelayerTier) => void; +export function PlanSection(props: { + onSelectTier: (tier: DedicatedRelayerSKU) => void; isLoading?: boolean; - selectedTier?: RelayerTier | null; -}; - -export function TierSelection(props: TierSelectionProps) { + selectedTier?: DedicatedRelayerSKU | null; +}) { return ( -
    -
    - {TIERS.map((tier) => ( - +
    +

    Select a plan

    +
    +
    + {TIERS.map((tier, index) => ( + props.onSelectTier(tier.id)} + className={ + index !== 0 + ? "border-t lg:border-t-0 lg:border-l border-dashed" + : "" + } + isRecommended={tier.isRecommended} /> ))}
    @@ -81,78 +105,88 @@ export function TierSelection(props: TierSelectionProps) { ); } -function TierCard(props: { +function PlanCard(props: { tier: TierConfig; onSelect: () => void; isLoading?: boolean; isDisabled?: boolean; + className?: string; + isRecommended?: boolean; }) { const { tier } = props; - const isEnterprise = tier.id === "enterprise"; + const isEnterprise = tier.id === "product:dedicated_relayer_enterprise"; return ( -
    - {tier.highlighted && ( -
    - - Most Popular - +
    +
    +
    +
    - )} - -
    -

    {tier.name}

    -

    {tier.description}

    +
    +
    +

    + {tier.name} +

    + {props.isRecommended && ( + + Recommended + + )} +
    +
    +

    + {tier.description} +

    -
    - {tier.price} - {tier.priceSubtext && ( - {tier.priceSubtext} +
    + + {tier.price} + + {tier.isPerMonth && ( + / month )}
    -
      +

      Includes:

      + +
        {tier.features.map((feature) => ( -
      • - - {feature} +
      • + + {feature.label}
      • ))}
      {isEnterprise ? ( - ) : ( )}
    diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/layout.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/layout.tsx index 348984f7f01..93318436f87 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/layout.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/layout.tsx @@ -37,7 +37,7 @@ export default async function Layout(props: { icon: WalletProductIcon, title: "Dedicated Relayer", description: - "Your own executor fleet for guaranteed transaction throughput", + "Executor wallets which automatically relay transactions with higher throughput and dedicated infrastructure", actions: null, client, links: [ diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/types.ts b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/types.ts index fa304921a80..3430e8608ca 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/types.ts +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/types.ts @@ -16,7 +16,7 @@ export type FleetStatus = "not-purchased" | "pending-setup" | "active"; /** * Derives the fleet status from the fleet object. */ -export function getFleetStatus(fleet: Fleet | null): FleetStatus { +function _getFleetStatus(fleet: Fleet | null): FleetStatus { if (!fleet) { return "not-purchased"; } From de3b33062ab41162904f4811fdd564623a00f490 Mon Sep 17 00:00:00 2001 From: 0xFirekeeper <0xFirekeeper@gmail.com> Date: Wed, 10 Dec 2025 04:44:02 +0700 Subject: [PATCH 4/5] Refactor dedicated relayer setup and purchase flow Improves the dedicated relayer onboarding by adding a modal for chain selection during purchase, updating the purchase flow to handle dynamic chain IDs, and refining the pending state UI to reflect setup progress and transaction status. Also updates billing utilities and checkout page to support both string and array types for chain IDs. --- .../checkout/[team_slug]/[sku]/page.tsx | 2 +- .../src/app/(app)/(stripe)/utils/billing.ts | 16 ++++-- .../components/empty-state.tsx | 57 ++++++++++++++++++- .../components/page-client.tsx | 46 +++++++-------- .../components/pending-state.tsx | 53 +++++++---------- 5 files changed, 108 insertions(+), 66 deletions(-) diff --git a/apps/dashboard/src/app/(app)/(stripe)/checkout/[team_slug]/[sku]/page.tsx b/apps/dashboard/src/app/(app)/(stripe)/checkout/[team_slug]/[sku]/page.tsx index c9304aff460..92d38a54a78 100644 --- a/apps/dashboard/src/app/(app)/(stripe)/checkout/[team_slug]/[sku]/page.tsx +++ b/apps/dashboard/src/app/(app)/(stripe)/checkout/[team_slug]/[sku]/page.tsx @@ -18,7 +18,7 @@ export default async function CheckoutPage(props: { amount?: string; invoice_id?: string; project_id?: string; - chain_id?: string[]; + chain_id?: string | string[]; }>; }) { const [params, searchParams] = await Promise.all([ diff --git a/apps/dashboard/src/app/(app)/(stripe)/utils/billing.ts b/apps/dashboard/src/app/(app)/(stripe)/utils/billing.ts index b52e3df597c..83b4fd008f0 100644 --- a/apps/dashboard/src/app/(app)/(stripe)/utils/billing.ts +++ b/apps/dashboard/src/app/(app)/(stripe)/utils/billing.ts @@ -8,8 +8,8 @@ export async function getBillingCheckoutUrl(options: { teamSlug: string; sku: Exclude; params: { - projectId?: string; - chain_id?: string[]; + project_id?: string; + chain_id?: string | string[]; }; }) { const token = await getAuthToken(); @@ -20,11 +20,19 @@ export async function getBillingCheckoutUrl(options: { status: "error", } as const; } + + const chainIds = options.params.chain_id + ? (Array.isArray(options.params.chain_id) + ? options.params.chain_id + : [options.params.chain_id] + ).map(Number) + : undefined; + const body = { redirectTo: getAbsoluteStripeRedirectUrl(), sku: options.sku, - projectId: options.params.projectId, - chainIds: options.params.chain_id?.map(Number), + projectId: options.params.project_id, + chainIds: chainIds, }; const res = await fetch( diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/empty-state.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/empty-state.tsx index 643dea51aa6..bfade57e485 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/empty-state.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/empty-state.tsx @@ -3,6 +3,16 @@ import { Img } from "@workspace/ui/components/img"; import { useTheme } from "next-themes"; import { useState } from "react"; +import type { ThirdwebClient } from "thirdweb"; +import { MultiNetworkSelector } from "@/components/blocks/NetworkSelectors"; +import { Button } from "@/components/ui/button"; +import { + Dialog, + DialogContent, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; import type { DedicatedRelayerSKU } from "@/types/billing"; import { PlanSection } from "./tier-selection"; @@ -13,6 +23,7 @@ type DedicatedRelayerEmptyStateProps = { tier: DedicatedRelayerSKU, chainIds: number[], ) => Promise; + client: ThirdwebClient; }; /** @@ -26,19 +37,35 @@ export function DedicatedRelayerEmptyState( const [selectedTier, setSelectedTier] = useState( null, ); + const [isModalOpen, setIsModalOpen] = useState(false); + const [selectedChainIds, setSelectedChainIds] = useState([]); const handleSelectTier = async (tier: DedicatedRelayerSKU) => { + if (tier === "product:dedicated_relayer_enterprise") { + window.open("https://thirdweb.com/contact-us", "_blank"); + return; + } + setSelectedTier(tier); + setSelectedChainIds([]); + setIsModalOpen(true); + }; + + const handlePurchase = async () => { + if (!selectedTier) return; setIsLoading(true); try { - // TODO-FLEET: pass the actual chain ids up - await props.onPurchaseTier(tier, [84532, 421614]); + await props.onPurchaseTier(selectedTier, selectedChainIds); + setIsModalOpen(false); } finally { setIsLoading(false); setSelectedTier(null); } }; + const requiredChains = + selectedTier === "product:dedicated_relayer_standard" ? 2 : 4; + return (
    @@ -47,6 +74,32 @@ export function DedicatedRelayerEmptyState( isLoading={isLoading} selectedTier={selectedTier} /> + + + + + Select Chains + +
    +

    + Select {requiredChains} chains for your dedicated relayer. +

    + +
    + + + +
    +
    ); } diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/page-client.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/page-client.tsx index 3fa82cfa666..34e4a7be6b7 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/page-client.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/page-client.tsx @@ -4,6 +4,7 @@ import { useState } from "react"; import type { ThirdwebClient } from "thirdweb"; import type { DedicatedRelayerSKU } from "@/types/billing"; import { getAbsoluteUrl } from "@/utils/vercel"; +import { useFleetTransactionsSummary } from "../lib/hooks"; import type { Fleet, FleetStatus } from "../types"; import { DedicatedRelayerActiveState } from "./active-state"; import { DedicatedRelayerEmptyState } from "./empty-state"; @@ -24,11 +25,21 @@ type DedicatedRelayerPageClientProps = { export function DedicatedRelayerPageClient( props: DedicatedRelayerPageClientProps, ) { - const [fleet, setFleet] = useState(props.initialFleet); - const [fleetStatus, setFleetStatus] = useState(() => + const [fleet, _setFleet] = useState(props.initialFleet); + const [fleetStatus, _setFleetStatus] = useState(() => getInitialStatus(props.initialFleet), ); + const summaryQuery = useFleetTransactionsSummary({ + teamId: props.teamId, + fleetId: props.fleetId, + from: props.from, + to: props.to, + }); + + const totalTransactions = summaryQuery.data?.data.totalTransactions ?? 0; + const hasTransactions = totalTransactions > 0; + // TODO-FLEET: Implement purchase flow // 1. Call Stripe checkout API to create a checkout session for the selected tier // 2. Redirect user to Stripe checkout @@ -53,28 +64,6 @@ export function DedicatedRelayerPageClient( `${getAbsoluteUrl()}/checkout/${props.teamSlug}/${sku}?${search.toString()}`, "_blank", ); - - const mockFleet: Fleet = { - id: props.fleetId, - chainIds: [], - executors: [], - }; - setFleet(mockFleet); - setFleetStatus("pending-setup"); - }; - - // TODO-FLEET: This is a dev helper to skip purchase - remove in production - const handleSkipSetup = () => { - // TODO-FLEET: Replace with actual setup completion flow - // For now, simulate setup completion by transitioning to active with mock executors - if (fleet) { - setFleet({ - ...fleet, - executors: ["0x1234567890123456789012345678901234567890"], - chainIds: [1, 137], - }); - setFleetStatus("active"); - } }; return ( @@ -84,17 +73,22 @@ export function DedicatedRelayerPageClient( projectSlug={props.projectSlug} teamSlug={props.teamSlug} onPurchaseTier={handlePurchaseTier} + client={props.client} /> )} {fleetStatus === "pending-setup" && fleet && ( + + )} + + {fleetStatus === "active" && fleet && !hasTransactions && ( )} - {fleetStatus === "active" && fleet && ( + {fleetStatus === "active" && fleet && hasTransactions && ( void; + hasTransactions?: boolean; }; /** @@ -17,7 +16,8 @@ type DedicatedRelayerPendingStateProps = { export function DedicatedRelayerPendingState( props: DedicatedRelayerPendingStateProps, ) { - const { fleet } = props; + const { fleet, hasTransactions } = props; + const hasExecutors = fleet.executors.length > 0; return (
    @@ -26,10 +26,14 @@ export function DedicatedRelayerPendingState(

    - Setting up your dedicated relayer + {hasExecutors + ? "Waiting for first transaction" + : "Setting up your dedicated relayer"}

    - Your relayer is being provisioned. This usually takes a few minutes. + {hasExecutors + ? "Your dedicated relayer is ready. Waiting for the first transaction to appear." + : "Your relayer is being provisioned. This usually takes a few minutes."}

    @@ -63,7 +67,11 @@ export function DedicatedRelayerPendingState(
    - Awaiting executor deployment + + {hasExecutors + ? "Active, waiting for transactions" + : "Awaiting executor deployment"} +
    @@ -86,41 +94,20 @@ export function DedicatedRelayerPendingState( title="Subscription activated" /> -
    - - {/* Dev Skip Button */} - {props.onSkipSetup && ( -
    -
    -
    -

    ๐Ÿงช Dev Mode

    -

    - Skip the setup wait and go straight to active state -

    -
    - -
    -
    - )}
    ); } From 39a3e75bd59dd262e6ea78853db3d5ad9f15e6e8 Mon Sep 17 00:00:00 2001 From: 0xFirekeeper <0xFirekeeper@gmail.com> Date: Wed, 10 Dec 2025 04:49:28 +0700 Subject: [PATCH 5/5] Update feature descriptions in empty state component Revised the descriptions for Prioritized Queueing, Zero Configuration, and Monitoring features in the dedicated relayer empty state component to provide clearer and more detailed explanations. --- .../wallets/dedicated-relayer/components/empty-state.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/empty-state.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/empty-state.tsx index bfade57e485..1689f758a92 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/empty-state.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/empty-state.tsx @@ -109,7 +109,7 @@ function FeatureSection() {