Skip to content

Commit 4672475

Browse files
committed
feat: Support multiple wallets in Engine dashboard (#4956)
- Adds support for configuring creds for AWS + GCP independently - Moves all touched UI to tailwind - Import / Create wallet buttons allow selecting the wallet type - Unrelated: Moved version upgrade button to the new enpdoint. Will deprecate the old one. <!-- start pr-codex --> --- ## PR-Codex overview This PR focuses on refactoring the engine configuration components to improve how instances and wallet configurations are handled, enhancing the overall clarity and functionality of the engine's wallet management features. ### Detailed summary - Removed `instanceUrl` prop in favor of `instance` for components. - Introduced `LocalConfig` component for local wallet configuration. - Added support for multiple wallet types with `EngineBackendWalletOptions`. - Updated wallet configuration forms to use a unified `Form` component. - Improved error handling and user feedback for wallet operations. - Enhanced dialog components for creating and importing backend wallets. > ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}` <!-- end pr-codex -->
1 parent 926d823 commit 4672475

File tree

16 files changed

+956
-700
lines changed

16 files changed

+956
-700
lines changed

apps/dashboard/src/@3rdweb-sdk/react/hooks/useEngine.ts

Lines changed: 58 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
44
import type { ResultItem } from "components/engine/system-metrics/components/StatusCodes";
55
import { THIRDWEB_API_HOST } from "constants/urls";
6+
import type { EngineBackendWalletType } from "lib/engine";
67
import { useState } from "react";
78
import { useActiveAccount, useActiveWalletChain } from "thirdweb/react";
89
import invariant from "tiny-invariant";
@@ -111,10 +112,16 @@ export function useEngineBackendWallets(instance: string) {
111112
});
112113
}
113114

115+
type EngineFeature =
116+
| "KEYPAIR_AUTH"
117+
| "CONTRACT_SUBSCRIPTIONS"
118+
| "IP_ALLOWLIST"
119+
| "HETEROGENEOUS_WALLET_TYPES";
120+
114121
interface EngineSystemHealth {
115122
status: string;
116123
engineVersion?: string;
117-
features?: string[];
124+
features?: EngineFeature[];
118125
}
119126

120127
export function useEngineSystemHealth(
@@ -138,6 +145,18 @@ export function useEngineSystemHealth(
138145
});
139146
}
140147

