Skip to content

Commit 11e6b24

Browse files
committed
Refactor dedicated relayer to use new fleet analytics API
Replaces legacy analytics and fleet API calls with new endpoints for fleet transactions and summary. Updates the active state UI to show paginated transaction tables, summary stats, and chain filtering. Refactors types and hooks to match new API responses, removes unused code, and updates documentation links and UI copy for clarity.
1 parent 635721d commit 11e6b24

File tree

11 files changed

+544
-377
lines changed

11 files changed

+544
-377
lines changed

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

Lines changed: 314 additions & 182 deletions
Large diffs are not rendered by default.

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ export function DedicatedRelayerEmptyState(
7070

7171
<Button asChild size="lg" variant="outline">
7272
<Link
73-
href="https://portal.thirdweb.com/transactions/relayer"
73+
href="https://portal.thirdweb.com/wallets/server"
7474
rel="noopener noreferrer"
7575
target="_blank"
7676
>
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
export { DedicatedRelayerActiveState } from "./active-state";
22
export { DedicatedRelayerEmptyState } from "./empty-state";
3-
export { DedicatedRelayerPageClient } from "./page-client";
43
export { DedicatedRelayerPendingState } from "./pending-state";
54
export { TierSelection } from "./tier-selection";

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

Lines changed: 35 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,21 @@
22

33
import { useState } from "react";
44
import type { ThirdwebClient } from "thirdweb";
5-
import type { Project } from "@/api/project/projects";
6-
import type { Fleet, FleetExecutor, FleetStatus } from "../types";
5+
import type { Fleet, FleetStatus } from "../types";
76
import { DedicatedRelayerActiveState } from "./active-state";
87
import { DedicatedRelayerEmptyState } from "./empty-state";
98
import { DedicatedRelayerPendingState } from "./pending-state";
109
import type { RelayerTier } from "./tier-selection";
1110

12-
// Mock executor address for demo
13-
const MOCK_EXECUTOR_ADDRESS = "0xE0F28D9d95143858Be492BDf3abBCA746d0d2272";
14-
15-
// Chain IDs
16-
const BASE_MAINNET = 8453;
17-
const BASE_SEPOLIA = 84532;
18-
1911
type DedicatedRelayerPageClientProps = {
20-
project: Project;
21-
authToken: string;
12+
teamId: string;
13+
projectId: string;
2214
teamSlug: string;
2315
projectSlug: string;
2416
client: ThirdwebClient;
17+
fleetId: string;
18+
from: string;
19+
to: string;
2520
initialFleet: Fleet | null;
2621
};
2722

@@ -33,51 +28,39 @@ export function DedicatedRelayerPageClient(
3328
getInitialStatus(props.initialFleet),
3429
);
3530

36-
const handlePurchaseTier = async (tier: RelayerTier) => {
37-
// Simulate API call delay
38-
await new Promise((resolve) => setTimeout(resolve, 1500));
39-
40-
// Create mock fleet based on tier (pending state - no executors yet)
31+
// TODO-FLEET: Implement purchase flow
32+
// 1. Call Stripe checkout API to create a checkout session for the selected tier
33+
// 2. Redirect user to Stripe checkout
34+
// 3. On success callback, call API server to provision fleet
35+
// 4. API server should:
36+
// - Create fleet record in DB
37+
// - Call bundler service to provision executor wallets
38+
// - Update ProjectBundlerService with fleet config
39+
// 5. Refetch fleet status and update UI
40+
const handlePurchaseTier = async (_tier: RelayerTier) => {
41+
// TODO-FLEET: Replace with actual Stripe + API server integration
42+
// For now, simulate purchase by transitioning to pending-setup
4143
const mockFleet: Fleet = {
42-
id: `fleet-${Date.now()}`,
43-
tier,
44-
chainIds: [BASE_MAINNET, BASE_SEPOLIA],
45-
executors: [], // Empty initially - pending setup
46-
createdAt: new Date().toISOString(),
47-
updatedAt: new Date().toISOString(),
44+
id: props.fleetId,
45+
chainIds: [],
46+
executors: [],
4847
};
49-
5048
setFleet(mockFleet);
5149
setFleetStatus("pending-setup");
5250
};
5351

52+
// TODO-FLEET: This is a dev helper to skip purchase - remove in production
5453
const handleSkipSetup = () => {
55-
if (!fleet) return;
56-
57-
const executorCount = fleet.tier === "starter" ? 1 : 10;
58-
const executors: FleetExecutor[] = [];
59-
60-
for (let i = 0; i < executorCount; i++) {
61-
executors.push({
62-
address: MOCK_EXECUTOR_ADDRESS,
63-
chainId: BASE_MAINNET,
64-
});
65-
executors.push({
66-
address: MOCK_EXECUTOR_ADDRESS,
67-
chainId: BASE_SEPOLIA,
54+
// TODO-FLEET: Replace with actual setup completion flow
55+
// For now, simulate setup completion by transitioning to active with mock executors
56+
if (fleet) {
57+
setFleet({
58+
...fleet,
59+
executors: ["0x1234567890123456789012345678901234567890"],
60+
chainIds: [1, 137],
6861
});
62+
setFleetStatus("active");
6963
}
70-
71-
setFleet((prev) =>
72-
prev
73-
? {
74-
...prev,
75-
executors,
76-
updatedAt: new Date().toISOString(),
77-
}
78-
: null,
79-
);
80-
setFleetStatus("active");
8164
};
8265

8366
return (
@@ -99,11 +82,12 @@ export function DedicatedRelayerPageClient(
9982

10083
{fleetStatus === "active" && fleet && (
10184
<DedicatedRelayerActiveState
102-
authToken={props.authToken}
103-
client={props.client}
10485
fleet={fleet}
105-
project={props.project}
106-
teamSlug={props.teamSlug}
86+
teamId={props.teamId}
87+
fleetId={props.fleetId}
88+
client={props.client}
89+
from={props.from}
90+
to={props.to}
10791
/>
10892
)}
10993
</div>

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ export function DedicatedRelayerPendingState(
8383
<SetupStep
8484
completed={true}
8585
number={1}
86-
title="Stripe subscription activated"
86+
title="Subscription activated"
8787
/>
8888
<SetupStep
8989
completed={false}

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

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -37,23 +37,17 @@ export default async function Layout(props: {
3737
icon: WalletProductIcon,
3838
title: "Dedicated Relayer",
3939
description:
40-
"Your own executor fleet for automatic transaction relaying at scale",
40+
"Your own executor fleet for guaranteed transaction throughput",
4141
actions: null,
4242
client,
4343
links: [
4444
{
4545
type: "docs",
46-
href: "https://portal.thirdweb.com/transactions/relayer",
46+
href: "https://portal.thirdweb.com/wallets/server",
4747
},
4848
],
4949
}}
50-
tabs={[
51-
{
52-
name: "Overview",
53-
path: `${basePath}`,
54-
exactMatch: true,
55-
},
56-
]}
50+
tabs={[]}
5751
>
5852
{props.children}
5953
</ProjectPage>
Lines changed: 57 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,91 +1,75 @@
1-
import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "@/constants/public-envs";
2-
import type { Fleet, FleetAnalytics } from "../types";
1+
"use server";
32

4-
type GetFleetParams = {
3+
import { analyticsServerProxy } from "@/actions/proxies";
4+
import type { FleetTransaction, FleetTransactionsSummary } from "../types";
5+
6+
type GetFleetTransactionsParams = {
57
teamId: string;
6-
projectId: string;
7-
authToken: string;
8+
fleetId: string;
9+
from: string;
10+
to: string;
11+
limit: number;
12+
offset: number;
13+
chainId?: number;
814
};
915

1016
/**
11-
* Fetches the fleet data for a project.
12-
* Returns null if no fleet exists (dev hasn't purchased).
17+
* Fetches paginated fleet transactions from the analytics service.
1318
*/
14-
export async function getFleet(params: GetFleetParams): Promise<Fleet | null> {
15-
try {
16-
const response = await fetch(
17-
`${NEXT_PUBLIC_THIRDWEB_API_HOST}/v1/teams/${params.teamId}/projects/${params.projectId}/fleet`,
18-
{
19-
headers: {
20-
Authorization: `Bearer ${params.authToken}`,
21-
"Content-Type": "application/json",
22-
},
23-
method: "GET",
24-
},
25-
);
26-
27-
if (response.status === 404) {
28-
// Fleet not purchased yet
29-
return null;
30-
}
19+
export async function getFleetTransactions(params: GetFleetTransactionsParams) {
20+
const res = await analyticsServerProxy<{
21+
data: FleetTransaction[];
22+
meta: { total: number };
23+
}>({
24+
method: "GET",
25+
pathname: "/v2/bundler/fleet-transactions",
26+
searchParams: {
27+
teamId: params.teamId,
28+
fleetId: params.fleetId,
29+
from: params.from,
30+
to: params.to,
31+
limit: params.limit.toString(),
32+
offset: params.offset.toString(),
33+
...(params.chainId && { chainId: params.chainId.toString() }),
34+
},
35+
});
3136

32-
if (!response.ok) {
33-
console.error("Error fetching fleet:", response.status);
34-
return null;
35-
}
36-
37-
const data = await response.json();
38-
return data.result as Fleet;
39-
} catch (error) {
40-
console.error("Error fetching fleet:", error);
41-
return null;
37+
if (!res.ok) {
38+
throw new Error(res.error);
4239
}
40+
41+
return res.data;
4342
}
4443

45-
export type GetFleetAnalyticsParams = {
44+
type GetFleetTransactionsSummaryParams = {
4645
teamId: string;
47-
projectId: string;
48-
authToken: string;
49-
startDate?: string;
50-
endDate?: string;
46+
fleetId: string;
47+
from: string;
48+
to: string;
5149
};
5250

5351
/**
54-
* Fetches analytics for a fleet.
55-
* Only call this when fleet has executors (active state).
52+
* Fetches fleet transactions summary from the analytics service.
5653
*/
57-
export async function getFleetAnalytics(
58-
params: GetFleetAnalyticsParams,
59-
): Promise<FleetAnalytics | null> {
60-
try {
61-
const url = new URL(
62-
`${NEXT_PUBLIC_THIRDWEB_API_HOST}/v1/teams/${params.teamId}/projects/${params.projectId}/fleet/analytics`,
63-
);
54+
export async function getFleetTransactionsSummary(
55+
params: GetFleetTransactionsSummaryParams,
56+
) {
57+
const res = await analyticsServerProxy<{
58+
data: FleetTransactionsSummary;
59+
}>({
60+
method: "GET",
61+
pathname: "/v2/bundler/fleet-transactions/summary",
62+
searchParams: {
63+
teamId: params.teamId,
64+
fleetId: params.fleetId,
65+
from: params.from,
66+
to: params.to,
67+
},
68+
});
6469

65-
if (params.startDate) {
66-
url.searchParams.set("startDate", params.startDate);
67-
}
68-
if (params.endDate) {
69-
url.searchParams.set("endDate", params.endDate);
70-
}
71-
72-
const response = await fetch(url.toString(), {
73-
headers: {
74-
Authorization: `Bearer ${params.authToken}`,
75-
"Content-Type": "application/json",
76-
},
77-
method: "GET",
78-
});
79-
80-
if (!response.ok) {
81-
console.error("Error fetching fleet analytics:", response.status);
82-
return null;
83-
}
84-
85-
const data = await response.json();
86-
return data.result as FleetAnalytics;
87-
} catch (error) {
88-
console.error("Error fetching fleet analytics:", error);
89-
return null;
70+
if (!res.ok) {
71+
throw new Error(res.error);
9072
}
73+
74+
return res.data;
9175
}
Lines changed: 38 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,46 @@
11
"use client";
22

3-
import { useQuery } from "@tanstack/react-query";
4-
import { type GetFleetAnalyticsParams, getFleetAnalytics } from "./api";
3+
import { keepPreviousData, useQuery } from "@tanstack/react-query";
4+
import { getFleetTransactions, getFleetTransactionsSummary } from "./api";
55

6-
export function useFleetAnalytics(
7-
params: GetFleetAnalyticsParams & { enabled?: boolean },
6+
type UseFleetTransactionsParams = {
7+
teamId: string;
8+
fleetId: string;
9+
from: string;
10+
to: string;
11+
limit: number;
12+
offset: number;
13+
chainId?: number;
14+
};
15+
16+
/**
17+
* React Query hook for fetching paginated fleet transactions.
18+
*/
19+
export function useFleetTransactions(params: UseFleetTransactionsParams) {
20+
return useQuery({
21+
queryKey: ["fleet-transactions", params],
22+
queryFn: () => getFleetTransactions(params),
23+
placeholderData: keepPreviousData,
24+
refetchOnWindowFocus: false,
25+
});
26+
}
27+
28+
type UseFleetTransactionsSummaryParams = {
29+
teamId: string;
30+
fleetId: string;
31+
from: string;
32+
to: string;
33+
};
34+
35+
/**
36+
* React Query hook for fetching fleet transactions summary.
37+
*/
38+
export function useFleetTransactionsSummary(
39+
params: UseFleetTransactionsSummaryParams,
840
) {
941
return useQuery({
10-
queryKey: [
11-
"fleet-analytics",
12-
params.teamId,
13-
params.projectId,
14-
params.startDate,
15-
params.endDate,
16-
],
17-
queryFn: () => getFleetAnalytics(params),
42+
queryKey: ["fleet-transactions-summary", params],
43+
queryFn: () => getFleetTransactionsSummary(params),
1844
refetchOnWindowFocus: false,
19-
enabled: params.enabled ?? true,
2045
});
2146
}

0 commit comments

Comments
 (0)