diff --git a/apps/dashboard/.env.example b/apps/dashboard/.env.example
index 918d54845d4..b995bdcd0a7 100644
--- a/apps/dashboard/.env.example
+++ b/apps/dashboard/.env.example
@@ -33,7 +33,6 @@ NEXT_PUBLIC_TYPESENSE_CONTRACT_API_KEY=
NEXT_PUBLIC_POSTHOG_KEY=""
# required for server wallet management
-NEXT_PUBLIC_THIRDWEB_VAULT_URL=""
NEXT_PUBLIC_ENGINE_CLOUD_URL=""
diff --git a/apps/dashboard/knip.json b/apps/dashboard/knip.json
index 03076122bc2..aa7cddb28e7 100644
--- a/apps/dashboard/knip.json
+++ b/apps/dashboard/knip.json
@@ -10,7 +10,6 @@
"ignoreDependencies": [
"@thirdweb-dev/api",
"@thirdweb-dev/service-utils",
- "@thirdweb-dev/vault-sdk",
"thirdweb",
"@types/color",
"fast-xml-parser",
diff --git a/apps/dashboard/package.json b/apps/dashboard/package.json
index 7d28b3d88fd..de5c8a7f41a 100644
--- a/apps/dashboard/package.json
+++ b/apps/dashboard/package.json
@@ -28,7 +28,6 @@
"@tanstack/react-table": "^8.21.3",
"@thirdweb-dev/api": "workspace:*",
"@thirdweb-dev/service-utils": "workspace:*",
- "@thirdweb-dev/vault-sdk": "workspace:*",
"@vercel/functions": "2.2.2",
"@vercel/og": "^0.6.8",
"@workspace/ui": "workspace:*",
diff --git a/apps/dashboard/src/@/constants/public-envs.ts b/apps/dashboard/src/@/constants/public-envs.ts
index 80f2d5d965a..fe471c31f92 100644
--- a/apps/dashboard/src/@/constants/public-envs.ts
+++ b/apps/dashboard/src/@/constants/public-envs.ts
@@ -1,9 +1,6 @@
export const NEXT_PUBLIC_DASHBOARD_CLIENT_ID =
process.env.NEXT_PUBLIC_DASHBOARD_CLIENT_ID || "";
-export const NEXT_PUBLIC_THIRDWEB_VAULT_URL =
- process.env.NEXT_PUBLIC_THIRDWEB_VAULT_URL || "";
-
export const NEXT_PUBLIC_ENGINE_CLOUD_URL =
process.env.NEXT_PUBLIC_ENGINE_CLOUD_URL || "";
diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/transactions/components/server-wallets-table.client.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/transactions/components/server-wallets-table.client.tsx
index d47cdeefa60..7122e0fc917 100644
--- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/transactions/components/server-wallets-table.client.tsx
+++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/transactions/components/server-wallets-table.client.tsx
@@ -63,7 +63,6 @@ import { updateDefaultProjectWallet } from "../lib/vault.client";
import { CreateServerWallet } from "../server-wallets/components/create-server-wallet.client";
import type { Wallet as EVMWallet } from "../server-wallets/wallet-table/types";
import { CreateSolanaWallet } from "../solana-wallets/components/create-solana-wallet.client";
-import { UpgradeSolanaPermissions } from "../solana-wallets/components/upgrade-solana-permissions.client";
import type { SolanaWallet } from "../solana-wallets/wallet-table/types";
type WalletChain = "evm" | "solana";
@@ -238,7 +237,21 @@ export function ServerWalletsTable(props: ServerWalletsTableProps) {
{/* Table Content */}
{activeChain === "solana" && solanaPermissionError ? (
-
+
+
+ Solana access not enabled
+
+
+ This project's vault access tokens were issued before Solana
+ support existed. Rotate the admin key to reissue them with
+ Solana permissions.
+
+
+
+ Go to Vault
+
+
+
) : (
<>
diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/transactions/lib/vault.client.ts b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/transactions/lib/vault.client.ts
index 41c7728c41c..577bc3959b2 100644
--- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/transactions/lib/vault.client.ts
+++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/transactions/lib/vault.client.ts
@@ -1,38 +1,10 @@
"use client";
-import { decrypt, encrypt } from "@thirdweb-dev/service-utils";
-import {
- createAccessToken,
- createVaultClient,
- type VaultClient,
-} from "@thirdweb-dev/vault-sdk";
import {
createVaultServerWallet,
setVaultProjectWallet,
} from "@/actions/vault";
import type { Project } from "@/api/project/projects";
-import { NEXT_PUBLIC_THIRDWEB_VAULT_URL } from "@/constants/public-envs";
-import { updateProjectClient } from "@/hooks/useApi";
-
-const SERVER_WALLET_ACCESS_TOKEN_PURPOSE =
- "Access Token for All Server Wallets";
-
-const SERVER_WALLET_MANAGEMENT_ACCESS_TOKEN_PURPOSE =
- "Management Token for Dashboard";
-
-let vc: VaultClient | null = null;
-
-// Only `upgradeAccessTokensForSolana` still needs this; every other vault
-// operation goes through api-server.
-async function initVaultClient() {
- if (vc) {
- return vc;
- }
- vc = await createVaultClient({
- baseUrl: NEXT_PUBLIC_THIRDWEB_VAULT_URL,
- });
- return vc;
-}
export async function createProjectServerWallet(props: {
project: Project;
@@ -59,650 +31,6 @@ export async function updateDefaultProjectWallet(props: {
});
}
-async function createWalletAccessToken(props: {
- project: Project;
- adminKey: string;
- vaultClient: VaultClient;
-}) {
- return createAccessToken({
- client: props.vaultClient,
- request: {
- auth: {
- adminKey: props.adminKey,
- },
- options: {
- expiresAt: new Date(
- Date.now() + 60 * 60 * 1000 * 24 * 365 * 1000,
- ).toISOString(), // 100 years from now
- metadata: {
- projectId: props.project.id,
- purpose: SERVER_WALLET_ACCESS_TOKEN_PURPOSE,
- teamId: props.project.teamId,
- },
- policies: [
- {
- metadataPatterns: [
- {
- key: "projectId",
- rule: {
- pattern: props.project.id,
- },
- },
- {
- key: "teamId",
- rule: {
- pattern: props.project.teamId,
- },
- },
- {
- key: "type",
- rule: {
- pattern: "server-wallet",
- },
- },
- ],
- type: "eoa:read",
- },
- {
- requiredMetadataPatterns: [
- {
- key: "projectId",
- rule: {
- pattern: props.project.id,
- },
- },
- {
- key: "teamId",
- rule: {
- pattern: props.project.teamId,
- },
- },
- {
- key: "type",
- rule: {
- pattern: "server-wallet",
- },
- },
- ],
- type: "eoa:create",
- },
- {
- metadataPatterns: [
- {
- key: "projectId",
- rule: {
- pattern: props.project.id,
- },
- },
- {
- key: "teamId",
- rule: {
- pattern: props.project.teamId,
- },
- },
- {
- key: "type",
- rule: {
- pattern: "server-wallet",
- },
- },
- ],
- type: "eoa:signMessage",
- },
- {
- metadataPatterns: [
- {
- key: "projectId",
- rule: {
- pattern: props.project.id,
- },
- },
- {
- key: "teamId",
- rule: {
- pattern: props.project.teamId,
- },
- },
- {
- key: "type",
- rule: {
- pattern: "server-wallet",
- },
- },
- ],
- payloadPatterns: {},
- type: "eoa:signTransaction",
- },
- {
- metadataPatterns: [
- {
- key: "projectId",
- rule: {
- pattern: props.project.id,
- },
- },
- {
- key: "teamId",
- rule: {
- pattern: props.project.teamId,
- },
- },
- {
- key: "type",
- rule: {
- pattern: "server-wallet",
- },
- },
- ],
- type: "eoa:signTypedData",
- },
- {
- metadataPatterns: [
- {
- key: "projectId",
- rule: {
- pattern: props.project.id,
- },
- },
- {
- key: "teamId",
- rule: {
- pattern: props.project.teamId,
- },
- },
- {
- key: "type",
- rule: {
- pattern: "server-wallet",
- },
- },
- ],
- type: "eoa:signAuthorization",
- },
- {
- metadataPatterns: [
- {
- key: "projectId",
- rule: {
- pattern: props.project.id,
- },
- },
- {
- key: "teamId",
- rule: {
- pattern: props.project.teamId,
- },
- },
- {
- key: "type",
- rule: {
- pattern: "server-wallet",
- },
- },
- ],
- structuredPatterns: {
- useropV06: {},
- useropV07: {},
- },
- type: "eoa:signStructuredMessage",
- },
- {
- requiredMetadataPatterns: [
- {
- key: "projectId",
- rule: {
- pattern: props.project.id,
- },
- },
- {
- key: "teamId",
- rule: {
- pattern: props.project.teamId,
- },
- },
- {
- key: "type",
- rule: {
- pattern: "server-wallet",
- },
- },
- ],
- type: "eoa:create",
- },
- {
- metadataPatterns: [
- {
- key: "projectId",
- rule: {
- pattern: props.project.id,
- },
- },
- {
- key: "teamId",
- rule: {
- pattern: props.project.teamId,
- },
- },
- {
- key: "type",
- rule: {
- pattern: "server-wallet",
- },
- },
- ],
- type: "solana:read",
- },
- {
- requiredMetadataPatterns: [
- {
- key: "projectId",
- rule: {
- pattern: props.project.id,
- },
- },
- {
- key: "teamId",
- rule: {
- pattern: props.project.teamId,
- },
- },
- {
- key: "type",
- rule: {
- pattern: "server-wallet",
- },
- },
- ],
- type: "solana:create",
- },
- {
- metadataPatterns: [
- {
- key: "projectId",
- rule: {
- pattern: props.project.id,
- },
- },
- {
- key: "teamId",
- rule: {
- pattern: props.project.teamId,
- },
- },
- {
- key: "type",
- rule: {
- pattern: "server-wallet",
- },
- },
- ],
- type: "solana:signTransaction",
- },
- {
- metadataPatterns: [
- {
- key: "projectId",
- rule: {
- pattern: props.project.id,
- },
- },
- {
- key: "teamId",
- rule: {
- pattern: props.project.teamId,
- },
- },
- {
- key: "type",
- rule: {
- pattern: "server-wallet",
- },
- },
- ],
- type: "solana:signMessage",
- },
- ],
- },
- },
- });
-}
-
-async function createManagementAccessToken(props: {
- project: Project;
- adminKey: string;
- vaultClient: VaultClient;
-}) {
- return createAccessToken({
- client: props.vaultClient,
- request: {
- auth: {
- adminKey: props.adminKey,
- },
- options: {
- expiresAt: new Date(
- Date.now() + 60 * 60 * 1000 * 24 * 365 * 1000,
- ).toISOString(), // 100 years from now
- metadata: {
- projectId: props.project.id,
- purpose: SERVER_WALLET_MANAGEMENT_ACCESS_TOKEN_PURPOSE,
- teamId: props.project.teamId,
- },
- policies: [
- {
- metadataPatterns: [
- {
- key: "projectId",
- rule: {
- pattern: props.project.id,
- },
- },
- {
- key: "teamId",
- rule: {
- pattern: props.project.teamId,
- },
- },
- {
- key: "type",
- rule: {
- pattern: "server-wallet",
- },
- },
- ],
- type: "eoa:read",
- },
- {
- requiredMetadataPatterns: [
- {
- key: "projectId",
- rule: {
- pattern: props.project.id,
- },
- },
- {
- key: "teamId",
- rule: {
- pattern: props.project.teamId,
- },
- },
- {
- key: "type",
- rule: {
- pattern: "server-wallet",
- },
- },
- ],
- type: "eoa:create",
- },
- {
- metadataPatterns: [
- {
- key: "projectId",
- rule: {
- pattern: props.project.id,
- },
- },
- {
- key: "teamId",
- rule: {
- pattern: props.project.teamId,
- },
- },
- {
- key: "type",
- rule: {
- pattern: "server-wallet",
- },
- },
- ],
- type: "solana:read",
- },
- {
- requiredMetadataPatterns: [
- {
- key: "projectId",
- rule: {
- pattern: props.project.id,
- },
- },
- {
- key: "teamId",
- rule: {
- pattern: props.project.teamId,
- },
- },
- {
- key: "type",
- rule: {
- pattern: "server-wallet",
- },
- },
- ],
- type: "solana:create",
- },
- {
- metadataPatterns: [
- {
- key: "projectId",
- rule: {
- pattern: props.project.id,
- },
- },
- {
- key: "teamId",
- rule: {
- pattern: props.project.teamId,
- },
- },
- ],
- revealSensitive: false,
- type: "accessToken:read",
- },
- ],
- },
- },
- });
-}
-
-/**
- * Upgrades existing access tokens to include Solana permissions
- * This is needed when a project was created before Solana support was added
- *
- * Returns an object with success/error instead of throwing for Next.js server actions
- */
-export async function upgradeAccessTokensForSolana(props: {
- project: Project;
- projectSecretKey?: string;
-}): Promise<{
- success: boolean;
- error?: string;
- data?: {
- managementToken: string;
- walletToken?: string;
- };
-}> {
- const { project, projectSecretKey } = props;
-
- try {
- // Find the engineCloud service
- const engineCloudService = project.services.find(
- (service) => service.name === "engineCloud",
- );
-
- if (!engineCloudService) {
- return {
- success: false,
- error: "No engineCloud service found on project",
- };
- }
-
- const vaultClient = await initVaultClient();
- const hasEncryptedAdminKey = !!engineCloudService.encryptedAdminKey;
-
- // Check if this is an ejected vault (no encrypted admin key stored)
- if (!hasEncryptedAdminKey) {
- // For ejected vaults, we only need to update the management token
- // User manages their own admin key, so we can't create wallet tokens
-
- // We need the admin key from the user
- if (!projectSecretKey) {
- return {
- success: false,
- error: "Admin key required. Please enter your vault admin key.",
- };
- }
-
- // For ejected vault, the "secret key" parameter is actually the admin key
- const managementTokenResult = await createManagementAccessToken({
- project,
- adminKey: projectSecretKey,
- vaultClient,
- });
-
- if (!managementTokenResult.success) {
- return {
- success: false,
- error: `Failed to create management token: ${managementTokenResult.error}`,
- };
- }
-
- // Update only the management token for ejected vaults
- // Keep everything else the same (no encrypted keys to update)
- await updateProjectClient(
- {
- projectId: project.id,
- teamId: project.teamId,
- },
- {
- services: [
- ...project.services.filter(
- (service) => service.name !== "engineCloud",
- ),
- {
- ...engineCloudService,
- managementAccessToken: managementTokenResult.data.accessToken,
- },
- ],
- },
- );
-
- return {
- success: true,
- data: {
- managementToken: managementTokenResult.data.accessToken,
- },
- };
- }
-
- // For non-ejected vaults (with encrypted admin key)
- if (!projectSecretKey) {
- return {
- success: false,
- error: "Project secret key is required to upgrade tokens",
- };
- }
-
- // Verify the project secret key
- const projectSecretKeyHash = await hashSecretKey(projectSecretKey);
- if (!project.secretKeys.some((key) => key?.hash === projectSecretKeyHash)) {
- return {
- success: false,
- error: "Invalid project secret key",
- };
- }
-
- // Decrypt the admin key (we know it exists from the hasEncryptedAdminKey check)
- const adminKey = await decrypt(
- engineCloudService.encryptedAdminKey as string,
- projectSecretKey,
- );
-
- // Create new tokens with Solana permissions
- const [managementTokenResult, walletTokenResult] = await Promise.all([
- createManagementAccessToken({ project, adminKey, vaultClient }),
- createWalletAccessToken({ project, adminKey, vaultClient }),
- ]);
-
- if (!managementTokenResult.success) {
- return {
- success: false,
- error: `Failed to create management token: ${managementTokenResult.error}`,
- };
- }
-
- if (!walletTokenResult.success) {
- return {
- success: false,
- error: `Failed to create wallet token: ${walletTokenResult.error}`,
- };
- }
-
- const managementToken = managementTokenResult.data;
- const walletToken = walletTokenResult.data;
-
- // Encrypt the new wallet token
- const [encryptedAdminKey, encryptedWalletAccessToken] = await Promise.all([
- encrypt(adminKey, projectSecretKey),
- encrypt(walletToken.accessToken, projectSecretKey),
- ]);
-
- // Update the project with new tokens
- await updateProjectClient(
- {
- projectId: project.id,
- teamId: project.teamId,
- },
- {
- services: [
- ...project.services.filter(
- (service) => service.name !== "engineCloud",
- ),
- {
- ...engineCloudService,
- managementAccessToken: managementToken.accessToken,
- encryptedAdminKey,
- encryptedWalletAccessToken,
- },
- ],
- },
- );
-
- return {
- success: true,
- data: {
- managementToken: managementToken.accessToken,
- walletToken: walletToken.accessToken,
- },
- };
- } catch (error) {
- return {
- success: false,
- error:
- error instanceof Error
- ? error.message
- : "Failed to upgrade access tokens",
- };
- }
-}
-
export function maskSecret(secret: string) {
return `${secret.substring(0, 11)}...${secret.substring(secret.length - 5)}`;
}
-
-async function hashSecretKey(secretKey: string) {
- if (typeof window === "undefined" || !window.crypto?.subtle) {
- throw new Error(
- "This function can only be used in the browser environment with Web Crypto API support.",
- );
- }
- const encoder = new TextEncoder();
- const data = encoder.encode(secretKey);
- const hashBuffer = await window.crypto.subtle.digest("SHA-256", data);
- return bufferToHex(hashBuffer);
-}
-
-function bufferToHex(buffer: ArrayBuffer) {
- return [...new Uint8Array(buffer)]
- .map((x) => x.toString(16).padStart(2, "0"))
- .join("");
-}
diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/transactions/solana-wallets/components/upgrade-solana-permissions.client.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/transactions/solana-wallets/components/upgrade-solana-permissions.client.tsx
deleted file mode 100644
index dfa0ff77b2e..00000000000
--- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/transactions/solana-wallets/components/upgrade-solana-permissions.client.tsx
+++ /dev/null
@@ -1,218 +0,0 @@
-"use client";
-
-import { zodResolver } from "@hookform/resolvers/zod";
-import { useMutation } from "@tanstack/react-query";
-import { KeyRoundIcon, Loader2Icon, ShieldCheckIcon } from "lucide-react";
-import { useState } from "react";
-import { useForm } from "react-hook-form";
-import { toast } from "sonner";
-import * as z from "zod";
-import type { Project } from "@/api/project/projects";
-import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
-import { Button } from "@/components/ui/button";
-import {
- Card,
- CardContent,
- CardDescription,
- CardHeader,
- CardTitle,
-} from "@/components/ui/card";
-import { Input } from "@/components/ui/input";
-import { Label } from "@/components/ui/label";
-import { useDashboardRouter } from "@/lib/DashboardRouter";
-import { upgradeAccessTokensForSolana } from "../../lib/vault.client";
-
-const formSchema = z.object({
- secretKey: z.string().min(1, "Secret key is required"),
-});
-
-type FormValues = z.infer;
-
-export function UpgradeSolanaPermissions(props: { project: Project }) {
- const router = useDashboardRouter();
- const [isComplete, setIsComplete] = useState(false);
-
- // Check if this is an ejected vault
- const engineCloudService = props.project.services.find(
- (s) => s.name === "engineCloud",
- );
- const isEjectedVault = !engineCloudService?.encryptedAdminKey;
-
- const form = useForm({
- defaultValues: {
- secretKey: "",
- },
- resolver: zodResolver(formSchema),
- });
-
- const upgradeMutation = useMutation({
- mutationFn: async (data: FormValues) => {
- return upgradeAccessTokensForSolana({
- project: props.project,
- projectSecretKey: data.secretKey,
- });
- },
- onSuccess: (result) => {
- if (!result.success) {
- toast.error(result.error || "Failed to upgrade Solana permissions");
- return;
- }
- setIsComplete(true);
- toast.success("Solana permissions enabled successfully!");
- // Refresh the page after a short delay to show updated state
- setTimeout(() => {
- router.refresh();
- }, 1500);
- },
- onError: (error) => {
- toast.error(
- error instanceof Error
- ? error.message
- : "Failed to upgrade Solana permissions",
- );
- },
- });
-
- if (isComplete) {
- return (
-
-
-
-
-
-
-
-
- Solana Access Enabled
-
-
- Your project now has full Solana functionality
-
-
-
-
-
-
-
-
- Refreshing page to load Solana wallets...
-
-
-
-
- );
- }
-
- return (
-
-
-
-
-
-
-
- Enable Solana Functionality
-
- Upgrade your project to support Solana wallets and transactions
-
-
-
-
-
-
-
- {isEjectedVault
- ? "Why do you need my admin key?"
- : "Why do you need my secret key?"}
-
-
-
- Your project was created before Solana support was added. To
- enable Solana features, we need to update your access tokens with
- new permissions.
-
- {isEjectedVault ? (
- <>
-
- Since you're using an ejected vault, your admin key is used
- to:
-
-
- Create new access tokens with Solana permissions
- Update your management token with the new permissions
-
-
- Your admin key is never stored and is only used during this
- one-time upgrade process.
-
- >
- ) : (
- <>
- Your secret key is used to:
-
- Decrypt your existing admin credentials
- Create new access tokens with Solana permissions
- Re-encrypt and securely store the updated credentials
-
-
- Your secret key is never stored and is only used during this
- one-time upgrade process.
-
- >
- )}
-
-
-
-
-
-
- );
-}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 79758ec65d5..ef7ffa32d77 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -147,9 +147,6 @@ importers:
'@thirdweb-dev/service-utils':
specifier: workspace:*
version: link:../../packages/service-utils
- '@thirdweb-dev/vault-sdk':
- specifier: workspace:*
- version: link:../../packages/vault-sdk
'@vercel/functions':
specifier: 2.2.2
version: 2.2.2(@aws-sdk/credential-provider-web-identity@3.840.0)