148+
// Helper function to check if a feature is supported.
149+
export function useHasEngineFeature(
150+
instanceUrl: string,
151+
feature: EngineFeature,
152+
) {
153+
const query = useEngineSystemHealth(instanceUrl);
154+
return {
155+
query,
156+
isSupported: !!query.data?.features?.includes(feature),
157+
};
158+
}
159+
141160
interface EngineSystemQueueMetrics {
142161
result: {
143162
queued: number;
@@ -188,17 +207,15 @@ export function useEngineLatestVersion() {
188207
}
189208

190209
interface UpdateVersionInput {
191-
engineId: string;
210+
deploymentId: string;
192211
serverVersion: string;
193212
}
194213

195214
export function useEngineUpdateServerVersion() {
196215
return useMutation({
197216
mutationFn: async (input: UpdateVersionInput) => {
198-
invariant(input.engineId, "engineId is required");
199-
200217
const res = await fetch(
201-
`${THIRDWEB_API_HOST}/v2/engine/${input.engineId}/infrastructure`,
218+
`${THIRDWEB_API_HOST}/v2/engine/deployments/${input.deploymentId}/infrastructure`,
202219
{
203220
method: "PUT",
204221
headers: {
@@ -401,29 +418,22 @@ export function useEngineTransactions(instance: string, autoUpdate: boolean) {
401418
});
402419
}
403420

404-
type WalletConfig =
405-
| {
406-
type: "local";
407-
}
408-
| {
409-
type: "aws-kms";
410-
awsAccessKeyId: string;
411-
awsSecretAccessKey: string;
412-
awsRegion: string;
413-
}
414-
| {
415-
type: "gcp-kms";
416-
gcpApplicationProjectId: string;
417-
gcpKmsLocationId: string;
418-
gcpKmsKeyRingId: string;
419-
gcpApplicationCredentialEmail: string;
420-
gcpApplicationCredentialPrivateKey: string;
421-
};
421+
export interface WalletConfigResponse {
422+
type: EngineBackendWalletType;
423+
424+
awsAccessKeyId?: string | null;
425+
awsRegion?: string | null;
426+
427+
gcpApplicationProjectId?: string | null;
428+
gcpKmsLocationId?: string | null;
429+
gcpKmsKeyRingId?: string | null;
430+
gcpApplicationCredentialEmail?: string | null;
431+
}
422432

423433
export function useEngineWalletConfig(instance: string) {
424434
const token = useLoggedInUser().user?.jwt ?? null;
425435

426-
return useQuery({
436+
return useQuery<WalletConfigResponse>({
427437
queryKey: engineKeys.walletConfig(instance),
428438
queryFn: async () => {
429439
const res = await fetch(`${instance}configuration/wallets`, {
@@ -432,8 +442,7 @@ export function useEngineWalletConfig(instance: string) {
432442
});
433443

434444
const json = await res.json();
435-
436-
return (json.result as WalletConfig) || {};
445+
return json.result;
437446
},
438447
enabled: !!instance && !!token,
439448
});
@@ -799,9 +808,6 @@ export function useEngineWebhooks(instance: string) {
799808

800809
// POST REQUESTS
801810
export type SetWalletConfigInput =
802-
| {
803-
type: "local";
804-
}
805811
| {
806812
type: "aws-kms";
807813
awsAccessKeyId: string;
@@ -821,8 +827,8 @@ export function useEngineSetWalletConfig(instance: string) {
821827
const token = useLoggedInUser().user?.jwt ?? null;
822828
const queryClient = useQueryClient();
823829

824-
return useMutation({
825-
mutationFn: async (input: SetWalletConfigInput) => {
830+
return useMutation<WalletConfigResponse, void, SetWalletConfigInput>({
831+
mutationFn: async (input) => {
826832
invariant(instance, "instance is required");
827833

828834
const res = await fetch(`${instance}configuration/wallets`, {
@@ -847,6 +853,7 @@ export function useEngineSetWalletConfig(instance: string) {
847853
}
848854

849855
export type CreateBackendWalletInput = {
856+
type: EngineBackendWalletType;
850857
label?: string;
851858
};
852859

@@ -913,25 +920,20 @@ export function useEngineUpdateBackendWallet(instance: string) {
913920
});
914921
}
915922

916-
export type ImportBackendWalletInput =
917-
| {
918-
awsKmsKeyId: string;
919-
awsKmsArn: string;
920-
}
921-
| {
922-
gcpKmsKeyId: string;
923-
gcpKmsKeyVersionId: string;
924-
}
925-
| {
926-
privateKey?: string;
927-
}
928-
| {
929-
mnemonic?: string;
930-
}
931-
| {
932-
encryptedJson?: string;
933-
password?: string;
934-
};
923+
// The backend determines the wallet imported based on the provided fields.
924+
export type ImportBackendWalletInput = {
925+
label?: string;
926+
927+
awsKmsArn?: string;
928+
929+
gcpKmsKeyId?: string;
930+
gcpKmsKeyVersionId?: string;
931+
932+
privateKey?: string;
933+
mnemonic?: string;
934+
encryptedJson?: string;
935+
password?: string;
936+
};
935937

936938
export function useEngineImportBackendWallet(instance: string) {
937939
const token = useLoggedInUser().user?.jwt ?? null;
@@ -1639,6 +1641,9 @@ export function useEngineCreateNotificationChannel(engineId: string) {
16391641
`${THIRDWEB_API_HOST}/v1/engine/${engineId}/notification-channels`,
16401642
{
16411643
method: "POST",
1644+
headers: {
1645+
"Content-Type": "application/json",
1646+
},
16421647
body: JSON.stringify(input),
16431648
},
16441649
);
@@ -1667,7 +1672,9 @@ export function useEngineDeleteNotificationChannel(engineId: string) {
16671672

16681673
const res = await fetch(
16691674
`${THIRDWEB_API_HOST}/v1/engine/${engineId}/notification-channels/${notificationChannelId}`,
1670-
{ method: "DELETE" },
1675+
{
1676+
method: "DELETE",
1677+
},
16711678
);
16721679
if (!res.ok) {
16731680
throw new Error(`Unexpected status ${res.status}: ${await res.text()}`);

apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/(instance)/[engineId]/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ export default function Page(props: EngineInstancePageProps) {
88
return (
99
<WithEngineInstance
1010
engineId={props.params.engineId}
11-
content={(res) => <EngineOverview instanceUrl={res.instance.url} />}
11+
content={(res) => <EngineOverview instance={res.instance} />}
1212
rootPath={`/team/${props.params.team_slug}/${props.params.project_slug}/engine`}
1313
/>
1414
);

apps/dashboard/src/components/engine/badges/version.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
} from "@/components/ui/dialog";
2121
import { TrackedLinkTW } from "@/components/ui/tracked-link";
2222
import { toast } from "sonner";
23+
import invariant from "tiny-invariant";
2324

2425
export const EngineVersionBadge = ({
2526
instance,
@@ -119,9 +120,11 @@ const UpdateVersionModal = (props: {
119120
}
120121

121122
const onClick = async () => {
123+
invariant(instance.deploymentId, "Engine is missing deploymentId.");
124+
122125
try {
123126
const promise = updateEngineServerMutation.mutateAsync({
124-
engineId: instance.id,
127+
deploymentId: instance.deploymentId,
125128
serverVersion: latestVersion,
126129
});
127130
toast.promise(promise, {

apps/dashboard/src/components/engine/configuration/engine-configuration.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export const EngineConfiguration: React.FC<EngineConfigurationProps> = ({
1515
}) => {
1616
return (
1717
<div className="flex flex-col gap-12">
18-
<EngineWalletConfig instanceUrl={instance.url} />
18+
<EngineWalletConfig instance={instance} />
1919
<EngineCorsConfig instanceUrl={instance.url} />
2020
<EngineIpAllowlistConfig instanceUrl={instance.url} />
2121
<EngineSystem instance={instance} />

0 commit comments

Comments
 (0)