Skip to content

Conversation

@0xFirekeeper
Copy link
Member

@0xFirekeeper 0xFirekeeper commented Dec 1, 2025

PR-Codex overview

This PR adds a Dedicated Relayer feature to the service utilities, enhancing the billing system, and introduces related components for managing dedicated relayers in the dashboard.

Detailed summary

  • Introduced dedicatedRelayer type in api.ts.
  • Added DedicatedRelayerSKU to billing types.
  • Updated ProjectSidebarLayout to include a link for Dedicated Relayer.
  • Created new components for Dedicated Relayer management.
  • Implemented hooks for fetching fleet status and transactions.
  • Added constants for supported chains.
  • Developed UI for tier selection and empty/pending/active states of the relayer.

✨ Ask PR-Codex anything about this PR by commenting with /codex {your question}

Summary by CodeRabbit

  • New Features

    • Dedicated Relayer offering added: Standard, Premium, Enterprise tiers, purchase flow, tier selector, and checkout that accepts project and chain selection.
    • Dedicated Relayer workspace: conditional sidebar entry, dedicated page/layout, pending/setup and active dashboards with stats, transaction list, and per-row details.
    • Server APIs and client hooks for fleet status, transactions, and summaries.
  • Chores

    • Fleet types, supported-chain list, fleet ID helper, and billing SKU support added.

✏️ Tip: You can customize this high-level summary in your review settings.

@changeset-bot
Copy link

changeset-bot bot commented Dec 1, 2025

🦋 Changeset detected

Latest commit: 6bc328f

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@thirdweb-dev/service-utils Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel
Copy link

vercel bot commented Dec 1, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
thirdweb-www Ready Ready Preview Comment Dec 10, 2025 9:18pm
4 Skipped Deployments
Project Deployment Preview Comments Updated (UTC)
docs-v2 Skipped Skipped Dec 10, 2025 9:18pm
nebula Skipped Skipped Dec 10, 2025 9:18pm
thirdweb_playground Skipped Skipped Dec 10, 2025 9:18pm
wallet-ui Skipped Skipped Dec 10, 2025 9:18pm

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 1, 2025

Walkthrough

Adds a Dedicated Relayer feature: new billing SKU types and checkout params, sidebar link, dedicated-relayer pages/layout, multiple client UI components (empty/pending/active/tier selection), server API/hooks/types for fleet data, supported-chains constant, and a service-utils schema extension.

Changes

