-
Notifications
You must be signed in to change notification settings - Fork 629
Dedicated Relayer #8479
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Dedicated Relayer #8479
Conversation
🦋 Changeset detectedLatest commit: 6bc328f The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
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 |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
WalkthroughAdds 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
Sequence DiagramsequenceDiagram
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes
Pre-merge checks and finishing touches❌ Failed checks (2 warnings, 1 inconclusive)
✨ Finishing touches
🧪 Generate unit tests (beta)
Comment |
How to use the Graphite Merge QueueAdd either label to this PR to merge it via the merge queue:
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. |
Codecov Report✅ All modified and coverable lines are covered by tests. 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
🚀 New features to boost your workflow:
|
size-limit report 📦
|
There was a problem hiding this 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: Hardenchain_idparsing to avoid NaNs inchainIds
chain_idcomes from query params and is cast with.map(Number), which will quietly produceNaNentries 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 sidebarThe 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 loggingResolving
paramsandsearchParamstogether and threadingproject_id/chain_idintogetBillingCheckoutUrllooks good and matches the updated billing util.The
console.log("params", …)andconsole.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 makinghandlePurchaseTiersynchronous.The function is marked
asyncbut doesn't useawait. Sincewindow.openis synchronous, removingasyncwould 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 addingstaleTimeconfiguration.Per coding guidelines, React Query hooks should configure
staleTimebased 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 addingstaleTimefor 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 addingimport "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 addingimport "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: AlignchainIdtyping across fleet and transaction types
Fleet.chainIdsisnumber[], whileFleetTransaction.chainIdandFleetTransactionsSummary.transactionsByChain[].chainIdarestring. 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
ChainIdtype (e.g.type ChainId = numberorstring) 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
@/typesor 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_getFleetStatustyping and placementThe 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
executorsis always a non-null array fromProjectBundlerService.fleet. If the service can ever sendnull/undefined, you may want a defensive guard (or adjust theFleettype) to avoid runtime errors when accessing.length.Given this file is named
types.ts, consider whether runtime helpers like_getFleetStatusshould live in a smallfleet/utilsmodule 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:buildFleetIdimplementation looks good; consider centralizing ID conventionsThe 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/projectIdcan 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
buildFleetIdand_getFleetStatusinto a smallfleet/utilsmodule, keepingtypes.tsfocused 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.
⛔ Files ignored due to path filters (6)
apps/dashboard/public/assets/dedicated-relayer/monitoring-dark.pngis excluded by!**/*.pngapps/dashboard/public/assets/dedicated-relayer/monitoring-light.pngis excluded by!**/*.pngapps/dashboard/public/assets/dedicated-relayer/no-config-dark.pngis excluded by!**/*.pngapps/dashboard/public/assets/dedicated-relayer/no-config-light.pngis excluded by!**/*.pngapps/dashboard/public/assets/dedicated-relayer/server-wallet-dark.pngis excluded by!**/*.pngapps/dashboard/public/assets/dedicated-relayer/server-wallet-light.pngis 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@/typesor localtypes.tsbarrels
Prefer type aliases over interface except for nominal shapes in TypeScript
Avoidanyandunknownin 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.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/empty-state.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/layout.tsxapps/dashboard/src/@/types/billing.tsapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/lib/api.tsapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/active-state.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/page-client.tsxapps/dashboard/src/app/(app)/(stripe)/checkout/[team_slug]/[sku]/page.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/pending-state.tsxapps/dashboard/src/app/(app)/(stripe)/utils/billing.tspackages/service-utils/src/core/api.tsapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/lib/hooks.tsapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/page.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/types.tsapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/components/ProjectSidebarLayout.tsxapps/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
Usecn()from@/lib/utilsfor conditional Tailwind class merging
Use design system tokens for styling (backgrounds:bg-card, borders:border-border, muted text:text-muted-foreground)
ExposeclassNameprop on root element for component overrides
Files:
apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/layout.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/empty-state.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/layout.tsxapps/dashboard/src/@/types/billing.tsapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/lib/api.tsapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/active-state.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/page-client.tsxapps/dashboard/src/app/(app)/(stripe)/checkout/[team_slug]/[sku]/page.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/pending-state.tsxapps/dashboard/src/app/(app)/(stripe)/utils/billing.tsapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/lib/hooks.tsapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/page.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/types.tsapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/components/ProjectSidebarLayout.tsxapps/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}: UseNavLinkfor internal navigation with automatic active states in dashboard
Start server component files withimport "server-only";in Next.js
Read cookies/headers withnext/headersin server components
Access server-only environment variables in server components
Perform heavy data fetching in server components
Implement redirect logic withredirect()fromnext/navigationin 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 callgetAuthToken()to retrieve JWT from cookies on server side
UseAuthorization: Bearerheader for API calls – never embed tokens in URLs
Return typed results (Project[],User[]) from server-side data fetches – avoidany
Wrap client-side API calls in React Query (@tanstack/react-query)
Use descriptive, stablequeryKeysin React Query for cache hits
ConfigurestaleTime/cacheTimein React Query based on freshness (default ≥ 60s)
Keep tokens secret via internal API routes or server actions
Never importposthog-jsin server components – only use analytics client-side
Files:
apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/layout.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/empty-state.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/layout.tsxapps/dashboard/src/@/types/billing.tsapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/lib/api.tsapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/active-state.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/page-client.tsxapps/dashboard/src/app/(app)/(stripe)/checkout/[team_slug]/[sku]/page.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/pending-state.tsxapps/dashboard/src/app/(app)/(stripe)/utils/billing.tsapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/lib/hooks.tsapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/page.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/types.tsapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/components/ProjectSidebarLayout.tsxapps/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 likeButton,Input,Select,Tabs,Card,Sidebar,Separator,Badge
UseNavLinkfrom@/components/ui/NavLinkfor internal navigation to ensure active states are handled automatically
For notices and skeletons, rely onAnnouncementBanner,GenericLoadingPage, andEmptyStateCardcomponents
Import icons fromlucide-reactor the project-specific…/iconsexports; 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 withcnfrom@/lib/utilsto keep conditional logic readable
Stick to design tokens: usebg-card,border-border,text-muted-foregroundand 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 viacn, and exposeclassNameprop 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.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/empty-state.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/layout.tsxapps/dashboard/src/@/types/billing.tsapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/lib/api.tsapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/active-state.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/page-client.tsxapps/dashboard/src/app/(app)/(stripe)/checkout/[team_slug]/[sku]/page.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/pending-state.tsxapps/dashboard/src/app/(app)/(stripe)/utils/billing.tsapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/lib/hooks.tsapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/page.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/types.tsapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/components/ProjectSidebarLayout.tsxapps/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}: ReuseSidebarLayoutorFullWidthSidebarLayoutfrom@/components/blocks/SidebarLayoutfor 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.tsxapps/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 lintbefore committing, ensure there are no linting errors
Files:
apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/layout.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/empty-state.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/layout.tsxapps/dashboard/src/@/types/billing.tsapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/lib/api.tsapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/active-state.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/page-client.tsxapps/dashboard/src/app/(app)/(stripe)/checkout/[team_slug]/[sku]/page.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/pending-state.tsxapps/dashboard/src/app/(app)/(stripe)/utils/billing.tspackages/service-utils/src/core/api.tsapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/lib/hooks.tsapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/page.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/types.tsapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/components/ProjectSidebarLayout.tsxapps/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.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/empty-state.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/layout.tsxapps/dashboard/src/@/types/billing.tsapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/lib/api.tsapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/active-state.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/page-client.tsxapps/dashboard/src/app/(app)/(stripe)/checkout/[team_slug]/[sku]/page.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/pending-state.tsxapps/dashboard/src/app/(app)/(stripe)/utils/billing.tsapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/lib/hooks.tsapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/page.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/types.tsapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/components/ProjectSidebarLayout.tsxapps/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.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/empty-state.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/layout.tsxapps/dashboard/src/@/types/billing.tsapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/lib/api.tsapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/active-state.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/page-client.tsxapps/dashboard/src/app/(app)/(stripe)/checkout/[team_slug]/[sku]/page.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/pending-state.tsxapps/dashboard/src/app/(app)/(stripe)/utils/billing.tsapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/lib/hooks.tsapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/page.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/types.tsapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/components/ProjectSidebarLayout.tsxapps/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.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/empty-state.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/layout.tsxapps/dashboard/src/@/types/billing.tsapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/lib/api.tsapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/active-state.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/page-client.tsxapps/dashboard/src/app/(app)/(stripe)/checkout/[team_slug]/[sku]/page.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/pending-state.tsxapps/dashboard/src/app/(app)/(stripe)/utils/billing.tspackages/service-utils/src/core/api.tsapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/lib/hooks.tsapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/page.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/types.tsapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/components/ProjectSidebarLayout.tsxapps/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
classNameprop 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.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/active-state.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/page-client.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/pending-state.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/components/ProjectSidebarLayout.tsxapps/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.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/active-state.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/page-client.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/pending-state.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/components/ProjectSidebarLayout.tsxapps/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
containerclass with amax-w-7xlcap for consistent page width
Files:
apps/dashboard/src/app/(app)/(stripe)/checkout/[team_slug]/[sku]/page.tsxapps/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 inProjectBundlerServicelooks goodThe new
dedicatedRelayerfield 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‑safeAdding
DedicatedRelayerSKUand including it inProductSKUcleanly models the new plans without affecting existing callers (they still handlenullexplicitly); no changes needed here.apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/layout.tsx (1)
106-113: Prop plumbing forteamIdintoProjectSidebarLayoutis correct
team.idis already validated above, so passing it through asteamIdis 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
getInitialStatushelper 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_KEYis 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.01for very small values is good UX for readability.
...am/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/empty-state.tsx
Show resolved
Hide resolved
...am/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/empty-state.tsx
Show resolved
Hide resolved
.../[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/pending-state.tsx
Show resolved
Hide resolved
...[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/tier-selection.tsx
Show resolved
Hide resolved
...d/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/page.tsx
Outdated
Show resolved
Hide resolved
There was a problem hiding this 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
dedicatedRelayerfield 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.
📒 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
patchversion bump. However, per semantic versioning, adding new public fields (like thededicatedRelayerfield inProjectBundlerService) typically warrants aminorversion bump, notpatch. Apatchis generally reserved for bug fixes.Please confirm whether this should be
patchorminor. If new public APIs are being exported, consider bumping tominor.
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.
There was a problem hiding this 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
onPurchaseTierthrows. 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.paramsshape is fine; consider optional + shared type aliasMaking
paramsa 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 aboutproject_id/chain_id, consider:
- Making
paramsitself optional and guarding withoptions.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 typePulling the request payload into a
bodyconstant is a nice cleanup and makes it clearer what’s sent to the API. UsingprojectIdandchainIdsas optional properties also works well sinceJSON.stringifyomitsundefinedfields.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 Testnetapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/page-client.tsx (1)
60-84: Consider makinghandlePurchaseTiersynchronous.The function is declared
asyncbut doesn'tawaitanything. Since it just opens a new tab synchronously, consider removingasync:- const handlePurchaseTier = async ( + const handlePurchaseTier = ( sku: DedicatedRelayerSKU, chainIds: number[], ) => {However, keeping it
asyncis acceptable if the parent components expect aPromisereturn 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
UseFleetTransactionsParamsandGetFleetTransactionsParams(inlib/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../typesor a smallparams.ts) and reusing it in bothhooks.tsandapi.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:
useFleetTransactionsSummaryuses the fullparamsobject (includingenabledandrefetchInterval) as part of thequeryKey. 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 passenabled/refetchIntervalseparately.- 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: AlignchainIdtyping and consider reusing chain metadata to avoid repeated hooksTwo small cleanups in the transaction row/cell helpers:
TransactionRowpassestransaction.chainIdintoTransactionHashCellandChainCell, whose props currently declarechainId: string. Elsewhere (UseFleetTransactionsParams,ChainFilter)chainIdis anumber. It would be clearer to standardize on a single type (likelynumber) for chain IDs across the dedicated-relayer types and components, and only stringify where needed for display.- Both
TransactionHashCellandChainCellcalluseAllChainsData()separately per row. Since that hook likely wraps a global query/cache, you could retrieveidToChain/allChainsonce higher up (e.g. inDedicatedRelayerActiveState) and pass the derivedchain/ explorer URL oridToChainmap 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 explicitchainIdpresence check in search paramsThe spread:
...(params.chainId && { chainId: params.chainId.toString() })relies on
chainIdbeing truthy, which would silently drop a value of0. Even if0is not a valid chain ID today, it’s clearer and more future-proof to check explicitly forundefined/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.
📒 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@/typesor localtypes.tsbarrels
Prefer type aliases over interface except for nominal shapes in TypeScript
Avoidanyandunknownin 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.tsapps/dashboard/src/app/(app)/(stripe)/utils/billing.tsapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/tier-selection.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/lib/api.tsapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/active-state.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/empty-state.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/page.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/lib/hooks.tsapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/pending-state.tsxapps/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
Usecn()from@/lib/utilsfor conditional Tailwind class merging
Use design system tokens for styling (backgrounds:bg-card, borders:border-border, muted text:text-muted-foreground)
ExposeclassNameprop on root element for component overrides
Files:
apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/constants.tsapps/dashboard/src/app/(app)/(stripe)/utils/billing.tsapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/tier-selection.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/lib/api.tsapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/active-state.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/empty-state.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/page.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/lib/hooks.tsapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/pending-state.tsxapps/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}: UseNavLinkfor internal navigation with automatic active states in dashboard
Start server component files withimport "server-only";in Next.js
Read cookies/headers withnext/headersin server components
Access server-only environment variables in server components
Perform heavy data fetching in server components
Implement redirect logic withredirect()fromnext/navigationin 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 callgetAuthToken()to retrieve JWT from cookies on server side
UseAuthorization: Bearerheader for API calls – never embed tokens in URLs
Return typed results (Project[],User[]) from server-side data fetches – avoidany
Wrap client-side API calls in React Query (@tanstack/react-query)
Use descriptive, stablequeryKeysin React Query for cache hits
ConfigurestaleTime/cacheTimein React Query based on freshness (default ≥ 60s)
Keep tokens secret via internal API routes or server actions
Never importposthog-jsin server components – only use analytics client-side
Files:
apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/constants.tsapps/dashboard/src/app/(app)/(stripe)/utils/billing.tsapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/tier-selection.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/lib/api.tsapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/active-state.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/empty-state.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/page.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/lib/hooks.tsapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/pending-state.tsxapps/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 likeButton,Input,Select,Tabs,Card,Sidebar,Separator,Badge
UseNavLinkfrom@/components/ui/NavLinkfor internal navigation to ensure active states are handled automatically
For notices and skeletons, rely onAnnouncementBanner,GenericLoadingPage, andEmptyStateCardcomponents
Import icons fromlucide-reactor the project-specific…/iconsexports; 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 withcnfrom@/lib/utilsto keep conditional logic readable
Stick to design tokens: usebg-card,border-border,text-muted-foregroundand 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 viacn, and exposeclassNameprop 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.tsapps/dashboard/src/app/(app)/(stripe)/utils/billing.tsapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/tier-selection.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/lib/api.tsapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/active-state.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/empty-state.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/page.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/lib/hooks.tsapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/pending-state.tsxapps/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 lintbefore committing, ensure there are no linting errors
Files:
apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/constants.tsapps/dashboard/src/app/(app)/(stripe)/utils/billing.tsapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/tier-selection.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/lib/api.tsapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/active-state.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/empty-state.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/page.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/lib/hooks.tsapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/pending-state.tsxapps/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.tsapps/dashboard/src/app/(app)/(stripe)/utils/billing.tsapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/tier-selection.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/lib/api.tsapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/active-state.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/empty-state.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/page.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/lib/hooks.tsapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/pending-state.tsxapps/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.tsapps/dashboard/src/app/(app)/(stripe)/utils/billing.tsapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/tier-selection.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/lib/api.tsapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/active-state.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/empty-state.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/page.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/lib/hooks.tsapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/pending-state.tsxapps/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.tsapps/dashboard/src/app/(app)/(stripe)/utils/billing.tsapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/tier-selection.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/lib/api.tsapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/active-state.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/empty-state.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/page.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/lib/hooks.tsapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/pending-state.tsxapps/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
classNameprop 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.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/active-state.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/empty-state.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/pending-state.tsxapps/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.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/active-state.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/empty-state.tsxapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/pending-state.tsxapps/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
containerclass with amax-w-7xlcap 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 invalidchain_idvaluesThe normalization correctly handles both
stringandstring[]and filters to finite numbers only. One behavioral nuance: values like"abc"or""end up asNaNand are filtered out, so an invalidchain_idsimply results inchainIdsbeing[](orundefinedif nothing valid remains).If it’s important to surface malformed
chain_idinputs (e.g. from a corrupted URL or buggy caller), you might instead want to:
- Return an error state when all provided
chain_idvalues 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
Reacttype import has been added (line 11), and theclassNameprop is now exposed onPlanSection(line 82) and correctly merged usingcn()(line 85). The component follows dashboard coding guidelines.
30-76: LGTM!The tier configuration is well-structured with clear SKU IDs matching the
DedicatedRelayerSKUtype, appropriate icons, and properly differentiated features for each plan level.
78-108: LGTM!The
PlanSectioncomponent correctly handles loading states per-tier and disables non-selected tiers during loading. The responsive grid layout follows Tailwind conventions.
110-196: LGTM!The
PlanCardcomponent 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
classNameprop 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
requiredChainslogic 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
FeatureSectionandFeatureCardcomponents are well-structured with proper theme-aware image handling usinguseTheme. 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.logstatements have been removed. The server component correctly usesforce-dynamicand follows proper authentication patterns.
13-36: LGTM!The server component efficiently uses concurrent data fetching with
Promise.alland 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
ChainBadgecomponent now uses optional chaining (chain?.name) with a fallback value to handle cases whereuseV5DashboardChainreturns undefined.
131-147: LGTM!The curl example with
YOUR_SECRET_KEYplaceholder 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
DedicatedRelayerPendingStatecomponent 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
SetupStephelper 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
DedicatedRelayerPageClientPropstype clearly documents the expected props.
25-58: LGTM!The state management efficiently handles polling only when needed (
not-purchasedorpending-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
DedicatedRelayerPendingStatefor active fleets without transactions provides appropriate "waiting for first transaction" UI feedback.
122-130: LGTM!The
getInitialStatushelper 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 viewThe main
DedicatedRelayerActiveStatecomponent cleanly wires summary stats, executors, filtering, and a paginated table, all while respecting the design system (UI primitives, Tailwind tokens,classNameoverride 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.
There was a problem hiding this 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 unnecessaryasynckeyword.
handleSelectTieris markedasyncbut contains noawaitexpressions. 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 tiersAlternatively, 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
imageAltprop toFeatureCard.
📜 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.
📒 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@/typesor localtypes.tsbarrels
Prefer type aliases over interface except for nominal shapes in TypeScript
Avoidanyandunknownin 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
Usecn()from@/lib/utilsfor conditional Tailwind class merging
Use design system tokens for styling (backgrounds:bg-card, borders:border-border, muted text:text-muted-foreground)
ExposeclassNameprop 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}: UseNavLinkfor internal navigation with automatic active states in dashboard
Start server component files withimport "server-only";in Next.js
Read cookies/headers withnext/headersin server components
Access server-only environment variables in server components
Perform heavy data fetching in server components
Implement redirect logic withredirect()fromnext/navigationin 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 callgetAuthToken()to retrieve JWT from cookies on server side
UseAuthorization: Bearerheader for API calls – never embed tokens in URLs
Return typed results (Project[],User[]) from server-side data fetches – avoidany
Wrap client-side API calls in React Query (@tanstack/react-query)
Use descriptive, stablequeryKeysin React Query for cache hits
ConfigurestaleTime/cacheTimein React Query based on freshness (default ≥ 60s)
Keep tokens secret via internal API routes or server actions
Never importposthog-jsin 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 likeButton,Input,Select,Tabs,Card,Sidebar,Separator,Badge
UseNavLinkfrom@/components/ui/NavLinkfor internal navigation to ensure active states are handled automatically
For notices and skeletons, rely onAnnouncementBanner,GenericLoadingPage, andEmptyStateCardcomponents
Import icons fromlucide-reactor the project-specific…/iconsexports; 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 withcnfrom@/lib/utilsto keep conditional logic readable
Stick to design tokens: usebg-card,border-border,text-muted-foregroundand 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 viacn, and exposeclassNameprop 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
classNameprop 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 lintbefore 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
classNamefor styling overrides, and properly types theonPurchaseTiercallback.
58-72: LGTM - purchase flow state handling is correct.State reset (
setIsModalOpen,setSelectedTier,setSelectedChainIds) now happens only on success (insidetrybeforecatch), whileisLoadingis reset infinally. This preserves user context on failure as expected.
116-149: LGTM!
FeatureSectionproperly uses design tokens (bg-card,border-border), mobile-first responsive design withlg:breakpoints, and the copy has been corrected from the previous review.
PR-Codex overview
This PR adds a
Dedicated Relayerfeature to the service utilities, enhancing the billing system, and introduces related components for managing dedicated relayers in the dashboard.Detailed summary
dedicatedRelayertype inapi.ts.DedicatedRelayerSKUto billing types.ProjectSidebarLayoutto include a link forDedicated Relayer.Dedicated Relayermanagement.Summary by CodeRabbit
New Features
Chores
✏️ Tip: You can customize this high-level summary in your review settings.