Cohort / File(s) Change Summary
Billing types & checkout
apps/dashboard/src/@/types/billing.ts, apps/dashboard/src/app/(app)/(stripe)/checkout/[team_slug]/[sku]/page.tsx, apps/dashboard/src/app/(app)/(stripe)/utils/billing.ts
Added DedicatedRelayerSKU and included it in ProductSKU. Checkout page reads searchParams (amount, invoice_id, project_id, chain_id) in parallel and forwards project_id/chain_id. getBillingCheckoutUrl accepts params and sends projectId and normalized chainIds in the request body.
Sidebar integration
apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/components/ProjectSidebarLayout.tsx, apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/layout.tsx
ProjectSidebarLayout now accepts teamId: string; caller passes team.id. Sidebar conditionally renders a "Dedicated Relayer" wallet link when the teamId gating condition matches.
Dedicated Relayer pages & layout
apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/page.tsx, .../layout.tsx
New server page and layout that fetch auth, team, project in parallel, redirect on missing auth/project, construct Thirdweb client, build fleetId, set date range, and render the client wrapper / ProjectPage header.
Dedicated Relayer UI components
.../components/active-state.tsx, .../components/empty-state.tsx, .../components/page-client.tsx, .../components/pending-state.tsx, .../components/tier-selection.tsx
New client components: Active (transactions table, filters, stats), Empty (tier & chain selection, purchase modal and Stripe flow), PageClient (status branching, polling, purchase handler), Pending (setup steps/status), and PlanSection/tier selection UI with per-tier CTAs.
Dedicated Relayer data layer
.../types.ts, .../lib/api.ts, .../lib/hooks.ts, .../constants.ts
New types: Fleet, FleetStatus, FleetTransaction, FleetTransactionsSummary; buildFleetId util; server API functions getFleetTransactions, getFleetTransactionsSummary, getFleetStatus; client hooks useFleetTransactions, useFleetTransactionsSummary, useFleetStatus; RELAYER_SUPPORTED_CHAINS constant.
Service utils schema
packages/service-utils/src/core/api.ts
Extended ProjectBundlerService with optional `dedicatedRelayer?: { sku: string; chainIds: number[]; executors: string[] }
Metadata / changeset
.changeset/some-mice-own.md
Added changeset documenting addition of dedicated relayer to service-utils.

Sequence Diagram

sequenceDiagram
    participant User
    participant ServerPage as Server Page
    participant ClientComp as PageClient
    participant BillingAPI as Billing API
    participant Stripe
    participant FleetAPI as Fleet API

    User->>ServerPage: GET /dedicated-relayer
    ServerPage->>ServerPage: fetch auth, team, project (parallel)
    alt no auth
        ServerPage->>User: redirect to login
    else no project/team
        ServerPage->>User: redirect to team page
    else
        ServerPage->>ClientComp: render with client, team, project, initialFleet
        ClientComp->>FleetAPI: getFleetStatus / summary / transactions (polling when active)
        alt not-purchased
            User->>ClientComp: select tier + chains
            ClientComp->>BillingAPI: getBillingCheckoutUrl(sku, project_id, chain_ids)
            BillingAPI-->>ClientComp: checkoutUrl
            ClientComp->>Stripe: open checkoutUrl (new tab)
        else pending-setup
            ClientComp->>User: show PendingState (setup steps)
        else active
            ClientComp->>FleetAPI: fetch transactions/summary
            FleetAPI-->>ClientComp: return transactions
            ClientComp->>User: show ActiveState (transactions, stats, filters)
        end
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Focus areas:
    • apps/.../components/active-state.tsx — complex UI, pagination, filters, large JSX and hook usage.
    • apps/.../components/page-client.tsx — orchestration, status polling, Stripe checkout composition and window behavior.
    • apps/.../lib/api.ts and lib/hooks.ts — server/client API contract, error handling, and query lifecycle.
    • Billing changes (billing.ts, checkout page, billing utils) — ensure param normalization and request payloads match backend expectations.
    • packages/service-utils/src/core/api.ts — schema extension impact on consumers and serialization.

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings, 1 inconclusive)
Check name Status Explanation Resolution
Description check ⚠️ Warning The description fails to follow the required template format with the specified title format, issue tag, reviewer notes, and testing instructions. Update description to follow the template: add proper title format '[SDK/Dashboard] Feature:...', include issue tag, provide specific testing instructions, and add reviewer notes.
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title check ❓ Inconclusive The title 'Dedicated Relayer' is vague and generic, using a feature name without describing the specific changes or scope of the implementation. Provide a more descriptive title that clarifies the main change, such as 'Add dedicated relayer fleet management with UI and API integration' or 'Introduce dedicated relayer feature for fleet transactions and configuration'.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch firekeeper/fleets

Comment @coderabbitai help to get the list of available commands and usage tips.

@graphite-app
Copy link
Contributor

graphite-app bot commented Dec 1, 2025

How to use the Graphite Merge Queue

Add either label to this PR to merge it via the merge queue:

  • merge-queue - adds this PR to the back of the merge queue
  • hotfix - for urgent hot fixes, skip the queue and merge this PR next

You must have a Graphite account in order to use the merge queue. Sign up using this link.

An organization admin has enabled the Graphite Merge Queue in this repository.

Please do not merge from GitHub as this will restart CI on PRs being processed by the merge queue.

@github-actions github-actions bot added the Dashboard Involves changes to the Dashboard. label Dec 1, 2025
@codecov
Copy link

codecov bot commented Dec 1, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 54.66%. Comparing base (1487de6) to head (6bc328f).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #8479   +/-   ##
=======================================
  Coverage   54.66%   54.66%           
=======================================
  Files         921      921           
  Lines       61163    61163           
  Branches     4151     4151           
=======================================
  Hits        33435    33435           
  Misses      27627    27627           
  Partials      101      101           
Flag Coverage Δ
packages 54.66% <ø> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions
Copy link
Contributor

github-actions bot commented Dec 1, 2025

size-limit report 📦

Path Size
@thirdweb-dev/nexus (esm) 105.66 KB (0%)
@thirdweb-dev/nexus (cjs) 319.47 KB (0%)

@vercel vercel bot temporarily deployed to Preview – nebula December 3, 2025 21:58 Inactive
@vercel vercel bot temporarily deployed to Preview – docs-v2 December 3, 2025 21:58 Inactive
@vercel vercel bot temporarily deployed to Preview – thirdweb_playground December 3, 2025 21:58 Inactive
@vercel vercel bot temporarily deployed to Preview – wallet-ui December 3, 2025 21:58 Inactive
@vercel vercel bot temporarily deployed to Preview – docs-v2 December 5, 2025 18:17 Inactive
@vercel vercel bot temporarily deployed to Preview – thirdweb_playground December 5, 2025 18:17 Inactive
@vercel vercel bot temporarily deployed to Preview – wallet-ui December 5, 2025 18:17 Inactive
@vercel vercel bot temporarily deployed to Preview – nebula December 5, 2025 18:17 Inactive
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/dashboard/src/app/(app)/(stripe)/utils/billing.ts (1)

10-36: Harden chain_id parsing to avoid NaNs in chainIds

chain_id comes from query params and is cast with .map(Number), which will quietly produce NaN entries for invalid values. That may be surprising for the API consumer.

Consider filtering or validating before sending:

-  const chainIds = options.params.chain_id
-    ? (Array.isArray(options.params.chain_id)
-        ? options.params.chain_id
-        : [options.params.chain_id]
-      ).map(Number)
-    : undefined;
+  const chainIds = options.params.chain_id
+    ? (Array.isArray(options.params.chain_id)
+        ? options.params.chain_id
+        : [options.params.chain_id]
+      )
+        .map((value) => Number(value))
+        .filter((value) => Number.isFinite(value))
+    : undefined;

This keeps the payload clean and avoids leaking malformed input downstream.

🧹 Nitpick comments (10)
apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/components/ProjectSidebarLayout.tsx (1)

20-26: Avoid hard‑coding team IDs for feature gating in the sidebar

The Dedicated Relayer link is currently gated via a literal team id:

...(props.teamId === "team_clmb33q9w00gn1x0u2ri8z0k0" ? [/* link */] : [])

That’s brittle, hard to discover, and will be painful to change later. Prefer a feature flag, environment‑based allowlist, or a capability flag from the API (e.g. “team has dedicated relayer access”) to decide when to render this link.

Also applies to: 55-62

apps/dashboard/src/app/(app)/(stripe)/checkout/[team_slug]/[sku]/page.tsx (1)

17-22: Keep the new param plumbing, but remove debug logging

Resolving params and searchParams together and threading project_id / chain_id into getBillingCheckoutUrl looks good and matches the updated billing util.

The console.log("params", …) and console.log("searchParams", …) calls, though, are debug noise and will spam server logs in production. Safe to drop them now that you’ve validated the flow.

Also applies to: 24-31, 34-35, 52-53, 70-74

apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/page-client.tsx (1)

54-69: Consider making handlePurchaseTier synchronous.

The function is marked async but doesn't use await. Since window.open is synchronous, removing async would be cleaner.

-  const handlePurchaseTier = async (
+  const handlePurchaseTier = (
     sku: DedicatedRelayerSKU,
     chainIds: number[],
   ) => {
apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/lib/hooks.ts (2)

19-26: Consider adding staleTime configuration.

Per coding guidelines, React Query hooks should configure staleTime based on freshness requirements (default ≥ 60s). Fleet transactions data is unlikely to change rapidly.

 export function useFleetTransactions(params: UseFleetTransactionsParams) {
   return useQuery({
     queryKey: ["fleet-transactions", params],
     queryFn: () => getFleetTransactions(params),
     placeholderData: keepPreviousData,
     refetchOnWindowFocus: false,
+    staleTime: 60_000, // 1 minute
   });
 }

39-47: Consider adding staleTime for the summary hook as well.

Same recommendation for consistency with the transactions hook.

 export function useFleetTransactionsSummary(
   params: UseFleetTransactionsSummaryParams,
 ) {
   return useQuery({
     queryKey: ["fleet-transactions-summary", params],
     queryFn: () => getFleetTransactionsSummary(params),
     refetchOnWindowFocus: false,
     enabled: params.enabled !== false,
+    staleTime: 60_000, // 1 minute
   });
 }
apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/layout.tsx (1)

1-8: Consider adding import "server-only" for explicit server component marking.

Per coding guidelines, server component files should start with import "server-only"; to prevent accidental client bundling.

+import "server-only";
+
 import { redirect } from "next/navigation";
 import { getAuthToken } from "@/api/auth-token";
apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/page.tsx (1)

1-9: Consider adding import "server-only" for explicit server component marking.

Per coding guidelines, server component files should start with import "server-only"; to prevent accidental client bundling.

+import "server-only";
+
 import { redirect } from "next/navigation";
 import { getAuthToken } from "@/api/auth-token";
apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/types.ts (3)

5-9: Align chainId typing across fleet and transaction types

Fleet.chainIds is number[], while FleetTransaction.chainId and FleetTransactionsSummary.transactionsByChain[].chainId are string. That mismatch will likely force callers to cast/parse when correlating fleets with their transactions.

If the API truly returns different representations, consider:

  • Normalizing to a single ChainId type (e.g. type ChainId = number or string) and re-using it across all three shapes.
  • Or, if fleet config is numeric and API responses are stringly-typed by design, add a brief doc comment to call that out so consumers know they must convert.

Also, if there is already a shared chain ID type in @/types or a local barrel, it would be preferable to reuse it here to keep things consistent across the dashboard.

Also applies to: 32-53


16-27: Tighten _getFleetStatus typing and placement

The status derivation logic looks fine, but there are a couple of polish points:

  • Add an explicit return type to match the TS guidelines and make refactors safer:
function _getFleetStatus(fleet: Fleet | null): FleetStatus {
  if (!fleet) {
    return "not-purchased";
  }
  if (fleet.executors.length === 0) {
    return "pending-setup";
  }
  return "active";
}
  • Confirm that executors is always a non-null array from ProjectBundlerService.fleet. If the service can ever send null/undefined, you may want a defensive guard (or adjust the Fleet type) to avoid runtime errors when accessing .length.

  • Given this file is named types.ts, consider whether runtime helpers like _getFleetStatus should live in a small fleet/utils module instead, with this file reserved for pure types. That separation can make it clearer which imports are side‑effect free and which bundle logic.


56-61: buildFleetId implementation looks good; consider centralizing ID conventions

The ID builder is clear and deterministic, which is great:

export function buildFleetId(teamId: string, projectId: string): string {
  return `fleet-${teamId}-${projectId}`;
}

Two minor, non-blocking considerations:

  • If teamId / projectId can themselves contain - and you ever need to parse this ID back into components, you may want a less ambiguous encoding scheme (e.g. fixed prefix + delimiter-free IDs, or structured storage instead of parsing).
  • If there are (or will be) other fleet-related helpers, it might be worth moving buildFleetId and _getFleetStatus into a small fleet/utils module, keeping types.ts focused purely on type declarations.

Otherwise, the function is simple and fits the dedicated-relayer domain well.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 1487de6 and 0225f92.

⛔ Files ignored due to path filters (6)
  • apps/dashboard/public/assets/dedicated-relayer/monitoring-dark.png is excluded by !**/*.png
  • apps/dashboard/public/assets/dedicated-relayer/monitoring-light.png is excluded by !**/*.png
  • apps/dashboard/public/assets/dedicated-relayer/no-config-dark.png is excluded by !**/*.png
  • apps/dashboard/public/assets/dedicated-relayer/no-config-light.png is excluded by !**/*.png
  • apps/dashboard/public/assets/dedicated-relayer/server-wallet-dark.png is excluded by !**/*.png
  • apps/dashboard/public/assets/dedicated-relayer/server-wallet-light.png is excluded by !**/*.png
📒 Files selected for processing (16)
  • apps/dashboard/src/@/types/billing.ts (1 hunks)
  • apps/dashboard/src/app/(app)/(stripe)/checkout/[team_slug]/[sku]/page.tsx (3 hunks)
  • apps/dashboard/src/app/(app)/(stripe)/utils/billing.ts (2 hunks)
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/components/ProjectSidebarLayout.tsx (2 hunks)
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/layout.tsx (1 hunks)
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/active-state.tsx (1 hunks)
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/empty-state.tsx (1 hunks)
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/page-client.tsx (1 hunks)
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/pending-state.tsx (1 hunks)
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/tier-selection.tsx (1 hunks)
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/layout.tsx (1 hunks)
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/lib/api.ts (1 hunks)
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/lib/hooks.ts (1 hunks)
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/page.tsx (1 hunks)
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/types.ts (1 hunks)
  • packages/service-utils/src/core/api.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (12)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Write idiomatic TypeScript with explicit function declarations and return types
Limit each TypeScript file to one stateless, single-responsibility function for clarity
Re-use shared types from @/types or local types.ts barrels
Prefer type aliases over interface except for nominal shapes in TypeScript
Avoid any and unknown in TypeScript unless unavoidable; narrow generics when possible
Choose composition over inheritance; leverage utility types (Partial, Pick, etc.) in TypeScript

**/*.{ts,tsx}: Write idiomatic TypeScript with explicit function declarations and return types
Limit each file to one stateless, single-responsibility function for clarity and testability
Re-use shared types from @/types or local types.ts barrel exports
Prefer type aliases over interface except for nominal shapes
Avoid any and unknown unless unavoidable; narrow generics whenever possible
Choose composition over inheritance; leverage utility types (Partial, Pick, etc.)
Comment only ambiguous logic in TypeScript files; avoid restating TypeScript types and signatures in prose

Files:

  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/layout.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/empty-state.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/layout.tsx
  • apps/dashboard/src/@/types/billing.ts
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/lib/api.ts
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/active-state.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/page-client.tsx
  • apps/dashboard/src/app/(app)/(stripe)/checkout/[team_slug]/[sku]/page.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/pending-state.tsx
  • apps/dashboard/src/app/(app)/(stripe)/utils/billing.ts
  • packages/service-utils/src/core/api.ts
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/lib/hooks.ts
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/page.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/types.ts
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/components/ProjectSidebarLayout.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/tier-selection.tsx
apps/{dashboard,playground-web}/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

apps/{dashboard,playground-web}/src/**/*.{ts,tsx}: Import UI component primitives from @/components/ui/* (Button, Input, Select, Tabs, Card, Sidebar, Badge, Separator) in dashboard and playground
Use Tailwind CSS only – no inline styles or CSS modules in dashboard and playground
Use cn() from @/lib/utils for conditional Tailwind class merging
Use design system tokens for styling (backgrounds: bg-card, borders: border-border, muted text: text-muted-foreground)
Expose className prop on root element for component overrides

Files:

  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/layout.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/empty-state.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/layout.tsx
  • apps/dashboard/src/@/types/billing.ts
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/lib/api.ts
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/active-state.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/page-client.tsx
  • apps/dashboard/src/app/(app)/(stripe)/checkout/[team_slug]/[sku]/page.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/pending-state.tsx
  • apps/dashboard/src/app/(app)/(stripe)/utils/billing.ts
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/lib/hooks.ts
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/page.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/types.ts
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/components/ProjectSidebarLayout.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/tier-selection.tsx
apps/dashboard/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

apps/dashboard/src/**/*.{ts,tsx}: Use NavLink for internal navigation with automatic active states in dashboard
Start server component files with import "server-only"; in Next.js
Read cookies/headers with next/headers in server components
Access server-only environment variables in server components
Perform heavy data fetching in server components
Implement redirect logic with redirect() from next/navigation in server components
Begin client component files with 'use client'; directive in Next.js
Handle interactive UI with React hooks (useState, useEffect, React Query, wallet hooks) in client components
Access browser APIs (localStorage, window, IntersectionObserver) in client components
Support fast transitions with prefetched data in client components
Always call getAuthToken() to retrieve JWT from cookies on server side
Use Authorization: Bearer header for API calls – never embed tokens in URLs
Return typed results (Project[], User[]) from server-side data fetches – avoid any
Wrap client-side API calls in React Query (@tanstack/react-query)
Use descriptive, stable queryKeys in React Query for cache hits
Configure staleTime/cacheTime in React Query based on freshness (default ≥ 60s)
Keep tokens secret via internal API routes or server actions
Never import posthog-js in server components – only use analytics client-side

Files:

  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/layout.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/empty-state.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/layout.tsx
  • apps/dashboard/src/@/types/billing.ts
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/lib/api.ts
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/active-state.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/page-client.tsx
  • apps/dashboard/src/app/(app)/(stripe)/checkout/[team_slug]/[sku]/page.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/pending-state.tsx
  • apps/dashboard/src/app/(app)/(stripe)/utils/billing.ts
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/lib/hooks.ts
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/page.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/types.ts
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/components/ProjectSidebarLayout.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/tier-selection.tsx
apps/dashboard/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/dashboard.mdc)

apps/dashboard/**/*.{ts,tsx}: Always import from the central UI library under @/components/ui/* for reusable core UI components like Button, Input, Select, Tabs, Card, Sidebar, Separator, Badge
Use NavLink from @/components/ui/NavLink for internal navigation to ensure active states are handled automatically
For notices and skeletons, rely on AnnouncementBanner, GenericLoadingPage, and EmptyStateCard components
Import icons from lucide-react or the project-specific …/icons exports; never embed raw SVG
Keep components pure; fetch data outside using server components or hooks and pass it down via props
Use Tailwind CSS as the styling system; avoid inline styles or CSS modules
Merge class names with cn from @/lib/utils to keep conditional logic readable
Stick to design tokens: use bg-card, border-border, text-muted-foreground and other Tailwind variables instead of hard-coded colors
Use spacing utilities (px-*, py-*, gap-*) instead of custom margins
Follow mobile-first responsive design with Tailwind helpers (max-sm, md, lg, xl)
Never hard-code colors; always use Tailwind variables
Combine class names via cn, and expose className prop if useful in components
Use React Query (@tanstack/react-query) for all client-side data fetching with typed hooks

Files:

  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/layout.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/empty-state.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/layout.tsx
  • apps/dashboard/src/@/types/billing.ts
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/lib/api.ts
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/active-state.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/page-client.tsx
  • apps/dashboard/src/app/(app)/(stripe)/checkout/[team_slug]/[sku]/page.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/pending-state.tsx
  • apps/dashboard/src/app/(app)/(stripe)/utils/billing.ts
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/lib/hooks.ts
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/page.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/types.ts
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/components/ProjectSidebarLayout.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/tier-selection.tsx
apps/dashboard/**/layout.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/dashboard.mdc)

apps/dashboard/**/layout.{ts,tsx}: Reuse SidebarLayout or FullWidthSidebarLayout from @/components/blocks/SidebarLayout for all layouts
Export default async functions without 'use client'; in server components; they run on the Node edge

Files:

  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/layout.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/layout.tsx
**/*.{js,jsx,ts,tsx,json}

📄 CodeRabbit inference engine (AGENTS.md)

Biome governs formatting and linting; its rules live in biome.json. Run pnpm fix & pnpm lint before committing, ensure there are no linting errors

Files:

  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/layout.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/empty-state.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/layout.tsx
  • apps/dashboard/src/@/types/billing.ts
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/lib/api.ts
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/active-state.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/page-client.tsx
  • apps/dashboard/src/app/(app)/(stripe)/checkout/[team_slug]/[sku]/page.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/pending-state.tsx
  • apps/dashboard/src/app/(app)/(stripe)/utils/billing.ts
  • packages/service-utils/src/core/api.ts
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/lib/hooks.ts
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/page.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/types.ts
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/components/ProjectSidebarLayout.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/tier-selection.tsx
apps/{dashboard,playground}/**/*.{tsx,ts}

📄 CodeRabbit inference engine (AGENTS.md)

apps/{dashboard,playground}/**/*.{tsx,ts}: Import UI primitives from @/components/ui/_ (Button, Input, Select, Tabs, Card, Sidebar, Badge, Separator) in Dashboard and Playground apps
Use NavLink for internal navigation so active states are handled automatically
Use Tailwind CSS for styling – no inline styles or CSS modules
Merge class names with cn() from @/lib/utils to keep conditional logic readable
Stick to design tokens for styling: backgrounds (bg-card), borders (border-border), muted text (text-muted-foreground), etc.
Server Components: Read cookies/headers with next/headers, access server-only environment variables or secrets, perform heavy data fetching, implement redirect logic with redirect() from next/navigation, and start files with import 'server-only'; to prevent client bundling
Client Components: Begin files with 'use client'; before imports, handle interactive UI relying on React hooks (useState, useEffect, React Query, wallet hooks), access browser APIs (localStorage, window, IntersectionObserver, etc.), and support fast transitions with client-side data prefetching
For client-side data fetching: Wrap calls in React Query (@tanstack/react-query), use descriptive and stable queryKeys for cache hits, configure staleTime / cacheTime based on freshness requirements (default ≥ 60 s), and keep tokens secret by calling internal API routes or server actions

Files:

  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/layout.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/empty-state.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/layout.tsx
  • apps/dashboard/src/@/types/billing.ts
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/lib/api.ts
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/active-state.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/page-client.tsx
  • apps/dashboard/src/app/(app)/(stripe)/checkout/[team_slug]/[sku]/page.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/pending-state.tsx
  • apps/dashboard/src/app/(app)/(stripe)/utils/billing.ts
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/lib/hooks.ts
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/page.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/types.ts
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/components/ProjectSidebarLayout.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/tier-selection.tsx
apps/{dashboard,playground}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

apps/{dashboard,playground}/**/*.{ts,tsx}: For server-side data fetching: Always call getAuthToken() to retrieve the JWT from cookies and inject the token as an Authorization: Bearer header – never embed it in the URL. Return typed results (Project[], User[], …) – avoid any
Never import posthog-js in server components; analytics reporting is client-side only

Files:

  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/layout.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/empty-state.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/layout.tsx
  • apps/dashboard/src/@/types/billing.ts
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/lib/api.ts
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/active-state.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/page-client.tsx
  • apps/dashboard/src/app/(app)/(stripe)/checkout/[team_slug]/[sku]/page.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/pending-state.tsx
  • apps/dashboard/src/app/(app)/(stripe)/utils/billing.ts
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/lib/hooks.ts
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/page.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/types.ts
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/components/ProjectSidebarLayout.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/tier-selection.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Lazy-import optional features; avoid top-level side-effects

Files:

  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/layout.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/empty-state.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/layout.tsx
  • apps/dashboard/src/@/types/billing.ts
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/lib/api.ts
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/active-state.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/page-client.tsx
  • apps/dashboard/src/app/(app)/(stripe)/checkout/[team_slug]/[sku]/page.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/pending-state.tsx
  • apps/dashboard/src/app/(app)/(stripe)/utils/billing.ts
  • packages/service-utils/src/core/api.ts
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/lib/hooks.ts
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/page.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/types.ts
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/components/ProjectSidebarLayout.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/tier-selection.tsx
apps/dashboard/**/components/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/dashboard.mdc)

Add className prop to the root element of every component to allow external overrides

Files:

  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/empty-state.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/active-state.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/page-client.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/pending-state.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/components/ProjectSidebarLayout.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/tier-selection.tsx
apps/{dashboard,playground}/**/components/**/*.{tsx,ts}

📄 CodeRabbit inference engine (AGENTS.md)

apps/{dashboard,playground}/**/components/**/*.{tsx,ts}: Group feature-specific components under feature/components/_ and expose a barrel index.ts when necessary
Expose a className prop on the root element of every component for styling overrides

Files:

  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/empty-state.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/active-state.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/page-client.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/pending-state.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/components/ProjectSidebarLayout.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/tier-selection.tsx
apps/dashboard/**/page.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/dashboard.mdc)

Use the container class with a max-w-7xl cap for consistent page width

Files:

  • apps/dashboard/src/app/(app)/(stripe)/checkout/[team_slug]/[sku]/page.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/page.tsx
🧬 Code graph analysis (9)
apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/empty-state.tsx (4)
apps/dashboard/src/@/types/billing.ts (1)
  • DedicatedRelayerSKU (25-28)
packages/thirdweb/src/exports/thirdweb.ts (1)
  • ThirdwebClient (25-25)
apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/tier-selection.tsx (1)
  • PlanSection (77-106)
apps/dashboard/src/@/components/blocks/NetworkSelectors.tsx (1)
  • MultiNetworkSelector (17-150)
apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/layout.tsx (2)
apps/dashboard/src/@/utils/redirects.ts (1)
  • loginRedirect (3-9)
apps/dashboard/src/@/components/blocks/project-page/project-page.tsx (1)
  • ProjectPage (19-50)
apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/lib/api.ts (2)
apps/dashboard/src/@/actions/proxies.ts (1)
  • analyticsServerProxy (91-93)
apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/types.ts (2)
  • FleetTransaction (32-41)
  • FleetTransactionsSummary (46-54)
apps/dashboard/src/app/(app)/(stripe)/checkout/[team_slug]/[sku]/page.tsx (1)
apps/dashboard/src/app/(app)/(stripe)/utils/billing.ts (1)
  • getBillingCheckoutUrl (7-87)
apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/pending-state.tsx (2)
apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/types.ts (1)
  • Fleet (5-9)
apps/dashboard/src/@/hooks/chains/v5-adapter.ts (1)
  • useV5DashboardChain (14-28)
apps/dashboard/src/app/(app)/(stripe)/utils/billing.ts (1)
apps/dashboard/src/@/constants/public-envs.ts (1)
  • NEXT_PUBLIC_THIRDWEB_API_HOST (18-19)
apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/lib/hooks.ts (1)
apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/lib/api.ts (2)
  • getFleetTransactions (19-42)
  • getFleetTransactionsSummary (54-75)
apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/page.tsx (5)
apps/dashboard/src/@/utils/redirects.ts (1)
  • loginRedirect (3-9)
apps/dashboard/src/@/api/team/get-team.ts (1)
  • getTeamBySlug (16-35)
apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/types.ts (2)
  • buildFleetId (59-61)
  • Fleet (5-9)
apps/dashboard/src/@/components/analytics/date-range-selector.tsx (1)
  • getLastNDaysRange (83-101)
apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/page-client.tsx (1)
  • DedicatedRelayerPageClient (25-105)
apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/tier-selection.tsx (1)
apps/dashboard/src/@/types/billing.ts (1)
  • DedicatedRelayerSKU (25-28)
🪛 Gitleaks (8.30.0)
apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/pending-state.tsx

[high] 130-133: Discovered a potential authorization token provided in a curl command header, which could compromise the curl accessed resource.

(curl-auth-header)

🔇 Additional comments (15)
packages/service-utils/src/core/api.ts (1)

177-203: Dedicated relayer field wiring in ProjectBundlerService looks good

The new dedicatedRelayer field is a clean, backwards‑compatible extension and follows the same optional/null pattern as other nested configs; no issues from a typing perspective.

apps/dashboard/src/@/types/billing.ts (1)

16-28: SKU typing extension is consistent and type‑safe

Adding DedicatedRelayerSKU and including it in ProductSKU cleanly models the new plans without affecting existing callers (they still handle null explicitly); no changes needed here.

apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/layout.tsx (1)

106-113: Prop plumbing for teamId into ProjectSidebarLayout is correct

team.id is already validated above, so passing it through as teamId is safe and enables the sidebar’s new conditional behavior.

apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/lib/api.ts (2)

1-42: LGTM! Server actions are well-structured.

The API module correctly uses "use server" directive, properly types parameters and responses, and handles errors consistently. The pagination parameters are correctly converted to strings for query params.


44-75: LGTM! Summary endpoint follows the same pattern.

Consistent error handling and typing with the transactions endpoint.

apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/page-client.tsx (2)

71-104: LGTM! Clean conditional rendering based on fleet status.

The rendering logic correctly handles all status states with appropriate components.


107-115: LGTM! Status derivation logic is correct.

The getInitialStatus helper properly determines fleet status based on presence and executor count.

apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/layout.tsx (1)

9-54: LGTM! Layout structure is correct.

The layout properly fetches auth and project data in parallel, handles redirects for missing auth/project, and composes the ProjectPage with appropriate header configuration.

apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/pending-state.tsx (2)

129-145: The curl auth header is example documentation code—this is a false positive from static analysis.

The x-secret-key: YOUR_SECRET_KEY is intentionally a placeholder in documentation to show users how to make API calls. This is not a leaked secret.


17-111: LGTM! Well-structured pending state UI.

The component clearly communicates setup progress with appropriate visual states and helpful documentation.

apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/page.tsx (1)

13-49: LGTM! Server page structure is correct.

The page properly fetches data in parallel, handles auth/redirect logic, and builds the required data for the client component.

apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/active-state.tsx (4)

41-70: LGTM! Well-structured active state component.

Good use of React Query hooks with pagination state management. The page reset on filter change is correctly implemented.


222-262: LGTM! TransactionRow handles timestamp normalization correctly.

The UTC timestamp normalization (lines 227-229) ensures consistent date parsing regardless of backend format.


289-322: LGTM! TransactionHashCell provides good UX with explorer fallback.

Falls back to copy button when no explorer URL is available.


351-357: LGTM! Nice handling of small transaction fees.

Displaying <0.01 for very small values is good UX for readability.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
.changeset/some-mice-own.md (1)

5-5: Consider a more descriptive changelog message.

The message "add dedicated relayer to service utils" is brief but could be more informative for end users reading the changelog. Consider elaborating on what was added (e.g., the new dedicatedRelayer field and its structure).

Example:

add dedicatedRelayer field to ProjectBundlerService type with SKU, chainIds, and executors configuration
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 0225f92 and dac955e.

📒 Files selected for processing (1)
  • .changeset/some-mice-own.md (1 hunks)
🔇 Additional comments (1)
.changeset/some-mice-own.md (1)

1-3: Verify semantic versioning for the new public API.

The changeset declares a patch version bump. However, per semantic versioning, adding new public fields (like the dedicatedRelayer field in ProjectBundlerService) typically warrants a minor version bump, not patch. A patch is generally reserved for bug fixes.

Please confirm whether this should be patch or minor. If new public APIs are being exported, consider bumping to minor.

Introduces polling for fleet status in the dedicated relayer dashboard, ensuring UI updates for purchase/setup changes. Adds a constants file with supported chain IDs, passes them to relevant components, and improves type safety and className extensibility. Also refines transaction summary polling and cleans up unused console logs.
@vercel vercel bot temporarily deployed to Preview – docs-v2 December 10, 2025 20:44 Inactive
@vercel vercel bot temporarily deployed to Preview – wallet-ui December 10, 2025 20:44 Inactive
@vercel vercel bot temporarily deployed to Preview – nebula December 10, 2025 20:44 Inactive
@vercel vercel bot temporarily deployed to Preview – thirdweb_playground December 10, 2025 20:44 Inactive
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/empty-state.tsx (1)

57-68: Add error handling for failed purchases.

The purchase flow preserves user selection on failure (good), but there's no feedback when onPurchaseTier throws. Consider adding a catch block to surface errors:

   const handlePurchase = async () => {
     if (!selectedTier) return;
     setIsLoading(true);
     try {
       await props.onPurchaseTier(selectedTier, selectedChainIds);
       setIsModalOpen(false);
       setSelectedTier(null);
       setSelectedChainIds([]);
+    } catch (error) {
+      // Consider adding toast notification or error state
+      console.error("Failed to initiate purchase:", error);
     } finally {
       setIsLoading(false);
     }
   };
🧹 Nitpick comments (8)
apps/dashboard/src/app/(app)/(stripe)/utils/billing.ts (2)

7-14: options.params shape is fine; consider optional + shared type alias

Making params a required object with optional fields works, but it now forces every caller to pass at least {} even when no extra params are needed. If you expect call sites that don’t care about project_id / chain_id, consider:

  • Making params itself optional and guarding with options.params ?? {} inside, and/or
  • Extracting a small type BillingCheckoutParams = { project_id?: string; chain_id?: string | string[] } (either here or in a local/types barrel) to reuse and keep the signature a bit cleaner.

This is non-blocking, but would align better with the shared-types guideline.


33-39: Body extraction looks good; consider adding an explicit return type

Pulling the request payload into a body constant is a nice cleanup and makes it clearer what’s sent to the API. Using projectId and chainIds as optional properties also works well since JSON.stringify omits undefined fields.

Given you’re already touching this function, it might be a good moment to add an explicit return type to getBillingCheckoutUrl (e.g. a small union of { status: "success"; data: string } | { status: "error"; error: string }) to make the contract clearer to callers and keep things aligned with the explicit-return-type guideline. Behavior-wise, the new payload construction looks solid.

Also applies to: 43-43

apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/constants.ts (1)

48-59: Minor: Duplicate "testnet" in comments.

Several comment labels have redundant "testnet testnet" text which could be cleaned up for consistency:

-  9899, // Arena-Z Testnet testnet testnet
+  9899, // Arena-Z Testnet
-  1962, // T-Rex testnet testnet
+  1962, // T-Rex Testnet
-  2021, // Saigon testnet testnet
+  2021, // Saigon Testnet
-  4202, // Lisk Sepolia Testnet testnet
+  4202, // Lisk Sepolia Testnet
-  53302, // Superseed Sepolia Testnet testnet
+  53302, // Superseed Sepolia Testnet
-  1992, // Sanko Testnet testnet
+  1992, // Sanko Testnet
apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/page-client.tsx (1)

60-84: Consider making handlePurchaseTier synchronous.

The function is declared async but doesn't await anything. Since it just opens a new tab synchronously, consider removing async:

-  const handlePurchaseTier = async (
+  const handlePurchaseTier = (
     sku: DedicatedRelayerSKU,
     chainIds: number[],
   ) => {

However, keeping it async is acceptable if the parent components expect a Promise return type or if you plan to add async operations later (as the TODO suggests).

apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/lib/hooks.ts (2)

10-20: Consolidate duplicated param types between hooks and API layer

UseFleetTransactionsParams and GetFleetTransactionsParams (in lib/api.ts) as well as the summary param types are effectively the same shape. To avoid drift and keep things aligned with the backend, consider moving these to a shared type (e.g. in ../types or a small params.ts) and reusing it in both hooks.ts and api.ts. This also fits the guideline to reuse shared types from a local barrel.

Also applies to: 50-55


23-29: Tighten React Query configuration (keys vs options, caching, and server-actions usage)

A couple of React Query nits here:

  • useFleetTransactionsSummary uses the full params object (including enabled and refetchInterval) as part of the queryKey. Those fields control query behavior rather than data identity, so it’s cleaner to keep only the data params (teamId, fleetId, from, to) in the key and pass enabled / refetchInterval separately.
  • None of the hooks configure staleTime / cacheTime. Per the project guidelines, it’d be good to choose explicit values (e.g. ≥ 60s for summary/transactions analytics; something aligned with the 5s polling cadence for status) rather than relying on the default of “always stale”.
  • Since these hooks consume server actions from a "use server" module, just make sure the Next 15 build/lint is happy with importing these actions into a 'use client' file and that they’re not accidentally marked as “server-only” utilities instead of actions.

Also applies to: 44-53, 60-70

apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/active-state.tsx (1)

224-264: Align chainId typing and consider reusing chain metadata to avoid repeated hooks

Two small cleanups in the transaction row/cell helpers:

  • TransactionRow passes transaction.chainId into TransactionHashCell and ChainCell, whose props currently declare chainId: string. Elsewhere (UseFleetTransactionsParams, ChainFilter) chainId is a number. It would be clearer to standardize on a single type (likely number) for chain IDs across the dedicated-relayer types and components, and only stringify where needed for display.
  • Both TransactionHashCell and ChainCell call useAllChainsData() separately per row. Since that hook likely wraps a global query/cache, you could retrieve idToChain / allChains once higher up (e.g. in DedicatedRelayerActiveState) and pass the derived chain / explorer URL or idToChain map down as props to reduce per-row hook overhead.

Also applies to: 291-351

apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/lib/api.ts (1)

32-40: Prefer explicit chainId presence check in search params

The spread:

...(params.chainId && { chainId: params.chainId.toString() })

relies on chainId being truthy, which would silently drop a value of 0. Even if 0 is not a valid chain ID today, it’s clearer and more future-proof to check explicitly for undefined/null, e.g.:

...(params.chainId !== undefined && {
  chainId: params.chainId.toString(),
})
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 0225f92 and 05e964b.

📒 Files selected for processing (11)
  • .changeset/some-mice-own.md (1 hunks)
  • apps/dashboard/src/app/(app)/(stripe)/utils/billing.ts (2 hunks)
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/active-state.tsx (1 hunks)
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/empty-state.tsx (1 hunks)
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/page-client.tsx (1 hunks)
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/pending-state.tsx (1 hunks)
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/tier-selection.tsx (1 hunks)
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/constants.ts (1 hunks)
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/lib/api.ts (1 hunks)
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/lib/hooks.ts (1 hunks)
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/page.tsx (1 hunks)
🧰 Additional context used
📓 Path-based instructions (11)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Write idiomatic TypeScript with explicit function declarations and return types
Limit each TypeScript file to one stateless, single-responsibility function for clarity
Re-use shared types from @/types or local types.ts barrels
Prefer type aliases over interface except for nominal shapes in TypeScript
Avoid any and unknown in TypeScript unless unavoidable; narrow generics when possible
Choose composition over inheritance; leverage utility types (Partial, Pick, etc.) in TypeScript

**/*.{ts,tsx}: Write idiomatic TypeScript with explicit function declarations and return types
Limit each file to one stateless, single-responsibility function for clarity and testability
Re-use shared types from @/types or local types.ts barrel exports
Prefer type aliases over interface except for nominal shapes
Avoid any and unknown unless unavoidable; narrow generics whenever possible
Choose composition over inheritance; leverage utility types (Partial, Pick, etc.)
Comment only ambiguous logic in TypeScript files; avoid restating TypeScript types and signatures in prose

Files:

  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/constants.ts
  • apps/dashboard/src/app/(app)/(stripe)/utils/billing.ts
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/tier-selection.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/lib/api.ts
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/active-state.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/empty-state.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/page.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/lib/hooks.ts
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/pending-state.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/page-client.tsx
apps/{dashboard,playground-web}/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

apps/{dashboard,playground-web}/src/**/*.{ts,tsx}: Import UI component primitives from @/components/ui/* (Button, Input, Select, Tabs, Card, Sidebar, Badge, Separator) in dashboard and playground
Use Tailwind CSS only – no inline styles or CSS modules in dashboard and playground
Use cn() from @/lib/utils for conditional Tailwind class merging
Use design system tokens for styling (backgrounds: bg-card, borders: border-border, muted text: text-muted-foreground)
Expose className prop on root element for component overrides

Files:

  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/constants.ts
  • apps/dashboard/src/app/(app)/(stripe)/utils/billing.ts
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/tier-selection.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/lib/api.ts
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/active-state.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/empty-state.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/page.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/lib/hooks.ts
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/pending-state.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/page-client.tsx
apps/dashboard/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

apps/dashboard/src/**/*.{ts,tsx}: Use NavLink for internal navigation with automatic active states in dashboard
Start server component files with import "server-only"; in Next.js
Read cookies/headers with next/headers in server components
Access server-only environment variables in server components
Perform heavy data fetching in server components
Implement redirect logic with redirect() from next/navigation in server components
Begin client component files with 'use client'; directive in Next.js
Handle interactive UI with React hooks (useState, useEffect, React Query, wallet hooks) in client components
Access browser APIs (localStorage, window, IntersectionObserver) in client components
Support fast transitions with prefetched data in client components
Always call getAuthToken() to retrieve JWT from cookies on server side
Use Authorization: Bearer header for API calls – never embed tokens in URLs
Return typed results (Project[], User[]) from server-side data fetches – avoid any
Wrap client-side API calls in React Query (@tanstack/react-query)
Use descriptive, stable queryKeys in React Query for cache hits
Configure staleTime/cacheTime in React Query based on freshness (default ≥ 60s)
Keep tokens secret via internal API routes or server actions
Never import posthog-js in server components – only use analytics client-side

Files:

  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/constants.ts
  • apps/dashboard/src/app/(app)/(stripe)/utils/billing.ts
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/tier-selection.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/lib/api.ts
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/active-state.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/empty-state.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/page.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/lib/hooks.ts
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/pending-state.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/page-client.tsx
apps/dashboard/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/dashboard.mdc)

apps/dashboard/**/*.{ts,tsx}: Always import from the central UI library under @/components/ui/* for reusable core UI components like Button, Input, Select, Tabs, Card, Sidebar, Separator, Badge
Use NavLink from @/components/ui/NavLink for internal navigation to ensure active states are handled automatically
For notices and skeletons, rely on AnnouncementBanner, GenericLoadingPage, and EmptyStateCard components
Import icons from lucide-react or the project-specific …/icons exports; never embed raw SVG
Keep components pure; fetch data outside using server components or hooks and pass it down via props
Use Tailwind CSS as the styling system; avoid inline styles or CSS modules
Merge class names with cn from @/lib/utils to keep conditional logic readable
Stick to design tokens: use bg-card, border-border, text-muted-foreground and other Tailwind variables instead of hard-coded colors
Use spacing utilities (px-*, py-*, gap-*) instead of custom margins
Follow mobile-first responsive design with Tailwind helpers (max-sm, md, lg, xl)
Never hard-code colors; always use Tailwind variables
Combine class names via cn, and expose className prop if useful in components
Use React Query (@tanstack/react-query) for all client-side data fetching with typed hooks

Files:

  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/constants.ts
  • apps/dashboard/src/app/(app)/(stripe)/utils/billing.ts
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/tier-selection.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/lib/api.ts
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/active-state.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/empty-state.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/page.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/lib/hooks.ts
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/pending-state.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/page-client.tsx
**/*.{js,jsx,ts,tsx,json}

📄 CodeRabbit inference engine (AGENTS.md)

Biome governs formatting and linting; its rules live in biome.json. Run pnpm fix & pnpm lint before committing, ensure there are no linting errors

Files:

  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/constants.ts
  • apps/dashboard/src/app/(app)/(stripe)/utils/billing.ts
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/tier-selection.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/lib/api.ts
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/active-state.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/empty-state.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/page.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/lib/hooks.ts
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/pending-state.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/page-client.tsx
apps/{dashboard,playground}/**/*.{tsx,ts}

📄 CodeRabbit inference engine (AGENTS.md)

apps/{dashboard,playground}/**/*.{tsx,ts}: Import UI primitives from @/components/ui/_ (Button, Input, Select, Tabs, Card, Sidebar, Badge, Separator) in Dashboard and Playground apps
Use NavLink for internal navigation so active states are handled automatically
Use Tailwind CSS for styling – no inline styles or CSS modules
Merge class names with cn() from @/lib/utils to keep conditional logic readable
Stick to design tokens for styling: backgrounds (bg-card), borders (border-border), muted text (text-muted-foreground), etc.
Server Components: Read cookies/headers with next/headers, access server-only environment variables or secrets, perform heavy data fetching, implement redirect logic with redirect() from next/navigation, and start files with import 'server-only'; to prevent client bundling
Client Components: Begin files with 'use client'; before imports, handle interactive UI relying on React hooks (useState, useEffect, React Query, wallet hooks), access browser APIs (localStorage, window, IntersectionObserver, etc.), and support fast transitions with client-side data prefetching
For client-side data fetching: Wrap calls in React Query (@tanstack/react-query), use descriptive and stable queryKeys for cache hits, configure staleTime / cacheTime based on freshness requirements (default ≥ 60 s), and keep tokens secret by calling internal API routes or server actions

Files:

  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/constants.ts
  • apps/dashboard/src/app/(app)/(stripe)/utils/billing.ts
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/tier-selection.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/lib/api.ts
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/active-state.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/empty-state.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/page.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/lib/hooks.ts
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/pending-state.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/page-client.tsx
apps/{dashboard,playground}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

apps/{dashboard,playground}/**/*.{ts,tsx}: For server-side data fetching: Always call getAuthToken() to retrieve the JWT from cookies and inject the token as an Authorization: Bearer header – never embed it in the URL. Return typed results (Project[], User[], …) – avoid any
Never import posthog-js in server components; analytics reporting is client-side only

Files:

  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/constants.ts
  • apps/dashboard/src/app/(app)/(stripe)/utils/billing.ts
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/tier-selection.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/lib/api.ts
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/active-state.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/empty-state.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/page.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/lib/hooks.ts
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/pending-state.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/page-client.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Lazy-import optional features; avoid top-level side-effects

Files:

  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/constants.ts
  • apps/dashboard/src/app/(app)/(stripe)/utils/billing.ts
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/tier-selection.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/lib/api.ts
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/active-state.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/empty-state.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/page.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/lib/hooks.ts
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/pending-state.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/page-client.tsx
apps/dashboard/**/components/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/dashboard.mdc)

Add className prop to the root element of every component to allow external overrides

Files:

  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/tier-selection.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/active-state.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/empty-state.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/pending-state.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/page-client.tsx
apps/{dashboard,playground}/**/components/**/*.{tsx,ts}

📄 CodeRabbit inference engine (AGENTS.md)

apps/{dashboard,playground}/**/components/**/*.{tsx,ts}: Group feature-specific components under feature/components/_ and expose a barrel index.ts when necessary
Expose a className prop on the root element of every component for styling overrides

Files:

  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/tier-selection.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/active-state.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/empty-state.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/pending-state.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/page-client.tsx
apps/dashboard/**/page.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/dashboard.mdc)

Use the container class with a max-w-7xl cap for consistent page width

Files:

  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/page.tsx
🧬 Code graph analysis (4)
apps/dashboard/src/app/(app)/(stripe)/utils/billing.ts (1)
apps/dashboard/src/@/constants/public-envs.ts (1)
  • NEXT_PUBLIC_THIRDWEB_API_HOST (18-19)
apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/tier-selection.tsx (2)
apps/dashboard/src/@/types/billing.ts (1)
  • DedicatedRelayerSKU (25-28)
packages/thirdweb/src/react/web/ui/components/text.tsx (1)
  • Link (46-64)
apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/page.tsx (5)
apps/dashboard/src/@/utils/redirects.ts (1)
  • loginRedirect (3-9)
apps/dashboard/src/@/api/team/get-team.ts (1)
  • getTeamBySlug (16-35)
apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/types.ts (2)
  • buildFleetId (59-61)
  • Fleet (5-9)
apps/dashboard/src/@/components/analytics/date-range-selector.tsx (1)
  • getLastNDaysRange (83-101)
apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/page-client.tsx (1)
  • DedicatedRelayerPageClient (25-120)
apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/lib/hooks.ts (1)
apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/lib/api.ts (3)
  • getFleetTransactions (25-48)
  • getFleetTransactionsSummary (60-81)
  • getFleetStatus (86-111)
🪛 Gitleaks (8.30.0)
apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/pending-state.tsx

[high] 132-135: Discovered a potential authorization token provided in a curl command header, which could compromise the curl accessed resource.

(curl-auth-header)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Lint Packages
  • GitHub Check: Size
  • GitHub Check: Analyze (javascript)
🔇 Additional comments (22)
apps/dashboard/src/app/(app)/(stripe)/utils/billing.ts (1)

24-32: Confirm intent to silently drop invalid chain_id values

The normalization correctly handles both string and string[] and filters to finite numbers only. One behavioral nuance: values like "abc" or "" end up as NaN and are filtered out, so an invalid chain_id simply results in chainIds being [] (or undefined if nothing valid remains).

If it’s important to surface malformed chain_id inputs (e.g. from a corrupted URL or buggy caller), you might instead want to:

  • Return an error state when all provided chain_id values are invalid, or
  • At least log a warning when dropping any values.

If silently ignoring bad values is desired here (and the API will validate again), then this is fine as-is.

apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/tier-selection.tsx (4)

1-28: Previous review issues have been addressed.

The React type import has been added (line 11), and the className prop is now exposed on PlanSection (line 82) and correctly merged using cn() (line 85). The component follows dashboard coding guidelines.


30-76: LGTM!

The tier configuration is well-structured with clear SKU IDs matching the DedicatedRelayerSKU type, appropriate icons, and properly differentiated features for each plan level.


78-108: LGTM!

The PlanSection component correctly handles loading states per-tier and disables non-selected tiers during loading. The responsive grid layout follows Tailwind conventions.


110-196: LGTM!

The PlanCard component handles enterprise vs. non-enterprise CTAs correctly, with proper external link attributes (target="_blank", rel="noopener noreferrer") and appropriate loading/disabled state management.

.changeset/some-mice-own.md (1)

1-5: LGTM!

The changeset correctly specifies a patch version bump for the additive dedicated relayer feature in service-utils.

apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/constants.ts (1)

1-72: LGTM!

The chain ID constants are well-organized with clear section separation between mainnets and testnets. The inline comments provide helpful context for each chain.

apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/empty-state.tsx (3)

1-30: Previous review issues addressed.

The className prop is now exposed (line 29) and correctly applied to the root element (line 74). The component follows dashboard component guidelines.


70-71: Clarify chain count for Premium tier.

The requiredChains logic defaults non-standard tiers to 4 chains, but the TIERS config shows Premium supports "up to 4 chains". Should users be allowed to select fewer than 4 chains for Premium? Currently they must select exactly 4.

Consider if the business requirement is exactly 4 chains or up to 4 chains for Premium tier.


112-173: LGTM!

The FeatureSection and FeatureCard components are well-structured with proper theme-aware image handling using useTheme. The feature descriptions are clear and the layout follows responsive design patterns.

apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/page.tsx (3)

1-12: Previous review issues addressed.

Debug console.log statements have been removed. The server component correctly uses force-dynamic and follows proper authentication patterns.


13-36: LGTM!

The server component efficiently uses concurrent data fetching with Promise.all and properly handles authentication redirects with preserved return paths.


48-70: LGTM!

The fleet configuration extraction includes defensive checks and clear comments explaining the state derivation logic. The type assertion is acceptable given the internal API contract.

apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/pending-state.tsx (4)

155-163: Previous review issue addressed.

The ChainBadge component now uses optional chaining (chain?.name) with a fallback value to handle cases where useV5DashboardChain returns undefined.


131-147: LGTM!

The curl example with YOUR_SECRET_KEY placeholder is a standard documentation pattern for API testing instructions. The static analysis warning is a false positive since no actual secret is embedded.


19-113: LGTM!

The DedicatedRelayerPendingState component is well-structured with clear conditional rendering based on fleet status. The UI sections (status banner, fleet configuration, setup steps) provide good user feedback during the provisioning process.


165-201: LGTM!

The SetupStep helper component cleanly handles the three states with appropriate visual feedback using design tokens and the animated spinner for in-progress steps.

apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/page-client.tsx (4)

1-24: LGTM!

The imports and type definitions are well-organized. The DedicatedRelayerPageClientProps type clearly documents the expected props.


25-58: LGTM!

The state management efficiently handles polling only when needed (not-purchased or pending-setup) and fetches transaction summaries only for active fleets. The 5-second refetch interval provides timely updates without excessive requests.


86-120: LGTM!

The conditional rendering logic correctly handles all fleet states. Using DedicatedRelayerPendingState for active fleets without transactions provides appropriate "waiting for first transaction" UI feedback.


122-130: LGTM!

The getInitialStatus helper correctly derives the fleet status from the fleet structure, handling all three states cleanly.

apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/active-state.tsx (1)

43-205: Nice end-to-end wiring of the active state view

The main DedicatedRelayerActiveState component cleanly wires summary stats, executors, filtering, and a paginated table, all while respecting the design system (UI primitives, Tailwind tokens, className override on the root). No functional issues stand out in this section.

Displays a toast notification when purchasing a dedicated relayer fails in the empty state component. Also adds an eslint-disable comment in the page client component.
@vercel vercel bot temporarily deployed to Preview – thirdweb_playground December 10, 2025 21:11 Inactive
@vercel vercel bot temporarily deployed to Preview – docs-v2 December 10, 2025 21:11 Inactive
@vercel vercel bot temporarily deployed to Preview – wallet-ui December 10, 2025 21:11 Inactive
@vercel vercel bot temporarily deployed to Preview – nebula December 10, 2025 21:11 Inactive
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (4)
apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/empty-state.tsx (4)

37-39: Consider adding explicit return type.

Per coding guidelines, exported functions should have explicit return types for clarity and type safety.

 export function DedicatedRelayerEmptyState(
   props: DedicatedRelayerEmptyStateProps,
-) {
+): React.ReactElement {

47-56: Remove unnecessary async keyword.

handleSelectTier is marked async but contains no await expressions. This creates an implicit Promise wrapper that isn't needed.

-  const handleSelectTier = async (tier: DedicatedRelayerSKU) => {
+  const handleSelectTier = (tier: DedicatedRelayerSKU): void => {
     if (tier === "product:dedicated_relayer_enterprise") {
       window.open("https://thirdweb.com/contact-us", "_blank");
       return;
     }

     setSelectedTier(tier);
     setSelectedChainIds([]);
     setIsModalOpen(true);
   };

74-75: Consider making chain requirements more explicit.

The ternary defaults non-standard tiers to 4 chains. If new tiers are added, this implicit default could cause unexpected behavior.

-  const requiredChains =
-    selectedTier === "product:dedicated_relayer_standard" ? 2 : 4;
+  const requiredChains = selectedTier === "product:dedicated_relayer_standard" 
+    ? 2 
+    : selectedTier === "product:dedicated_relayer_premium" 
+      ? 4 
+      : 4; // Default for any future tiers

Alternatively, consider extracting tier configurations to a shared constant for maintainability.


164-176: Consider adding descriptive alt text for accessibility.

The images appear to illustrate the features being described. While alt="" is valid for purely decorative images, screen reader users would benefit from descriptive text that conveys what the illustration represents.

-      <Img src={imageSrc} alt="" className="object-cover" key={imageSrc} />
+      <Img src={imageSrc} alt={props.title} className="object-cover" key={imageSrc} />

Using the title as alt text provides minimal context. For richer accessibility, consider adding an imageAlt prop to FeatureCard.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 05e964b and 6bc328f.

📒 Files selected for processing (2)
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/empty-state.tsx (1 hunks)
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/page-client.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/page-client.tsx
🧰 Additional context used
📓 Path-based instructions (10)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Write idiomatic TypeScript with explicit function declarations and return types
Limit each TypeScript file to one stateless, single-responsibility function for clarity
Re-use shared types from @/types or local types.ts barrels
Prefer type aliases over interface except for nominal shapes in TypeScript
Avoid any and unknown in TypeScript unless unavoidable; narrow generics when possible
Choose composition over inheritance; leverage utility types (Partial, Pick, etc.) in TypeScript

**/*.{ts,tsx}: Write idiomatic TypeScript with explicit function declarations and return types
Limit each file to one stateless, single-responsibility function for clarity and testability
Re-use shared types from @/types or local types.ts barrel exports
Prefer type aliases over interface except for nominal shapes
Avoid any and unknown unless unavoidable; narrow generics whenever possible
Choose composition over inheritance; leverage utility types (Partial, Pick, etc.)
Comment only ambiguous logic in TypeScript files; avoid restating TypeScript types and signatures in prose

Files:

  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/empty-state.tsx
apps/{dashboard,playground-web}/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

apps/{dashboard,playground-web}/src/**/*.{ts,tsx}: Import UI component primitives from @/components/ui/* (Button, Input, Select, Tabs, Card, Sidebar, Badge, Separator) in dashboard and playground
Use Tailwind CSS only – no inline styles or CSS modules in dashboard and playground
Use cn() from @/lib/utils for conditional Tailwind class merging
Use design system tokens for styling (backgrounds: bg-card, borders: border-border, muted text: text-muted-foreground)
Expose className prop on root element for component overrides

Files:

  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/empty-state.tsx
apps/dashboard/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

apps/dashboard/src/**/*.{ts,tsx}: Use NavLink for internal navigation with automatic active states in dashboard
Start server component files with import "server-only"; in Next.js
Read cookies/headers with next/headers in server components
Access server-only environment variables in server components
Perform heavy data fetching in server components
Implement redirect logic with redirect() from next/navigation in server components
Begin client component files with 'use client'; directive in Next.js
Handle interactive UI with React hooks (useState, useEffect, React Query, wallet hooks) in client components
Access browser APIs (localStorage, window, IntersectionObserver) in client components
Support fast transitions with prefetched data in client components
Always call getAuthToken() to retrieve JWT from cookies on server side
Use Authorization: Bearer header for API calls – never embed tokens in URLs
Return typed results (Project[], User[]) from server-side data fetches – avoid any
Wrap client-side API calls in React Query (@tanstack/react-query)
Use descriptive, stable queryKeys in React Query for cache hits
Configure staleTime/cacheTime in React Query based on freshness (default ≥ 60s)
Keep tokens secret via internal API routes or server actions
Never import posthog-js in server components – only use analytics client-side

Files:

  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/empty-state.tsx
apps/dashboard/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/dashboard.mdc)

apps/dashboard/**/*.{ts,tsx}: Always import from the central UI library under @/components/ui/* for reusable core UI components like Button, Input, Select, Tabs, Card, Sidebar, Separator, Badge
Use NavLink from @/components/ui/NavLink for internal navigation to ensure active states are handled automatically
For notices and skeletons, rely on AnnouncementBanner, GenericLoadingPage, and EmptyStateCard components
Import icons from lucide-react or the project-specific …/icons exports; never embed raw SVG
Keep components pure; fetch data outside using server components or hooks and pass it down via props
Use Tailwind CSS as the styling system; avoid inline styles or CSS modules
Merge class names with cn from @/lib/utils to keep conditional logic readable
Stick to design tokens: use bg-card, border-border, text-muted-foreground and other Tailwind variables instead of hard-coded colors
Use spacing utilities (px-*, py-*, gap-*) instead of custom margins
Follow mobile-first responsive design with Tailwind helpers (max-sm, md, lg, xl)
Never hard-code colors; always use Tailwind variables
Combine class names via cn, and expose className prop if useful in components
Use React Query (@tanstack/react-query) for all client-side data fetching with typed hooks

Files:

  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/empty-state.tsx
apps/dashboard/**/components/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/dashboard.mdc)

Add className prop to the root element of every component to allow external overrides

Files:

  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/empty-state.tsx
**/*.{js,jsx,ts,tsx,json}

📄 CodeRabbit inference engine (AGENTS.md)

Biome governs formatting and linting; its rules live in biome.json. Run pnpm fix & pnpm lint before committing, ensure there are no linting errors

Files:

  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/empty-state.tsx
apps/{dashboard,playground}/**/*.{tsx,ts}

📄 CodeRabbit inference engine (AGENTS.md)

apps/{dashboard,playground}/**/*.{tsx,ts}: Import UI primitives from @/components/ui/_ (Button, Input, Select, Tabs, Card, Sidebar, Badge, Separator) in Dashboard and Playground apps
Use NavLink for internal navigation so active states are handled automatically
Use Tailwind CSS for styling – no inline styles or CSS modules
Merge class names with cn() from @/lib/utils to keep conditional logic readable
Stick to design tokens for styling: backgrounds (bg-card), borders (border-border), muted text (text-muted-foreground), etc.
Server Components: Read cookies/headers with next/headers, access server-only environment variables or secrets, perform heavy data fetching, implement redirect logic with redirect() from next/navigation, and start files with import 'server-only'; to prevent client bundling
Client Components: Begin files with 'use client'; before imports, handle interactive UI relying on React hooks (useState, useEffect, React Query, wallet hooks), access browser APIs (localStorage, window, IntersectionObserver, etc.), and support fast transitions with client-side data prefetching
For client-side data fetching: Wrap calls in React Query (@tanstack/react-query), use descriptive and stable queryKeys for cache hits, configure staleTime / cacheTime based on freshness requirements (default ≥ 60 s), and keep tokens secret by calling internal API routes or server actions

Files:

  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/empty-state.tsx
apps/{dashboard,playground}/**/components/**/*.{tsx,ts}

📄 CodeRabbit inference engine (AGENTS.md)

apps/{dashboard,playground}/**/components/**/*.{tsx,ts}: Group feature-specific components under feature/components/_ and expose a barrel index.ts when necessary
Expose a className prop on the root element of every component for styling overrides

Files:

  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/empty-state.tsx
apps/{dashboard,playground}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

apps/{dashboard,playground}/**/*.{ts,tsx}: For server-side data fetching: Always call getAuthToken() to retrieve the JWT from cookies and inject the token as an Authorization: Bearer header – never embed it in the URL. Return typed results (Project[], User[], …) – avoid any
Never import posthog-js in server components; analytics reporting is client-side only

Files:

  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/empty-state.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Lazy-import optional features; avoid top-level side-effects

Files:

  • apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/empty-state.tsx
🧬 Code graph analysis (1)
apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/empty-state.tsx (6)
apps/dashboard/src/@/types/billing.ts (1)
  • DedicatedRelayerSKU (25-28)
packages/thirdweb/src/exports/thirdweb.ts (1)
  • ThirdwebClient (25-25)
apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/tier-selection.tsx (1)
  • PlanSection (78-108)
apps/playground-web/src/components/ui/dialog.tsx (5)
  • Dialog (112-112)
  • DialogContent (117-117)
  • DialogHeader (118-118)
  • DialogTitle (120-120)
  • DialogFooter (119-119)
apps/dashboard/src/@/components/blocks/NetworkSelectors.tsx (1)
  • MultiNetworkSelector (17-150)
apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/constants.ts (1)
  • RELAYER_SUPPORTED_CHAINS (1-72)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: E2E Tests (pnpm, vite)
  • GitHub Check: E2E Tests (pnpm, esbuild)
  • GitHub Check: Size
  • GitHub Check: E2E Tests (pnpm, webpack)
  • GitHub Check: Lint Packages
  • GitHub Check: Unit Tests
  • GitHub Check: Analyze (javascript)
🔇 Additional comments (4)
apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/empty-state.tsx (4)

1-20: LGTM!

Imports are well-organized and follow coding guidelines: UI primitives from @/components/ui/*, utility from @/lib/utils, types from @/types, and appropriate client-side libraries for a "use client" component.


22-31: LGTM!

Props type definition is clean: uses type alias per guidelines, includes className for styling overrides, and properly types the onPurchaseTier callback.


58-72: LGTM - purchase flow state handling is correct.

State reset (setIsModalOpen, setSelectedTier, setSelectedChainIds) now happens only on success (inside try before catch), while isLoading is reset in finally. This preserves user context on failure as expected.


116-149: LGTM!

FeatureSection properly uses design tokens (bg-card, border-border), mobile-first responsive design with lg: breakpoints, and the copy has been corrected from the previous review.

@0xFirekeeper 0xFirekeeper changed the title [WIP] Dedicated Relayer Dedicated Relayer Dec 10, 2025
@0xFirekeeper 0xFirekeeper merged commit 7967d7f into main Dec 10, 2025
25 checks passed
@0xFirekeeper 0xFirekeeper deleted the firekeeper/fleets branch December 10, 2025 21:26
@joaquim-verges joaquim-verges mentioned this pull request Dec 10, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Dashboard Involves changes to the Dashboard. packages

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants