From 69a33a7b80e302e10add1817130143bbc8948d2e Mon Sep 17 00:00:00 2001 From: Manan Tank Date: Fri, 4 Apr 2025 01:31:30 +0530 Subject: [PATCH 001/101] [TOOL-3337] Dashboard: Add Transactions pages in project layout --- apps/dashboard/src/@/lib/time.ts | 55 ++ .../components/transactions-table.tsx | 1 + .../components/ProjectSidebarLayout.tsx | 7 + .../connect/account-abstraction/page.tsx | 2 +- .../analytics/nebula-analytics-filter.tsx | 8 +- .../analytics/nebula-analytics-page.tsx | 2 +- .../transactions/analytics/analytics-page.tsx | 27 + .../transactions/analytics/filter.tsx | 51 ++ .../getTransactionAnalyticsFilter.ts | 14 + .../transactions/analytics/summary.tsx | 83 +++ .../analytics/tx-chart/tx-chart-ui.tsx | 202 +++++++ .../analytics/tx-chart/tx-chart.tsx | 78 +++ .../analytics/tx-table/tx-table-ui.tsx | 470 +++++++++++++++ .../analytics/tx-table/tx-table.tsx | 539 ++++++++++++++++++ .../transactions/explorer/page.tsx | 3 + .../[project_slug]/transactions/layout.tsx | 121 ++++ .../[project_slug]/transactions/page.tsx | 49 ++ .../transactions/server-wallets/page.tsx | 3 + .../analytics/date-range-selector.tsx | 2 +- .../SponsoredTransactionsChartCard.tsx | 2 +- apps/dashboard/src/lib/time.ts | 53 +- 21 files changed, 1717 insertions(+), 55 deletions(-) create mode 100644 apps/dashboard/src/@/lib/time.ts create mode 100644 apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/analytics/analytics-page.tsx create mode 100644 apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/analytics/filter.tsx create mode 100644 apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/analytics/getTransactionAnalyticsFilter.ts create mode 100644 apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/analytics/summary.tsx create mode 100644 apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/analytics/tx-chart/tx-chart-ui.tsx create mode 100644 apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/analytics/tx-chart/tx-chart.tsx create mode 100644 apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/analytics/tx-table/tx-table-ui.tsx create mode 100644 apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/analytics/tx-table/tx-table.tsx create mode 100644 apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/explorer/page.tsx create mode 100644 apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/layout.tsx create mode 100644 apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/page.tsx create mode 100644 apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/page.tsx diff --git a/apps/dashboard/src/@/lib/time.ts b/apps/dashboard/src/@/lib/time.ts new file mode 100644 index 00000000000..3e151924cca --- /dev/null +++ b/apps/dashboard/src/@/lib/time.ts @@ -0,0 +1,55 @@ +import { differenceInCalendarDays } from "date-fns"; +import { + type DurationId, + type Range, + getLastNDaysRange, +} from "../../components/analytics/date-range-selector"; + +export function normalizeTime(date: Date) { + const newDate = new Date(date); + newDate.setHours(1, 0, 0, 0); + return newDate; +} + +export function normalizeTimeISOString(date: Date) { + return normalizeTime(date).toISOString(); +} + +export function getFiltersFromSearchParams(params: { + from: string | undefined | string[]; + to: string | undefined | string[]; + interval: string | undefined | string[]; + defaultRange: DurationId; +}) { + const fromStr = params.from; + const toStr = params.to; + const defaultRange = getLastNDaysRange(params.defaultRange); + + const range: Range = + fromStr && toStr && typeof fromStr === "string" && typeof toStr === "string" + ? { + from: normalizeTime(new Date(fromStr)), + to: normalizeTime(new Date(toStr)), + type: "custom", + } + : { + from: normalizeTime(defaultRange.from), + to: normalizeTime(defaultRange.to), + type: defaultRange.type, + }; + + const defaultInterval = + differenceInCalendarDays(range.to, range.from) > 30 + ? "week" + : ("day" as const); + + return { + range, + interval: + params.interval === "day" + ? ("day" as const) + : params.interval === "week" + ? ("week" as const) + : defaultInterval, + }; +} diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/overview/components/transactions-table.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/overview/components/transactions-table.tsx index dee8ceb28d9..7a6aec39452 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/overview/components/transactions-table.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/overview/components/transactions-table.tsx @@ -33,6 +33,7 @@ import { TableRow, } from "@/components/ui/table"; import { ToolTipLabel } from "@/components/ui/tooltip"; +import { normalizeTime } from "@/lib/time"; import { type Transaction, useEngineTransactions, diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/components/ProjectSidebarLayout.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/components/ProjectSidebarLayout.tsx index 7d9438a2d4a..d199634f071 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/components/ProjectSidebarLayout.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/components/ProjectSidebarLayout.tsx @@ -1,6 +1,7 @@ "use client"; import { FullWidthSidebarLayout } from "@/components/blocks/SidebarLayout"; import { + ArrowRightLeftIcon, BookTextIcon, BoxIcon, HomeIcon, @@ -71,6 +72,12 @@ export function ProjectSidebarLayout(props: { icon: InsightIcon, tracking: tracking("insight"), }, + { + href: `${layoutPath}/transactions`, + label: "Transactions", + icon: ArrowRightLeftIcon, + tracking: tracking("transactions"), + }, ]} footerSidebarLinks={[ { diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/connect/account-abstraction/page.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/connect/account-abstraction/page.tsx index 2391d605a1d..fe64eccb850 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/connect/account-abstraction/page.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/connect/account-abstraction/page.tsx @@ -75,7 +75,7 @@ export default async function Page(props: { }); return ( -
+
+
+
+
+ +
+
+
+ + +
+
+ + ); +} diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/analytics/filter.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/analytics/filter.tsx new file mode 100644 index 00000000000..d7a4e7a4c7b --- /dev/null +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/analytics/filter.tsx @@ -0,0 +1,51 @@ +"use client"; + +import { normalizeTimeISOString } from "@/lib/time"; +import { + useResponsiveSearchParams, + useSetResponsiveSearchParams, +} from "responsive-rsc"; +import { DateRangeSelector } from "../../../../../../components/analytics/date-range-selector"; +import { IntervalSelector } from "../../../../../../components/analytics/interval-selector"; +import { getTxAnalyticsFiltersFromSearchParams } from "./getTransactionAnalyticsFilter"; + +export function TransactionAnalyticsFilter() { + const responsiveSearchParams = useResponsiveSearchParams(); + const setResponsiveSearchParams = useSetResponsiveSearchParams(); + + const { range, interval } = getTxAnalyticsFiltersFromSearchParams({ + from: responsiveSearchParams.from, + to: responsiveSearchParams.to, + interval: responsiveSearchParams.interval, + }); + + return ( +
+ { + setResponsiveSearchParams((v) => { + return { + ...v, + from: normalizeTimeISOString(newRange.from), + to: normalizeTimeISOString(newRange.to), + }; + }); + }} + /> + + { + setResponsiveSearchParams((v) => { + return { + ...v, + interval: newInterval, + }; + }); + }} + /> +
+ ); +} diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/analytics/getTransactionAnalyticsFilter.ts b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/analytics/getTransactionAnalyticsFilter.ts new file mode 100644 index 00000000000..6b6d9c0adb2 --- /dev/null +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/analytics/getTransactionAnalyticsFilter.ts @@ -0,0 +1,14 @@ +import { getFiltersFromSearchParams } from "@/lib/time"; + +export function getTxAnalyticsFiltersFromSearchParams(params: { + from?: string | undefined | string[]; + to?: string | undefined | string[]; + interval?: string | undefined | string[]; +}) { + return getFiltersFromSearchParams({ + from: params.from, + to: params.to, + interval: params.interval, + defaultRange: "last-30", + }); +} diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/analytics/summary.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/analytics/summary.tsx new file mode 100644 index 00000000000..bc81dba3e86 --- /dev/null +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/analytics/summary.tsx @@ -0,0 +1,83 @@ +import { StatCard } from "components/analytics/stat"; +import { ActivityIcon, CoinsIcon } from "lucide-react"; +import { Suspense } from "react"; + +// TODO: implement this +async function getTransactionAnalyticsSummary(props: { + teamId: string; + projectId: string; +}) { + console.log("getTransactionAnalyticsSummary called with", props); + await new Promise((resolve) => setTimeout(resolve, 1000)); + + return { + foo: 100, + bar: 200, + }; +} + +// TODO: rename props, change labels and icons +function TransactionAnalyticsSummaryUI(props: { + data: + | { + foo: number; + bar: number; + } + | undefined; + isPending: boolean; +}) { + return ( +
+ + + new Intl.NumberFormat("en-US", { + style: "currency", + currency: "USD", + }).format(value) + } + isPending={props.isPending} + /> +
+ ); +} + +// fetches data and renders the UI +async function AsyncTransactionsAnalyticsSummary(props: { + teamId: string; + projectId: string; +}) { + const data = await getTransactionAnalyticsSummary({ + teamId: props.teamId, + projectId: props.projectId, + }); + + return ; +} + +// shows loading state while fetching data +export function TransactionAnalyticsSummary(props: { + teamId: string; + projectId: string; +}) { + return ( + + } + > + + + ); +} diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/analytics/tx-chart/tx-chart-ui.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/analytics/tx-chart/tx-chart-ui.tsx new file mode 100644 index 00000000000..8403b13c8b1 --- /dev/null +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/analytics/tx-chart/tx-chart-ui.tsx @@ -0,0 +1,202 @@ +"use client"; + +import { ExportToCSVButton } from "@/components/blocks/ExportToCSVButton"; +import { ThirdwebBarChart } from "@/components/blocks/charts/bar-chart"; +import type { ChartConfig } from "@/components/ui/chart"; +import { DotNetIcon } from "components/icons/brand-icons/DotNetIcon"; +import { ReactIcon } from "components/icons/brand-icons/ReactIcon"; +import { TypeScriptIcon } from "components/icons/brand-icons/TypeScriptIcon"; +import { UnityIcon } from "components/icons/brand-icons/UnityIcon"; +import { UnrealIcon } from "components/icons/brand-icons/UnrealIcon"; +import { DocLink } from "components/shared/DocLink"; +import { formatDate } from "date-fns"; +import { useAllChainsData } from "hooks/chains/allChains"; +import { useMemo } from "react"; +import type { UserOpStats } from "types/analytics"; +import { formatTickerNumber } from "../../../../../../../lib/format-utils"; + +type ChartData = Record & { + time: string; +}; + +// TODO: write a story for this component when its finalized + +// This is copy of SponsoredTransactionsChartCard component +// TODO - update the name of the component to something more relevant +export function TransactionsChartCardUI(props: { + // TODO - update this + userOpStats: UserOpStats[]; + isPending: boolean; +}) { + const { userOpStats } = props; + const topChainsToShow = 10; + const chainsStore = useAllChainsData(); + + // TODO - update this if we need to change it + const { chartConfig, chartData } = useMemo(() => { + const _chartConfig: ChartConfig = {}; + const _chartDataMap: Map = new Map(); + const chainIdToVolumeMap: Map = new Map(); + // for each stat, add it in _chartDataMap + for (const stat of userOpStats) { + const chartData = _chartDataMap.get(stat.date); + const { chainId } = stat; + const chain = chainsStore.idToChain.get(Number(chainId)); + + const chainName = chain?.name || chainId || "Unknown"; + // if no data for current day - create new entry + if (!chartData) { + _chartDataMap.set(stat.date, { + time: stat.date, + [chainName]: stat.successful, + } as ChartData); + } else { + chartData[chainName] = (chartData[chainName] || 0) + stat.successful; + } + + chainIdToVolumeMap.set( + chainName, + stat.successful + (chainIdToVolumeMap.get(chainName) || 0), + ); + } + + const chainsSorted = Array.from(chainIdToVolumeMap.entries()) + .sort((a, b) => b[1] - a[1]) + .map((w) => w[0]); + + const chainsToShow = chainsSorted.slice(0, topChainsToShow); + const chainsToTagAsOthers = chainsSorted.slice(topChainsToShow); + + // replace chainIdsToTagAsOther chainId with "other" + for (const data of _chartDataMap.values()) { + for (const chainId in data) { + if (chainsToTagAsOthers.includes(chainId)) { + data.others = (data.others || 0) + (data[chainId] || 0); + delete data[chainId]; + } + } + } + + chainsToShow.forEach((chainName, i) => { + _chartConfig[chainName] = { + label: chainName, + color: `hsl(var(--chart-${(i % 10) + 1}))`, + }; + }); + + // Add Other + chainsToShow.push("others"); + _chartConfig.others = { + label: "Others", + color: "hsl(var(--muted-foreground))", + }; + + return { + chartData: Array.from(_chartDataMap.values()), + chartConfig: _chartConfig, + }; + }, [userOpStats, chainsStore]); + + const uniqueChainIds = Object.keys(chartConfig); + const disableActions = + props.isPending || + chartData.length === 0 || + chartData.every((data) => data.transactions === 0); + + return ( + +

+ Lorem Ipsum +

+

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. +

+ +
+ { + const header = ["Date", ...uniqueChainIds]; + const rows = chartData.map((data) => { + const { time, ...rest } = data; + return [ + time, + ...uniqueChainIds.map((w) => (rest[w] || 0).toString()), + ]; + }); + return { header, rows }; + }} + /> +
+
+ } + config={chartConfig} + data={chartData} + isPending={props.isPending} + chartClassName="aspect-[1.5] lg:aspect-[3.5]" + showLegend + hideLabel={false} + toolTipLabelFormatter={(_v, item) => { + if (Array.isArray(item)) { + const time = item[0].payload.time as number; + return formatDate(new Date(time), "MMM d, yyyy"); + } + return undefined; + }} + toolTipValueFormatter={(value) => formatTickerNumber(Number(value))} + emptyChartState={} + /> + ); +} + +// TODO - update the title and doc links +function EmptyChartContent() { + return ( +
+ {/* TODO - update this */} + Foo BAR +
+ {/* TODO - replace this */} + + {/* TODO - replace this */} + + {/* TODO - replace this */} + + {/* TODO - replace this */} + + {/* TODO - replace this */} + + {/* TODO - replace this */} + +
+
+ ); +} diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/analytics/tx-chart/tx-chart.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/analytics/tx-chart/tx-chart.tsx new file mode 100644 index 00000000000..a63b0f2517b --- /dev/null +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/analytics/tx-chart/tx-chart.tsx @@ -0,0 +1,78 @@ +import { differenceInDays } from "date-fns"; +import { ResponsiveSuspense } from "responsive-rsc"; +import type { UserOpStats } from "../../../../../../../types/analytics"; +import { getTxAnalyticsFiltersFromSearchParams } from "../getTransactionAnalyticsFilter"; +import { TransactionsChartCardUI } from "./tx-chart-ui"; + +async function AsyncTransactionsChartCard(props: { + from: string; + to: string; + interval: "day" | "week"; +}) { + // TODO - remove stub, implement it + await new Promise((resolve) => setTimeout(resolve, 1000)); + const data = getTransactionChartStub(props.from, props.to, props.interval); + + return ; +} + +export function TransactionsChartCard(props: { + searchParams: { + from?: string | undefined | string[]; + to?: string | undefined | string[]; + interval?: string | undefined | string[]; + }; +}) { + const { range, interval } = getTxAnalyticsFiltersFromSearchParams( + props.searchParams, + ); + + return ( + } + > + + + ); +} + +// TODO: remove +function getTransactionChartStub( + from: string, + to: string, + interval: "day" | "week", +) { + const length = differenceInDays(new Date(to), new Date(from)); + const stub: UserOpStats[] = []; + const startDate = new Date(from); + const chainIdsToChooseFrom = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + const increment = interval === "day" ? 1 : 7; + + for (let i = 0; i < length; i += increment) { + // pick from 1 - 4 + const numberToTxPerDay = Math.floor(Math.random() * 4) + 1; + + for (let j = 0; j < numberToTxPerDay; j++) { + stub.push({ + date: new Date( + startDate.getTime() + i * 24 * 60 * 60 * 1000, + ).toISOString(), + successful: Math.floor(Math.random() * 100), + failed: Math.floor(Math.random() * 100), + sponsoredUsd: Math.floor(Math.random() * 100), + chainId: + chainIdsToChooseFrom[ + Math.floor(Math.random() * chainIdsToChooseFrom.length) + ]?.toString(), + }); + } + } + + return stub; +} diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/analytics/tx-table/tx-table-ui.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/analytics/tx-table/tx-table-ui.tsx new file mode 100644 index 00000000000..d51b5505298 --- /dev/null +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/analytics/tx-table/tx-table-ui.tsx @@ -0,0 +1,470 @@ +"use client"; + +import { WalletAddress } from "@/components/blocks/wallet-address"; +import { PaginationButtons } from "@/components/pagination-buttons"; +import { CopyAddressButton } from "@/components/ui/CopyAddressButton"; +import { CopyTextButton } from "@/components/ui/CopyTextButton"; +import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; +import { Label } from "@/components/ui/label"; +import { + Select, + SelectContent, + SelectGroup, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { Skeleton } from "@/components/ui/skeleton"; +import { Switch } from "@/components/ui/switch"; +import { + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table"; +import { ToolTipLabel } from "@/components/ui/tooltip"; +import { keepPreviousData, useQuery } from "@tanstack/react-query"; +import { ChainIcon } from "components/icons/ChainIcon"; +import { formatDistanceToNowStrict } from "date-fns"; +import { format } from "date-fns/format"; +import { useAllChainsData } from "hooks/chains/allChains"; +import { ExternalLinkIcon, InfoIcon } from "lucide-react"; +import Link from "next/link"; +import { useState } from "react"; + +// Copied from Engine overview page + +type Transaction = { + queueId?: string | null; + chainId?: string | null; + fromAddress?: string | null; + // toAddress?: string | null; + // data?: string | null; + // extension?: string | null; + // value?: string | null; + // nonce?: number | null; + // gasLimit?: string | null; + // gasPrice?: string | null; + // maxFeePerGas?: string | null; + // maxPriorityFeePerGas?: string | null; + // effectiveGasPrice?: string | null; + // transactionType?: number | null; + transactionHash?: string | null; + queuedAt?: string | null; + // processedAt?: string | null; + // sentAt?: string | null; + minedAt?: string | null; + // cancelledAt?: string | null; + // deployedContractAddress?: string | null; + // deployedContractType?: string | null; + errorMessage?: string | null; + // sentAtBlockNumber?: number | null; + blockNumber?: number | null; + status?: string | null; + // retryCount: number; + // retryGasValues?: boolean | null; + // retryMaxFeePerGas?: string | null; + // retryMaxPriorityFeePerGas?: string | null; + // signerAddress?: string | null; + // accountAddress?: string | null; + // target?: string | null; + // sender?: string | null; + // initCode?: string | null; + // callData?: string | null; + // callGasLimit?: string | null; + // verificationGasLimit?: string | null; + // preVerificationGas?: string | null; + // paymasterAndData?: string | null; + // userOpHash?: string | null; + // functionName?: string | null; + // functionArgs?: string | null; +}; + +type TransactionResponse = { + transactions: Transaction[]; + totalCount: number; +}; + +type EngineStatus = + | "errored" + | "mined" + | "cancelled" + | "sent" + | "retried" + | "processed" + | "queued" + | "user-op-sent"; + +const statusDetails: Record< + EngineStatus, + { + name: string; + type: "success" | "destructive" | "warning"; + } +> = { + mined: { + name: "Mined", + type: "success", + }, + errored: { + name: "Failed", + type: "destructive", + }, + cancelled: { + name: "Cancelled", + type: "destructive", + }, + queued: { + name: "Queued", + type: "warning", + }, + processed: { + name: "Processed", + type: "warning", + }, + sent: { + name: "Sent", + type: "warning", + }, + "user-op-sent": { + name: "User Op Sent", + type: "warning", + }, + retried: { + name: "Retried", + type: "success", + }, +}; + +// TODO - add Status selector dropdown here +export function TransactionsTableUI(props: { + getData: (params: { + page: number; + status: EngineStatus | undefined; + }) => Promise; +}) { + const [autoUpdate, setAutoUpdate] = useState(true); + const [page, setPage] = useState(1); + const [status, setStatus] = useState(undefined); + + const pageSize = 10; + const transactionsQuery = useQuery({ + queryKey: ["transactions", page, status], + queryFn: () => props.getData({ page, status }), + refetchInterval: autoUpdate ? 4_000 : false, + placeholderData: keepPreviousData, + }); + + const transactions = transactionsQuery.data?.transactions ?? []; + + const totalCount = transactionsQuery.data?.totalCount ?? 0; + const totalPages = Math.ceil(totalCount / pageSize); + const showPagination = totalCount > pageSize; + + const showSkeleton = + (transactionsQuery.isPlaceholderData && transactionsQuery.isFetching) || + (transactionsQuery.isLoading && !transactionsQuery.isPlaceholderData); + + return ( +
+
+
+

+ Transaction History +

+

+ Transactions sent from server wallets +

+
+ +
+
+ + setAutoUpdate(!!v)} + id="auto-update" + /> +
+ { + setStatus(v); + // reset page + setPage(1); + }} + /> +
+
+ + + + + + Queue ID + Chain + Status + From + Tx Hash + Queued + + + + {showSkeleton ? ( + <> + {new Array(pageSize).fill(0).map((_, i) => ( + // biome-ignore lint/suspicious/noArrayIndexKey: + + ))} + + ) : ( + <> + {transactions.map((tx) => ( + + {/* Queue ID */} + + + + + {/* Chain Id */} + + + + + {/* Status */} + + + + + {/* From Address */} + + {tx.fromAddress ? ( + + ) : ( + "N/A" + )} + + + {/* Tx Hash */} + + + + + {/* Queued At */} + + + + + ))} + + )} + +
+ + {!showSkeleton && transactions.length === 0 && ( +
+ No transactions found +
+ )} +
+ + {showPagination && ( +
+ +
+ )} +
+ ); +} + +function StatusSelector(props: { + status: EngineStatus | undefined; + setStatus: (value: EngineStatus | undefined) => void; +}) { + const statuses = Object.keys(statusDetails) as EngineStatus[]; + + return ( + + ); +} + +function SkeletonRow() { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + ); +} + +function TxChainCell(props: { chainId: string | undefined }) { + const { chainId } = props; + const { idToChain } = useAllChainsData(); + + if (!chainId) { + return "N/A"; + } + + const chain = idToChain.get(Number.parseInt(chainId)); + + if (!chain) { + return `Chain ID: ${chainId}`; + } + + return ( +
+ +
+ {chain.name ?? `Chain ID: ${chainId}`} +
+
+ ); +} + +function TxStatusCell(props: { + transaction: Transaction; +}) { + const { transaction } = props; + const { errorMessage, minedAt } = transaction; + const status = (transaction.status as EngineStatus) ?? null; + if (!status) { + return null; + } + + const tooltip = + status === "errored" + ? errorMessage + : (status === "mined" || status === "retried") && minedAt + ? `Completed ${format(new Date(minedAt), "PP pp")}` + : undefined; + + return ( + + + {statusDetails[status].name} + {errorMessage && } + + + ); +} + +function TxHashCell(props: { transaction: Transaction }) { + const { idToChain } = useAllChainsData(); + const { chainId, transactionHash } = props.transaction; + if (!transactionHash) { + return "N/A"; + } + + const chain = chainId ? idToChain.get(Number.parseInt(chainId)) : undefined; + const explorer = chain?.explorers?.[0]; + + const shortHash = `${transactionHash.slice(0, 6)}...${transactionHash.slice(-4)}`; + if (!explorer) { + return ( + + ); + } + + return ( + + ); +} + +function TxQueuedAtCell(props: { transaction: Transaction }) { + const value = props.transaction.queuedAt; + if (!value) { + return; + } + + const date = new Date(value); + return ( + +

{formatDistanceToNowStrict(date, { addSuffix: true })}

+
+ ); +} diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/analytics/tx-table/tx-table.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/analytics/tx-table/tx-table.tsx new file mode 100644 index 00000000000..92098377fa4 --- /dev/null +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/analytics/tx-table/tx-table.tsx @@ -0,0 +1,539 @@ +"use client"; + +import { TransactionsTableUI } from "./tx-table-ui"; + +export function TransactionsTable() { + return ( + { + return stubResponse; + }} + /> + ); +} + +const stubResponse = { + transactions: [ + { + queueId: "cf9e7e04-2aa1-4a59-843a-7da105be8fad", + status: "mined", + chainId: "137", + fromAddress: "0xee0661a1e54A3011a0613a5F5B3124D830d64a46", + toAddress: "0x409D530A6961297ECE29121dbEE2c917c3398659", + data: "0x44d46c8e00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000320000000000000000000000000c7f9331ed0ba9481761027284193776ebdcd87e9000000000000000000000000f5b896ddb5146d5da77eff4efbb3eae36e30080800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000044988000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000008900000000000000000000000000000000000000000000000000000000000001c4d50299e6000000000000000000000000c7f9331ed0ba9481761027284193776ebdcd87e900000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000160b1d48bd0df06426663e20308e2dc8cb141a4255e0d1916128cfc3e352fbc1c930000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c43726f776466756e64696e6700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000037697066733a2f2f516d59506942444433674631665533574d6271685650437268457842376f336d374b5a74766d55325773327a396d2f300000000000000000000000000000000000000000000000000000000000000000000000000000000035697066733a2f2f516d6538367a397a636158576b41786773456347415753707277396b5174546b666b457a45567757686142315359000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004175cfd0a27e53cafa3064a734f5865ceeff9a332bfd4980944f6d47831181cdf3075fe0f6b99f98e8fe40ddbb15ee1b07017aea8d8800bc2df49076e42a84a3ed1c00000000000000000000000000000000000000000000000000000000000000", + extension: "relayer", + value: "0", + nonce: 1086699, + gasLimit: null, + gasPrice: null, + maxFeePerGas: null, + maxPriorityFeePerGas: null, + transactionType: 2, + transactionHash: + "0x71d4421a7d2ac624d639c9df9bdf1cb0f018a2654ec9dcb9452c171ad1185044", + queuedAt: "2025-04-02T20:57:24.548Z", + sentAt: "2025-04-02T20:57:25.156Z", + minedAt: "2025-04-02T20:57:31.800Z", + cancelledAt: null, + deployedContractAddress: null, + deployedContractType: null, + errorMessage: null, + sentAtBlockNumber: 69819429, + blockNumber: 69819433, + retryCount: 0, + retryGasValues: null, + retryMaxFeePerGas: null, + retryMaxPriorityFeePerGas: null, + signerAddress: "0xee0661a1e54A3011a0613a5F5B3124D830d64a46", + accountAddress: null, + accountSalt: null, + accountFactoryAddress: null, + target: null, + sender: null, + initCode: null, + callData: null, + callGasLimit: null, + verificationGasLimit: null, + preVerificationGas: null, + paymasterAndData: null, + userOpHash: null, + functionName: "execute", + functionArgs: + '[{"from":"0xc7f9331Ed0BA9481761027284193776eBdCD87e9","to":"0xf5b896Ddb5146D5dA77efF4efBb3Eae36E300808","value":"0","gas":"280968","nonce":"7","data":"0xd50299e6000000000000000000000000c7f9331ed0ba9481761027284193776ebdcd87e900000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000160b1d48bd0df06426663e20308e2dc8cb141a4255e0d1916128cfc3e352fbc1c930000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c43726f776466756e64696e6700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000037697066733a2f2f516d59506942444433674631665533574d6271685650437268457842376f336d374b5a74766d55325773327a396d2f300000000000000000000000000000000000000000000000000000000000000000000000000000000035697066733a2f2f516d6538367a397a636158576b41786773456347415753707277396b5174546b666b457a455677576861423153590000000000000000000000","chainid":"137"},"0x75cfd0a27e53cafa3064a734f5865ceeff9a332bfd4980944f6d47831181cdf3075fe0f6b99f98e8fe40ddbb15ee1b07017aea8d8800bc2df49076e42a84a3ed1c"]', + onChainTxStatus: 1, + onchainStatus: "success", + effectiveGasPrice: "36190327231", + cumulativeGasUsed: "12181957", + batchOperations: null, + }, + { + queueId: "b1b21666-27d7-482d-8e6a-07a010563912", + status: "mined", + chainId: "137", + fromAddress: "0xee0661a1e54A3011a0613a5F5B3124D830d64a46", + toAddress: "0x409D530A6961297ECE29121dbEE2c917c3398659", + data: "0x44d46c8e00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000320000000000000000000000000f7b910300bf913343e52f7f9eedc78d7469068b4000000000000000000000000f5b896ddb5146d5da77eff4efbb3eae36e30080800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000073f2c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000008900000000000000000000000000000000000000000000000000000000000001c4d50299e6000000000000000000000000f7b910300bf913343e52f7f9eedc78d7469068b400000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000160600c7b5acf28a7d1199095d1a2c33a5a04f5502da6b6ee8d2ee4b7819fc54fc90000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c43726f776466756e64696e6700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000037697066733a2f2f516d61726d5879794d4a4e6773657851717a657166394e384135744e51554b5567757a75373646747039753252772f300000000000000000000000000000000000000000000000000000000000000000000000000000000035697066733a2f2f516d656d78516932666d34436d69324b5043525433434e686b54584363535a795a6242454838797371454a65797a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041651ab1ad648b83b21af0f16dd362d1675819d2dec9fc391f5d093ecafa3ae760317fcc843887ed56eeb0bcc522d5d5588dc0b51817231b655ade132d0f0cbeed1b00000000000000000000000000000000000000000000000000000000000000", + extension: "relayer", + value: "0", + nonce: 1086698, + gasLimit: null, + gasPrice: null, + maxFeePerGas: null, + maxPriorityFeePerGas: null, + transactionType: 2, + transactionHash: + "0x10353da145860507b81acfcbe1ebab6645b682b92fcf1b5c4d6126d2cccfa17e", + queuedAt: "2025-04-02T20:48:43.338Z", + sentAt: "2025-04-02T20:48:43.874Z", + minedAt: "2025-04-02T20:48:50.400Z", + cancelledAt: null, + deployedContractAddress: null, + deployedContractType: null, + errorMessage: null, + sentAtBlockNumber: 69819185, + blockNumber: 69819188, + retryCount: 0, + retryGasValues: null, + retryMaxFeePerGas: null, + retryMaxPriorityFeePerGas: null, + signerAddress: "0xee0661a1e54A3011a0613a5F5B3124D830d64a46", + accountAddress: null, + accountSalt: null, + accountFactoryAddress: null, + target: null, + sender: null, + initCode: null, + callData: null, + callGasLimit: null, + verificationGasLimit: null, + preVerificationGas: null, + paymasterAndData: null, + userOpHash: null, + functionName: "execute", + functionArgs: + '[{"from":"0xF7b910300bF913343E52f7F9eedC78d7469068b4","to":"0xf5b896Ddb5146D5dA77efF4efBb3Eae36E300808","value":"0","gas":"474924","nonce":"0","data":"0xd50299e6000000000000000000000000f7b910300bf913343e52f7f9eedc78d7469068b400000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000160600c7b5acf28a7d1199095d1a2c33a5a04f5502da6b6ee8d2ee4b7819fc54fc90000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c43726f776466756e64696e6700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000037697066733a2f2f516d61726d5879794d4a4e6773657851717a657166394e384135744e51554b5567757a75373646747039753252772f300000000000000000000000000000000000000000000000000000000000000000000000000000000035697066733a2f2f516d656d78516932666d34436d69324b5043525433434e686b54584363535a795a6242454838797371454a65797a0000000000000000000000","chainid":"137"},"0x651ab1ad648b83b21af0f16dd362d1675819d2dec9fc391f5d093ecafa3ae760317fcc843887ed56eeb0bcc522d5d5588dc0b51817231b655ade132d0f0cbeed1b"]', + onChainTxStatus: 1, + onchainStatus: "success", + effectiveGasPrice: "54436666286", + cumulativeGasUsed: "2720512", + batchOperations: null, + }, + { + queueId: "df494cfb-923d-4c8b-9e8c-f0a4cbb5f6d5", + status: "mined", + chainId: "137", + fromAddress: "0xee0661a1e54A3011a0613a5F5B3124D830d64a46", + toAddress: "0x409D530A6961297ECE29121dbEE2c917c3398659", + data: "0x44d46c8e00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000320000000000000000000000000bc4c8697628f1b53062c320ec8643011f97e849f000000000000000000000000f5b896ddb5146d5da77eff4efbb3eae36e3008080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007828b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000008900000000000000000000000000000000000000000000000000000000000001c4d50299e6000000000000000000000000bc4c8697628f1b53062c320ec8643011f97e849f00000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000160ccf4d4a6b404a4cd869bcbf0721189658982e18afe2e0b6144f328ac850d14430000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d53696d706c654d657373616765000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000037697066733a2f2f516d59557051586d7973554c414a79556d6a5a4b56787339414e636d575658676f764352666978467042426839502f300000000000000000000000000000000000000000000000000000000000000000000000000000000035697066733a2f2f516d5771763651646a544b416f63544e4d6869503975457542684a554a674647335876633758724e34426b3233540000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041d0968ef506cc2c9a20e29b1b826a40516857e5268610211aad315b5a4e0025e47dd8ae72386a0ebe1b2c61e4a3fdf2d75ff22b0512ca5d5fa7bfb2d63f408a751c00000000000000000000000000000000000000000000000000000000000000", + extension: "relayer", + value: "0", + nonce: 1086697, + gasLimit: null, + gasPrice: null, + maxFeePerGas: null, + maxPriorityFeePerGas: null, + transactionType: 2, + transactionHash: + "0x3a1c77073fc822d6aaa35280d1582f7f1b6431c180a2b84d81c0ed6116daab71", + queuedAt: "2025-04-02T17:03:23.508Z", + sentAt: "2025-04-02T17:03:24.090Z", + minedAt: "2025-04-02T17:03:31.016Z", + cancelledAt: null, + deployedContractAddress: null, + deployedContractType: null, + errorMessage: null, + sentAtBlockNumber: 69812825, + blockNumber: 69812828, + retryCount: 0, + retryGasValues: null, + retryMaxFeePerGas: null, + retryMaxPriorityFeePerGas: null, + signerAddress: "0xee0661a1e54A3011a0613a5F5B3124D830d64a46", + accountAddress: null, + accountSalt: null, + accountFactoryAddress: null, + target: null, + sender: null, + initCode: null, + callData: null, + callGasLimit: null, + verificationGasLimit: null, + preVerificationGas: null, + paymasterAndData: null, + userOpHash: null, + functionName: "execute", + functionArgs: + '[{"from":"0xbc4C8697628F1b53062c320eC8643011f97e849f","to":"0xf5b896Ddb5146D5dA77efF4efBb3Eae36E300808","value":"0","gas":"492171","nonce":"0","data":"0xd50299e6000000000000000000000000bc4c8697628f1b53062c320ec8643011f97e849f00000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000160ccf4d4a6b404a4cd869bcbf0721189658982e18afe2e0b6144f328ac850d14430000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d53696d706c654d657373616765000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000037697066733a2f2f516d59557051586d7973554c414a79556d6a5a4b56787339414e636d575658676f764352666978467042426839502f300000000000000000000000000000000000000000000000000000000000000000000000000000000035697066733a2f2f516d5771763651646a544b416f63544e4d6869503975457542684a554a674647335876633758724e34426b3233540000000000000000000000","chainid":"137"},"0xd0968ef506cc2c9a20e29b1b826a40516857e5268610211aad315b5a4e0025e47dd8ae72386a0ebe1b2c61e4a3fdf2d75ff22b0512ca5d5fa7bfb2d63f408a751c"]', + onChainTxStatus: 1, + onchainStatus: "success", + effectiveGasPrice: "45877875759", + cumulativeGasUsed: "13727629", + batchOperations: null, + }, + { + queueId: "22a5b75b-450e-45b8-8de9-b94fdf655ef9", + status: "mined", + chainId: "137", + fromAddress: "0xee0661a1e54A3011a0613a5F5B3124D830d64a46", + toAddress: "0x409D530A6961297ECE29121dbEE2c917c3398659", + data: "0x44d46c8e00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000320000000000000000000000000d4a752efd48ecdad3cfb6415ab4e6225afd19ea2000000000000000000000000f5b896ddb5146d5da77eff4efbb3eae36e30080800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000073f50000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000008900000000000000000000000000000000000000000000000000000000000001c4d50299e6000000000000000000000000d4a752efd48ecdad3cfb6415ab4e6225afd19ea200000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001601b1eb405b60b1d82b8cc79dec4b16979e22ee7e8d56f25ae7b425cfc553b648d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f50656572636f696e66756e64696e6700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000037697066733a2f2f516d4e656b38665072734b63735038316447646a386f36706672384862334b725a464d6b476f4c703838516537472f300000000000000000000000000000000000000000000000000000000000000000000000000000000035697066733a2f2f516d57543641346176775170324e386666645a7a5a3333784d43487746657866696878447438616644697657474a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000410beab7e0e9008369e0075b800757df5b70669b2d71d05479293f3e3788d6d92350402d476bf5323186656c035fd98571f4ba2190d0dac95c58bba8ca4fc6ea941c00000000000000000000000000000000000000000000000000000000000000", + extension: "relayer", + value: "0", + nonce: 1086696, + gasLimit: null, + gasPrice: null, + maxFeePerGas: null, + maxPriorityFeePerGas: null, + transactionType: 2, + transactionHash: + "0x6096dec9ab379a4ac4f9387b26bd5bd769a1ab931efa86f8a198dd422d00d50b", + queuedAt: "2025-04-02T16:32:57.356Z", + sentAt: "2025-04-02T16:32:57.833Z", + minedAt: "2025-04-02T16:33:04.896Z", + cancelledAt: null, + deployedContractAddress: null, + deployedContractType: null, + errorMessage: null, + sentAtBlockNumber: 69811966, + blockNumber: 69811969, + retryCount: 0, + retryGasValues: null, + retryMaxFeePerGas: null, + retryMaxPriorityFeePerGas: null, + signerAddress: "0xee0661a1e54A3011a0613a5F5B3124D830d64a46", + accountAddress: null, + accountSalt: null, + accountFactoryAddress: null, + target: null, + sender: null, + initCode: null, + callData: null, + callGasLimit: null, + verificationGasLimit: null, + preVerificationGas: null, + paymasterAndData: null, + userOpHash: null, + functionName: "execute", + functionArgs: + '[{"from":"0xD4A752efd48ecDad3CFb6415AB4E6225afD19EA2","to":"0xf5b896Ddb5146D5dA77efF4efBb3Eae36E300808","value":"0","gas":"474960","nonce":"1","data":"0xd50299e6000000000000000000000000d4a752efd48ecdad3cfb6415ab4e6225afd19ea200000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001601b1eb405b60b1d82b8cc79dec4b16979e22ee7e8d56f25ae7b425cfc553b648d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f50656572636f696e66756e64696e6700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000037697066733a2f2f516d4e656b38665072734b63735038316447646a386f36706672384862334b725a464d6b476f4c703838516537472f300000000000000000000000000000000000000000000000000000000000000000000000000000000035697066733a2f2f516d57543641346176775170324e386666645a7a5a3333784d43487746657866696878447438616644697657474a0000000000000000000000","chainid":"137"},"0x0beab7e0e9008369e0075b800757df5b70669b2d71d05479293f3e3788d6d92350402d476bf5323186656c035fd98571f4ba2190d0dac95c58bba8ca4fc6ea941c"]', + onChainTxStatus: 1, + onchainStatus: "success", + effectiveGasPrice: "51243023334", + cumulativeGasUsed: "16508201", + batchOperations: null, + }, + { + queueId: "ee6ad711-6273-44cf-a8c0-69413d947539", + status: "mined", + chainId: "137", + fromAddress: "0xee0661a1e54A3011a0613a5F5B3124D830d64a46", + toAddress: "0x409D530A6961297ECE29121dbEE2c917c3398659", + data: "0x44d46c8e000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000003200000000000000000000000008229c00a08bc4daa20c516b53bb34e41a1e294db000000000000000000000000f5b896ddb5146d5da77eff4efbb3eae36e3008080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007827f000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000008900000000000000000000000000000000000000000000000000000000000001c4d50299e60000000000000000000000008229c00a08bc4daa20c516b53bb34e41a1e294db00000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000016089a5e8366218288cca1ede961ec9aafffa70a4ab9c771982eb6bcb10efbaa17f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c43726f776446756e64696e6700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000037697066733a2f2f516d5650516a424c664a4c4c377062673664387a7266624b37457a4d617975684278697779547a6473656e426d632f300000000000000000000000000000000000000000000000000000000000000000000000000000000035697066733a2f2f516d53545a424d713936663163585a5a7674767461376d36736e6b666b5069564b6435516f68685857764c37674e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041f373ad3728948aa81ab3495024882b9094b38c7b78ff30eedbe74a2a49f22bd452772ce4b975abeb9281f404b6e8a2d6cf3f987920b80e233093fa31140141281c00000000000000000000000000000000000000000000000000000000000000", + extension: "relayer", + value: "0", + nonce: 1086695, + gasLimit: null, + gasPrice: null, + maxFeePerGas: null, + maxPriorityFeePerGas: null, + transactionType: 2, + transactionHash: + "0x0560d73c941ef9f5dfc50915b35fb63f7a8a3229153b1ec55d68749c1e7cf077", + queuedAt: "2025-04-02T11:00:22.659Z", + sentAt: "2025-04-02T11:00:23.115Z", + minedAt: "2025-04-02T11:00:29.767Z", + cancelledAt: null, + deployedContractAddress: null, + deployedContractType: null, + errorMessage: null, + sentAtBlockNumber: 69802575, + blockNumber: 69802578, + retryCount: 0, + retryGasValues: null, + retryMaxFeePerGas: null, + retryMaxPriorityFeePerGas: null, + signerAddress: "0xee0661a1e54A3011a0613a5F5B3124D830d64a46", + accountAddress: null, + accountSalt: null, + accountFactoryAddress: null, + target: null, + sender: null, + initCode: null, + callData: null, + callGasLimit: null, + verificationGasLimit: null, + preVerificationGas: null, + paymasterAndData: null, + userOpHash: null, + functionName: "execute", + functionArgs: + '[{"from":"0x8229C00a08bc4daa20C516B53Bb34e41a1e294db","to":"0xf5b896Ddb5146D5dA77efF4efBb3Eae36E300808","value":"0","gas":"492159","nonce":"0","data":"0xd50299e60000000000000000000000008229c00a08bc4daa20c516b53bb34e41a1e294db00000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000016089a5e8366218288cca1ede961ec9aafffa70a4ab9c771982eb6bcb10efbaa17f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c43726f776446756e64696e6700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000037697066733a2f2f516d5650516a424c664a4c4c377062673664387a7266624b37457a4d617975684278697779547a6473656e426d632f300000000000000000000000000000000000000000000000000000000000000000000000000000000035697066733a2f2f516d53545a424d713936663163585a5a7674767461376d36736e6b666b5069564b6435516f68685857764c37674e0000000000000000000000","chainid":"137"},"0xf373ad3728948aa81ab3495024882b9094b38c7b78ff30eedbe74a2a49f22bd452772ce4b975abeb9281f404b6e8a2d6cf3f987920b80e233093fa31140141281c"]', + onChainTxStatus: 1, + onchainStatus: "success", + effectiveGasPrice: "32988871131", + cumulativeGasUsed: "5177044", + batchOperations: null, + }, + { + queueId: "8a7a79e9-799f-44cc-be3e-8f17e8dc5c9b", + status: "mined", + chainId: "137", + fromAddress: "0xee0661a1e54A3011a0613a5F5B3124D830d64a46", + toAddress: "0x409D530A6961297ECE29121dbEE2c917c3398659", + data: "0x44d46c8e000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000003200000000000000000000000007548bc9ef3864fa3369950dcd49ce2994e9a0961000000000000000000000000f5b896ddb5146d5da77eff4efbb3eae36e30080800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000073eef000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000008900000000000000000000000000000000000000000000000000000000000001c4d50299e60000000000000000000000007548bc9ef3864fa3369950dcd49ce2994e9a096100000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000160324ee7ae1c2e4fd6644f46b4d87b0b484b75eac8bb6c4dea4178b92fd731c9b60000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000741697264726f70000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000037697066733a2f2f516d50796362774b6d4c766337584a6a5859394832394e736e5358517a31456a7554384d684b70766965426d6a732f300000000000000000000000000000000000000000000000000000000000000000000000000000000035697066733a2f2f516d5a68693464704d51336155777a507a68757a64456d36736b4d68764a43696b66415857574a64396f504a3372000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004192698343b815783232840e755f0ead3d6395b8368dd79321b2417202b2551ec06f25625c9eb523a1e9856109834787a562096fefe99d80ae899c643d768fde861b00000000000000000000000000000000000000000000000000000000000000", + extension: "relayer", + value: "0", + nonce: 1086694, + gasLimit: null, + gasPrice: null, + maxFeePerGas: null, + maxPriorityFeePerGas: null, + transactionType: 2, + transactionHash: + "0x8c3b39a13fae3851ccf9c4e99130b2ea34c8d52ad301a8df98ed448b5e529b79", + queuedAt: "2025-04-01T21:00:07.228Z", + sentAt: "2025-04-01T21:00:07.828Z", + minedAt: "2025-04-01T21:00:14.532Z", + cancelledAt: null, + deployedContractAddress: null, + deployedContractType: null, + errorMessage: null, + sentAtBlockNumber: 69778881, + blockNumber: 69778884, + retryCount: 0, + retryGasValues: null, + retryMaxFeePerGas: null, + retryMaxPriorityFeePerGas: null, + signerAddress: "0xee0661a1e54A3011a0613a5F5B3124D830d64a46", + accountAddress: null, + accountSalt: null, + accountFactoryAddress: null, + target: null, + sender: null, + initCode: null, + callData: null, + callGasLimit: null, + verificationGasLimit: null, + preVerificationGas: null, + paymasterAndData: null, + userOpHash: null, + functionName: "execute", + functionArgs: + '[{"from":"0x7548BC9EF3864fA3369950dcD49CE2994E9a0961","to":"0xf5b896Ddb5146D5dA77efF4efBb3Eae36E300808","value":"0","gas":"474863","nonce":"3","data":"0xd50299e60000000000000000000000007548bc9ef3864fa3369950dcd49ce2994e9a096100000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000160324ee7ae1c2e4fd6644f46b4d87b0b484b75eac8bb6c4dea4178b92fd731c9b60000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000741697264726f70000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000037697066733a2f2f516d50796362774b6d4c766337584a6a5859394832394e736e5358517a31456a7554384d684b70766965426d6a732f300000000000000000000000000000000000000000000000000000000000000000000000000000000035697066733a2f2f516d5a68693464704d51336155777a507a68757a64456d36736b4d68764a43696b66415857574a64396f504a33720000000000000000000000","chainid":"137"},"0x92698343b815783232840e755f0ead3d6395b8368dd79321b2417202b2551ec06f25625c9eb523a1e9856109834787a562096fefe99d80ae899c643d768fde861b"]', + onChainTxStatus: 1, + onchainStatus: "success", + effectiveGasPrice: "30732951157", + cumulativeGasUsed: "4340147", + batchOperations: null, + }, + { + queueId: "ed9ce5d5-d30a-4992-b25f-59d342e6c6be", + status: "mined", + chainId: "137", + fromAddress: "0xee0661a1e54A3011a0613a5F5B3124D830d64a46", + toAddress: "0x409D530A6961297ECE29121dbEE2c917c3398659", + data: "0x44d46c8e0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000032000000000000000000000000001790bc4d5cf3a90f5c8801a2cd7ff780dbbabde000000000000000000000000f5b896ddb5146d5da77eff4efbb3eae36e30080800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000044988000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000008900000000000000000000000000000000000000000000000000000000000001c4d50299e600000000000000000000000001790bc4d5cf3a90f5c8801a2cd7ff780dbbabde00000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000160600c7b5acf28a7d1199095d1a2c33a5a04f5502da6b6ee8d2ee4b7819fc54fc90000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c43726f776466756e64696e6700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000037697066733a2f2f516d54635a6339656a386247434864513369645445634d4a326936584648416579554b674b4352556f6e526459682f300000000000000000000000000000000000000000000000000000000000000000000000000000000035697066733a2f2f516d656d78516932666d34436d69324b5043525433434e686b54584363535a795a6242454838797371454a65797a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004112324eb5e985197526cea7fb24c951404f19acadec2b54b30243f473a92a1be80be3e513f9f28c8a236ee7b90580bb65aaa31f0db907bfd472b18f103244cbc21c00000000000000000000000000000000000000000000000000000000000000", + extension: "relayer", + value: "0", + nonce: 1086693, + gasLimit: null, + gasPrice: null, + maxFeePerGas: null, + maxPriorityFeePerGas: null, + transactionType: 2, + transactionHash: + "0xcba53f73828c123f5e4c96e7655fcdff88015a0615378c4fc08c1c4b72e75bf5", + queuedAt: "2025-04-01T09:58:32.423Z", + sentAt: "2025-04-01T09:58:32.905Z", + minedAt: "2025-04-01T09:58:39.491Z", + cancelledAt: null, + deployedContractAddress: null, + deployedContractType: null, + errorMessage: null, + sentAtBlockNumber: 69760321, + blockNumber: 69760324, + retryCount: 0, + retryGasValues: null, + retryMaxFeePerGas: null, + retryMaxPriorityFeePerGas: null, + signerAddress: "0xee0661a1e54A3011a0613a5F5B3124D830d64a46", + accountAddress: null, + accountSalt: null, + accountFactoryAddress: null, + target: null, + sender: null, + initCode: null, + callData: null, + callGasLimit: null, + verificationGasLimit: null, + preVerificationGas: null, + paymasterAndData: null, + userOpHash: null, + functionName: "execute", + functionArgs: + '[{"from":"0x01790bC4D5cF3a90f5C8801A2cd7fF780DBbABDe","to":"0xf5b896Ddb5146D5dA77efF4efBb3Eae36E300808","value":"0","gas":"280968","nonce":"2","data":"0xd50299e600000000000000000000000001790bc4d5cf3a90f5c8801a2cd7ff780dbbabde00000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000160600c7b5acf28a7d1199095d1a2c33a5a04f5502da6b6ee8d2ee4b7819fc54fc90000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c43726f776466756e64696e6700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000037697066733a2f2f516d54635a6339656a386247434864513369645445634d4a326936584648416579554b674b4352556f6e526459682f300000000000000000000000000000000000000000000000000000000000000000000000000000000035697066733a2f2f516d656d78516932666d34436d69324b5043525433434e686b54584363535a795a6242454838797371454a65797a0000000000000000000000","chainid":"137"},"0x12324eb5e985197526cea7fb24c951404f19acadec2b54b30243f473a92a1be80be3e513f9f28c8a236ee7b90580bb65aaa31f0db907bfd472b18f103244cbc21c"]', + onChainTxStatus: 1, + onchainStatus: "success", + effectiveGasPrice: "60356363007", + cumulativeGasUsed: "6430369", + batchOperations: null, + }, + { + queueId: "7f51531d-b546-4efa-a645-a008261f8834", + status: "mined", + chainId: "137", + fromAddress: "0xee0661a1e54A3011a0613a5F5B3124D830d64a46", + toAddress: "0x409D530A6961297ECE29121dbEE2c917c3398659", + data: "0x44d46c8e0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000032000000000000000000000000001790bc4d5cf3a90f5c8801a2cd7ff780dbbabde000000000000000000000000f5b896ddb5146d5da77eff4efbb3eae36e30080800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000044988000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000008900000000000000000000000000000000000000000000000000000000000001c4d50299e600000000000000000000000001790bc4d5cf3a90f5c8801a2cd7ff780dbbabde00000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000160600c7b5acf28a7d1199095d1a2c33a5a04f5502da6b6ee8d2ee4b7819fc54fc90000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c43726f776466756e64696e6700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000037697066733a2f2f516d636a52475544556d6a70746961667468356f325452626942476d526165593262524577704e7055565059627a2f300000000000000000000000000000000000000000000000000000000000000000000000000000000035697066733a2f2f516d656d78516932666d34436d69324b5043525433434e686b54584363535a795a6242454838797371454a65797a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004135a4dd57f9a45095d1ecd5d6f21a2620a25687cc262ba6bc8c697b69a802f3d821373ece1e90c6693c4a8b20b21c4bb662f924c8b99b2126f80520eed68185c21b00000000000000000000000000000000000000000000000000000000000000", + extension: "relayer", + value: "0", + nonce: 1086692, + gasLimit: null, + gasPrice: null, + maxFeePerGas: null, + maxPriorityFeePerGas: null, + transactionType: 2, + transactionHash: + "0xd59bfc1c136ce1fc9f04a64b3df8469b3477e93e9a37eeb6f03849dd2703ed47", + queuedAt: "2025-04-01T09:56:42.918Z", + sentAt: "2025-04-01T09:56:43.355Z", + minedAt: "2025-04-01T09:56:50.294Z", + cancelledAt: null, + deployedContractAddress: null, + deployedContractType: null, + errorMessage: null, + sentAtBlockNumber: 69760270, + blockNumber: 69760273, + retryCount: 0, + retryGasValues: null, + retryMaxFeePerGas: null, + retryMaxPriorityFeePerGas: null, + signerAddress: "0xee0661a1e54A3011a0613a5F5B3124D830d64a46", + accountAddress: null, + accountSalt: null, + accountFactoryAddress: null, + target: null, + sender: null, + initCode: null, + callData: null, + callGasLimit: null, + verificationGasLimit: null, + preVerificationGas: null, + paymasterAndData: null, + userOpHash: null, + functionName: "execute", + functionArgs: + '[{"from":"0x01790bC4D5cF3a90f5C8801A2cd7fF780DBbABDe","to":"0xf5b896Ddb5146D5dA77efF4efBb3Eae36E300808","value":"0","gas":"280968","nonce":"1","data":"0xd50299e600000000000000000000000001790bc4d5cf3a90f5c8801a2cd7ff780dbbabde00000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000160600c7b5acf28a7d1199095d1a2c33a5a04f5502da6b6ee8d2ee4b7819fc54fc90000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c43726f776466756e64696e6700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000037697066733a2f2f516d636a52475544556d6a70746961667468356f325452626942476d526165593262524577704e7055565059627a2f300000000000000000000000000000000000000000000000000000000000000000000000000000000035697066733a2f2f516d656d78516932666d34436d69324b5043525433434e686b54584363535a795a6242454838797371454a65797a0000000000000000000000","chainid":"137"},"0x35a4dd57f9a45095d1ecd5d6f21a2620a25687cc262ba6bc8c697b69a802f3d821373ece1e90c6693c4a8b20b21c4bb662f924c8b99b2126f80520eed68185c21b"]', + onChainTxStatus: 1, + onchainStatus: "success", + effectiveGasPrice: "47077269291", + cumulativeGasUsed: "12705661", + batchOperations: null, + }, + { + queueId: "aec76d80-f050-4b17-9237-d3a5e8994f0b", + status: "mined", + chainId: "137", + fromAddress: "0xee0661a1e54A3011a0613a5F5B3124D830d64a46", + toAddress: "0x409D530A6961297ECE29121dbEE2c917c3398659", + data: "0x44d46c8e000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000003200000000000000000000000003a4b01d8bb73f12ce1a6e44eb4ff36906689c139000000000000000000000000f5b896ddb5146d5da77eff4efbb3eae36e30080800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000044988000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000008900000000000000000000000000000000000000000000000000000000000001c4d50299e60000000000000000000000003a4b01d8bb73f12ce1a6e44eb4ff36906689c13900000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000160600c7b5acf28a7d1199095d1a2c33a5a04f5502da6b6ee8d2ee4b7819fc54fc90000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c43726f776466756e64696e6700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000037697066733a2f2f516d5073574a61744470724b616d7955545972364e556762557a65525664624b67636e516f7058694d48677647612f300000000000000000000000000000000000000000000000000000000000000000000000000000000035697066733a2f2f516d656d78516932666d34436d69324b5043525433434e686b54584363535a795a6242454838797371454a65797a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041bcac9c033595114b0d08c757bbaebb3935feda0ba461c0dc5905d33775288f094cb2460185ea91d46f9e6aeb956da1853f12a437a21c708a4ec868ed0ca03be61b00000000000000000000000000000000000000000000000000000000000000", + extension: "relayer", + value: "0", + nonce: 1086691, + gasLimit: null, + gasPrice: null, + maxFeePerGas: null, + maxPriorityFeePerGas: null, + transactionType: 2, + transactionHash: + "0x485b94f33eb9e2fdf17f55f377c8e63c95e6e83dcc2943df35eed00545a41aad", + queuedAt: "2025-04-01T08:29:08.415Z", + sentAt: "2025-04-01T08:29:09.385Z", + minedAt: "2025-04-01T08:29:16.018Z", + cancelledAt: null, + deployedContractAddress: null, + deployedContractType: null, + errorMessage: null, + sentAtBlockNumber: 69757800, + blockNumber: 69757802, + retryCount: 0, + retryGasValues: null, + retryMaxFeePerGas: null, + retryMaxPriorityFeePerGas: null, + signerAddress: "0xee0661a1e54A3011a0613a5F5B3124D830d64a46", + accountAddress: null, + accountSalt: null, + accountFactoryAddress: null, + target: null, + sender: null, + initCode: null, + callData: null, + callGasLimit: null, + verificationGasLimit: null, + preVerificationGas: null, + paymasterAndData: null, + userOpHash: null, + functionName: "execute", + functionArgs: + '[{"from":"0x3A4B01D8bB73f12CE1a6e44Eb4ff36906689C139","to":"0xf5b896Ddb5146D5dA77efF4efBb3Eae36E300808","value":"0","gas":"280968","nonce":"2","data":"0xd50299e60000000000000000000000003a4b01d8bb73f12ce1a6e44eb4ff36906689c13900000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000160600c7b5acf28a7d1199095d1a2c33a5a04f5502da6b6ee8d2ee4b7819fc54fc90000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c43726f776466756e64696e6700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000037697066733a2f2f516d5073574a61744470724b616d7955545972364e556762557a65525664624b67636e516f7058694d48677647612f300000000000000000000000000000000000000000000000000000000000000000000000000000000035697066733a2f2f516d656d78516932666d34436d69324b5043525433434e686b54584363535a795a6242454838797371454a65797a0000000000000000000000","chainid":"137"},"0xbcac9c033595114b0d08c757bbaebb3935feda0ba461c0dc5905d33775288f094cb2460185ea91d46f9e6aeb956da1853f12a437a21c708a4ec868ed0ca03be61b"]', + onChainTxStatus: 1, + onchainStatus: "success", + effectiveGasPrice: "80814538225", + cumulativeGasUsed: "7393231", + batchOperations: null, + }, + { + queueId: "a2ede4c1-6959-46c7-8b16-46fadcf309cf", + status: "mined", + chainId: "137", + fromAddress: "0xee0661a1e54A3011a0613a5F5B3124D830d64a46", + toAddress: "0x409D530A6961297ECE29121dbEE2c917c3398659", + data: "0x44d46c8e000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000003200000000000000000000000003a4b01d8bb73f12ce1a6e44eb4ff36906689c139000000000000000000000000f5b896ddb5146d5da77eff4efbb3eae36e30080800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000044988000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000008900000000000000000000000000000000000000000000000000000000000001c4d50299e60000000000000000000000003a4b01d8bb73f12ce1a6e44eb4ff36906689c13900000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000160600c7b5acf28a7d1199095d1a2c33a5a04f5502da6b6ee8d2ee4b7819fc54fc90000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c43726f776466756e64696e6700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000037697066733a2f2f516d506b3262756a4a65526b5136477947696e38534e734a316d5759313668474d6661555a41317167747a7039422f300000000000000000000000000000000000000000000000000000000000000000000000000000000035697066733a2f2f516d656d78516932666d34436d69324b5043525433434e686b54584363535a795a6242454838797371454a65797a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041c4e294b218d2a0e4a2992dd588916b9e96af69a33d6f87fb52e62550be19bf121900f8c9cb0e047bfe1ca571dff7fdb385ab5f712ce4d65d8e1654458d11e8231c00000000000000000000000000000000000000000000000000000000000000", + extension: "relayer", + value: "0", + nonce: 1086690, + gasLimit: null, + gasPrice: null, + maxFeePerGas: null, + maxPriorityFeePerGas: null, + transactionType: 2, + transactionHash: + "0x168f5e0023efc00f5476a6358f83d8b5729581424b8bea6c424c5c6f6cfb64e3", + queuedAt: "2025-04-01T07:39:26.194Z", + sentAt: "2025-04-01T07:39:26.691Z", + minedAt: "2025-04-01T07:39:34.095Z", + cancelledAt: null, + deployedContractAddress: null, + deployedContractType: null, + errorMessage: null, + sentAtBlockNumber: 69756397, + blockNumber: 69756399, + retryCount: 0, + retryGasValues: null, + retryMaxFeePerGas: null, + retryMaxPriorityFeePerGas: null, + signerAddress: "0xee0661a1e54A3011a0613a5F5B3124D830d64a46", + accountAddress: null, + accountSalt: null, + accountFactoryAddress: null, + target: null, + sender: null, + initCode: null, + callData: null, + callGasLimit: null, + verificationGasLimit: null, + preVerificationGas: null, + paymasterAndData: null, + userOpHash: null, + functionName: "execute", + functionArgs: + '[{"from":"0x3A4B01D8bB73f12CE1a6e44Eb4ff36906689C139","to":"0xf5b896Ddb5146D5dA77efF4efBb3Eae36E300808","value":"0","gas":"280968","nonce":"1","data":"0xd50299e60000000000000000000000003a4b01d8bb73f12ce1a6e44eb4ff36906689c13900000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000160600c7b5acf28a7d1199095d1a2c33a5a04f5502da6b6ee8d2ee4b7819fc54fc90000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c43726f776466756e64696e6700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000037697066733a2f2f516d506b3262756a4a65526b5136477947696e38534e734a316d5759313668474d6661555a41317167747a7039422f300000000000000000000000000000000000000000000000000000000000000000000000000000000035697066733a2f2f516d656d78516932666d34436d69324b5043525433434e686b54584363535a795a6242454838797371454a65797a0000000000000000000000","chainid":"137"},"0xc4e294b218d2a0e4a2992dd588916b9e96af69a33d6f87fb52e62550be19bf121900f8c9cb0e047bfe1ca571dff7fdb385ab5f712ce4d65d8e1654458d11e8231c"]', + onChainTxStatus: 1, + onchainStatus: "success", + effectiveGasPrice: "58598281987", + cumulativeGasUsed: "11547319", + batchOperations: null, + }, + ], + totalCount: 100000, +}; diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/explorer/page.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/explorer/page.tsx new file mode 100644 index 00000000000..b17aab885d7 --- /dev/null +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/explorer/page.tsx @@ -0,0 +1,3 @@ +export default function TransactionsExplorerPage() { + return
Explorer
; +} diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/layout.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/layout.tsx new file mode 100644 index 00000000000..120b8e020b3 --- /dev/null +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/layout.tsx @@ -0,0 +1,121 @@ +import { getProject } from "@/api/projects"; +import { getTeamBySlug } from "@/api/team"; +import { CopyTextButton } from "@/components/ui/CopyTextButton"; +import { TabPathLinks } from "@/components/ui/tabs"; +import { redirect } from "next/navigation"; + +export default async function Page(props: { + params: Promise<{ team_slug: string; project_slug: string }>; + children: React.ReactNode; +}) { + const { team_slug, project_slug } = await props.params; + + const [team, project] = await Promise.all([ + getTeamBySlug(team_slug), + getProject(team_slug, project_slug), + ]); + + if (!team) { + redirect("/team"); + } + + if (!project) { + redirect(`/team/${team_slug}`); + } + + return ( + + {props.children} + + ); +} + +function TransactionsLayout(props: { + projectSlug: string; + teamSlug: string; + clientId: string; + children: React.ReactNode; +}) { + const smartWalletsLayoutSlug = `/team/${props.teamSlug}/${props.projectSlug}/transactions`; + + return ( +
+ {/* top */} +
+ {/* header */} +
+
+

+ Transactions +

+ +
+
+ +
+ + {/* Nav */} + +
+ + {/* content */} +
+
+
{props.children}
+
+
+
+ ); +} + +// TODO: seo and add metadata + +// const seo = { +// title: "TODO", +// desc: "TODO", +// }; + +// export const metadata: Metadata = { +// title: seo.title, +// description: seo.desc, +// openGraph: { +// title: seo.title, +// description: seo.desc, +// images: [ +// { +// url: `${getAbsoluteUrl()}/assets/og-image/TODO`, +// width: 1200, +// height: 630, +// alt: seo.title, +// }, +// ], +// }, +// }; diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/page.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/page.tsx new file mode 100644 index 00000000000..5954c179095 --- /dev/null +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/page.tsx @@ -0,0 +1,49 @@ +import { getProject } from "@/api/projects"; +import { getTeamBySlug } from "@/api/team"; +import { notFound, redirect } from "next/navigation"; +import { getAuthToken } from "../../../../api/lib/getAuthToken"; +import { TransactionsAnalyticsPageContent } from "./analytics/analytics-page"; +import { TransactionAnalyticsSummary } from "./analytics/summary"; + +export default async function TransactionsAnalyticsPage(props: { + params: Promise<{ team_slug: string; project_slug: string }>; + searchParams: Promise<{ + from?: string | string[] | undefined; + to?: string | string[] | undefined; + interval?: string | string[] | undefined; + }>; +}) { + const [params, searchParams, authToken] = await Promise.all([ + props.params, + props.searchParams, + getAuthToken(), + ]); + + if (!authToken) { + notFound(); + } + + const [team, project] = await Promise.all([ + getTeamBySlug(params.team_slug), + getProject(params.team_slug, params.project_slug), + ]); + + if (!team) { + redirect("/team"); + } + + if (!project) { + redirect(`/team/${params.team_slug}`); + } + + return ( +
+ +
+ +
+ ); +} diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/page.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/page.tsx new file mode 100644 index 00000000000..69b369d7abd --- /dev/null +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/page.tsx @@ -0,0 +1,3 @@ +export default function TransactionsServerWalletsPage() { + return
Server Wallets
; +} diff --git a/apps/dashboard/src/components/analytics/date-range-selector.tsx b/apps/dashboard/src/components/analytics/date-range-selector.tsx index 4bf77a9bbb6..484004c1056 100644 --- a/apps/dashboard/src/components/analytics/date-range-selector.tsx +++ b/apps/dashboard/src/components/analytics/date-range-selector.tsx @@ -6,8 +6,8 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select"; +import { normalizeTime } from "@/lib/time"; import { differenceInCalendarDays, format, subDays } from "date-fns"; -import { normalizeTime } from "../../lib/time"; export function DateRangeSelector(props: { range: Range; diff --git a/apps/dashboard/src/components/smart-wallets/AccountAbstractionAnalytics/SponsoredTransactionsChartCard.tsx b/apps/dashboard/src/components/smart-wallets/AccountAbstractionAnalytics/SponsoredTransactionsChartCard.tsx index 39283af4cc3..491c1a2eafd 100644 --- a/apps/dashboard/src/components/smart-wallets/AccountAbstractionAnalytics/SponsoredTransactionsChartCard.tsx +++ b/apps/dashboard/src/components/smart-wallets/AccountAbstractionAnalytics/SponsoredTransactionsChartCard.tsx @@ -16,7 +16,7 @@ import type { UserOpStats } from "types/analytics"; import { formatTickerNumber } from "../../../lib/format-utils"; type ChartData = Record & { - time: string; // human readable date + time: string; }; export function SponsoredTransactionsChartCard(props: { diff --git a/apps/dashboard/src/lib/time.ts b/apps/dashboard/src/lib/time.ts index 2f774160934..02b913b6dbd 100644 --- a/apps/dashboard/src/lib/time.ts +++ b/apps/dashboard/src/lib/time.ts @@ -1,53 +1,14 @@ -import { differenceInCalendarDays } from "date-fns"; -import { - type Range, - getLastNDaysRange, -} from "../components/analytics/date-range-selector"; - -export function normalizeTime(date: Date) { - const newDate = new Date(date); - newDate.setHours(1, 0, 0, 0); - return newDate; -} +import { getFiltersFromSearchParams } from "@/lib/time"; export function getNebulaFiltersFromSearchParams(params: { from: string | undefined | string[]; to: string | undefined | string[]; interval: string | undefined | string[]; }) { - const fromStr = params.from; - const toStr = params.to; - const defaultRange = getLastNDaysRange("last-30"); - - const range: Range = - fromStr && toStr && typeof fromStr === "string" && typeof toStr === "string" - ? { - from: normalizeTime(new Date(fromStr)), - to: normalizeTime(new Date(toStr)), - type: "custom", - } - : { - from: normalizeTime(defaultRange.from), - to: normalizeTime(defaultRange.to), - type: defaultRange.type, - }; - - const defaultInterval = - differenceInCalendarDays(range.to, range.from) > 30 - ? "week" - : ("day" as const); - - return { - range, - interval: - params.interval === "day" - ? ("day" as const) - : params.interval === "week" - ? ("week" as const) - : defaultInterval, - }; -} - -export function normalizeTimeISOString(date: Date) { - return normalizeTime(date).toISOString(); + return getFiltersFromSearchParams({ + from: params.from, + to: params.to, + interval: params.interval, + defaultRange: "last-30", + }); } From 98615129ce34895bbee72c2a5923f7c6024cd373 Mon Sep 17 00:00:00 2001 From: Prithvish Baidya Date: Wed, 9 Apr 2025 04:04:10 +0530 Subject: [PATCH 002/101] add vault-sdk package --- apps/dashboard/package.json | 1 + apps/dashboard/src/@/constants/env.ts | 3 + .../transactions/server-wallets/page.tsx | 29 +- package.json | 2 +- packages/vault-sdk/CHANGELOG.md | 117 +++ packages/vault-sdk/README.md | 129 +++ packages/vault-sdk/package.json | 62 ++ packages/vault-sdk/src/exports/thirdweb.ts | 20 + packages/vault-sdk/src/sdk.ts | 346 +++++++ packages/vault-sdk/src/transaction-parser.ts | 923 ++++++++++++++++++ packages/vault-sdk/src/transaction-types.ts | 144 +++ packages/vault-sdk/src/types.ts | 394 ++++++++ packages/vault-sdk/tsconfig.base.json | 47 + packages/vault-sdk/tsconfig.build.json | 16 + packages/vault-sdk/tsconfig.json | 13 + pnpm-lock.yaml | 34 + 16 files changed, 2277 insertions(+), 3 deletions(-) create mode 100644 packages/vault-sdk/CHANGELOG.md create mode 100644 packages/vault-sdk/README.md create mode 100644 packages/vault-sdk/package.json create mode 100644 packages/vault-sdk/src/exports/thirdweb.ts create mode 100644 packages/vault-sdk/src/sdk.ts create mode 100644 packages/vault-sdk/src/transaction-parser.ts create mode 100644 packages/vault-sdk/src/transaction-types.ts create mode 100644 packages/vault-sdk/src/types.ts create mode 100644 packages/vault-sdk/tsconfig.base.json create mode 100644 packages/vault-sdk/tsconfig.build.json create mode 100644 packages/vault-sdk/tsconfig.json diff --git a/apps/dashboard/package.json b/apps/dashboard/package.json index 3c207cbaf84..6554daa8baf 100644 --- a/apps/dashboard/package.json +++ b/apps/dashboard/package.json @@ -50,6 +50,7 @@ "@tanstack/react-query": "5.74.4", "@tanstack/react-table": "^8.21.3", "@thirdweb-dev/service-utils": "workspace:*", + "@thirdweb-dev/vault-sdk": "workspace:*", "@vercel/functions": "2.0.0", "@vercel/og": "^0.6.8", "abitype": "1.0.8", diff --git a/apps/dashboard/src/@/constants/env.ts b/apps/dashboard/src/@/constants/env.ts index 5d08f25ae51..4fa5bd6772a 100644 --- a/apps/dashboard/src/@/constants/env.ts +++ b/apps/dashboard/src/@/constants/env.ts @@ -9,6 +9,9 @@ export const NEXT_PUBLIC_NEBULA_APP_CLIENT_ID = export const NEBULA_APP_SECRET_KEY = process.env.NEBULA_APP_SECRET_KEY || ""; +export const THIRDWEB_VAULT_URL = + process.env.NEXT_PUBLIC_THIRDWEB_VAULT_URL || ""; + export const THIRDWEB_API_SECRET = process.env.API_SERVER_SECRET || ""; export const IPFS_GATEWAY_URL = diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/page.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/page.tsx index 69b369d7abd..9f645474dbd 100644 --- a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/page.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/page.tsx @@ -1,3 +1,28 @@ -export default function TransactionsServerWalletsPage() { - return
Server Wallets
; +import { THIRDWEB_VAULT_URL } from "@/constants/env"; +import { createVaultClient, listEoas } from "@thirdweb-dev/vault-sdk"; + +export default async function TransactionsServerWalletsPage() { + const vaultClient = await createVaultClient({ + baseUrl: THIRDWEB_VAULT_URL, + }); + + console.log(vaultClient); + + const eoas = await listEoas({ + client: vaultClient, + request: { + auth: { + adminKey: + "sa_adm_UHST_NIWG_AR5B_VLWM_LBLS_OQFT_793e1701-9a96-4625-9f53-35a8c41d7068", + }, + options: { + page: 1, + pageSize: 10, + }, + }, + }).catch((e) => console.log(e)); + + console.log(eoas); + + return
Server Wallets {JSON.stringify(eoas, null, 4)}
; } diff --git a/package.json b/package.json index 8801513270b..2bfb307df2a 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "playground:build": "turbo run build --filter=./apps/playground-web", "portal": "turbo run dev --filter=./apps/portal --filter=./packages/thirdweb --filter=./packages/insight", "portal:build": "turbo run build --filter=./apps/portal", - "dashboard": "turbo run dev --filter=./apps/dashboard --filter=./packages/thirdweb --filter=./packages/insight", + "dashboard": "turbo run dev --filter=./apps/dashboard --filter=./packages/thirdweb --filter=./packages/insight --filter=./packages/vault-sdk", "dashboard:build": "turbo run build --filter=./apps/dashboard", "build": "turbo run build --filter=./packages/*", "build:release": "turbo run build --filter=./packages/* --force", diff --git a/packages/vault-sdk/CHANGELOG.md b/packages/vault-sdk/CHANGELOG.md new file mode 100644 index 00000000000..8dfd958d627 --- /dev/null +++ b/packages/vault-sdk/CHANGELOG.md @@ -0,0 +1,117 @@ +# @thirdweb-dev/react-native-adapter + +## 1.5.4 + +### Patch Changes + +- [#6527](https://github.com/thirdweb-dev/js/pull/6527) [`4f51201`](https://github.com/thirdweb-dev/js/commit/4f51201249713cdac28bf7d6062cdf67a4e89144) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Update dependencies + +## 1.5.3 + +### Patch Changes + +- [#5746](https://github.com/thirdweb-dev/js/pull/5746) [`e42ffc6`](https://github.com/thirdweb-dev/js/commit/e42ffc6a931a8d80492a091d79e2d9b38e4ba1d7) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Downgrade aws libraries + +## 1.5.2 + +### Patch Changes + +- [#5594](https://github.com/thirdweb-dev/js/pull/5594) [`b7c8854`](https://github.com/thirdweb-dev/js/commit/b7c885432726eeaf3401217573f2cff0f5247ff7) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Support for Expo 52 and React Native 0.76 + +## 1.5.1 + +### Patch Changes + +- [#5150](https://github.com/thirdweb-dev/js/pull/5150) [`9fadbcc`](https://github.com/thirdweb-dev/js/commit/9fadbcc17abfe302933f7b860ab7c3b4fb577789) Thanks [@jnsdls](https://github.com/jnsdls)! - update dependencies + +- [#5151](https://github.com/thirdweb-dev/js/pull/5151) [`06c0cf3`](https://github.com/thirdweb-dev/js/commit/06c0cf3898419edc79ba042d041612a8ae6967f8) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - update to latest @mobile-wallet-protocol/client + +## 1.5.0 + +### Minor Changes + +- [#4992](https://github.com/thirdweb-dev/js/pull/4992) [`1994d9e`](https://github.com/thirdweb-dev/js/commit/1994d9e52d3a3874e6111ff7bc688f95618fbc25) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Updated required dependencies + +## 1.4.4 + +### Patch Changes + +- [#4874](https://github.com/thirdweb-dev/js/pull/4874) [`783d844`](https://github.com/thirdweb-dev/js/commit/783d84467d81db856a1f0b3509036bbc612ba7e5) Thanks [@jnsdls](https://github.com/jnsdls)! - update dependencies + +## 1.4.3 + +### Patch Changes + +- [#4789](https://github.com/thirdweb-dev/js/pull/4789) [`e384001`](https://github.com/thirdweb-dev/js/commit/e38400195f2644ef8dfcfbce5fa127a9a218247d) Thanks [@MananTank](https://github.com/MananTank)! - Fix whitespaces in UI components + +## 1.4.2 + +### Patch Changes + +- [#4665](https://github.com/thirdweb-dev/js/pull/4665) [`6ce7c83`](https://github.com/thirdweb-dev/js/commit/6ce7c83a3b9eb2374ad2f8163d9c6a68bba4bc42) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Update @mobile-wallet-protocol/client to 0.0.3 + +## 1.4.1 + +### Patch Changes + +- [#4603](https://github.com/thirdweb-dev/js/pull/4603) [`b837b69`](https://github.com/thirdweb-dev/js/commit/b837b690ae27fb8bf45f6cd51820f7591e94dab0) Thanks [@jnsdls](https://github.com/jnsdls)! - bump various dependencies + +## 1.4.0 + +### Minor Changes + +- [#4527](https://github.com/thirdweb-dev/js/pull/4527) [`b76a82c`](https://github.com/thirdweb-dev/js/commit/b76a82c30345e06d7b2c203c1e20bf7ec7e0dd9d) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Update React Native dependencies and add support for React Native 0.75 + +## 1.3.0 + +### Minor Changes + +- [#4094](https://github.com/thirdweb-dev/js/pull/4094) [`f1d087e`](https://github.com/thirdweb-dev/js/commit/f1d087e24e8ad74948c0cdfa85d3705319753850) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Support for Coinbase Smart Wallet in React Native + + You can now use the Coinbase Smart Wallet in your React Native apps. + + ```ts + const wallet = createWallet("com.coinbase.wallet", { + appMetadata: { + name: "My app name", + }, + mobileConfig: { + callbackURL: "https://example.com", + }, + walletConfig: { + options: "smartWalletOnly", + }, + }); + + await wallet.connect({ + client, + }); + ``` + +## 1.2.0 + +### Minor Changes + +- [#3271](https://github.com/thirdweb-dev/js/pull/3271) [`3a1fd98`](https://github.com/thirdweb-dev/js/commit/3a1fd985fc4b2720f4d46d54b562c00b2edf21ce) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Align inAppWallet secret sharing implementation with web and previous RN SDK + +## 1.1.1 + +### Patch Changes + +- [#3162](https://github.com/thirdweb-dev/js/pull/3162) [`b9b185b`](https://github.com/thirdweb-dev/js/commit/b9b185b665e9cc2085f0cc07e3a3cc06a755b42a) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Add support for CB wallet in react native + +## 1.1.0 + +### Minor Changes + +- [#3064](https://github.com/thirdweb-dev/js/pull/3064) [`f55fa4c`](https://github.com/thirdweb-dev/js/commit/f55fa4ca856924a0a1eb6b8e5fe743d76b6e2760) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - In-app wallet support for react native + +### Patch Changes + +- [#3110](https://github.com/thirdweb-dev/js/pull/3110) [`49aa6e4`](https://github.com/thirdweb-dev/js/commit/49aa6e41d5df93204821b72f17f1ba5c3cfe41f7) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Phone number sign in for React Native + +## 1.0.0 + +### Major Changes + +- [#3037](https://github.com/thirdweb-dev/js/pull/3037) [`f006429`](https://github.com/thirdweb-dev/js/commit/f006429d7c2415e9f2206e081f6b867854842f0b) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - initial release diff --git a/packages/vault-sdk/README.md b/packages/vault-sdk/README.md new file mode 100644 index 00000000000..d2eb074e6cb --- /dev/null +++ b/packages/vault-sdk/README.md @@ -0,0 +1,129 @@ +# React Native Adapter + +This package is required to run the thirdweb connect SDK in React Native. + +## Instructions + +### 1. Install the packages + +Using your favorite pacakge manager, install all the require dependencies + +```shell +npx expo install thirdweb @thirdweb-dev/react-native-adapter +``` + +Since react native requires installing native dependencies directly, you also have to install these required peer dependencies: + +```shell +npx expo install react-native-get-random-values @react-native-community/netinfo expo-application @react-native-async-storage/async-storage expo-web-browser expo-linking react-native-aes-gcm-crypto react-native-quick-crypto@0.7.0-rc.6 amazon-cognito-identity-js @coinbase/wallet-mobile-sdk react-native-mmkv react-native-svg @react-native-clipboard/clipboard +``` + +Here's an explanation of each peer dependency and why its needed: + +``` +// needed for wallet connect +react-native-get-random-values +@react-native-community/netinfo +expo-application + +// needed wallet connect + in-app wallet +@react-native-async-storage/async-storage + +// needed for inapp wallet +expo-web-browser // for oauth flows +amazon-cognito-identity-js // for authentication +react-native-aes-gcm-crypto // for encryption +react-native-quick-crypto@0.7.0-rc.6 //for fast hashing + +// needed for the prebuilt UIs +react-native-svg +@react-native-clipboard/clipboard +``` + +### 2. Edit your `metro.config.js` + +If you don't already have a `metro.config.file.js` in your project, you can create one by running: + +```shell +npx expo customize metro.config.js +``` + +Then, you need to add 2 properties to the metro resolver: `unstable_enablePackageExports` and `unstable_conditionNames`. This is to tell metro to resolve named `exports` properly. + +```js +// file: metro.config.js + +// Learn more https://docs.expo.io/guides/customizing-metro +const { getDefaultConfig } = require("expo/metro-config"); + +/** @type {import('expo/metro-config').MetroConfig} */ +const config = getDefaultConfig(__dirname); + +// ADD THESE 2 PROPERTIES +config.resolver.unstable_enablePackageExports = true; +config.resolver.unstable_conditionNames = [ + "react-native", + "browser", + "require", +]; + +module.exports = config; +``` + +### 3. Import `@thirdweb-dev/react-native-adapter` at the top of your `App.tsx` + +This will polyfill all the required functionality needed. + +```js +// this needs to be imported before anything else +import "@thirdweb-dev/react-native-adapter"; +// the rest of your app +``` + +If you're using `expo-router`, you need to polyfill before the router entry: + +1. create a `app/index.ts` + +This will be the new entrypoint to your app, ensuring the polyfills happen before any routing. + +```ts +// file: app/index.ts + +// this needs to be imported before expo-router +import "@thirdweb-dev/react-native-adapter"; +import "expo-router/entry"; +``` + +2. Change your main entrypoint in `package.json` + +Now you can replace `expo-router/entry` with `./app/index` as your main entrypoint. + +``` +// file: package.json + +"main": "./app/index", +``` + +### Additional notes + +1. `react-native-aes-gcm-crypto` requires `minSDK 26` for android, you can edit this in your `build.gradle` file +2. You will get some warnings about unresolved exports, this is normal and will get better as the libraries get updated. + + +### Use the `thirdweb` package in React Native + +Once all the setup above is all done, you can use the most of functionality in the `thirdweb` package out of the box, without having to do any react native specific code. + +This means that you can follow all the React documentation and expect it all to be exactly the same. + +Examples: + +```tsx +import { ThirdwebProvider } form "thirdweb/react"; +``` + +### Resources + +- [Full working demo](https://github.com/thirdweb-dev/expo-starter) +- [React docs](https://portal.thirdweb.com/typescript/v5/react) +- [TypeScript docs](https://portal.thirdweb.com/typescript/v5) diff --git a/packages/vault-sdk/package.json b/packages/vault-sdk/package.json new file mode 100644 index 00000000000..fc3dfbdbafe --- /dev/null +++ b/packages/vault-sdk/package.json @@ -0,0 +1,62 @@ +{ + "name": "@thirdweb-dev/vault-sdk", + "version": "1.5.4", + "repository": { + "type": "git", + "url": "git+https://github.com/thirdweb-dev/js.git#main" + }, + "license": "Apache-2.0", + "bugs": { + "url": "https://github.com/thirdweb-dev/js/issues" + }, + "author": "thirdweb eng ", + "type": "module", + "main": "./dist/cjs/exports/thirdweb.js", + "module": "./dist/esm/exports/thirdweb.js", + "types": "./dist/types/exports/thirdweb.d.ts", + "typings": "./dist/types/exports/thirdweb.d.ts", + "exports": { + ".": { + "types": "./dist/types/exports/thirdweb.d.ts", + "import": "./dist/esm/exports/thirdweb.js", + "default": "./dist/cjs/exports/thirdweb.js" + }, + "./package.json": "./package.json" + }, + "files": [ + "dist/*", + "src/*" + ], + "dependencies": { + "@noble/ciphers": "^1.2.1", + "@noble/curves": "1.8.1", + "@noble/hashes": "1.7.1", + "abitype": "1.0.8", + "ky": "^1.8.0" + }, + "devDependencies": { + "rimraf": "6.0.1" + }, + "peerDependencies": { + "typescript": ">=5.0.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + }, + "scripts": { + "format": "biome format ./src --write", + "lint": "biome check ./src", + "fix": "biome check ./src --fix", + "dev": "tsc --project ./tsconfig.build.json --module es2020 --outDir ./dist/esm --watch", + "build": "pnpm clean && pnpm build:cjs && pnpm build:esm && pnpm build:types", + "build:cjs": "tsc --project ./tsconfig.build.json --module commonjs --outDir ./dist/cjs --verbatimModuleSyntax false && printf '{\"type\":\"commonjs\"}' > ./dist/cjs/package.json", + "build:esm": "tsc --project ./tsconfig.build.json --module es2020 --outDir ./dist/esm && printf '{\"type\": \"module\",\"sideEffects\":false}' > ./dist/esm/package.json", + "build:types": "tsc --project ./tsconfig.build.json --module esnext --declarationDir ./dist/types --emitDeclarationOnly --declaration --declarationMap", + "clean": "rimraf dist" + }, + "engines": { + "node": ">=18" + } +} diff --git a/packages/vault-sdk/src/exports/thirdweb.ts b/packages/vault-sdk/src/exports/thirdweb.ts new file mode 100644 index 00000000000..43504d626cd --- /dev/null +++ b/packages/vault-sdk/src/exports/thirdweb.ts @@ -0,0 +1,20 @@ +export { + type VaultClient, + createAccessToken, + createEoa, + createServiceAccount, + createVaultClient, + getServiceAccount, + listEoas, + ping, + revokeAccessToken, + rotateServiceAccount, + signMessage, + signTransaction, + signTypedData, +} from "../sdk.js"; + +export { + ParseTransactionError, + parseTransaction, +} from "../transaction-parser.js"; diff --git a/packages/vault-sdk/src/sdk.ts b/packages/vault-sdk/src/sdk.ts new file mode 100644 index 00000000000..953f489dae1 --- /dev/null +++ b/packages/vault-sdk/src/sdk.ts @@ -0,0 +1,346 @@ +import type { TypedData } from "abitype"; + +import ky from "ky"; +import { x25519 } from "@noble/curves/ed25519"; +import { randomBytes } from "@noble/hashes/utils"; +import { sha256 } from "@noble/hashes/sha256"; +import { hkdf } from "@noble/hashes/hkdf"; +import { xchacha20poly1305 } from "@noble/ciphers/chacha"; +import { hexToBytes, bytesToHex } from "@noble/hashes/utils"; + +import type { + CreateAccessTokenPayload, + CreateEoaPayload, + CreateServiceAccountPayload, + EncryptedPayload, + GetServiceAccountPayload, + ListEoaPayload, + Payload, + PingPayload, + RevokeAccessTokenPayload, + RotateServiceAccountPayload, + SignMessagePayload, + SignTransactionPayload, + Prettify, + UnencryptedErrorResponse, + CheckedSignTypedDataPayload, +} from "./types.js"; + +function encryptForEnclave( + payload: Payload["input"], + enclavePublicKey: Uint8Array, +): { encryptedPayload: EncryptedPayload; ephemeralPrivateKey: Uint8Array } { + const ephemeralPrivateKey = randomBytes(32); + // Generate ephemeral keypair + const ephemeralPublicKey = x25519.getPublicKey(ephemeralPrivateKey); + + // Derive shared secret using X25519 + const sharedSecret = x25519.getSharedSecret( + ephemeralPrivateKey, + enclavePublicKey, + ); + + // Key derivation + const encryptionKey = hkdf(sha256, sharedSecret, undefined, "encryption", 32); + + // Convert message to bytes if it's not already + const messageBytes = + typeof payload === "string" + ? new TextEncoder().encode(payload) + : new TextEncoder().encode(JSON.stringify(payload)); + + // XChaCha20-Poly1305 encryption + const nonce = randomBytes(24); // 24-byte nonce for XChaCha20 + const cipher = xchacha20poly1305(encryptionKey, nonce); + const ciphertext = cipher.encrypt(messageBytes); + + // Format the encrypted package + return { + encryptedPayload: { + ephemeralPublicKey: bytesToHex(ephemeralPublicKey), + nonce: bytesToHex(nonce), + ciphertext: bytesToHex(ciphertext), + }, + ephemeralPrivateKey: ephemeralPrivateKey, + }; +} + +function isErrorResponse( + response: UnencryptedErrorResponse | EncryptedPayload, +): response is UnencryptedErrorResponse { + return (response as UnencryptedErrorResponse).error !== undefined; +} + +function decryptFromEnclave( + encryptedPackage: EncryptedPayload, + ephemeralPrivateKey: Uint8Array, +) { + const { ephemeralPublicKey, nonce, ciphertext } = encryptedPackage; + + // Convert hex strings back to bytes + const pubKey = hexToBytes(ephemeralPublicKey); + const nonceBytes = hexToBytes(nonce); + const ciphertextBytes = hexToBytes(ciphertext); + + // Derive the same shared secret (from the enclave's ephemeral public key) + const sharedSecret = x25519.getSharedSecret(ephemeralPrivateKey, pubKey); + // Derive the same encryption key + const encryptionKey = hkdf(sha256, sharedSecret, undefined, "encryption", 32); + + // Decrypt the ciphertext + const cipher = xchacha20poly1305(encryptionKey, nonceBytes); + const decrypted = cipher.decrypt(ciphertextBytes); + + // Convert bytes back to string and parse JSON if needed + const decryptedText = new TextDecoder().decode(decrypted); + try { + return JSON.parse(decryptedText); + } catch { + return decryptedText; + } +} + +export type VaultClient = { + baseUrl: string; + publicKey: Uint8Array; +}; + +export async function createVaultClient({ baseUrl }: { baseUrl: string }) { + const vaultApi = ky.create({ + prefixUrl: baseUrl, + headers: { + "Content-Type": "application/json", + }, + throwHttpErrors: false, + }); + + type IntrospectionResponse = { + publicKey: string; + }; + + const res = await vaultApi + .get("api/v1/enclave") + .json(); + + const publicKeyBytes = hexToBytes(res.publicKey); + + return { + baseUrl: baseUrl, + publicKey: publicKeyBytes, + } as VaultClient; +} + +// ========== Main API function ========== +type SendRequestParams

= { + request: P["input"]; + client: VaultClient; +}; + +async function sendRequest

({ + request, + client, +}: SendRequestParams

) { + const { encryptedPayload, ephemeralPrivateKey } = encryptForEnclave( + request, + client.publicKey, + ); + + console.log(client); + + const vaultApi = ky.create({ + prefixUrl: client.baseUrl, + headers: { + "Content-Type": "application/json", + }, + throwHttpErrors: false, + }); + + console.log( + "Encrypted payload for operation ", + request.operation, + encryptedPayload, + ); + + const res = await vaultApi + .post("api/v1/enclave", { + json: encryptedPayload, + }) + .json() + .catch((e) => { + console.log("Error from vault: ", e); + throw e; + }); + + if (isErrorResponse(res)) { + console.log("Error response from vault: ", res); + return { + success: false, + data: null, + error: res.error, + } as Prettify; + } + + console.log("Encrypted response:", res); + + const decryptedResponse = decryptFromEnclave( + res, + ephemeralPrivateKey, + ) as Prettify; + + console.log("Decrypted response from vault: ", decryptedResponse); + return decryptedResponse; +} + +// ========== Generic Helper Params ========== +type PayloadParams

= { + request: Prettify>; + client: VaultClient; +}; + +// ========== Helper functions ========== +export function ping({ client, request: options }: PayloadParams) { + return sendRequest({ + request: { + operation: "ping", + ...options, + }, + client, + }); +} + +export function createServiceAccount({ + client, + request: options, +}: PayloadParams) { + return sendRequest({ + request: { + operation: "serviceAccount:create", + ...options, + }, + client, + }); +} + +export function getServiceAccount({ + client, + request: options, +}: PayloadParams) { + return sendRequest({ + request: { + operation: "serviceAccount:get", + ...options, + }, + client, + }); +} + +export function rotateServiceAccount({ + client, + request: options, +}: PayloadParams) { + return sendRequest({ + request: { + operation: "serviceAccount:rotate", + ...options, + }, + client, + }); +} + +export function createEoa({ + client, + request: options, +}: PayloadParams) { + return sendRequest({ + request: { + operation: "eoa:create", + ...options, + }, + client, + }); +} + +export function listEoas({ + client, + request: options, +}: PayloadParams) { + return sendRequest({ + request: { + operation: "eoa:list", + ...options, + }, + client, + }); +} + +export function signTransaction({ + client, + request: options, +}: PayloadParams) { + return sendRequest({ + request: { + operation: "eoa:signTransaction", + ...options, + }, + client, + }); +} + +export function signMessage({ + client, + request: options, +}: PayloadParams) { + return sendRequest({ + request: { + operation: "eoa:signMessage", + ...options, + }, + client, + }); +} + +export function createAccessToken({ + client, + request: options, +}: PayloadParams) { + return sendRequest({ + request: { + operation: "accessToken:create", + ...options, + }, + client, + }); +} + +export function signTypedData< + Types extends TypedData, + PrimaryType extends keyof Types, +>({ + client, + request: options, +}: PayloadParams>) { + return sendRequest>({ + request: { + operation: "eoa:signTypedData", + auth: options.auth, + options: { + from: options.options.from, + typedData: options.options.typedData, + }, + }, + client, + }); +} + +export function revokeAccessToken({ + client, + request: options, +}: PayloadParams) { + return sendRequest({ + request: { + operation: "accessToken:revoke", + ...options, + }, + client, + }); +} diff --git a/packages/vault-sdk/src/transaction-parser.ts b/packages/vault-sdk/src/transaction-parser.ts new file mode 100644 index 00000000000..b1e9ecdbca1 --- /dev/null +++ b/packages/vault-sdk/src/transaction-parser.ts @@ -0,0 +1,923 @@ +// Update type definitions for our standardized types +import type { + Address, + AccessList, + TxLegacy, + TxEip2930, + TxEip1559, + TxEip4844, + TxEip4844WithSidecar, + TxEip7702, + EthereumTypedTransaction, + SignedAuthorization, +} from "./transaction-types.js"; + +// Custom error class for transaction parsing errors +class ParseTransactionError extends Error { + constructor(message: string) { + super(message); + this.name = "ParseTransactionError"; + } +} + +/** + * Type predicate assert function that helps TypeScript understand + * that the condition guarantees a value is not null or undefined + */ +function assert( + condition: T | null | undefined, + message: string, + // The double negative here (!!) converts the value to a boolean + // while preserving the type information + predicate: (value: T) => boolean = (value) => !!value, +): asserts condition is T { + if (!condition || !predicate(condition)) { + throw new ParseTransactionError(message); + } +} +// Type definitions for client-side data +type Hex = string; + +// Define possible sidecar formats we might receive +type SingleSidecar = { + blobs?: string[]; + commitments?: string[]; + proofs?: string[]; +}; + +type MultipleSidecar = { + blob: string; + commitment: string; + proof: string; +}[]; + +// Client-side transaction input format +type ClientSideSignTransactionOptions = { + type?: string | undefined; + accessList?: + | Array<{ + address: string; + storageKeys: string[]; + }> + | readonly { + address: string; + storageKeys: readonly string[]; + }[] + | undefined; + chainId?: number | bigint | string | undefined; + gasPrice?: bigint | string | undefined; + maxFeePerGas?: bigint | string | undefined; + maxPriorityFeePerGas?: bigint | string | undefined; + data?: Hex | undefined; + input?: Hex | undefined; + to?: string | null | undefined; + nonce?: number | bigint | string | undefined; + value?: bigint | string | undefined; + gas?: bigint | string | number | undefined; + gasLimit?: bigint | string | number | undefined; + authorizationList?: readonly { + address: string; + r: Hex | bigint; + s: Hex | bigint; + // always need either v or yParity. v is only for backwards compatibility + v?: bigint | number | string; + yParity?: number | bigint | string; + nonce: number | bigint | string; + chainId: number | bigint | string; + }[]; + + blobVersionedHashes?: string[] | undefined; + maxFeePerBlobGas?: bigint | string | undefined; + // Direct blob fields + blobs?: string[] | undefined; + commitments?: string[] | undefined; + proofs?: string[] | undefined; + // Sidecar options - could be in different formats + sidecar?: SingleSidecar | undefined; + sidecars?: MultipleSidecar | undefined; +}; + +/** + * Parse client transaction data into standardized transaction format + */ +function parseTransaction( + clientTx: ClientSideSignTransactionOptions, +): EthereumTypedTransaction { + try { + // Normalize and validate basics first + const normalizedTx = { + // Handle string/data field, prioritizing input + input: normalizeInputData(clientTx), + + // Convert nonce to hex string + nonce: normalizeNonce(clientTx), + + // Convert value to hex string + value: normalizeValue(clientTx), + + // Handle to address, null for contract creation + to: normalizeTo(clientTx), + + // Handle gas/gasLimit field, converting to hex + gasLimit: normalizeGasLimit(clientTx), + }; + + // Determine transaction type + let transaction: EthereumTypedTransaction; + + // First check if type is explicitly provided + if (clientTx.type !== undefined) { + const normalizedType = normalizeTypeField(clientTx.type); + transaction = createTransactionFromType( + normalizedType, + clientTx, + normalizedTx, + ); + } else { + // If no type, infer from available fields + transaction = inferTransactionType(clientTx, normalizedTx); + } + + // Final validation + validateTransaction(transaction); + + return transaction; + } catch (error) { + // Ensure all errors are ParseTransactionError + if (error instanceof ParseTransactionError) { + throw error; + } + + throw new ParseTransactionError( + error instanceof Error ? error.message : String(error), + ); + } +} + +/** + * Convert any numeric value to a standardized hex string + */ +function toHexString(value: number | bigint | string | undefined): string { + if (value === undefined) { + return "0x0"; + } + + let bigIntValue: bigint; + + // Convert to BigInt for consistent handling + if (typeof value === "string") { + // Check if it's already a hex string + if (value.startsWith("0x")) { + return value; + } + // Parse decimal string + bigIntValue = BigInt(value); + } else if (typeof value === "number") { + bigIntValue = BigInt(value); + } else { + bigIntValue = value; + } + + // Convert to lowercase hex string with 0x prefix + // biome-ignore lint/style/useTemplate: + return "0x" + bigIntValue.toString(16); +} + +/** + * Normalize the type field to standard hex format (0x00, 0x01, etc.) + */ +function normalizeTypeField(typeValue: string): string { + // Handle hex format (0x0, 0x01, etc.) + if (typeValue.startsWith("0x")) { + const numericPart = typeValue.substring(2); + return `0x${numericPart.padStart(2, "0")}`; + } + + // Handle numeric format (0, 1, etc.) + const numericValue = Number.parseInt(typeValue, 10); + return `0x${numericValue.toString(16).padStart(2, "0")}`; +} + +/** + * Create transaction based on explicit type + */ +function createTransactionFromType( + normalizedType: string, + clientTx: ClientSideSignTransactionOptions, + normalizedFields: { + input: string; + nonce: string; + value: string; + to: Address | null; + gasLimit: string; + }, +): EthereumTypedTransaction { + switch (normalizedType) { + case "0x00": + return createLegacyTransaction(clientTx, normalizedFields); + case "0x01": + return createEip2930Transaction(clientTx, normalizedFields); + case "0x02": + return createEip1559Transaction(clientTx, normalizedFields); + case "0x03": + return createEip4844Transaction(clientTx, normalizedFields); + case "0x05": + return createEip7702Transaction(clientTx, normalizedFields); + default: + throw new ParseTransactionError( + `Unsupported transaction type: ${normalizedType}`, + ); + } +} + +/** + * Infer transaction type based on available fields + */ +function inferTransactionType( + clientTx: ClientSideSignTransactionOptions, + normalizedFields: { + input: string; + nonce: string; + value: string; + to: Address | null; + gasLimit: string; + }, +): EthereumTypedTransaction { + // Check for EIP-4844 specific fields (blob-related) + if (hasBlobFields(clientTx)) { + return createEip4844Transaction(clientTx, normalizedFields); + } + + // Check for EIP-7702 specific fields (authorization list) + if (clientTx.authorizationList) { + return createEip7702Transaction(clientTx, normalizedFields); + } + + // Check for EIP-1559 specific fields (fee market) + if ( + clientTx.maxFeePerGas !== undefined && + clientTx.maxPriorityFeePerGas !== undefined + ) { + return createEip1559Transaction(clientTx, normalizedFields); + } + + // Check for EIP-2930 specific fields (access list + chain ID) + if (clientTx.accessList !== undefined && clientTx.chainId !== undefined) { + return createEip2930Transaction(clientTx, normalizedFields); + } + + // Check for legacy transaction with chainId + if (clientTx.chainId !== undefined) { + return createLegacyTransaction(clientTx, normalizedFields); + } + + // Default to legacy with no chainId + return createLegacyTransaction(clientTx, normalizedFields); +} + +/** + * Check if transaction has blob-related fields (EIP-4844) + */ +function hasBlobFields(clientTx: ClientSideSignTransactionOptions): boolean { + return ( + clientTx.blobVersionedHashes !== undefined || + clientTx.blobs !== undefined || + clientTx.sidecar !== undefined || + clientTx.sidecars !== undefined || + clientTx.maxFeePerBlobGas !== undefined + ); +} + +/** + * Normalize input/data field + */ +function normalizeInputData( + clientTx: ClientSideSignTransactionOptions, +): string { + const { data, input } = clientTx; + + // If both exist, verify they match + if (data !== undefined && input !== undefined) { + // Check if they're effectively the same (considering empty values) + const isDataEmpty = data === "0x" || data === ""; + const isInputEmpty = input === "0x" || input === ""; + + assert( + data === input || (isDataEmpty && isInputEmpty), + "Data and input fields do not match", + ); + + // Prefer input, but use whichever has content + return input || data; + } + + // If only one exists, use that + if (input !== undefined) return input; + if (data !== undefined) return data; + + // Default to empty hex + return "0x"; +} + +/** + * Normalize nonce to hex string + */ +function normalizeNonce(clientTx: ClientSideSignTransactionOptions): string { + const { nonce } = clientTx; + + if (nonce === undefined) { + return "0x0"; // Default nonce as hex + } + + return toHexString(nonce); +} + +/** + * Normalize value to hex string + */ +function normalizeValue(clientTx: ClientSideSignTransactionOptions): string { + const { value } = clientTx; + + if (value === undefined) { + return "0x0"; // Default value as hex + } + + return toHexString(value); +} + +/** + * Normalize to address + */ +function normalizeTo( + clientTx: ClientSideSignTransactionOptions, +): Address | null { + const { to } = clientTx; + + if (to === undefined || to === null) { + return null; // Contract creation + } + + return to as Address; +} + +/** + * Normalize gas limit to hex string + */ +function normalizeGasLimit(clientTx: ClientSideSignTransactionOptions): string { + const { gas, gasLimit } = clientTx; + + // Prefer gasLimit, fall back to gas + const rawGasLimit = gasLimit !== undefined ? gasLimit : gas; + + assert(rawGasLimit, "Gas limit not specified"); + + return toHexString(rawGasLimit); +} + +/** + * Normalize chain ID to hex string + */ +function normalizeChainId( + chainId: number | bigint | string | undefined, +): string | undefined { + if (chainId === undefined) { + return undefined; + } + + return toHexString(chainId); +} + +/** + * Normalize gas price to hex string + */ +function normalizeGasPrice(value: bigint | string | undefined): string { + assert(value, "Gas price value not specified"); + + return toHexString(value); +} + +/** + * Normalize access list + */ +function normalizeAccessList( + clientTx: ClientSideSignTransactionOptions, +): AccessList { + const { accessList } = clientTx; + + // Always return an array, empty if not provided + if (!accessList) { + return []; + } + + return accessList as AccessList; +} + +/** + * Normalizes the authorization list from client format to standardized format + */ +function normalizeAuthorizationList( + clientAuthList?: readonly { + address: string; + r: string | bigint; + s: string | bigint; + v?: bigint | number | string; + yParity?: number | bigint | string; + nonce: number | bigint | string; + chainId: number | bigint | string; + }[], +): SignedAuthorization[] { + if (!clientAuthList || clientAuthList.length === 0) { + throw new ParseTransactionError( + "Authorization list is required and cannot be empty", + ); + } + + return Array.from(clientAuthList).map((auth, index) => { + // Validate required fields + assert(auth.address, `Authorization at index ${index} is missing address`); + assert(auth.r, `Authorization at index ${index} is missing r value`); + assert(auth.s, `Authorization at index ${index} is missing s value`); + assert(auth.chainId, `Authorization at index ${index} is missing chainId`); + + // Get yParity either from yParity field or derived from v + let yParity: number; + + if (auth.yParity !== undefined) { + // Convert yParity to number (0 or 1) + const rawYParity = + typeof auth.yParity === "string" + ? auth.yParity.startsWith("0x") + ? Number.parseInt(auth.yParity, 16) + : Number.parseInt(auth.yParity, 10) + : Number(auth.yParity); + + // Validate yParity is 0 or 1 + if (rawYParity !== 0 && rawYParity !== 1) { + throw new ParseTransactionError( + `Authorization at index ${index} has invalid yParity: must be 0 or 1`, + ); + } + + yParity = rawYParity; + } else if (auth.v !== undefined) { + // Derive yParity from v + // Note: v is either 27/28 (for legacy) or chainId*2 + 35/36 + const v = + typeof auth.v === "string" + ? auth.v.startsWith("0x") + ? BigInt(auth.v) + : BigInt(auth.v) + : BigInt(auth.v); + + // Extract yParity from v + if (v === 27n || v === 28n) { + // Legacy format + yParity = v === 27n ? 0 : 1; + } else { + // EIP-155 format: v = chainId * 2 + 35 + yParity + const chainId = + typeof auth.chainId === "string" + ? auth.chainId.startsWith("0x") + ? BigInt(auth.chainId) + : BigInt(auth.chainId) + : BigInt(auth.chainId); + + const expectedBase = chainId * 2n + 35n; + if (v === expectedBase) { + yParity = 0; + } else if (v === expectedBase + 1n) { + yParity = 1; + } else { + throw new ParseTransactionError( + `Authorization at index ${index} has invalid v value for the given chainId`, + ); + } + } + } else { + throw new ParseTransactionError( + `Authorization at index ${index} is missing both yParity and v`, + ); + } + + return { + address: auth.address as Address, + chainId: toHexString(auth.chainId), + r: toHexString(auth.r), + s: toHexString(auth.s), + yParity, + }; + }); +} + +/** + * Extract and normalize blob data from various possible formats + */ +function extractBlobData(clientTx: ClientSideSignTransactionOptions): { + blobs: string[]; + commitments: string[]; + proofs: string[]; +} | null { + // Direct fields on the transaction + if (clientTx.blobs && clientTx.commitments && clientTx.proofs) { + return { + blobs: clientTx.blobs, + commitments: clientTx.commitments, + proofs: clientTx.proofs, + }; + } + + // Single sidecar object + if (clientTx.sidecar) { + const { blobs, commitments, proofs } = clientTx.sidecar; + if (blobs && commitments && proofs) { + return { blobs, commitments, proofs }; + } + } + + // Multiple sidecars array + if ( + clientTx.sidecars && + Array.isArray(clientTx.sidecars) && + clientTx.sidecars.length > 0 + ) { + const blobs: string[] = []; + const commitments: string[] = []; + const proofs: string[] = []; + + for (const sidecar of clientTx.sidecars) { + if (sidecar.blob && sidecar.commitment && sidecar.proof) { + blobs.push(sidecar.blob); + commitments.push(sidecar.commitment); + proofs.push(sidecar.proof); + } + } + + if (blobs.length > 0 && commitments.length > 0 && proofs.length > 0) { + return { blobs, commitments, proofs }; + } + } + + return null; +} + +/** + * Create a legacy transaction + */ +function createLegacyTransaction( + clientTx: ClientSideSignTransactionOptions, + normalizedFields: { + input: string; + nonce: string; + value: string; + to: Address | null; + gasLimit: string; + }, +): TxLegacy { + const { chainId, gasPrice } = clientTx; + const { input, nonce, value, to, gasLimit } = normalizedFields; + + assert(gasPrice, "Gas price is required for legacy transactions"); + + return { + type: "0x00", + chainId: normalizeChainId(chainId), // Optional for legacy + nonce, + gasPrice: normalizeGasPrice(gasPrice), + gasLimit, + to, + value, + input, + }; +} + +/** + * Create an EIP-2930 transaction + */ +function createEip2930Transaction( + clientTx: ClientSideSignTransactionOptions, + normalizedFields: { + input: string; + nonce: string; + value: string; + to: Address | null; + gasLimit: string; + }, +): TxEip2930 { + const { chainId, gasPrice } = clientTx; + const { input, nonce, value, to, gasLimit } = normalizedFields; + + assert(chainId, "Chain ID is required for EIP-2930 transactions"); + assert(gasPrice, "Gas price is required for EIP-2930 transactions"); + + return { + type: "0x01", + chainId: normalizeChainId(chainId) as string, // Required for EIP-2930 + nonce, + gasPrice: normalizeGasPrice(gasPrice), + gasLimit, + to, + value, + accessList: normalizeAccessList(clientTx), + input, + }; +} + +/** + * Create an EIP-1559 transaction + */ +function createEip1559Transaction( + clientTx: ClientSideSignTransactionOptions, + normalizedFields: { + input: string; + nonce: string; + value: string; + to: Address | null; + gasLimit: string; + }, +): TxEip1559 { + const { chainId, maxFeePerGas, maxPriorityFeePerGas } = clientTx; + const { input, nonce, value, to, gasLimit } = normalizedFields; + + assert(chainId, "Chain ID is required for EIP-1559 transactions"); + assert(maxFeePerGas, "Max fee per gas is required for EIP-1559 transactions"); + assert( + maxPriorityFeePerGas, + "Max priority fee per gas is required for EIP-1559 transactions", + ); + + return { + type: "0x02", + chainId: normalizeChainId(chainId) as string, + nonce, + gasLimit, + maxFeePerGas: normalizeGasPrice(maxFeePerGas), + maxPriorityFeePerGas: normalizeGasPrice(maxPriorityFeePerGas), + to, + value, + accessList: normalizeAccessList(clientTx), // Always provide access list, empty array if not specified + input, + }; +} + +/** + * Create an EIP-4844 transaction + */ +function createEip4844Transaction( + clientTx: ClientSideSignTransactionOptions, + normalizedFields: { + input: string; + nonce: string; + value: string; + to: Address | null; + gasLimit: string; + }, +): TxEip4844 | TxEip4844WithSidecar { + const { + chainId, + maxFeePerGas, + maxPriorityFeePerGas, + blobVersionedHashes, + maxFeePerBlobGas, + } = clientTx; + const { input, nonce, value, gasLimit } = normalizedFields; + const { to } = normalizedFields; + + // Validate required fields + assert(to !== null, "EIP-4844 transactions require a valid to address"); + assert(chainId, "Chain ID is required for EIP-4844 transactions"); + assert(maxFeePerGas, "Max fee per gas is required for EIP-4844 transactions"); + assert( + maxPriorityFeePerGas, + "Max priority fee per gas is required for EIP-4844 transactions", + ); + assert( + blobVersionedHashes, + "Blob versioned hashes are required for EIP-4844 transactions", + ); + assert( + maxFeePerBlobGas, + "Max fee per blob gas is required for EIP-4844 transactions", + ); + + // Base transaction without sidecar + const baseTransaction: TxEip4844 = { + type: "0x03", + chainId: normalizeChainId(chainId) as string, + nonce, + gasLimit, + maxFeePerGas: normalizeGasPrice(maxFeePerGas), + maxPriorityFeePerGas: normalizeGasPrice(maxPriorityFeePerGas), + to: to as Address, + value, + accessList: normalizeAccessList(clientTx), + blobVersionedHashes, + maxFeePerBlobGas: normalizeGasPrice(maxFeePerBlobGas), + input, + }; + + // Check for blob data in any of the possible formats + const blobData = extractBlobData(clientTx); + + // If we have blob data, include it flattened in the transaction + if (blobData) { + return { + ...baseTransaction, + ...blobData, + } as TxEip4844WithSidecar; + } + + return baseTransaction; +} + +/** + * Create an EIP-7702 transaction + */ +function createEip7702Transaction( + clientTx: ClientSideSignTransactionOptions, + normalizedFields: { + input: string; + nonce: string; + value: string; + to: Address | null; + gasLimit: string; + }, +): TxEip7702 { + const { chainId, maxFeePerGas, maxPriorityFeePerGas, authorizationList } = + clientTx; + const { input, nonce, value, gasLimit } = normalizedFields; + const { to } = normalizedFields; + + // Validations + assert(to !== null, "EIP-7702 transactions require a valid to address"); + assert(chainId, "Chain ID is required for EIP-7702 transactions"); + assert(maxFeePerGas, "Max fee per gas is required for EIP-7702 transactions"); + assert( + maxPriorityFeePerGas, + "Max priority fee per gas is required for EIP-7702 transactions", + ); + assert( + authorizationList, + "Authorization list is required for EIP-7702 transactions", + ); + + return { + type: "0x05", + chainId: normalizeChainId(chainId) as string, + nonce, + gasLimit, + maxFeePerGas: normalizeGasPrice(maxFeePerGas), + maxPriorityFeePerGas: normalizeGasPrice(maxPriorityFeePerGas), + to: to as Address, + value, + accessList: normalizeAccessList(clientTx), + authorizationList: normalizeAuthorizationList(authorizationList), + input, + }; +} + +/** + * Validate that a transaction has all required fields + */ +function validateTransaction(transaction: EthereumTypedTransaction): void { + // Common validations for all transaction types + assert(transaction.nonce, "Transaction nonce is required"); + assert(transaction.value, "Transaction value is required"); + assert(transaction.input, "Transaction input is required"); + + // Type-specific validations + switch (transaction.type) { + case "0x00": // Legacy + validateLegacyTransaction(transaction as TxLegacy); + break; + + case "0x01": // EIP-2930 + validateEip2930Transaction(transaction as TxEip2930); + break; + + case "0x02": // EIP-1559 + validateEip1559Transaction(transaction as TxEip1559); + break; + + case "0x03": // EIP-4844 + // Determine if it has sidecar data + if ("blobs" in transaction) { + validateEip4844WithSidecarTransaction( + transaction as TxEip4844WithSidecar, + ); + } else { + validateEip4844Transaction(transaction as TxEip4844); + } + break; + + case "0x05": // EIP-7702 + validateEip7702Transaction(transaction as TxEip7702); + break; + + default: + throw new ParseTransactionError( + `Unknown transaction type: ${transaction}`, + ); + } +} + +/** + * Validate legacy transaction + */ +function validateLegacyTransaction(tx: TxLegacy): void { + assert(tx.gasPrice, "Legacy transaction requires gasPrice"); + assert(tx.gasLimit, "Legacy transaction requires gasLimit"); +} + +/** + * Validate EIP-2930 transaction + */ +function validateEip2930Transaction(tx: TxEip2930): void { + assert(tx.chainId, "EIP-2930 transaction requires chainId"); + assert(tx.gasPrice, "EIP-2930 transaction requires gasPrice"); + assert(tx.gasLimit, "EIP-2930 transaction requires gasLimit"); + assert(tx.accessList, "EIP-2930 transaction requires accessList"); +} + +/** + * Validate EIP-1559 transaction + */ +function validateEip1559Transaction(tx: TxEip1559): void { + assert(tx.chainId, "EIP-1559 transaction requires chainId"); + assert(tx.maxFeePerGas, "EIP-1559 transaction requires maxFeePerGas"); + assert( + tx.maxPriorityFeePerGas, + "EIP-1559 transaction requires maxPriorityFeePerGas", + ); + assert(tx.gasLimit, "EIP-1559 transaction requires gasLimit"); + assert(tx.accessList, "EIP-1559 transaction requires accessList"); +} + +/** + * Validate EIP-4844 transaction + */ +function validateEip4844Transaction(tx: TxEip4844): void { + assert(tx.chainId, "EIP-4844 transaction requires chainId"); + assert(tx.maxFeePerGas, "EIP-4844 transaction requires maxFeePerGas"); + assert( + tx.maxPriorityFeePerGas, + "EIP-4844 transaction requires maxPriorityFeePerGas", + ); + assert(tx.gasLimit, "EIP-4844 transaction requires gasLimit"); + assert(tx.accessList, "EIP-4844 transaction requires accessList"); + assert( + tx.blobVersionedHashes, + "EIP-4844 transaction requires blobVersionedHashes", + ); + assert(tx.maxFeePerBlobGas, "EIP-4844 transaction requires maxFeePerBlobGas"); +} + +/** + * Validate EIP-4844 transaction with sidecar + */ +function validateEip4844WithSidecarTransaction(tx: TxEip4844WithSidecar): void { + // First validate base EIP-4844 fields + validateEip4844Transaction(tx); + + // Then validate sidecar fields + assert( + tx.blobs && tx.blobs.length > 0, + "EIP-4844 transaction with sidecar requires blobs", + ); + assert( + tx.commitments && tx.commitments.length > 0, + "EIP-4844 transaction with sidecar requires commitments", + ); + assert( + tx.proofs && tx.proofs.length > 0, + "EIP-4844 transaction with sidecar requires proofs", + ); + + // Check counts match + assert( + tx.blobs.length === tx.commitments.length && + tx.blobs.length === tx.proofs.length, + "EIP-4844 transaction with sidecar requires matching counts of blobs, commitments, and proofs", + ); +} + +/** + * Validate EIP-7702 transaction + */ +function validateEip7702Transaction(tx: TxEip7702): void { + assert(tx.chainId, "EIP-7702 transaction requires chainId"); + assert(tx.maxFeePerGas, "EIP-7702 transaction requires maxFeePerGas"); + assert( + tx.maxPriorityFeePerGas, + "EIP-7702 transaction requires maxPriorityFeePerGas", + ); + assert(tx.gasLimit, "EIP-7702 transaction requires gasLimit"); + assert(tx.accessList, "EIP-7702 transaction requires accessList"); + assert( + tx.authorizationList, + "EIP-7702 transaction requires non-empty authorizationList", + () => tx.authorizationList.length > 0, + ); +} + +// Export the main parser and error type +export { parseTransaction, ParseTransactionError }; diff --git a/packages/vault-sdk/src/transaction-types.ts b/packages/vault-sdk/src/transaction-types.ts new file mode 100644 index 00000000000..a30d68fbb58 --- /dev/null +++ b/packages/vault-sdk/src/transaction-types.ts @@ -0,0 +1,144 @@ +// Basic types that will be used across transactions +type Address = string; +type ChainId = string; // Hex string for chain ID +type Bytes = string; // Hex string +type U256 = string; // Hex string for big numbers +type B256 = string; // 32-byte hash as hex string +type Bytes48 = string; // 48-byte value as hex string + +// Common transaction destination type (address or contract creation) +type TxKind = Address | null; // null represents contract creation + +// Access list for EIP-2930 and later +type AccessListEntry = { + address: Address; + storageKeys: string[]; // Array of hex strings representing storage keys +}; +type AccessList = AccessListEntry[]; + +// Authorization for EIP-7702 +type SignedAuthorization = { + chainId: U256; + address: Address; + r: U256; + s: U256; + yParity: number; // 0 or 1 +}; + +// Blob structures for EIP-4844 +type Blob = string; // Raw data as string + +type BlobTransactionSidecar = { + blobs: Blob[]; + commitments: Bytes48[]; + proofs: Bytes48[]; +}; + +// Legacy transaction (pre-EIP-2930) +type TxLegacy = { + type: "0x00"; // Legacy + chainId?: ChainId; // Optional for legacy transactions + nonce: string; // Hex string + gasPrice: string; // Hex string + gasLimit: string; // Hex string + to: TxKind; + value: U256; + input: Bytes; +}; + +// EIP-2930 transaction +type TxEip2930 = { + type: "0x01"; // EIP-2930 + chainId: ChainId; + nonce: string; // Hex string + gasPrice: string; // Hex string + gasLimit: string; // Hex string + to: TxKind; + value: U256; + accessList: AccessList; + input: Bytes; +}; + +// EIP-1559 transaction +type TxEip1559 = { + type: "0x02"; // EIP-1559 + chainId: ChainId; + nonce: string; // Hex string + gasLimit: string; // Hex string + maxFeePerGas: string; // Hex string + maxPriorityFeePerGas: string; // Hex string + to: TxKind; + value: U256; + accessList: AccessList; + input: Bytes; +}; + +// EIP-7702 transaction +type TxEip7702 = { + type: "0x05"; // EIP-7702 + chainId: ChainId; + nonce: string; // Hex string + gasLimit: string; // Hex string + maxFeePerGas: string; // Hex string + maxPriorityFeePerGas: string; // Hex string + to: Address; // Note: different from others, only Address not TxKind + value: U256; + accessList: AccessList; + authorizationList: SignedAuthorization[]; + input: Bytes; +}; + +// Basic EIP-4844 transaction +type TxEip4844 = { + type: "0x03"; // EIP-4844 + chainId: ChainId; + nonce: string; // Hex string + gasLimit: string; // Hex string + maxFeePerGas: string; // Hex string + maxPriorityFeePerGas: string; // Hex string + to: Address; // Note: only Address not TxKind + value: U256; + accessList: AccessList; + blobVersionedHashes: B256[]; + maxFeePerBlobGas: string; // Hex string + input: Bytes; +}; + +// EIP-4844 transaction with sidecar - flattened format +type TxEip4844WithSidecar = TxEip4844 & { + // Flattened sidecar fields + blobs: string[]; + commitments: Bytes48[]; + proofs: Bytes48[]; +}; + +// Union type for all Ethereum transaction types +type EthereumTypedTransaction = + | TxLegacy + | TxEip2930 + | TxEip1559 + | TxEip4844 + | TxEip4844WithSidecar + | TxEip7702; + +export type { + Address, + ChainId, + Bytes, + U256, + B256, + Bytes48, + TxKind, + AccessListEntry, + AccessList, + SignedAuthorization, + Blob, + BlobTransactionSidecar, + TxLegacy, + TxEip2930, + TxEip1559, + TxEip7702, + TxEip4844, + TxEip4844WithSidecar, + EthereumTypedTransaction, +}; diff --git a/packages/vault-sdk/src/types.ts b/packages/vault-sdk/src/types.ts new file mode 100644 index 00000000000..ba05ee80ea0 --- /dev/null +++ b/packages/vault-sdk/src/types.ts @@ -0,0 +1,394 @@ +import type { + Address, + TypedData, + TypedDataDomain, + TypedDataToPrimitiveTypes, +} from "abitype"; +import type { EthereumTypedTransaction } from "./transaction-types.js"; + +export type Prettify = { + [K in keyof T]: T[K]; +} & {}; + +// Encrypted types +export type EncryptedPayload = { + ephemeralPublicKey: string; + nonce: string; + ciphertext: string; +}; + +// ========== Authentication Types ========== +type AdminKeyAuth = { + adminKey: string; +}; + +type AccessTokenAuth = { + accessToken: string; +}; + +type SessionTokenAuth = { + sessionToken: string; +}; + +// Regular Auth union (excluding RotationCodeAuth) +export type Auth = AdminKeyAuth | AccessTokenAuth | SessionTokenAuth; + +// Separate RotationCodeAuth (used only for rotation) +type RotationCodeAuth = { + rotationCode: string; +}; + +// ========== Base Types ========== +type UnencryptedError = { + message: string; + status: number; + type: string; + details?: string; +}; + +export type UnencryptedErrorResponse = { + error: UnencryptedError; +}; + +type EncryptedError = { + code: string; + message: string; + details?: string; +}; + +type GenericSuccessResponse = { + success: true; + data: Data; + error: null; +}; + +type GenericErrorResponse = { + success: false; + data: null; + error: EncryptedError | UnencryptedError; +}; + +export type VaultError = EncryptedError | UnencryptedError; + +type GenericResponse = + | GenericSuccessResponse + | GenericErrorResponse; + +// ========== Payload Type ========== +export type OmitNever = { + [K in keyof T as T[K] extends never ? never : K]: T[K]; +}; + +export type GenericPayload< + T extends { + operation: string; + auth?: Auth | RotationCodeAuth | never; + options?: Record | never; + data: unknown; + }, +> = { + input: OmitNever<{ + operation: T["operation"]; + options: T["options"]; + auth: T["auth"]; + }>; + output: GenericResponse; +}; + +// ========== Options Types ========== +type CreateServiceAccountOptions = { + metadata: Record; +}; + +type MetadataValue = string | number | boolean; + +type CreateEoaOptions = { + metadata: Record; +}; + +type GetEoasOptions = { + page?: number; + pageSize?: number; +}; + +type PingOptions = { + message: string; +}; + +// type Transaction = { +// to: string; +// value: string; +// gasLimit: string; +// gasPrice: string; +// nonce: string; +// chainId: number; +// data: string; +// }; + +type SignTransactionOptions = { + transaction: EthereumTypedTransaction; + from: string; +}; + +type SignMessageOptions = { + message: string; + from: string; + chainId?: number; + format?: "text" | "hex"; +}; + +type CheckedSignTypedDataOptions< + Types extends TypedData, + PrimaryType extends keyof Types | "EIP712Domain" = keyof Types, +> = { + typedData: { + domain: TypedDataDomain; + types: Types; + primaryType: PrimaryType; + message: TypedDataToPrimitiveTypes[PrimaryType]; + }; + from: string; +}; + +// biome-ignore lint/suspicious/noExplicitAny: +type SignedTypedDataOptions = CheckedSignTypedDataOptions; + +// ========== Policy Types ========== +type RegexRule = { + pattern: string; +}; + +type NumberRuleOp = "greaterThan" | "lessThan" | "equalTo"; + +type NumberRule = { + op: NumberRuleOp; + value: number | bigint; +}; + +type Rule = NumberRule | RegexRule; + +type MetadataRule = { + key: string; + rule: Rule; +}; + +type PolicyComponent = + | { + type: "eoa:create"; + requiredMetadataPatterns?: MetadataRule[]; + allowedMetadataPatterns?: MetadataRule[]; + } + | { + type: "eoa:read"; + metadataPatterns?: MetadataRule[]; + } + | { + type: "eoa:signTransaction"; + allowlist?: Address[]; + metadataPatterns?: MetadataRule[]; + payloadPatterns: Record; + } + | { + type: "eoa:signMessage"; + allowlist?: Address[]; + metadataPatterns?: MetadataRule[]; + chainId?: number; + messagePattern?: string; + } + | { + type: "eoa:signTypedData"; + allowlist?: Address[]; + metadataPatterns?: MetadataRule[]; + }; + +type OwnerType = string; // Define based on your eoa models + +type CreateAccessTokenOptions = { + policies: PolicyComponent[]; + expiresAt: string; // ISO date string + metadata: Record; +}; + +type RevokeAccessTokenOptions = { + id: string; // UUID +}; + +// ========== Response Data Types ========== +type PingData = { + timestamp: number; + pong: string; +}; + +type CreateServiceAccountData = { + id: string; // UUID + adminKey: string; + rotationCode: string; + createdAt: string; // ISO date string + updatedAt: string; // ISO date string +}; + +type GetServiceAccountData = { + id: string; // UUID + metadata: Record; + createdAt: string; // ISO date string + updatedAt: string; // ISO date string +}; + +type RotateServiceAccountData = { + newAdminKey: string; + newRotationCode: string; +}; + +type EoaData = { + id: string; // UUID + address: string; + metadata: Record; + createdAt: string; // ISO date string + updatedAt: string; // ISO date string +}; + +type GetEoasData = { + items: EoaData[]; + page: number; + pageSize: number; + totalRecords: number; +}; + +type SignTransactionData = { + signature: string; +}; + +type SignMessageData = { + signature: string; +}; + +type SignTypedDataData = { + signature: string; +}; + +type CreateAccessTokenData = { + accessToken: string; + id: string; // UUID + issuerId: string; // UUID + issuerType: OwnerType; + policies: PolicyComponent[]; + expiresAt: string; // ISO date string + metadata: Record; + createdAt: string; // ISO date string + updatedAt: string; // ISO date string + revokedAt?: string; // ISO date string +}; + +type RevokeAccessTokenData = { + id: string; // UUID + issuerId: string; // UUID + issuerType: OwnerType; + policies: PolicyComponent[]; + expiresAt: string; // ISO date string + metadata: Record; + createdAt: string; // ISO date string + updatedAt: string; // ISO date string + revokedAt?: string; // ISO date string +}; + +// ========== Operation Payloads ========== +export type PingPayload = GenericPayload<{ + operation: "ping"; + options: PingOptions; + auth: never; + data: PingData; +}>; + +export type CreateServiceAccountPayload = GenericPayload<{ + operation: "serviceAccount:create"; + options: CreateServiceAccountOptions; + auth: never; + data: CreateServiceAccountData; +}>; + +export type GetServiceAccountPayload = GenericPayload<{ + operation: "serviceAccount:get"; + options: never; + auth: Auth; + data: GetServiceAccountData; +}>; + +export type RotateServiceAccountPayload = GenericPayload<{ + operation: "serviceAccount:rotate"; + options: never; + auth: RotationCodeAuth; // Only accepts RotationCodeAuth + data: RotateServiceAccountData; +}>; + +export type CreateEoaPayload = GenericPayload<{ + operation: "eoa:create"; + auth: Auth; + options: CreateEoaOptions; + data: EoaData; +}>; + +export type ListEoaPayload = GenericPayload<{ + operation: "eoa:list"; + auth: Auth; + options: GetEoasOptions; + data: GetEoasData; +}>; + +export type SignTransactionPayload = GenericPayload<{ + operation: "eoa:signTransaction"; + auth: Auth; + options: SignTransactionOptions; + data: SignTransactionData; +}>; + +export type CheckedSignTypedDataPayload< + Types extends TypedData, + PrimaryType extends keyof Types | "EIP712Domain" = keyof Types, +> = GenericPayload<{ + operation: "eoa:signTypedData"; + auth: Auth; + options: CheckedSignTypedDataOptions; + data: SignTypedDataData; +}>; + +export type SignTypedDataPayload = GenericPayload<{ + operation: "eoa:signTypedData"; + auth: Auth; + options: SignedTypedDataOptions; + data: SignTypedDataData; +}>; + +export type SignMessagePayload = GenericPayload<{ + operation: "eoa:signMessage"; + auth: Auth; + options: SignMessageOptions; + data: SignMessageData; +}>; + +export type CreateAccessTokenPayload = GenericPayload<{ + operation: "accessToken:create"; + auth: Auth; + options: CreateAccessTokenOptions; + data: CreateAccessTokenData; +}>; + +export type RevokeAccessTokenPayload = GenericPayload<{ + operation: "accessToken:revoke"; + auth: Auth; + options: RevokeAccessTokenOptions; + data: RevokeAccessTokenData; +}>; + +// ========== Union of all payloads ========== +export type Payload = + | PingPayload + | CreateServiceAccountPayload + | GetServiceAccountPayload + | RotateServiceAccountPayload + | CreateEoaPayload + | ListEoaPayload + | SignTransactionPayload + | SignMessagePayload + | SignTypedDataPayload + | CreateAccessTokenPayload + | RevokeAccessTokenPayload; diff --git a/packages/vault-sdk/tsconfig.base.json b/packages/vault-sdk/tsconfig.base.json new file mode 100644 index 00000000000..2b519cac7ea --- /dev/null +++ b/packages/vault-sdk/tsconfig.base.json @@ -0,0 +1,47 @@ +{ + // This tsconfig file contains the shared config for the build (tsconfig.build.json) and type checking (tsconfig.json) config. + "include": [], + "compilerOptions": { + // Incremental builds + // NOTE: Enabling incremental builds speeds up `tsc`. Keep in mind though that it does not reliably bust the cache when the `tsconfig.json` file changes. + "incremental": false, + + // Type checking + "strict": true, + "useDefineForClassFields": true, // Not enabled by default in `strict` mode unless we bump `target` to ES2022. + "noFallthroughCasesInSwitch": true, // Not enabled by default in `strict` mode. + "noImplicitReturns": true, // Not enabled by default in `strict` mode. + "useUnknownInCatchVariables": true, // TODO: This would normally be enabled in `strict` mode but would require some adjustments to the codebase. + "noImplicitOverride": true, // Not enabled by default in `strict` mode. + "noUnusedLocals": true, // Not enabled by default in `strict` mode. + "noUnusedParameters": true, // Not enabled by default in `strict` mode. + "exactOptionalPropertyTypes": false, // Not enabled by default in `strict` mode. + "noUncheckedIndexedAccess": true, // Not enabled by default in `strict` mode. + + // JavaScript support + "allowJs": false, + "checkJs": false, + + // Interop constraints + "esModuleInterop": false, + "allowSyntheticDefaultImports": true, + "forceConsistentCasingInFileNames": true, + "verbatimModuleSyntax": true, + "importHelpers": true, // This is only used for build validation. Since we do not have `tslib` installed, this will fail if we accidentally make use of anything that'd require injection of helpers. + + // Language and environment + "moduleResolution": "NodeNext", + "module": "NodeNext", + "target": "ES2021", // Setting this to `ES2021` enables native support for `Node v16+`: https://github.com/microsoft/TypeScript/wiki/Node-Target-Mapping. + "lib": [ + "ES2022", // By using ES2022 we get access to the `.cause` property on `Error` instances. + "DOM" // We are adding `DOM` here to get the `fetch`, etc. types. This should be removed once these types are available via DefinitelyTyped. + ], + + // Skip type checking for node modules + "skipLibCheck": true, + + // jsx for "/react" portion + "jsx": "react-jsx" + } +} diff --git a/packages/vault-sdk/tsconfig.build.json b/packages/vault-sdk/tsconfig.build.json new file mode 100644 index 00000000000..3ae3943df87 --- /dev/null +++ b/packages/vault-sdk/tsconfig.build.json @@ -0,0 +1,16 @@ +{ + "extends": "./tsconfig.base.json", + "include": ["src"], + "exclude": [ + "src/**/*.test.ts", + "src/**/*.test.tsx", + "src/**/*.test-d.ts", + "src/**/*.bench.ts", + "src/**/*.macro.ts" + ], + "compilerOptions": { + "moduleResolution": "node", + "sourceMap": true, + "rootDir": "./src" + } +} diff --git a/packages/vault-sdk/tsconfig.json b/packages/vault-sdk/tsconfig.json new file mode 100644 index 00000000000..f8b525d0c97 --- /dev/null +++ b/packages/vault-sdk/tsconfig.json @@ -0,0 +1,13 @@ +{ + // This configuration is used for local development and type checking. + "extends": "./tsconfig.base.json", + "include": ["src", "test"], + "exclude": [], + // "references": [{ "path": "./scripts/tsconfig.json" }], + "compilerOptions": { + "baseUrl": ".", + "paths": { + "~test/*": ["./test/src/*"] + } + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2bbbd6661e0..2178efeabd7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -145,6 +145,9 @@ 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.0.0 version: 2.0.0(@aws-sdk/credential-provider-web-identity@3.787.0) @@ -1295,6 +1298,31 @@ importers: specifier: 3.1.2 version: 3.1.2(@types/debug@4.1.12)(@types/node@22.14.1)(@vitest/ui@3.1.2)(happy-dom@17.4.4)(jiti@2.4.2)(lightningcss@1.29.3)(msw@2.7.5(@types/node@22.14.1)(typescript@5.8.3))(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1) + packages/vault-sdk: + dependencies: + '@noble/ciphers': + specifier: ^1.2.1 + version: 1.2.1 + '@noble/curves': + specifier: 1.8.1 + version: 1.8.1 + '@noble/hashes': + specifier: 1.7.1 + version: 1.7.1 + abitype: + specifier: 1.0.8 + version: 1.0.8(typescript@5.8.3)(zod@3.24.2) + ky: + specifier: ^1.8.0 + version: 1.8.0 + typescript: + specifier: '>=5.0.4' + version: 5.8.3 + devDependencies: + rimraf: + specifier: 6.0.1 + version: 6.0.1 + packages/wagmi-adapter: dependencies: typescript: @@ -10744,6 +10772,10 @@ packages: '@types/node': '>=18' typescript: '>=5.0.4' + ky@1.8.0: + resolution: {integrity: sha512-DoKGmG27nT8t/1F9gV8vNzggJ3mLAyD49J8tTMWHeZvS8qLc7GlyTieicYtFzvDznMe/q2u38peOjkWc5/pjvw==} + engines: {node: '>=18'} + language-subtag-registry@0.3.23: resolution: {integrity: sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==} @@ -29171,6 +29203,8 @@ snapshots: zod: 3.24.3 zod-validation-error: 3.4.0(zod@3.24.3) + ky@1.8.0: {} + language-subtag-registry@0.3.23: {} language-tags@1.0.9: From 9a8f2df4d300cc12c002c1579ad78b3cc857167b Mon Sep 17 00:00:00 2001 From: Joaquim Verges Date: Wed, 9 Apr 2025 11:06:28 +1200 Subject: [PATCH 003/101] add server wallet table --- .../transactions/server-wallets/page.tsx | 17 ++-- .../server-wallets/wallet-table/types.ts | 11 +++ .../wallet-table/wallet-table-ui.tsx | 81 +++++++++++++++++++ .../wallet-table/wallet-table.tsx | 6 ++ 4 files changed, 106 insertions(+), 9 deletions(-) create mode 100644 apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/wallet-table/types.ts create mode 100644 apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/wallet-table/wallet-table-ui.tsx create mode 100644 apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/wallet-table/wallet-table.tsx diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/page.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/page.tsx index 9f645474dbd..69c7cd5f84b 100644 --- a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/page.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/page.tsx @@ -1,13 +1,13 @@ import { THIRDWEB_VAULT_URL } from "@/constants/env"; import { createVaultClient, listEoas } from "@thirdweb-dev/vault-sdk"; +import type { Wallet } from "./wallet-table/types.js"; +import { ServerWalletsTable } from "./wallet-table/wallet-table"; export default async function TransactionsServerWalletsPage() { const vaultClient = await createVaultClient({ baseUrl: THIRDWEB_VAULT_URL, }); - console.log(vaultClient); - const eoas = await listEoas({ client: vaultClient, request: { @@ -15,14 +15,13 @@ export default async function TransactionsServerWalletsPage() { adminKey: "sa_adm_UHST_NIWG_AR5B_VLWM_LBLS_OQFT_793e1701-9a96-4625-9f53-35a8c41d7068", }, - options: { - page: 1, - pageSize: 10, - }, + options: {}, }, - }).catch((e) => console.log(e)); + }); - console.log(eoas); + if (!eoas.success) { + return

Error: {eoas.error.message}
; + } - return
Server Wallets {JSON.stringify(eoas, null, 4)}
; + return ; } diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/wallet-table/types.ts b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/wallet-table/types.ts new file mode 100644 index 00000000000..872d858d3ce --- /dev/null +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/wallet-table/types.ts @@ -0,0 +1,11 @@ +export type Wallet = { + id: string; + address: string; + metadata: { + type: string; + projectId: string; + name?: string; + }; + createdAt: string; + updatedAt: string; +}; diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/wallet-table/wallet-table-ui.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/wallet-table/wallet-table-ui.tsx new file mode 100644 index 00000000000..66a34e320f2 --- /dev/null +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/wallet-table/wallet-table-ui.tsx @@ -0,0 +1,81 @@ +"use client"; + +import { WalletAddress } from "@/components/blocks/wallet-address"; +import { + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table"; +import { ToolTipLabel } from "@/components/ui/tooltip"; +import { formatDistanceToNowStrict } from "date-fns"; +import { format } from "date-fns/format"; +import type { Wallet } from "./types"; + +export function ServerWalletsTableUI({ wallets }: { wallets: Wallet[] }) { + return ( +
+
+
+

+ Server Wallets +

+

+ Create and manage server wallets for you project +

+
+
+ + + + + + Address + Created At + Updated At + + + + {wallets.length === 0 ? ( + + + No server wallets found + + + ) : ( + wallets.map((wallet) => ( + + + + + + + + + + + + )) + )} + +
+
+
+ ); +} + +function WalletDateCell({ date }: { date: string }) { + if (!date) { + return "N/A"; + } + + const dateObj = new Date(date); + return ( + +

{formatDistanceToNowStrict(dateObj, { addSuffix: true })}

+
+ ); +} diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/wallet-table/wallet-table.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/wallet-table/wallet-table.tsx new file mode 100644 index 00000000000..c1b851c1989 --- /dev/null +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/wallet-table/wallet-table.tsx @@ -0,0 +1,6 @@ +import type { Wallet } from "./types"; +import { ServerWalletsTableUI } from "./wallet-table-ui"; + +export function ServerWalletsTable({ wallets }: { wallets: Wallet[] }) { + return ; +} From a98ef8de7595c3218a803a8a045f5ce792e92da2 Mon Sep 17 00:00:00 2001 From: Joaquim Verges Date: Wed, 9 Apr 2025 12:28:09 +1200 Subject: [PATCH 004/101] update service utils types --- packages/service-utils/src/core/api.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/service-utils/src/core/api.ts b/packages/service-utils/src/core/api.ts index 7bd0c9f5766..288b45a9ef4 100644 --- a/packages/service-utils/src/core/api.ts +++ b/packages/service-utils/src/core/api.ts @@ -198,6 +198,8 @@ export type ProjectService = | { name: "engineCloud"; actions: never[]; + maskedAdminKey?: string | null; + managementAccessToken?: string | null; } | ProjectBundlerService | ProjectEmbeddedWalletsService; From ad1603013b4b21a3b8141721a5f233c6558e29d7 Mon Sep 17 00:00:00 2001 From: Prithvish Baidya Date: Wed, 9 Apr 2025 06:08:31 +0530 Subject: [PATCH 005/101] Vault Service Account init + Create Vault EOA --- .../components/create-server-wallet.tsx | 402 ++++++++++++++++++ .../transactions/server-wallets/page.tsx | 65 ++- packages/vault-sdk/src/sdk.ts | 18 +- 3 files changed, 454 insertions(+), 31 deletions(-) create mode 100644 apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/create-server-wallet.tsx diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/create-server-wallet.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/create-server-wallet.tsx new file mode 100644 index 00000000000..1c075aa26f8 --- /dev/null +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/create-server-wallet.tsx @@ -0,0 +1,402 @@ +"use client"; + +import { Button } from "@/components/ui/button"; +import { THIRDWEB_VAULT_URL } from "@/constants/env"; +import { useDashboardRouter } from "@/lib/DashboardRouter"; +import { updateProjectClient } from "@3rdweb-sdk/react/hooks/useApi"; +import { useMutation } from "@tanstack/react-query"; +import { + createAccessToken, + createEoa, + createServiceAccount, + createVaultClient, +} from "@thirdweb-dev/vault-sdk"; +import { Loader2 } from "lucide-react"; +import { toast } from "sonner"; + +export default function CreateServerWallet(props: { + projectId: string; + teamId: string; + managementAccessToken: string | undefined; +}) { + const router = useDashboardRouter(); + + const initialiseProjectWithVaultMutation = useMutation({ + mutationFn: async () => { + const vaultClient = await createVaultClient({ + baseUrl: THIRDWEB_VAULT_URL, + }); + + const serviceAccount = await createServiceAccount({ + client: vaultClient, + request: { + options: { + metadata: { + projectId: props.projectId, + teamId: props.teamId, + purpose: "Thirdweb Project Server Wallet Service Account", + }, + }, + }, + }); + + if (!serviceAccount.success) { + throw new Error("Failed to create service account"); + } + + const managementAccessTokenPromise = createAccessToken({ + client: vaultClient, + request: { + options: { + expiresAt: new Date( + Date.now() + 60 * 60 * 1000 * 24 * 365 * 1000, + ).toISOString(), // 100 years from now + policies: [ + { + type: "eoa:read", + metadataPatterns: [ + { + key: "projectId", + rule: { + pattern: props.projectId, + }, + }, + { + key: "teamId", + rule: { + pattern: props.teamId, + }, + }, + { + key: "type", + rule: { + pattern: "server-wallet", + }, + }, + ], + }, + { + type: "eoa:create", + requiredMetadataPatterns: [ + { + key: "projectId", + rule: { + pattern: props.projectId, + }, + }, + { + key: "teamId", + rule: { + pattern: props.teamId, + }, + }, + { + key: "type", + rule: { + pattern: "server-wallet", + }, + }, + ], + }, + ], + metadata: { + projectId: props.projectId, + teamId: props.teamId, + purpose: "Thirdweb Project Server Wallet Access Token", + }, + }, + auth: { + adminKey: serviceAccount.data.adminKey, + }, + }, + }); + + const userAccesTokenPromise = createAccessToken({ + client: vaultClient, + request: { + options: { + expiresAt: new Date( + Date.now() + 60 * 60 * 1000 * 24 * 365 * 1000, + ).toISOString(), // 100 years from now + policies: [ + { + type: "eoa:read", + metadataPatterns: [ + { + key: "projectId", + rule: { + pattern: props.projectId, + }, + }, + { + key: "teamId", + rule: { + pattern: props.teamId, + }, + }, + { + key: "type", + rule: { + pattern: "server-wallet", + }, + }, + ], + }, + { + type: "eoa:create", + requiredMetadataPatterns: [ + { + key: "projectId", + rule: { + pattern: props.projectId, + }, + }, + { + key: "teamId", + rule: { + pattern: props.teamId, + }, + }, + { + key: "type", + rule: { + pattern: "server-wallet", + }, + }, + ], + }, + { + type: "eoa:signMessage", + metadataPatterns: [ + { + key: "projectId", + rule: { + pattern: props.projectId, + }, + }, + { + key: "teamId", + rule: { + pattern: props.teamId, + }, + }, + { + key: "type", + rule: { + pattern: "server-wallet", + }, + }, + ], + }, + { + type: "eoa:signTransaction", + payloadPatterns: {}, + metadataPatterns: [ + { + key: "projectId", + rule: { + pattern: props.projectId, + }, + }, + { + key: "teamId", + rule: { + pattern: props.teamId, + }, + }, + { + key: "type", + rule: { + pattern: "server-wallet", + }, + }, + ], + }, + { + type: "eoa:signTypedData", + metadataPatterns: [ + { + key: "projectId", + rule: { + pattern: props.projectId, + }, + }, + { + key: "teamId", + rule: { + pattern: props.teamId, + }, + }, + { + key: "type", + rule: { + pattern: "server-wallet", + }, + }, + ], + }, + ], + metadata: { + projectId: props.projectId, + teamId: props.teamId, + purpose: "Thirdweb Project Server Wallet Access Token", + }, + }, + auth: { + adminKey: serviceAccount.data.adminKey, + }, + }, + }); + + const [userAccessTokenRes, managementAccessTokenRes] = await Promise.all([ + userAccesTokenPromise, + managementAccessTokenPromise, + ]); + + if (!managementAccessTokenRes.success || !userAccessTokenRes.success) { + throw new Error("Failed to create access token"); + } + + console.log(JSON.stringify(userAccessTokenRes.data, null, 2)); + console.log(JSON.stringify(serviceAccount.data, null, 2)); + console.log(JSON.stringify(managementAccessTokenRes.data, null, 2)); + + const apiServerResult = await updateProjectClient( + { + projectId: props.projectId, + teamId: props.teamId, + }, + { + services: [ + { + name: "engineCloud", + managementAccessToken: managementAccessTokenRes.data.accessToken, + maskedAdminKey: maskSecret(serviceAccount.data.adminKey), + actions: [], + }, + ], + }, + ); + + console.log(apiServerResult); + + // todo: show modal with credentials here + // This should display: + // - Service Account Admin Key + // - Service Account Rotation Code + // - "Project Level" Access Token: this allows all EOA operations for this service account, scoped to type, teamId and projectId by metadata + return { + serviceAccount: serviceAccount.data, + userAccessToken: userAccessTokenRes.data, + managementAccessToken: managementAccessTokenRes.data, + }; + }, + onError: (error) => { + toast.error(error.message); + }, + }); + + const createEoaMutation = useMutation({ + mutationFn: async ({ + managementAccessToken, + }: { + managementAccessToken: string; + }) => { + const vaultClient = await createVaultClient({ + baseUrl: THIRDWEB_VAULT_URL, + }); + + const eoa = await createEoa({ + request: { + options: { + metadata: { + projectId: props.projectId, + teamId: props.teamId, + type: "server-wallet", + }, + }, + auth: { + accessToken: managementAccessToken, + }, + }, + client: vaultClient, + }); + + if (!eoa.success) { + throw new Error("Failed to create eoa"); + } + + console.log(JSON.stringify(eoa.data, null, 2)); + + router.refresh(); + + return eoa; + }, + onError: (error) => { + toast.error(error.message); + }, + }); + + return ( + <> + + {initialiseProjectWithVaultMutation.data ? ( +
+ Success!

Admin Key

+

+ {initialiseProjectWithVaultMutation.data.serviceAccount.adminKey} +

+

Rotation Code

+

+ { + initialiseProjectWithVaultMutation.data.serviceAccount + .rotationCode + } +

+

Access Token

+

+ { + initialiseProjectWithVaultMutation.data.userAccessToken + .accessToken + } +

+

Management Access Token

+

+ { + initialiseProjectWithVaultMutation.data.managementAccessToken + .accessToken + } +

+
+ ) : ( + <> + )} + + ); +} + +function maskSecret(secret: string) { + return `${secret.substring(0, 13)}...${secret.substring(secret.length - 4)}`; +} diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/page.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/page.tsx index 69c7cd5f84b..0981edb16bb 100644 --- a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/page.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/page.tsx @@ -2,26 +2,63 @@ import { THIRDWEB_VAULT_URL } from "@/constants/env"; import { createVaultClient, listEoas } from "@thirdweb-dev/vault-sdk"; import type { Wallet } from "./wallet-table/types.js"; import { ServerWalletsTable } from "./wallet-table/wallet-table"; +import { getProject } from "@/api/projects"; +import CreateServerWallet from "./components/create-server-wallet"; -export default async function TransactionsServerWalletsPage() { +type EngineCloudService = { + name: "engineCloud"; + actions: []; + maskedAdminKey: string; + managementAccessToken: string; +}; + +export default async function TransactionsServerWalletsPage(props: { + params: Promise<{ team_slug: string; project_slug: string }>; +}) { const vaultClient = await createVaultClient({ baseUrl: THIRDWEB_VAULT_URL, }); - const eoas = await listEoas({ - client: vaultClient, - request: { - auth: { - adminKey: - "sa_adm_UHST_NIWG_AR5B_VLWM_LBLS_OQFT_793e1701-9a96-4625-9f53-35a8c41d7068", - }, - options: {}, - }, - }); + const { team_slug, project_slug } = await props.params; + + const project = await getProject(team_slug, project_slug); + + const projectEngineCloudService = project?.services.find( + (service) => service.name === "engineCloud", + ) as EngineCloudService | undefined; - if (!eoas.success) { - return
Error: {eoas.error.message}
; + const managementAccessToken = + projectEngineCloudService?.managementAccessToken; + + const eoas = managementAccessToken + ? await listEoas({ + client: vaultClient, + request: { + auth: { + accessToken: managementAccessToken, + }, + options: {}, + }, + }) + : { data: { items: [] }, error: null, success: true }; + + if (!project) { + return
Error: Project not found
; } - return ; + return ( + <> + + + {eoas.error ? ( +
Error: {eoas.error.message}
+ ) : ( + + )} + + ); } diff --git a/packages/vault-sdk/src/sdk.ts b/packages/vault-sdk/src/sdk.ts index 953f489dae1..9083e407744 100644 --- a/packages/vault-sdk/src/sdk.ts +++ b/packages/vault-sdk/src/sdk.ts @@ -145,8 +145,6 @@ async function sendRequest

({ client.publicKey, ); - console.log(client); - const vaultApi = ky.create({ prefixUrl: client.baseUrl, headers: { @@ -155,24 +153,13 @@ async function sendRequest

({ throwHttpErrors: false, }); - console.log( - "Encrypted payload for operation ", - request.operation, - encryptedPayload, - ); - const res = await vaultApi .post("api/v1/enclave", { json: encryptedPayload, }) - .json() - .catch((e) => { - console.log("Error from vault: ", e); - throw e; - }); + .json(); if (isErrorResponse(res)) { - console.log("Error response from vault: ", res); return { success: false, data: null, @@ -180,14 +167,11 @@ async function sendRequest

({ } as Prettify; } - console.log("Encrypted response:", res); - const decryptedResponse = decryptFromEnclave( res, ephemeralPrivateKey, ) as Prettify; - console.log("Decrypted response from vault: ", decryptedResponse); return decryptedResponse; } From 1fbe3cafe4cf138476da6bcecd74399bf6bf3321 Mon Sep 17 00:00:00 2001 From: Joaquim Verges Date: Wed, 9 Apr 2025 13:58:44 +1200 Subject: [PATCH 006/101] UI polish --- apps/dashboard/.env.example | 5 +- .../components/create-server-wallet.tsx | 212 +++++++++++++----- .../server-wallets/components/try-it-out.tsx | 49 ++++ .../transactions/server-wallets/page.tsx | 25 +-- .../wallet-table/wallet-table-ui.tsx | 36 ++- .../wallet-table/wallet-table.tsx | 25 ++- 6 files changed, 262 insertions(+), 90 deletions(-) create mode 100644 apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/try-it-out.tsx diff --git a/apps/dashboard/.env.example b/apps/dashboard/.env.example index 04e0f385e60..abd1d5ee320 100644 --- a/apps/dashboard/.env.example +++ b/apps/dashboard/.env.example @@ -104,4 +104,7 @@ ANALYTICS_SERVICE_URL="" NEXT_PUBLIC_NEBULA_URL="" # required for billing parts of the dashboard (team -> settings -> billing / invoices) -STRIPE_SECRET_KEY="" \ No newline at end of file +STRIPE_SECRET_KEY="" + +# required for server wallet management +NEXT_PUBLIC_THIRDWEB_VAULT_URL="" diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/create-server-wallet.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/create-server-wallet.tsx index 1c075aa26f8..f78aaff10c6 100644 --- a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/create-server-wallet.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/create-server-wallet.tsx @@ -1,8 +1,20 @@ "use client"; +import { CopyTextButton } from "@/components/ui/CopyTextButton"; +import { Spinner } from "@/components/ui/Spinner/Spinner"; +import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; import { Button } from "@/components/ui/button"; +import { CheckboxWithLabel } from "@/components/ui/checkbox"; +import { Checkbox } from "@/components/ui/checkbox"; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; import { THIRDWEB_VAULT_URL } from "@/constants/env"; import { useDashboardRouter } from "@/lib/DashboardRouter"; +import { cn } from "@/lib/utils"; import { updateProjectClient } from "@3rdweb-sdk/react/hooks/useApi"; import { useMutation } from "@tanstack/react-query"; import { @@ -12,6 +24,7 @@ import { createVaultClient, } from "@thirdweb-dev/vault-sdk"; import { Loader2 } from "lucide-react"; +import { useState } from "react"; import { toast } from "sonner"; export default function CreateServerWallet(props: { @@ -20,9 +33,13 @@ export default function CreateServerWallet(props: { managementAccessToken: string | undefined; }) { const router = useDashboardRouter(); + const [modalOpen, setModalOpen] = useState(false); + const [keysConfirmed, setKeysConfirmed] = useState(false); const initialiseProjectWithVaultMutation = useMutation({ mutationFn: async () => { + setModalOpen(true); + const vaultClient = await createVaultClient({ baseUrl: THIRDWEB_VAULT_URL, }); @@ -257,11 +274,8 @@ export default function CreateServerWallet(props: { throw new Error("Failed to create access token"); } - console.log(JSON.stringify(userAccessTokenRes.data, null, 2)); - console.log(JSON.stringify(serviceAccount.data, null, 2)); - console.log(JSON.stringify(managementAccessTokenRes.data, null, 2)); - - const apiServerResult = await updateProjectClient( + // store the management access token in the project + await updateProjectClient( { projectId: props.projectId, teamId: props.teamId, @@ -278,13 +292,6 @@ export default function CreateServerWallet(props: { }, ); - console.log(apiServerResult); - - // todo: show modal with credentials here - // This should display: - // - Service Account Admin Key - // - Service Account Rotation Code - // - "Project Level" Access Token: this allows all EOA operations for this service account, scoped to type, teamId and projectId by metadata return { serviceAccount: serviceAccount.data, userAccessToken: userAccessTokenRes.data, @@ -293,6 +300,7 @@ export default function CreateServerWallet(props: { }, onError: (error) => { toast.error(error.message); + setModalOpen(false); }, }); @@ -337,62 +345,144 @@ export default function CreateServerWallet(props: { }, }); + const handleCreateServerWallet = async () => { + if (!props.managementAccessToken) { + const initResult = await initialiseProjectWithVaultMutation.mutateAsync(); + await createEoaMutation.mutateAsync({ + managementAccessToken: initResult.managementAccessToken.accessToken, + }); + } else { + await createEoaMutation.mutateAsync({ + managementAccessToken: props.managementAccessToken, + }); + } + }; + + const handleCloseModal = () => { + if (!keysConfirmed) { + return; + } + setModalOpen(false); + setKeysConfirmed(false); + }; + + const isLoading = + initialiseProjectWithVaultMutation.isPending || createEoaMutation.isPending; + return ( <> - {initialiseProjectWithVaultMutation.data ? ( -

- Success!

Admin Key

-

- {initialiseProjectWithVaultMutation.data.serviceAccount.adminKey} -

-

Rotation Code

-

- { - initialiseProjectWithVaultMutation.data.serviceAccount - .rotationCode - } -

-

Access Token

-

- { - initialiseProjectWithVaultMutation.data.userAccessToken - .accessToken - } -

-

Management Access Token

-

- { - initialiseProjectWithVaultMutation.data.managementAccessToken - .accessToken - } -

-
- ) : ( - <> - )} + + + + {initialiseProjectWithVaultMutation.isPending ? ( +
+ + Generating your wallet management keys +
+ ) : initialiseProjectWithVaultMutation.data ? ( +
+ + Wallet Management Keys + + +
+ + Secure your keys + + These keys cannot be recovered. Store them securely as they + provide access to your wallet. + + + +
+
+

Admin Key

+
+ +
+
+ +
+

Rotation Code

+
+ +
+
+ +
+

Access Token

+
+ +
+
+
+
+ +
+ + setKeysConfirmed(!!v)} + /> + I confirm that I've securely stored these keys + + + +
+
+ ) : null} +
+
); } diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/try-it-out.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/try-it-out.tsx new file mode 100644 index 00000000000..49164b9c472 --- /dev/null +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/try-it-out.tsx @@ -0,0 +1,49 @@ +import { Button } from "@/components/ui/button"; +import { CodeServer } from "../../../../../../../@/components/ui/code/code.server"; + +export function TryItOut() { + return ( +
+
+
+
+

Usage

+

+ Simple http API to send transactions to the blockchain +

+
+
+ +
+
+ +
+
+ ); +} +const typescriptCodeExample = () => `\ +const response = fetch("https://wallet.thirdweb.com/v1/account/send-transaction", { + method: "POST", + headers: { + "Content-Type": "application/json", + "x-secret-key": , + }, + body: JSON.stringify({ + "executionOptions": { + "type": "AA", + "signerAddress": + }, + "transactionParams": [ + { + "to": "0xeb0effdfb4dc5b3d5d3ac6ce29f3ed213e95d675", + "value": "0" + } + ], + "vaultAccessToken": , + "chainId": "84532" + }), +});`; diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/page.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/page.tsx index 0981edb16bb..72a3ee401b2 100644 --- a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/page.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/page.tsx @@ -1,16 +1,8 @@ +import { getProject } from "@/api/projects"; import { THIRDWEB_VAULT_URL } from "@/constants/env"; import { createVaultClient, listEoas } from "@thirdweb-dev/vault-sdk"; import type { Wallet } from "./wallet-table/types.js"; import { ServerWalletsTable } from "./wallet-table/wallet-table"; -import { getProject } from "@/api/projects"; -import CreateServerWallet from "./components/create-server-wallet"; - -type EngineCloudService = { - name: "engineCloud"; - actions: []; - maskedAdminKey: string; - managementAccessToken: string; -}; export default async function TransactionsServerWalletsPage(props: { params: Promise<{ team_slug: string; project_slug: string }>; @@ -25,7 +17,7 @@ export default async function TransactionsServerWalletsPage(props: { const projectEngineCloudService = project?.services.find( (service) => service.name === "engineCloud", - ) as EngineCloudService | undefined; + ); const managementAccessToken = projectEngineCloudService?.managementAccessToken; @@ -48,16 +40,15 @@ export default async function TransactionsServerWalletsPage(props: { return ( <> - - {eoas.error ? (
Error: {eoas.error.message}
) : ( - + )} ); diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/wallet-table/wallet-table-ui.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/wallet-table/wallet-table-ui.tsx index 66a34e320f2..c9385f9fc15 100644 --- a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/wallet-table/wallet-table-ui.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/wallet-table/wallet-table-ui.tsx @@ -13,20 +13,38 @@ import { import { ToolTipLabel } from "@/components/ui/tooltip"; import { formatDistanceToNowStrict } from "date-fns"; import { format } from "date-fns/format"; +import CreateServerWallet from "../components/create-server-wallet"; import type { Wallet } from "./types"; -export function ServerWalletsTableUI({ wallets }: { wallets: Wallet[] }) { +export function ServerWalletsTableUI({ + wallets, + projectId, + teamId, + managementAccessToken, +}: { + wallets: Wallet[]; + projectId: string; + teamId: string; + managementAccessToken: string | undefined; +}) { return (
-
-
-

- Server Wallets -

-

- Create and manage server wallets for you project -

+
+
+
+

+ Server Wallets +

+

+ Create and manage server wallets for you project +

+
+
diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/wallet-table/wallet-table.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/wallet-table/wallet-table.tsx index c1b851c1989..0a1987b1e2e 100644 --- a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/wallet-table/wallet-table.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/wallet-table/wallet-table.tsx @@ -1,6 +1,27 @@ +import { TryItOut } from "../components/try-it-out"; import type { Wallet } from "./types"; import { ServerWalletsTableUI } from "./wallet-table-ui"; -export function ServerWalletsTable({ wallets }: { wallets: Wallet[] }) { - return ; +export function ServerWalletsTable({ + wallets, + projectId, + teamId, + managementAccessToken, +}: { + wallets: Wallet[]; + projectId: string; + teamId: string; + managementAccessToken: string | undefined; +}) { + return ( +
+ + +
+ ); } From 8f56fbd746dcd0b9fa17d2d8e4e8e511d15bc65f Mon Sep 17 00:00:00 2001 From: Joaquim Verges Date: Wed, 9 Apr 2025 14:17:18 +1200 Subject: [PATCH 007/101] tooltips --- apps/dashboard/.env.example | 1 + apps/dashboard/src/@/constants/env.ts | 3 +++ .../components/create-server-wallet.tsx | 25 +++++++++++++------ .../server-wallets/components/try-it-out.tsx | 9 ++++--- 4 files changed, 28 insertions(+), 10 deletions(-) diff --git a/apps/dashboard/.env.example b/apps/dashboard/.env.example index abd1d5ee320..3f719125270 100644 --- a/apps/dashboard/.env.example +++ b/apps/dashboard/.env.example @@ -108,3 +108,4 @@ STRIPE_SECRET_KEY="" # required for server wallet management NEXT_PUBLIC_THIRDWEB_VAULT_URL="" +NEXT_PUBLIC_ENGINE_CLOUD_URL="" \ No newline at end of file diff --git a/apps/dashboard/src/@/constants/env.ts b/apps/dashboard/src/@/constants/env.ts index 4fa5bd6772a..d50d026b50d 100644 --- a/apps/dashboard/src/@/constants/env.ts +++ b/apps/dashboard/src/@/constants/env.ts @@ -12,6 +12,9 @@ export const NEBULA_APP_SECRET_KEY = process.env.NEBULA_APP_SECRET_KEY || ""; export const THIRDWEB_VAULT_URL = process.env.NEXT_PUBLIC_THIRDWEB_VAULT_URL || ""; +export const THIRDWEB_ENGINE_CLOUD_URL = + process.env.NEXT_PUBLIC_ENGINE_CLOUD_URL || ""; + export const THIRDWEB_API_SECRET = process.env.API_SERVER_SECRET || ""; export const IPFS_GATEWAY_URL = diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/create-server-wallet.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/create-server-wallet.tsx index f78aaff10c6..23ccb4b8108 100644 --- a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/create-server-wallet.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/create-server-wallet.tsx @@ -334,8 +334,6 @@ export default function CreateServerWallet(props: { throw new Error("Failed to create eoa"); } - console.log(JSON.stringify(eoa.data, null, 2)); - router.refresh(); return eoa; @@ -401,15 +399,15 @@ export default function CreateServerWallet(props: { Secure your keys - These keys cannot be recovered. Store them securely as they - provide access to your wallet. + These keys will not be displayed again. Store them securely + as they provide access to your server wallets.

Admin Key

-
+
+

+ This key is used to create or modify your server + wallets. Save it in a secure location. +

Rotation Code

-
+
+

+ This code is used to rotate your admin key in case you + loose it. Save it in a secure location. +

Access Token

-
+
+

+ This access token is used to send transactions to the + blockchain from your backend. Can be revoked and + recreated with your admin key. +

diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/try-it-out.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/try-it-out.tsx index 49164b9c472..fd07013e3f1 100644 --- a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/try-it-out.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/try-it-out.tsx @@ -1,5 +1,6 @@ import { Button } from "@/components/ui/button"; import { CodeServer } from "../../../../../../../@/components/ui/code/code.server"; +import { THIRDWEB_ENGINE_CLOUD_URL } from "../../../../../../../@/constants/env"; export function TryItOut() { return ( @@ -7,9 +8,11 @@ export function TryItOut() {
-

Usage

+

+ Usage from your backend +

- Simple http API to send transactions to the blockchain + Send transactions to the blockchain using a simple http API

@@ -26,7 +29,7 @@ export function TryItOut() { ); } const typescriptCodeExample = () => `\ -const response = fetch("https://wallet.thirdweb.com/v1/account/send-transaction", { +const response = fetch("${THIRDWEB_ENGINE_CLOUD_URL}/account/send-transaction", { method: "POST", headers: { "Content-Type": "application/json", From 19a60db10574d4ff85a10b88869d0ed1cf400b4d Mon Sep 17 00:00:00 2001 From: Joaquim Verges Date: Wed, 9 Apr 2025 14:48:01 +1200 Subject: [PATCH 008/101] rename --- .../server-wallets/components/create-server-wallet.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/create-server-wallet.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/create-server-wallet.tsx index 23ccb4b8108..081bf7b6e1f 100644 --- a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/create-server-wallet.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/create-server-wallet.tsx @@ -452,7 +452,9 @@ export default function CreateServerWallet(props: {
-

Access Token

+

+ Wallet Access Token +

This access token is used to send transactions to the From 801ef34c882ecb44c25631f1631fe1c6c5c1c6f7 Mon Sep 17 00:00:00 2001 From: Joaquim Verges Date: Wed, 9 Apr 2025 22:08:41 +1200 Subject: [PATCH 009/101] added access token flow --- .../components/create-access-token.tsx | 334 ++++++++++++++++++ ...et.tsx => create-server-wallet.client.tsx} | 76 ++-- .../components/key-management.tsx | 30 ++ .../server-wallets/components/try-it-out.tsx | 48 +-- .../transactions/server-wallets/page.tsx | 25 +- ...able-ui.tsx => wallet-table-ui.client.tsx} | 2 +- .../wallet-table/wallet-table.tsx | 18 +- 7 files changed, 459 insertions(+), 74 deletions(-) create mode 100644 apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/create-access-token.tsx rename apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/{create-server-wallet.tsx => create-server-wallet.client.tsx} (89%) create mode 100644 apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/key-management.tsx rename apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/wallet-table/{wallet-table-ui.tsx => wallet-table-ui.client.tsx} (99%) diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/create-access-token.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/create-access-token.tsx new file mode 100644 index 00000000000..cc140ebee5d --- /dev/null +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/create-access-token.tsx @@ -0,0 +1,334 @@ +"use client"; + +import { CopyTextButton } from "@/components/ui/CopyTextButton"; +import { Spinner } from "@/components/ui/Spinner/Spinner"; +import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; +import { Button } from "@/components/ui/button"; +import { CheckboxWithLabel } from "@/components/ui/checkbox"; +import { Checkbox } from "@/components/ui/checkbox"; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { Input } from "@/components/ui/input"; +import { THIRDWEB_VAULT_URL } from "@/constants/env"; +import { cn } from "@/lib/utils"; +import { useMutation } from "@tanstack/react-query"; +import { createAccessToken, createVaultClient } from "@thirdweb-dev/vault-sdk"; +import { Loader2 } from "lucide-react"; +import { useState } from "react"; +import { toast } from "sonner"; + +export default function CreateAccessToken(props: { + projectId: string; + teamId: string; +}) { + const [modalOpen, setModalOpen] = useState(false); + const [keysConfirmed, setKeysConfirmed] = useState(false); + const [adminKey, setAdminKey] = useState(""); + // TODO allow passing permissions to the access token + const createAccessTokenMutation = useMutation({ + mutationFn: async (args: { + adminKey: string; + }) => { + const vaultClient = await createVaultClient({ + baseUrl: THIRDWEB_VAULT_URL, + }); + + const userAccessTokenRes = await createAccessToken({ + client: vaultClient, + request: { + options: { + expiresAt: new Date( + Date.now() + 60 * 60 * 1000 * 24 * 365 * 1000, + ).toISOString(), // 100 years from now + policies: [ + { + type: "eoa:read", + metadataPatterns: [ + { + key: "projectId", + rule: { + pattern: props.projectId, + }, + }, + { + key: "teamId", + rule: { + pattern: props.teamId, + }, + }, + { + key: "type", + rule: { + pattern: "server-wallet", + }, + }, + ], + }, + { + type: "eoa:create", + requiredMetadataPatterns: [ + { + key: "projectId", + rule: { + pattern: props.projectId, + }, + }, + { + key: "teamId", + rule: { + pattern: props.teamId, + }, + }, + { + key: "type", + rule: { + pattern: "server-wallet", + }, + }, + ], + }, + { + type: "eoa:signMessage", + metadataPatterns: [ + { + key: "projectId", + rule: { + pattern: props.projectId, + }, + }, + { + key: "teamId", + rule: { + pattern: props.teamId, + }, + }, + { + key: "type", + rule: { + pattern: "server-wallet", + }, + }, + ], + }, + { + type: "eoa:signTransaction", + payloadPatterns: {}, + metadataPatterns: [ + { + key: "projectId", + rule: { + pattern: props.projectId, + }, + }, + { + key: "teamId", + rule: { + pattern: props.teamId, + }, + }, + { + key: "type", + rule: { + pattern: "server-wallet", + }, + }, + ], + }, + { + type: "eoa:signTypedData", + metadataPatterns: [ + { + key: "projectId", + rule: { + pattern: props.projectId, + }, + }, + { + key: "teamId", + rule: { + pattern: props.teamId, + }, + }, + { + key: "type", + rule: { + pattern: "server-wallet", + }, + }, + ], + }, + ], + metadata: { + projectId: props.projectId, + teamId: props.teamId, + purpose: "Thirdweb Project Server Wallet Access Token", + }, + }, + auth: { + adminKey: args.adminKey, + }, + }, + }); + + if (!userAccessTokenRes.success) { + throw new Error( + `Failed to create access token: ${userAccessTokenRes.error.message}`, + ); + } + + return { + userAccessToken: userAccessTokenRes.data, + }; + }, + onError: (error) => { + toast.error(error.message); + }, + }); + + const handleCloseModal = () => { + if (!keysConfirmed) { + return; + } + setModalOpen(false); + setKeysConfirmed(false); + }; + + const isLoading = createAccessTokenMutation.isPending; + + return ( + <> + + +

+ + + Create Wallet Access Token + + {createAccessTokenMutation.isPending ? ( +
+ + Generating your access token +
+ ) : createAccessTokenMutation.data ? ( +
+
+
+
+

+ Wallet Access Token +

+
+ +

+ This access token is used to send transactions to the + blockchain from your backend. Can be revoked and + recreated with your admin key. +

+
+
+
+ + Secure your keys + + These keys will not be displayed again. Store them securely + as they provide access to your server wallets. + +
+ + setKeysConfirmed(!!v)} + /> + I confirm that I've securely stored these keys + + +
+ +
+ +
+
+ ) : ( +
+
+

+ This action requries your admin key. +

+ setAdminKey(e.target.value)} + /> +
+ + +
+
+
+ )} + +
+ + ); +} + +function maskSecret(secret: string) { + return `${secret.substring(0, 10)}...${secret.substring(secret.length - 10)}`; +} diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/create-server-wallet.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/create-server-wallet.client.tsx similarity index 89% rename from apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/create-server-wallet.tsx rename to apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/create-server-wallet.client.tsx index 081bf7b6e1f..85f0ee7aaae 100644 --- a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/create-server-wallet.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/create-server-wallet.client.tsx @@ -1,11 +1,7 @@ "use client"; - -import { CopyTextButton } from "@/components/ui/CopyTextButton"; import { Spinner } from "@/components/ui/Spinner/Spinner"; import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; import { Button } from "@/components/ui/button"; -import { CheckboxWithLabel } from "@/components/ui/checkbox"; -import { Checkbox } from "@/components/ui/checkbox"; import { Dialog, DialogContent, @@ -16,6 +12,7 @@ import { THIRDWEB_VAULT_URL } from "@/constants/env"; import { useDashboardRouter } from "@/lib/DashboardRouter"; import { cn } from "@/lib/utils"; import { updateProjectClient } from "@3rdweb-sdk/react/hooks/useApi"; +import { Checkbox } from "@radix-ui/react-checkbox"; import { useMutation } from "@tanstack/react-query"; import { createAccessToken, @@ -26,6 +23,8 @@ import { import { Loader2 } from "lucide-react"; import { useState } from "react"; import { toast } from "sonner"; +import { CopyTextButton } from "../../../../../../../@/components/ui/CopyTextButton"; +import { CheckboxWithLabel } from "../../../../../../../@/components/ui/checkbox"; export default function CreateServerWallet(props: { projectId: string; @@ -385,10 +384,19 @@ export default function CreateServerWallet(props: { dialogCloseClassName={cn(!keysConfirmed && "hidden")} > {initialiseProjectWithVaultMutation.isPending ? ( -
- - Generating your wallet management keys -
+ <> + + + Generating your wallet management keys + + +
+ +

+ This may take a few seconds. +

+
+ ) : initialiseProjectWithVaultMutation.data ? (
@@ -396,18 +404,10 @@ export default function CreateServerWallet(props: {
- - Secure your keys - - These keys will not be displayed again. Store them securely - as they provide access to your server wallets. - - -

Admin Key

-
+

- This key is used to create or modify your server - wallets. Save it in a secure location. + This key is used to create or revoke your access tokens.

Rotation Code

-
+

This code is used to rotate your admin key in case you - loose it. Save it in a secure location. + loose it.

@@ -455,7 +454,7 @@ export default function CreateServerWallet(props: {

Wallet Access Token

-
+
+ + Secure your keys + + These keys will not be displayed again. Store them securely + as they provide access to your server wallets. + +
+ + setKeysConfirmed(!!v)} + /> + I confirm that I've securely stored these keys + +
-
- - setKeysConfirmed(!!v)} - /> - I confirm that I've securely stored these keys - - -
@@ -501,5 +511,5 @@ export default function CreateServerWallet(props: { } function maskSecret(secret: string) { - return `${secret.substring(0, 13)}...${secret.substring(secret.length - 4)}`; + return `${secret.substring(0, 11)}...${secret.substring(secret.length - 5)}`; } diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/key-management.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/key-management.tsx new file mode 100644 index 00000000000..e44121ba848 --- /dev/null +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/key-management.tsx @@ -0,0 +1,30 @@ +import CreateAccessToken from "./create-access-token"; + +export function KeyManagement({ + maskedAdminKey, + projectId, + teamId, +}: { maskedAdminKey?: string; projectId: string; teamId: string }) { + return ( +
+
+
+
+

+ Key Management +

+

+ Manage your admin key and access tokens. +

+
+
+ {/* TODO */} + +
+
+

Admin Key

+

{maskedAdminKey}

+
+
+ ); +} diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/try-it-out.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/try-it-out.tsx index fd07013e3f1..345f45ba1b9 100644 --- a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/try-it-out.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/try-it-out.tsx @@ -16,7 +16,7 @@ export function TryItOut() {

- +
); } + const typescriptCodeExample = () => `\ -const response = fetch("${THIRDWEB_ENGINE_CLOUD_URL}/account/send-transaction", { - method: "POST", - headers: { - "Content-Type": "application/json", - "x-secret-key": , - }, - body: JSON.stringify({ - "executionOptions": { - "type": "AA", - "signerAddress": - }, - "transactionParams": [ - { - "to": "0xeb0effdfb4dc5b3d5d3ac6ce29f3ed213e95d675", - "value": "0" - } - ], - "vaultAccessToken": , - "chainId": "84532" - }), -});`; +const response = fetch( + "${THIRDWEB_ENGINE_CLOUD_URL}/account/send-transaction", + { + method: "POST", + headers: { + "Content-Type": "application/json", + "x-secret-key": , + }, + body: JSON.stringify({ + "executionOptions": { + "type": "AA", + "signerAddress": + }, + "transactionParams": [ + { + "to": "0xeb0effdfb4dc5b3d5d3ac6ce29f3ed213e95d675", + "value": "0" + } + ], + "vaultAccessToken": , + "chainId": "84532" + }), + } +);`; diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/page.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/page.tsx index 72a3ee401b2..bd286b2fdb8 100644 --- a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/page.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/page.tsx @@ -1,7 +1,9 @@ import { getProject } from "@/api/projects"; import { THIRDWEB_VAULT_URL } from "@/constants/env"; import { createVaultClient, listEoas } from "@thirdweb-dev/vault-sdk"; -import type { Wallet } from "./wallet-table/types.js"; +import { KeyManagement } from "./components/key-management"; +import { TryItOut } from "./components/try-it-out"; +import type { Wallet } from "./wallet-table/types"; import { ServerWalletsTable } from "./wallet-table/wallet-table"; export default async function TransactionsServerWalletsPage(props: { @@ -21,6 +23,7 @@ export default async function TransactionsServerWalletsPage(props: { const managementAccessToken = projectEngineCloudService?.managementAccessToken; + const maskedAdminKey = projectEngineCloudService?.maskedAdminKey; const eoas = managementAccessToken ? await listEoas({ @@ -43,12 +46,20 @@ export default async function TransactionsServerWalletsPage(props: { {eoas.error ? (
Error: {eoas.error.message}
) : ( - +
+ + + +
)} ); diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/wallet-table/wallet-table-ui.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/wallet-table/wallet-table-ui.client.tsx similarity index 99% rename from apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/wallet-table/wallet-table-ui.tsx rename to apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/wallet-table/wallet-table-ui.client.tsx index c9385f9fc15..1e3b1316f22 100644 --- a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/wallet-table/wallet-table-ui.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/wallet-table/wallet-table-ui.client.tsx @@ -13,7 +13,7 @@ import { import { ToolTipLabel } from "@/components/ui/tooltip"; import { formatDistanceToNowStrict } from "date-fns"; import { format } from "date-fns/format"; -import CreateServerWallet from "../components/create-server-wallet"; +import CreateServerWallet from "../components/create-server-wallet.client"; import type { Wallet } from "./types"; export function ServerWalletsTableUI({ diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/wallet-table/wallet-table.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/wallet-table/wallet-table.tsx index 0a1987b1e2e..8c1f03ea62a 100644 --- a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/wallet-table/wallet-table.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/wallet-table/wallet-table.tsx @@ -1,6 +1,5 @@ -import { TryItOut } from "../components/try-it-out"; import type { Wallet } from "./types"; -import { ServerWalletsTableUI } from "./wallet-table-ui"; +import { ServerWalletsTableUI } from "./wallet-table-ui.client"; export function ServerWalletsTable({ wallets, @@ -14,14 +13,11 @@ export function ServerWalletsTable({ managementAccessToken: string | undefined; }) { return ( -
- - -
+ ); } From 80ad75ef53757dba477b2ecc905a34e9b2c3325b Mon Sep 17 00:00:00 2001 From: Joaquim Verges Date: Wed, 9 Apr 2025 23:05:17 +1200 Subject: [PATCH 010/101] add send transaction button --- ...ken.tsx => create-access-token.client.tsx} | 0 .../components/key-management.tsx | 2 +- .../components/send-dummy-tx.client.tsx | 180 ++++++++++++++++++ .../server-wallets/components/try-it-out.tsx | 22 ++- .../transactions/server-wallets/page.tsx | 19 +- 5 files changed, 209 insertions(+), 14 deletions(-) rename apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/{create-access-token.tsx => create-access-token.client.tsx} (100%) create mode 100644 apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/send-dummy-tx.client.tsx diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/create-access-token.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/create-access-token.client.tsx similarity index 100% rename from apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/create-access-token.tsx rename to apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/create-access-token.client.tsx diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/key-management.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/key-management.tsx index e44121ba848..372eaa4fe09 100644 --- a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/key-management.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/key-management.tsx @@ -1,4 +1,4 @@ -import CreateAccessToken from "./create-access-token"; +import CreateAccessToken from "./create-access-token.client"; export function KeyManagement({ maskedAdminKey, diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/send-dummy-tx.client.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/send-dummy-tx.client.tsx new file mode 100644 index 00000000000..cd6503cea44 --- /dev/null +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/send-dummy-tx.client.tsx @@ -0,0 +1,180 @@ +"use client"; +import { Spinner } from "@/components/ui/Spinner/Spinner"; +import {} from "@/components/ui/alert"; +import { Button } from "@/components/ui/button"; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { Input } from "@/components/ui/input"; +import { THIRDWEB_ENGINE_CLOUD_URL } from "@/constants/env"; +import { useMutation } from "@tanstack/react-query"; +import { Loader2 } from "lucide-react"; +import { useState } from "react"; +import { toast } from "sonner"; +import { WalletAddress } from "../../../../../../../@/components/blocks/wallet-address"; +import { CodeClient } from "../../../../../../../@/components/ui/code/code.client"; +import type { Wallet } from "../wallet-table/types"; + +export default function SendDummyTx(props: { + authToken: string; + wallet: Wallet; +}) { + const [modalOpen, setModalOpen] = useState(false); + const [accessToken, setAccessToken] = useState(""); + const sendDummyTxMutation = useMutation({ + mutationFn: async (args: { + walletAddress: string; + accessToken: string; + }) => { + const response = await fetch( + `${THIRDWEB_ENGINE_CLOUD_URL}/account/send-transaction`, + { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${props.authToken}`, + }, + body: JSON.stringify({ + executionOptions: { + type: "AA", + signerAddress: args.walletAddress, + }, + transactionParams: [ + { + to: "0xeb0effdfb4dc5b3d5d3ac6ce29f3ed213e95d675", + value: "0", + }, + ], + vaultAccessToken: args.accessToken, + chainId: "84532", + }), + }, + ); + return response.json(); + }, + onError: (error) => { + toast.error(error.message); + }, + }); + + const handleCloseModal = () => { + setModalOpen(false); + }; + + const isLoading = sendDummyTxMutation.isPending; + + return ( + <> + + + + + + Send Test Transaction + + {sendDummyTxMutation.isPending ? ( +
+ +

+ Sending transaction... +

+
+ ) : sendDummyTxMutation.data ? ( +
+
+
+
+

+ Transaction Status +

+

+ {sendDummyTxMutation.data.status} +

+

+ Transaction Result +

+
+ +
+
+
+
+ +
+ +
+
+ ) : ( +
+
+
+

Sending from

+ +
+

+ This action requries a wallet access token. +

+ setAccessToken(e.target.value)} + /> +
+ + +
+
+
+ )} +
+
+ + ); +} diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/try-it-out.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/try-it-out.tsx index 345f45ba1b9..f17e8d40f72 100644 --- a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/try-it-out.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/try-it-out.tsx @@ -1,8 +1,10 @@ import { Button } from "@/components/ui/button"; import { CodeServer } from "../../../../../../../@/components/ui/code/code.server"; import { THIRDWEB_ENGINE_CLOUD_URL } from "../../../../../../../@/constants/env"; +import type { Wallet } from "../wallet-table/types"; +import SendDummyTx from "./send-dummy-tx.client"; -export function TryItOut() { +export function TryItOut(props: { authToken: string; wallet?: Wallet }) { return (
@@ -16,32 +18,38 @@ export function TryItOut() {

-
+
+
+ + {props.wallet && ( + + )} +
); } -const typescriptCodeExample = () => `\ +const sendTransactionExample = () => `\ const response = fetch( "${THIRDWEB_ENGINE_CLOUD_URL}/account/send-transaction", { method: "POST", headers: { "Content-Type": "application/json", - "x-secret-key": , + "x-secret-key": "", }, body: JSON.stringify({ "executionOptions": { "type": "AA", - "signerAddress": + "signerAddress": "" }, "transactionParams": [ { @@ -49,7 +57,7 @@ const response = fetch( "value": "0" } ], - "vaultAccessToken": , + "vaultAccessToken": "", "chainId": "84532" }), } diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/page.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/page.tsx index bd286b2fdb8..f17d16ba9b4 100644 --- a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/page.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/page.tsx @@ -1,6 +1,8 @@ import { getProject } from "@/api/projects"; import { THIRDWEB_VAULT_URL } from "@/constants/env"; import { createVaultClient, listEoas } from "@thirdweb-dev/vault-sdk"; +import { notFound } from "next/navigation"; +import { getAuthToken } from "../../../../../api/lib/getAuthToken"; import { KeyManagement } from "./components/key-management"; import { TryItOut } from "./components/try-it-out"; import type { Wallet } from "./wallet-table/types"; @@ -15,9 +17,16 @@ export default async function TransactionsServerWalletsPage(props: { const { team_slug, project_slug } = await props.params; - const project = await getProject(team_slug, project_slug); + const [authToken, project] = await Promise.all([ + getAuthToken(), + getProject(team_slug, project_slug), + ]); - const projectEngineCloudService = project?.services.find( + if (!project || !authToken) { + notFound(); + } + + const projectEngineCloudService = project.services.find( (service) => service.name === "engineCloud", ); @@ -37,9 +46,7 @@ export default async function TransactionsServerWalletsPage(props: { }) : { data: { items: [] }, error: null, success: true }; - if (!project) { - return
Error: Project not found
; - } + const wallet = eoas.data?.items[0] as Wallet | undefined; return ( <> @@ -58,7 +65,7 @@ export default async function TransactionsServerWalletsPage(props: { projectId={project.id} teamId={project.teamId} /> - +
)} From 9c8bc94e870f13b55940bab10bff91b14bc469e9 Mon Sep 17 00:00:00 2001 From: Joaquim Verges Date: Wed, 9 Apr 2025 23:36:24 +1200 Subject: [PATCH 011/101] fix project update --- .../create-server-wallet.client.tsx | 79 ++++++++++--------- .../transactions/server-wallets/page.tsx | 3 +- .../wallet-table/wallet-table-ui.client.tsx | 11 +-- .../wallet-table/wallet-table.tsx | 10 +-- 4 files changed, 49 insertions(+), 54 deletions(-) diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/create-server-wallet.client.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/create-server-wallet.client.tsx index 85f0ee7aaae..1cb2459047a 100644 --- a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/create-server-wallet.client.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/create-server-wallet.client.tsx @@ -1,7 +1,10 @@ "use client"; +import type { Project } from "@/api/projects"; +import { CopyTextButton } from "@/components/ui/CopyTextButton"; import { Spinner } from "@/components/ui/Spinner/Spinner"; import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; import { Button } from "@/components/ui/button"; +import { Checkbox, CheckboxWithLabel } from "@/components/ui/checkbox"; import { Dialog, DialogContent, @@ -12,7 +15,6 @@ import { THIRDWEB_VAULT_URL } from "@/constants/env"; import { useDashboardRouter } from "@/lib/DashboardRouter"; import { cn } from "@/lib/utils"; import { updateProjectClient } from "@3rdweb-sdk/react/hooks/useApi"; -import { Checkbox } from "@radix-ui/react-checkbox"; import { useMutation } from "@tanstack/react-query"; import { createAccessToken, @@ -23,12 +25,9 @@ import { import { Loader2 } from "lucide-react"; import { useState } from "react"; import { toast } from "sonner"; -import { CopyTextButton } from "../../../../../../../@/components/ui/CopyTextButton"; -import { CheckboxWithLabel } from "../../../../../../../@/components/ui/checkbox"; export default function CreateServerWallet(props: { - projectId: string; - teamId: string; + project: Project; managementAccessToken: string | undefined; }) { const router = useDashboardRouter(); @@ -48,8 +47,8 @@ export default function CreateServerWallet(props: { request: { options: { metadata: { - projectId: props.projectId, - teamId: props.teamId, + projectId: props.project.id, + teamId: props.project.teamId, purpose: "Thirdweb Project Server Wallet Service Account", }, }, @@ -74,13 +73,13 @@ export default function CreateServerWallet(props: { { key: "projectId", rule: { - pattern: props.projectId, + pattern: props.project.id, }, }, { key: "teamId", rule: { - pattern: props.teamId, + pattern: props.project.teamId, }, }, { @@ -97,13 +96,13 @@ export default function CreateServerWallet(props: { { key: "projectId", rule: { - pattern: props.projectId, + pattern: props.project.id, }, }, { key: "teamId", rule: { - pattern: props.teamId, + pattern: props.project.teamId, }, }, { @@ -116,8 +115,8 @@ export default function CreateServerWallet(props: { }, ], metadata: { - projectId: props.projectId, - teamId: props.teamId, + projectId: props.project.id, + teamId: props.project.teamId, purpose: "Thirdweb Project Server Wallet Access Token", }, }, @@ -141,13 +140,13 @@ export default function CreateServerWallet(props: { { key: "projectId", rule: { - pattern: props.projectId, + pattern: props.project.id, }, }, { key: "teamId", rule: { - pattern: props.teamId, + pattern: props.project.teamId, }, }, { @@ -164,13 +163,13 @@ export default function CreateServerWallet(props: { { key: "projectId", rule: { - pattern: props.projectId, + pattern: props.project.id, }, }, { key: "teamId", rule: { - pattern: props.teamId, + pattern: props.project.teamId, }, }, { @@ -187,13 +186,13 @@ export default function CreateServerWallet(props: { { key: "projectId", rule: { - pattern: props.projectId, + pattern: props.project.id, }, }, { key: "teamId", rule: { - pattern: props.teamId, + pattern: props.project.teamId, }, }, { @@ -211,13 +210,13 @@ export default function CreateServerWallet(props: { { key: "projectId", rule: { - pattern: props.projectId, + pattern: props.project.id, }, }, { key: "teamId", rule: { - pattern: props.teamId, + pattern: props.project.teamId, }, }, { @@ -234,13 +233,13 @@ export default function CreateServerWallet(props: { { key: "projectId", rule: { - pattern: props.projectId, + pattern: props.project.id, }, }, { key: "teamId", rule: { - pattern: props.teamId, + pattern: props.project.teamId, }, }, { @@ -253,8 +252,8 @@ export default function CreateServerWallet(props: { }, ], metadata: { - projectId: props.projectId, - teamId: props.teamId, + projectId: props.project.id, + teamId: props.project.teamId, purpose: "Thirdweb Project Server Wallet Access Token", }, }, @@ -276,11 +275,12 @@ export default function CreateServerWallet(props: { // store the management access token in the project await updateProjectClient( { - projectId: props.projectId, - teamId: props.teamId, + projectId: props.project.id, + teamId: props.project.teamId, }, { services: [ + ...props.project.services, { name: "engineCloud", managementAccessToken: managementAccessTokenRes.data.accessToken, @@ -317,8 +317,8 @@ export default function CreateServerWallet(props: { request: { options: { metadata: { - projectId: props.projectId, - teamId: props.teamId, + projectId: props.project.id, + teamId: props.project.teamId, type: "server-wallet", }, }, @@ -343,16 +343,17 @@ export default function CreateServerWallet(props: { }); const handleCreateServerWallet = async () => { - if (!props.managementAccessToken) { - const initResult = await initialiseProjectWithVaultMutation.mutateAsync(); - await createEoaMutation.mutateAsync({ - managementAccessToken: initResult.managementAccessToken.accessToken, - }); - } else { - await createEoaMutation.mutateAsync({ - managementAccessToken: props.managementAccessToken, - }); - } + // FIXME uncomment this + // if (!props.managementAccessToken) { + const initResult = await initialiseProjectWithVaultMutation.mutateAsync(); + await createEoaMutation.mutateAsync({ + managementAccessToken: initResult.managementAccessToken.accessToken, + }); + // } else { + // await createEoaMutation.mutateAsync({ + // managementAccessToken: props.managementAccessToken, + // }); + // } }; const handleCloseModal = () => { diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/page.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/page.tsx index f17d16ba9b4..5a28bcab2a2 100644 --- a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/page.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/page.tsx @@ -56,8 +56,7 @@ export default async function TransactionsServerWalletsPage(props: {
diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/wallet-table/wallet-table.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/wallet-table/wallet-table.tsx index 8c1f03ea62a..2645e1fd1c6 100644 --- a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/wallet-table/wallet-table.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/wallet-table/wallet-table.tsx @@ -1,22 +1,20 @@ +import type { Project } from "@/api/projects"; import type { Wallet } from "./types"; import { ServerWalletsTableUI } from "./wallet-table-ui.client"; export function ServerWalletsTable({ wallets, - projectId, - teamId, + project, managementAccessToken, }: { wallets: Wallet[]; - projectId: string; - teamId: string; + project: Project; managementAccessToken: string | undefined; }) { return ( ); From d5a55e6a418d94fa4f1a24b30adfac996d47c233 Mon Sep 17 00:00:00 2001 From: Joaquim Verges Date: Thu, 10 Apr 2025 09:42:20 +1200 Subject: [PATCH 012/101] tabIndex --- .../@/components/blocks/wallet-address.tsx | 2 +- .../components/send-dummy-tx.client.tsx | 64 +++++++++++++------ .../server-wallets/components/try-it-out.tsx | 14 +++- .../transactions/server-wallets/page.tsx | 7 +- 4 files changed, 64 insertions(+), 23 deletions(-) diff --git a/apps/dashboard/src/@/components/blocks/wallet-address.tsx b/apps/dashboard/src/@/components/blocks/wallet-address.tsx index 9ff08bdea9d..dfedc9d6bae 100644 --- a/apps/dashboard/src/@/components/blocks/wallet-address.tsx +++ b/apps/dashboard/src/@/components/blocks/wallet-address.tsx @@ -166,7 +166,7 @@ export function WalletAddress(props: { ); } -function WalletAvatar(props: { +export function WalletAvatar(props: { address: string; profiles: SocialProfile[]; thirdwebClient: ThirdwebClient; diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/send-dummy-tx.client.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/send-dummy-tx.client.tsx index cd6503cea44..c39a4a4b67e 100644 --- a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/send-dummy-tx.client.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/send-dummy-tx.client.tsx @@ -1,7 +1,9 @@ "use client"; +import { WalletAvatar } from "@/components/blocks/wallet-address"; import { Spinner } from "@/components/ui/Spinner/Spinner"; import {} from "@/components/ui/alert"; import { Button } from "@/components/ui/button"; +import { CodeClient } from "@/components/ui/code/code.client"; import { Dialog, DialogContent, @@ -10,20 +12,26 @@ import { } from "@/components/ui/dialog"; import { Input } from "@/components/ui/input"; import { THIRDWEB_ENGINE_CLOUD_URL } from "@/constants/env"; +import { useThirdwebClient } from "@/constants/thirdweb.client"; import { useMutation } from "@tanstack/react-query"; import { Loader2 } from "lucide-react"; import { useState } from "react"; import { toast } from "sonner"; -import { WalletAddress } from "../../../../../../../@/components/blocks/wallet-address"; -import { CodeClient } from "../../../../../../../@/components/ui/code/code.client"; +import { shortenAddress } from "thirdweb/utils"; +import { useDashboardRouter } from "../../../../../../../@/lib/DashboardRouter"; import type { Wallet } from "../wallet-table/types"; export default function SendDummyTx(props: { authToken: string; wallet: Wallet; + team_slug: string; + project_slug: string; }) { + const router = useDashboardRouter(); + const thirdwebClient = useThirdwebClient(); const [modalOpen, setModalOpen] = useState(false); const [accessToken, setAccessToken] = useState(""); + const [projectSecretKey, setProjectSecretKey] = useState(""); const sendDummyTxMutation = useMutation({ mutationFn: async (args: { walletAddress: string; @@ -35,7 +43,7 @@ export default function SendDummyTx(props: { method: "POST", headers: { "Content-Type": "application/json", - Authorization: `Bearer ${props.authToken}`, + "x-secret-key": projectSecretKey, }, body: JSON.stringify({ executionOptions: { @@ -70,7 +78,10 @@ export default function SendDummyTx(props: { <>
@@ -126,14 +139,27 @@ export default function SendDummyTx(props: {

Sending from

- +
+ + + {shortenAddress(props.wallet.address)} + +

- This action requries a wallet access token. + This action requries a project secret key and a wallet access + token.

+ setProjectSecretKey(e.target.value)} + />
@@ -29,7 +34,12 @@ export function TryItOut(props: { authToken: string; wallet?: Wallet }) {
{props.wallet && ( - + )}
diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/page.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/page.tsx index 5a28bcab2a2..190da95718e 100644 --- a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/page.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/page.tsx @@ -64,7 +64,12 @@ export default async function TransactionsServerWalletsPage(props: { projectId={project.id} teamId={project.teamId} /> - +
)} From 515234ebef75b1b8b4e8ffaad572c935fbfe17b2 Mon Sep 17 00:00:00 2001 From: Joaquim Verges Date: Thu, 10 Apr 2025 13:21:57 +1200 Subject: [PATCH 013/101] hide admin key section if no key present --- .../transactions/server-wallets/components/key-management.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/key-management.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/key-management.tsx index 372eaa4fe09..0f4991db54c 100644 --- a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/key-management.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/key-management.tsx @@ -5,7 +5,7 @@ export function KeyManagement({ projectId, teamId, }: { maskedAdminKey?: string; projectId: string; teamId: string }) { - return ( + return maskedAdminKey ? (
@@ -26,5 +26,5 @@ export function KeyManagement({

{maskedAdminKey}

- ); + ) : null; } From fedc6975f8fe9351914a8552421691a4b8683c6d Mon Sep 17 00:00:00 2001 From: Prithvish Baidya Date: Thu, 10 Apr 2025 08:56:19 +0530 Subject: [PATCH 014/101] transaction querying --- apps/dashboard/src/@/actions/proxies.ts | 6 +- .../transactions/analytics/analytics-page.tsx | 4 +- .../analytics/tx-table/tx-table-ui.tsx | 184 ++---- .../analytics/tx-table/tx-table.tsx | 562 ++---------------- .../[project_slug]/transactions/page.tsx | 7 +- 5 files changed, 102 insertions(+), 661 deletions(-) diff --git a/apps/dashboard/src/@/actions/proxies.ts b/apps/dashboard/src/@/actions/proxies.ts index 9e705cbce8b..130e3c1d804 100644 --- a/apps/dashboard/src/@/actions/proxies.ts +++ b/apps/dashboard/src/@/actions/proxies.ts @@ -1,7 +1,7 @@ "use server"; import { getAuthToken } from "../../app/(app)/api/lib/getAuthToken"; -import { API_SERVER_URL } from "../constants/env"; +import { API_SERVER_URL, THIRDWEB_ENGINE_CLOUD_URL } from "../constants/env"; type ProxyActionParams = { pathname: string; @@ -79,6 +79,10 @@ export async function apiServerProxy(params: ProxyActionParams) { return proxy(API_SERVER_URL, params); } +export async function engineCloudProxy(params: ProxyActionParams) { + return proxy(THIRDWEB_ENGINE_CLOUD_URL, params); +} + export async function payServerProxy(params: ProxyActionParams) { return proxy( process.env.NEXT_PUBLIC_PAY_URL diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/analytics/analytics-page.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/analytics/analytics-page.tsx index addfe533572..0f42b7b9069 100644 --- a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/analytics/analytics-page.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/analytics/analytics-page.tsx @@ -9,6 +9,8 @@ export function TransactionsAnalyticsPageContent(props: { to?: string | undefined | string[]; interval?: string | undefined | string[]; }; + teamId: string; + clientId: string; }) { return ( @@ -19,7 +21,7 @@ export function TransactionsAnalyticsPageContent(props: {
- +
diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/analytics/tx-table/tx-table-ui.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/analytics/tx-table/tx-table-ui.tsx index d51b5505298..86de2792d03 100644 --- a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/analytics/tx-table/tx-table-ui.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/analytics/tx-table/tx-table-ui.tsx @@ -35,133 +35,33 @@ import { useAllChainsData } from "hooks/chains/allChains"; import { ExternalLinkIcon, InfoIcon } from "lucide-react"; import Link from "next/link"; import { useState } from "react"; - -// Copied from Engine overview page - -type Transaction = { - queueId?: string | null; - chainId?: string | null; - fromAddress?: string | null; - // toAddress?: string | null; - // data?: string | null; - // extension?: string | null; - // value?: string | null; - // nonce?: number | null; - // gasLimit?: string | null; - // gasPrice?: string | null; - // maxFeePerGas?: string | null; - // maxPriorityFeePerGas?: string | null; - // effectiveGasPrice?: string | null; - // transactionType?: number | null; - transactionHash?: string | null; - queuedAt?: string | null; - // processedAt?: string | null; - // sentAt?: string | null; - minedAt?: string | null; - // cancelledAt?: string | null; - // deployedContractAddress?: string | null; - // deployedContractType?: string | null; - errorMessage?: string | null; - // sentAtBlockNumber?: number | null; - blockNumber?: number | null; - status?: string | null; - // retryCount: number; - // retryGasValues?: boolean | null; - // retryMaxFeePerGas?: string | null; - // retryMaxPriorityFeePerGas?: string | null; - // signerAddress?: string | null; - // accountAddress?: string | null; - // target?: string | null; - // sender?: string | null; - // initCode?: string | null; - // callData?: string | null; - // callGasLimit?: string | null; - // verificationGasLimit?: string | null; - // preVerificationGas?: string | null; - // paymasterAndData?: string | null; - // userOpHash?: string | null; - // functionName?: string | null; - // functionArgs?: string | null; -}; - -type TransactionResponse = { - transactions: Transaction[]; - totalCount: number; -}; - -type EngineStatus = - | "errored" - | "mined" - | "cancelled" - | "sent" - | "retried" - | "processed" - | "queued" - | "user-op-sent"; - -const statusDetails: Record< - EngineStatus, - { - name: string; - type: "success" | "destructive" | "warning"; - } -> = { - mined: { - name: "Mined", - type: "success", - }, - errored: { - name: "Failed", - type: "destructive", - }, - cancelled: { - name: "Cancelled", - type: "destructive", - }, - queued: { - name: "Queued", - type: "warning", - }, - processed: { - name: "Processed", - type: "warning", - }, - sent: { - name: "Sent", - type: "warning", - }, - "user-op-sent": { - name: "User Op Sent", - type: "warning", - }, - retried: { - name: "Retried", - type: "success", - }, -}; +import type { + Transaction, + TransactionsResponse, + TransactionStatus, +} from "./types"; // TODO - add Status selector dropdown here export function TransactionsTableUI(props: { - getData: (params: { - page: number; - status: EngineStatus | undefined; - }) => Promise; + getData: (params: { page: number }) => Promise; }) { const [autoUpdate, setAutoUpdate] = useState(true); + const [status, setStatus] = useState( + undefined, + ); const [page, setPage] = useState(1); - const [status, setStatus] = useState(undefined); const pageSize = 10; const transactionsQuery = useQuery({ - queryKey: ["transactions", page, status], - queryFn: () => props.getData({ page, status }), + queryKey: ["transactions", page], + queryFn: () => props.getData({ page }), refetchInterval: autoUpdate ? 4_000 : false, placeholderData: keepPreviousData, }); const transactions = transactionsQuery.data?.transactions ?? []; - const totalCount = transactionsQuery.data?.totalCount ?? 0; + const totalCount = transactionsQuery.data?.pagination.totalCount ?? 0; const totalPages = Math.ceil(totalCount / pageSize); const showPagination = totalCount > pageSize; @@ -225,13 +125,13 @@ export function TransactionsTableUI(props: { <> {transactions.map((tx) => ( {/* Queue ID */} - {tx.fromAddress ? ( - - ) : ( - "N/A" - )} + {tx.from ? : "N/A"} {/* Tx Hash */} @@ -293,11 +189,30 @@ export function TransactionsTableUI(props: { ); } +const statusDetails = { + QUEUED: { + name: "Queued", + type: "warning", + }, + SUBMITTED: { + name: "Submitted", + type: "warning", + }, + CONFIRMED: { + name: "Confirmed", + type: "success", + }, + REVERTED: { + name: "Reverted", + type: "destructive", + }, +} as const; + function StatusSelector(props: { - status: EngineStatus | undefined; - setStatus: (value: EngineStatus | undefined) => void; + status: TransactionStatus | undefined; + setStatus: (value: TransactionStatus | undefined) => void; }) { - const statuses = Object.keys(statusDetails) as EngineStatus[]; + const statuses = Object.keys(statusDetails) as TransactionStatus[]; return ( setAccessToken(e.target.value)} /> From f6eb80ed118c92666ad01106c9fb4aeb1f79e7a9 Mon Sep 17 00:00:00 2001 From: Joaquim Verges Date: Fri, 18 Apr 2025 15:15:51 +1200 Subject: [PATCH 024/101] access token managment --- .../transactions/analytics/summary.tsx | 4 +- .../[project_slug]/transactions/layout.tsx | 22 +- .../components/key-management.tsx | 28 ++- ...ient.tsx => list-access-tokens.client.tsx} | 235 ++++++++++++------ 4 files changed, 197 insertions(+), 92 deletions(-) rename apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/{create-access-token.client.tsx => list-access-tokens.client.tsx} (55%) diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/analytics/summary.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/analytics/summary.tsx index 42b1910657a..608816aad44 100644 --- a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/analytics/summary.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/analytics/summary.tsx @@ -126,7 +126,7 @@ function TransactionAnalyticsSummaryUI(props: { isPending={props.isPending} /> v.toFixed(12)} + formatter={(v: number) => `${v.toFixed(10)} ETH`} icon={CoinsIcon} // Pass the formatter that handles the type juggling isPending={props.isPending} diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/layout.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/layout.tsx index e7bfeac4a89..8ad26b37da7 100644 --- a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/layout.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/layout.tsx @@ -1,8 +1,10 @@ import { getProject } from "@/api/projects"; import { getTeamBySlug } from "@/api/team"; import { TabPathLinks } from "@/components/ui/tabs"; +import { CloudIcon } from "lucide-react"; import Link from "next/link"; import { redirect } from "next/navigation"; +import { Badge } from "../../../../../@/components/ui/badge"; import { THIRDWEB_ENGINE_CLOUD_URL } from "../../../../../@/constants/env"; export default async function Page(props: { @@ -53,13 +55,19 @@ function TransactionsLayout(props: {

Transactions

- - {THIRDWEB_ENGINE_CLOUD_URL} - +
+ + {THIRDWEB_ENGINE_CLOUD_URL} + + + + Cloud + +
diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/key-management.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/key-management.tsx index 0f4991db54c..74ae13c459d 100644 --- a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/key-management.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/key-management.tsx @@ -1,4 +1,6 @@ -import CreateAccessToken from "./create-access-token.client"; +import { Button } from "@/components/ui/button"; +import { RefreshCcwIcon } from "lucide-react"; +import ListAccessTokensButton from "./list-access-tokens.client"; export function KeyManagement({ maskedAdminKey, @@ -6,8 +8,8 @@ export function KeyManagement({ teamId, }: { maskedAdminKey?: string; projectId: string; teamId: string }) { return maskedAdminKey ? ( -
-
+
+

@@ -18,12 +20,22 @@ export function KeyManagement({

- {/* TODO */} -
-
-

Admin Key

-

{maskedAdminKey}

+
+
+

Vault Admin Key

+

{maskedAdminKey}

+
+ +
+
+
) : null; diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/create-access-token.client.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/list-access-tokens.client.tsx similarity index 55% rename from apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/create-access-token.client.tsx rename to apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/list-access-tokens.client.tsx index cc140ebee5d..529e0448b86 100644 --- a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/create-access-token.client.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/list-access-tokens.client.tsx @@ -2,10 +2,7 @@ import { CopyTextButton } from "@/components/ui/CopyTextButton"; import { Spinner } from "@/components/ui/Spinner/Spinner"; -import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; import { Button } from "@/components/ui/button"; -import { CheckboxWithLabel } from "@/components/ui/checkbox"; -import { Checkbox } from "@/components/ui/checkbox"; import { Dialog, DialogContent, @@ -13,21 +10,23 @@ import { DialogTitle, } from "@/components/ui/dialog"; import { Input } from "@/components/ui/input"; -import { THIRDWEB_VAULT_URL } from "@/constants/env"; -import { cn } from "@/lib/utils"; -import { useMutation } from "@tanstack/react-query"; -import { createAccessToken, createVaultClient } from "@thirdweb-dev/vault-sdk"; -import { Loader2 } from "lucide-react"; +import { useMutation, useQuery } from "@tanstack/react-query"; +import { createAccessToken, revokeAccessToken } from "@thirdweb-dev/vault-sdk"; +import { createVaultClient } from "@thirdweb-dev/vault-sdk"; +import { Loader2, Trash2 } from "lucide-react"; import { useState } from "react"; import { toast } from "sonner"; +import { THIRDWEB_VAULT_URL } from "../../../../../../../@/constants/env"; -export default function CreateAccessToken(props: { +export default function ListAccessTokensButton(props: { projectId: string; teamId: string; }) { const [modalOpen, setModalOpen] = useState(false); - const [keysConfirmed, setKeysConfirmed] = useState(false); + const [typedAdminKey, setTypedAdminKey] = useState(""); const [adminKey, setAdminKey] = useState(""); + const [deletingTokenId, setDeletingTokenId] = useState(null); + // TODO allow passing permissions to the access token const createAccessTokenMutation = useMutation({ mutationFn: async (args: { @@ -187,96 +186,176 @@ export default function CreateAccessToken(props: { onError: (error) => { toast.error(error.message); }, + onSuccess: () => { + listAccessTokensQuery.refetch(); + }, + }); + + const revokeAccessTokenMutation = useMutation({ + mutationFn: async (args: { + adminKey: string; + accessTokenId: string; + }) => { + setDeletingTokenId(args.accessTokenId); + const vaultClient = await createVaultClient({ + baseUrl: THIRDWEB_VAULT_URL, + }); + + const revokeAccessTokenRes = await revokeAccessToken({ + client: vaultClient, + request: { + options: { + id: args.accessTokenId, + }, + auth: { + adminKey: args.adminKey, + }, + }, + }); + + if (!revokeAccessTokenRes.success) { + throw new Error( + `Failed to revoke access token: ${revokeAccessTokenRes.error.message}`, + ); + } + + return { + success: true, + }; + }, + onError: (error) => { + toast.error(error.message); + setDeletingTokenId(null); + }, + onSuccess: () => { + listAccessTokensQuery.refetch(); + setDeletingTokenId(null); + }, + }); + + // Stub data for now + const listAccessTokensQuery = useQuery({ + queryKey: ["list-access-tokens", maskSecret(adminKey)], + queryFn: async () => { + // TODO (engine-cloud): need the command in vault + await new Promise((resolve) => setTimeout(resolve, 1000)); + // Return stub data for now + return { + accessTokens: [ + { + key: "stub_1234567890abcdef", + id: "token_1", + }, + { + key: "stub_1234567890abcdef", + id: "token_2", + }, + { + key: "stub_1234567890abcdef", + id: "token_3", + }, + ], + }; + }, + enabled: !!adminKey, }); const handleCloseModal = () => { - if (!keysConfirmed) { - return; - } setModalOpen(false); - setKeysConfirmed(false); + setAdminKey(""); + setTypedAdminKey(""); }; - const isLoading = createAccessTokenMutation.isPending; + const isLoading = listAccessTokensQuery.isLoading; return ( <> - + - Create Wallet Access Token + Vault Access Tokens - {createAccessTokenMutation.isPending ? ( + {listAccessTokensQuery.isLoading ? (
- Generating your access token + Loading access tokens
- ) : createAccessTokenMutation.data ? ( + ) : listAccessTokensQuery.data ? (
-

- Wallet Access Token -

- ( +
+ + +
+ ))} +
+

+ These access tokens can be used for all server wallets + created within this project (more granular permissions + coming soon). +

- - Secure your keys - - These keys will not be displayed again. Store them securely - as they provide access to your server wallets. - -
- - setKeysConfirmed(!!v)} - /> - I confirm that I've securely stored these keys - -
-
@@ -285,19 +364,25 @@ export default function CreateAccessToken(props: {

- This action requries your admin key. + This action requires your Vault admin key.

setAdminKey(e.target.value)} + placeholder="Enter your Vault Admin Key" + value={typedAdminKey} + onChange={(e) => setTypedAdminKey(e.target.value)} + onKeyDown={(e) => { + if (e.key === "Enter") { + setAdminKey(typedAdminKey); + } + }} />
From e4725bb649127f8129e43973b22db7ed8a5b6a4c Mon Sep 17 00:00:00 2001 From: Joaquim Verges Date: Fri, 18 Apr 2025 15:36:40 +1200 Subject: [PATCH 025/101] rotate admin button --- .../components/key-management.tsx | 9 +- .../components/list-access-tokens.client.tsx | 4 +- .../components/rotate-admin-key.client.tsx | 247 ++++++++++++++++++ 3 files changed, 251 insertions(+), 9 deletions(-) create mode 100644 apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/rotate-admin-key.client.tsx diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/key-management.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/key-management.tsx index 74ae13c459d..eab3a6f7eda 100644 --- a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/key-management.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/key-management.tsx @@ -1,6 +1,7 @@ import { Button } from "@/components/ui/button"; import { RefreshCcwIcon } from "lucide-react"; import ListAccessTokensButton from "./list-access-tokens.client"; +import RotateAdminKeyButton from "./rotate-admin-key.client"; export function KeyManagement({ maskedAdminKey, @@ -26,13 +27,7 @@ export function KeyManagement({

Vault Admin Key

{maskedAdminKey}

- +
diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/list-access-tokens.client.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/list-access-tokens.client.tsx index 529e0448b86..50bf407db74 100644 --- a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/list-access-tokens.client.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/list-access-tokens.client.tsx @@ -368,7 +368,7 @@ export default function ListAccessTokensButton(props: {

setTypedAdminKey(e.target.value)} onKeyDown={(e) => { @@ -401,7 +401,7 @@ export default function ListAccessTokensButton(props: { Loading... ) : ( - "List Tokens" + "Manage Access Tokens" )}
diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/rotate-admin-key.client.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/rotate-admin-key.client.tsx new file mode 100644 index 00000000000..16cfc771e80 --- /dev/null +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/rotate-admin-key.client.tsx @@ -0,0 +1,247 @@ +"use client"; + +import { CopyTextButton } from "@/components/ui/CopyTextButton"; +import { Spinner } from "@/components/ui/Spinner/Spinner"; +import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; +import { Button } from "@/components/ui/button"; +import { Checkbox, CheckboxWithLabel } from "@/components/ui/checkbox"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { Input } from "@/components/ui/input"; +import { cn } from "@/lib/utils"; +import { useMutation } from "@tanstack/react-query"; +import { + createVaultClient, + rotateServiceAccount, +} from "@thirdweb-dev/vault-sdk"; +import { Loader2, RefreshCcwIcon } from "lucide-react"; +import { useState } from "react"; +import { toast } from "sonner"; +import { THIRDWEB_VAULT_URL } from "../../../../../../../@/constants/env"; + +export default function RotateAdminKeyButton() { + const [modalOpen, setModalOpen] = useState(false); + const [rotationCode, setRotationCode] = useState(""); + const [keysConfirmed, setKeysConfirmed] = useState(false); + + const rotateAdminKeyMutation = useMutation({ + mutationFn: async () => { + if (!rotationCode) { + throw new Error("Rotation code is required"); + } + await new Promise((resolve) => setTimeout(resolve, 1000)); + + const client = await createVaultClient({ + baseUrl: THIRDWEB_VAULT_URL, + }); + + const rotateServiceAccountRes = await rotateServiceAccount({ + client, + request: { + auth: { + rotationCode, + }, + }, + }); + + if (rotateServiceAccountRes.error) { + throw new Error(rotateServiceAccountRes.error.message); + } + + return { + success: true, + adminKey: rotateServiceAccountRes.data.newAdminKey, + rotationKey: rotateServiceAccountRes.data.newRotationCode, + }; + }, + onError: (error) => { + toast.error(error.message); + }, + }); + + const handleCloseModal = () => { + if (!keysConfirmed) { + return; + } + setModalOpen(false); + setRotationCode(""); + setKeysConfirmed(false); + }; + + const isLoading = rotateAdminKeyMutation.isPending; + + return ( + <> + + + + + {rotateAdminKeyMutation.isPending ? ( + <> + + Generating new keys... + +
+ +

+ This may take a few seconds. +

+
+ + ) : rotateAdminKeyMutation.data ? ( +
+ + New Vault Keys + + +
+
+
+

+ New Vault Admin Key +

+
+ +

+ This key is used to create or revoke your access tokens. +

+
+
+ +
+

+ New Rotation Key +

+
+ +

+ This key is used to rotate your admin key in the future. +

+
+
+
+ + Secure your keys + + These keys will not be displayed again. Store them securely + as they provide access to your server wallets. + +
+ + setKeysConfirmed(!!v)} + /> + I confirm that I've securely stored these keys + + +
+ +
+ +
+
+ ) : ( + <> + + Rotate your Vault admin key + + This action will generate a new Vault admin key and rotation + code. This will invalidate all existing access tokens. + + +
+
+

+ This action requires your Vault rotation code. +

+ setRotationCode(e.target.value)} + onKeyDown={(e) => { + if (e.key === "Enter") { + rotateAdminKeyMutation.mutate(); + } + }} + /> +
+ + +
+
+
+ + )} + +
+ + ); +} + +function maskSecret(secret: string) { + return `${secret.substring(0, 11)}...${secret.substring(secret.length - 5)}`; +} From f866ad38532f4f6d9c834479bba59abf35727f37 Mon Sep 17 00:00:00 2001 From: Joaquim Verges Date: Fri, 18 Apr 2025 23:00:35 +1200 Subject: [PATCH 026/101] add scalar react component --- apps/dashboard/package.json | 1 + .../transactions/explorer/page.tsx | 15 +- .../[project_slug]/transactions/layout.tsx | 4 +- pnpm-lock.yaml | 1534 ++++++++++++++++- 4 files changed, 1532 insertions(+), 22 deletions(-) diff --git a/apps/dashboard/package.json b/apps/dashboard/package.json index 6554daa8baf..f4060d10242 100644 --- a/apps/dashboard/package.json +++ b/apps/dashboard/package.json @@ -45,6 +45,7 @@ "@radix-ui/react-slot": "^1.2.0", "@radix-ui/react-switch": "^1.2.2", "@radix-ui/react-tooltip": "1.2.3", + "@scalar/api-reference-react": "^0.6.19", "@sentry/nextjs": "9.13.0", "@shazow/whatsabi": "0.21.0", "@tanstack/react-query": "5.74.4", diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/explorer/page.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/explorer/page.tsx index b17aab885d7..2eb65330ae5 100644 --- a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/explorer/page.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/explorer/page.tsx @@ -1,3 +1,16 @@ +"use client"; +import { ApiReferenceReact } from "@scalar/api-reference-react"; +import "@scalar/api-reference-react/style.css"; +import { THIRDWEB_ENGINE_CLOUD_URL } from "@/constants/env"; + export default function TransactionsExplorerPage() { - return
Explorer
; + return ( +
+ +
+ ); } diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/layout.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/layout.tsx index 8ad26b37da7..95d6e7ab6da 100644 --- a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/layout.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/layout.tsx @@ -4,8 +4,8 @@ import { TabPathLinks } from "@/components/ui/tabs"; import { CloudIcon } from "lucide-react"; import Link from "next/link"; import { redirect } from "next/navigation"; -import { Badge } from "../../../../../@/components/ui/badge"; -import { THIRDWEB_ENGINE_CLOUD_URL } from "../../../../../@/constants/env"; +import { Badge } from "@/components/ui/badge"; +import { THIRDWEB_ENGINE_CLOUD_URL } from "@/constants/env"; export default async function Page(props: { params: Promise<{ team_slug: string; project_slug: string }>; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2178efeabd7..215be2f30fd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -130,6 +130,9 @@ importers: '@radix-ui/react-tooltip': specifier: 1.2.3 version: 1.2.3(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@scalar/api-reference-react': + specifier: ^0.6.19 + version: 0.6.19(@hyperjump/browser@1.3.0)(axios@1.8.4)(idb-keyval@6.2.1)(nprogress@0.2.0)(qrcode@1.5.4)(react@19.1.0)(tailwindcss@3.4.17)(typescript@5.8.3) '@sentry/nextjs': specifier: 9.13.0 version: 9.13.0(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@15.3.1(@babel/core@7.26.10)(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0)(webpack@5.99.6(esbuild@0.25.2)) @@ -1311,10 +1314,10 @@ importers: version: 1.7.1 abitype: specifier: 1.0.8 - version: 1.0.8(typescript@5.8.3)(zod@3.24.2) + version: 1.0.8(typescript@5.8.3)(zod@3.24.3) ky: specifier: ^1.8.0 - version: 1.8.0 + version: 1.8.1 typescript: specifier: '>=5.0.4' version: 5.8.3 @@ -2551,6 +2554,45 @@ packages: '@cloudflare/workers-types@4.20250421.0': resolution: {integrity: sha512-GYSmQxF9ZE2kkmjCSiYTFdcXUHPeBrz9XvKhqGLTKT8ITYxN5kXy9OYlpkhtVWWUE+XNq7qm5uSKwluaOBKoAw==} + '@codemirror/autocomplete@6.18.6': + resolution: {integrity: sha512-PHHBXFomUs5DF+9tCOM/UoW6XQ4R44lLNNhRaW9PKPTU0D7lIjRg3ElxaJnTwsl/oHiR93WSXDBrekhoUGCPtg==} + + '@codemirror/commands@6.8.1': + resolution: {integrity: sha512-KlGVYufHMQzxbdQONiLyGQDUW0itrLZwq3CcY7xpv9ZLRHqzkBSoteocBHtMCoY7/Ci4xhzSrToIeLg7FxHuaw==} + + '@codemirror/lang-css@6.3.1': + resolution: {integrity: sha512-kr5fwBGiGtmz6l0LSJIbno9QrifNMUusivHbnA1H6Dmqy4HZFte3UAICix1VuKo0lMPKQr2rqB+0BkKi/S3Ejg==} + + '@codemirror/lang-html@6.4.9': + resolution: {integrity: sha512-aQv37pIMSlueybId/2PVSP6NPnmurFDVmZwzc7jszd2KAF8qd4VBbvNYPXWQq90WIARjsdVkPbw29pszmHws3Q==} + + '@codemirror/lang-javascript@6.2.3': + resolution: {integrity: sha512-8PR3vIWg7pSu7ur8A07pGiYHgy3hHj+mRYRCSG8q+mPIrl0F02rgpGv+DsQTHRTc30rydOsf5PZ7yjKFg2Ackw==} + + '@codemirror/lang-json@6.0.1': + resolution: {integrity: sha512-+T1flHdgpqDDlJZ2Lkil/rLiRy684WMLc74xUnjJH48GQdfJo/pudlTRreZmKwzP8/tGdKf83wlbAdOCzlJOGQ==} + + '@codemirror/lang-xml@6.1.0': + resolution: {integrity: sha512-3z0blhicHLfwi2UgkZYRPioSgVTo9PV5GP5ducFH6FaHy0IAJRg+ixj5gTR1gnT/glAIC8xv4w2VL1LoZfs+Jg==} + + '@codemirror/lang-yaml@6.1.2': + resolution: {integrity: sha512-dxrfG8w5Ce/QbT7YID7mWZFKhdhsaTNOYjOkSIMt1qmC4VQnXSDSYVHHHn8k6kJUfIhtLo8t1JJgltlxWdsITw==} + + '@codemirror/language@6.11.0': + resolution: {integrity: sha512-A7+f++LodNNc1wGgoRDTt78cOwWm9KVezApgjOMp1W4hM0898nsqBXwF+sbePE7ZRcjN7Sa1Z5m2oN27XkmEjQ==} + + '@codemirror/lint@6.8.5': + resolution: {integrity: sha512-s3n3KisH7dx3vsoeGMxsbRAgKe4O1vbrnKBClm99PU0fWxmxsx5rR2PfqQgIt+2MMJBHbiJ5rfIdLYfB9NNvsA==} + + '@codemirror/search@6.5.10': + resolution: {integrity: sha512-RMdPdmsrUf53pb2VwflKGHEe1XVM07hI7vV2ntgw1dmqhimpatSJKva4VA9h4TLUDOD4EIF02201oZurpnEFsg==} + + '@codemirror/state@6.5.2': + resolution: {integrity: sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==} + + '@codemirror/view@6.36.5': + resolution: {integrity: sha512-cd+FZEUlu3GQCYnguYm3EkhJ8KJVisqqUsCOKedBoAt/d9c76JUUap6U0UrpElln5k6VyrEOYliMuDAKIeDQLg==} + '@codspeed/core@4.0.1': resolution: {integrity: sha512-fJ53arfgtzCDZa8DuGJhpTZ3Ll9A1uW5nQ2jSJnfO4Hl5MRD2cP8P4vPvIUAGbdbjwCxR1jat6cW8OloMJkJXw==} @@ -3234,6 +3276,9 @@ packages: '@floating-ui/utils@0.2.9': resolution: {integrity: sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==} + '@floating-ui/vue@1.1.6': + resolution: {integrity: sha512-XFlUzGHGv12zbgHNk5FN2mUB7ROul3oG2ENdTpWdE+qMFxyNxWSRmsoyhiEnpmabNm6WnUvR1OvJfUfN4ojC1A==} + '@gerrit0/mini-shiki@1.27.2': resolution: {integrity: sha512-GeWyHz8ao2gBiUW4OJnQDxXQnFgZQwwQk05t/CVVgNBN7/rK8XZ7xY6YhLVv9tH3VppWWmr9DCl3MwemB/i+Og==} @@ -3244,6 +3289,18 @@ packages: react: ^18 || ^19 || ^19.0.0-rc react-dom: ^18 || ^19 || ^19.0.0-rc + '@headlessui/tailwindcss@0.2.2': + resolution: {integrity: sha512-xNe42KjdyA4kfUKLLPGzME9zkH7Q3rOZ5huFihWNWOQFxnItxPB3/67yBI8/qBfY8nwBRx5GHn4VprsoluVMGw==} + engines: {node: '>=10'} + peerDependencies: + tailwindcss: ^3.0 || ^4.0 + + '@headlessui/vue@1.7.23': + resolution: {integrity: sha512-JzdCNqurrtuu0YW6QaDtR2PIYCKPUWq28csDyMvN4zmGccmE7lz40Is6hc3LA4HFeCI7sekZ/PQMTNmn9I/4Wg==} + engines: {node: '>=10'} + peerDependencies: + vue: ^3.2.0 + '@heroicons/react@2.2.0': resolution: {integrity: sha512-LMcepvRaS9LYHJGsF0zzmgKCUim/X3N/DQKc4jepAXJ7l8QxJ1PmxJzqplF2Z3FE4PqBAIGyJAQ/w4B5dsqbtQ==} peerDependencies: @@ -3299,6 +3356,24 @@ packages: resolution: {integrity: sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==} engines: {node: '>=18.18'} + '@hyperjump/browser@1.3.0': + resolution: {integrity: sha512-bf2ZTqpjfvcEq3DAZSg1h0FuliNUddR6nDPuaPb9qNoPPBQQzD1ldtuXX0QggXKQZl0OgsI3eovGCR3Dl5kToA==} + engines: {node: '>=18.0.0'} + + '@hyperjump/json-pointer@1.1.0': + resolution: {integrity: sha512-tFCKxMKDKK3VEdtUA3EBOS9GmSOS4mbrTjh9v3RnK10BphDMOb6+bxTh++/ae1AyfHyWb6R54O/iaoAtPMZPCg==} + + '@hyperjump/json-schema@1.12.1': + resolution: {integrity: sha512-kbXbFsU7m4xPYk2ku0lgnBD8d+P2g4yS8imn8CF16Zf09gbAfeJpmiodbAXOT0e8j00AiW76McgBQdndEr0QbA==} + peerDependencies: + '@hyperjump/browser': ^1.1.0 + + '@hyperjump/pact@1.4.0': + resolution: {integrity: sha512-01Q7VY6BcAkp9W31Fv+ciiZycxZHGlR2N6ba9BifgyclHYHdbaZgITo0U6QMhYRlem4k8pf8J31/tApxvqAz8A==} + + '@hyperjump/uri@1.3.1': + resolution: {integrity: sha512-2ecKymxf6prQMgrNpAvlx4RhsuM5+PFT6oh6uUTZdv5qmBv0RZvxv8LJ7oR30ZxGhdPdZAl4We/1NFc0nqHeAw==} + '@img/sharp-darwin-arm64@0.33.5': resolution: {integrity: sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} @@ -3635,6 +3710,12 @@ packages: '@types/node': optional: true + '@internationalized/date@3.8.0': + resolution: {integrity: sha512-J51AJ0fEL68hE4CwGPa6E0PO6JDaVLd8aln48xFCSy7CZkZc96dGEGmLs2OEEbBxcsVZtfrqkXJwI2/MSG8yKw==} + + '@internationalized/number@3.6.1': + resolution: {integrity: sha512-UVsb4bCwbL944E0SX50CHFtWEeZ2uB5VozZ5yDXJdq6iPZsZO5p+bjVMZh2GxHf4Bs/7xtDCcPwEa2NU9DaG/g==} + '@ioredis/commands@1.2.0': resolution: {integrity: sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==} @@ -3711,6 +3792,33 @@ packages: '@jsdevtools/ono@7.1.3': resolution: {integrity: sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==} + '@lezer/common@1.2.3': + resolution: {integrity: sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA==} + + '@lezer/css@1.1.11': + resolution: {integrity: sha512-FuAnusbLBl1SEAtfN8NdShxYJiESKw9LAFysfea1T96jD3ydBn12oYjaSG1a04BQRIUd93/0D8e5CV1cUMkmQg==} + + '@lezer/highlight@1.2.1': + resolution: {integrity: sha512-Z5duk4RN/3zuVO7Jq0pGLJ3qynpxUVsh7IbUbGj88+uV2ApSAn6kWg2au3iJb+0Zi7kKtqffIESgNcRXWZWmSA==} + + '@lezer/html@1.3.10': + resolution: {integrity: sha512-dqpT8nISx/p9Do3AchvYGV3qYc4/rKr3IBZxlHmpIKam56P47RSHkSF5f13Vu9hebS1jM0HmtJIwLbWz1VIY6w==} + + '@lezer/javascript@1.5.0': + resolution: {integrity: sha512-i/uZt1eoiojC3BRsjtiYZjT8DhzgZvWiKJjpXW3Jc2y4FkXY9YoBLKx+jSct+ynUINv5GtRxjTK7hBNhujPwrg==} + + '@lezer/json@1.0.3': + resolution: {integrity: sha512-BP9KzdF9Y35PDpv04r0VeSTKDeox5vVr3efE7eBbx3r4s3oNLfunchejZhjArmeieBH+nVOpgIiBJpEAv8ilqQ==} + + '@lezer/lr@1.4.2': + resolution: {integrity: sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA==} + + '@lezer/xml@1.0.6': + resolution: {integrity: sha512-CdDwirL0OEaStFue/66ZmFSeppuL6Dwjlk8qk153mSQwiSH/Dlri4GNymrNWnUmPl2Um7QfV1FO9KFUyX3Twww==} + + '@lezer/yaml@1.0.3': + resolution: {integrity: sha512-GuBLekbw9jDBDhGur82nuwkxKQ+a3W5H0GfaAthDXcAu+XdpS43VlnxA9E9hllkpSP5ellRDKjLLj7Lu9Wr6xA==} + '@lit-labs/ssr-dom-shim@1.3.0': resolution: {integrity: sha512-nQIWonJ6eFAvUUrSlwyHDm/aE8PBDu5kRpL0vHMg6K8fK3Diq1xdPjTnsJSwxABhaZ+5eBi1btQB5ShUTKo4nQ==} @@ -3742,6 +3850,9 @@ packages: resolution: {integrity: sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==} hasBin: true + '@marijn/find-cluster-break@1.0.2': + resolution: {integrity: sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==} + '@marsidev/react-turnstile@0.4.1': resolution: {integrity: sha512-uZusUW9mPr0csWpls8bApe5iuRK0YK7H1PCKqfM4djW3OA9GB9rU68irjk7xRO8qlHyj0aDTeVu9tTLPExBO4Q==} peerDependencies: @@ -5088,6 +5199,13 @@ packages: '@reown/appkit@1.7.3': resolution: {integrity: sha512-aA/UIwi/dVzxEB62xlw3qxHa3RK1YcPMjNxoGj/fHNCqL2qWmbcOXT7coCUa9RG7/Bh26FZ3vdVT2v71j6hebQ==} + '@replit/codemirror-css-color-picker@6.3.0': + resolution: {integrity: sha512-19biDANghUm7Fz7L1SNMIhK48tagaWuCOHj4oPPxc7hxPGkTVY2lU/jVZ8tsbTKQPVG7BO2CBDzs7CBwb20t4A==} + peerDependencies: + '@codemirror/language': ^6.0.0 + '@codemirror/state': ^6.0.0 + '@codemirror/view': ^6.0.0 + '@resvg/resvg-wasm@2.4.0': resolution: {integrity: sha512-C7c51Nn4yTxXFKvgh2txJFNweaVcfUPQxwEUFw4aWsCmfiBDJsTSwviIF8EcwjQ6k8bPyMWCl1vw4BdxE569Cg==} engines: {node: '>= 10'} @@ -5321,6 +5439,88 @@ packages: resolution: {integrity: sha512-7ojVK/crhOaGowEO8uYWaopZzcr5rR76emgllGIfjCLR70aY4PbASpi9Pbs+7jIRzPDBBkM0RBo+zYx5UduX8Q==} engines: {node: '>=16'} + '@scalar/api-client@2.3.22': + resolution: {integrity: sha512-9gMFjWog7t0lUbLwqhQRXw0wOEc03WfkXNRwrCuvEjKmm3FgDQELQhG6V1m2a7SO4nI+Wmj5ubJxN9dD3Ehtdg==} + engines: {node: '>=18'} + + '@scalar/api-reference-react@0.6.19': + resolution: {integrity: sha512-LDnPm1S9muFNNKZVI4l5K2o+kmv+IxrzyQWjxG3RjFdiVMVdRluw7fEQfxqnZrKZ1gKwnwg1+wW1Mq8uflDg5A==} + engines: {node: '>=18'} + peerDependencies: + react: ^18.0.0 || ^19.0.0 + + '@scalar/api-reference@1.28.22': + resolution: {integrity: sha512-RM1AZafAZeQdNhE+/zhb4ptRYBwmFU6PcJP6Tjub55jYBZp5tMe8/BCMZIgCucEEO8v9t17NIMs703SK5vSzrg==} + engines: {node: '>=18'} + + '@scalar/code-highlight@0.0.27': + resolution: {integrity: sha512-A61FUxqD278L+iLtdbMl4+Pg72wtMrnAYft8v1FNY44uf6UfmM47eDVmzWrc7bSvDevg3ho5QA8cKiJBHXZHJA==} + engines: {node: '>=18'} + + '@scalar/components@0.13.49': + resolution: {integrity: sha512-Y2w2/xxvrDTb+PkFR8/texZ93LnTkvhLMYFe6bDqlfwp3m4vrbHlzfAmKpzOcYWzQYgx9Gfi/8BXKlyzC5c7Ow==} + engines: {node: '>=18'} + + '@scalar/draggable@0.1.11': + resolution: {integrity: sha512-EQW9N1+mDORhsbjdtCI3XDvmUKsuKw1uf6r3kT1Mm2zQKT+rWwA0ChsAkEN6OG62C0YumMuXpH71h1seAWptxw==} + engines: {node: '>=18'} + + '@scalar/icons@0.1.3': + resolution: {integrity: sha512-Bl46u7WsJ7NYjW1Fva7SMvw9c/92pGBP8B68tvDc+QevQ04DVNxw6+ny1NU/PnLtpuu1rUpPdtSCAkV1OdQGZQ==} + engines: {node: '>=18'} + + '@scalar/import@0.3.16': + resolution: {integrity: sha512-UfjH8WBmAnb/0UwQZOv+mGbFdgQKMKIgziDR/LpTuQcGpR2MnYdQKTJ8KTmpYG3KURMzDpZ5iEJss4YGPBlTZw==} + engines: {node: '>=18'} + + '@scalar/oas-utils@0.2.133': + resolution: {integrity: sha512-5uYlFh9/P3iS+P03OYJxpvCtunc6hUqoFTxXOu4VQ4VTCPx2tR747RQE+5titMHD3dbBK7Mvf4U9RzW5/J2Glw==} + engines: {node: '>=18'} + + '@scalar/object-utils@1.1.13': + resolution: {integrity: sha512-311eTykIXgOtjCs4VTELj9UMT97jHTWc5qkGNoIzZ5nxjCcvOVe7kDQobIkE8dGT+ybOgHz5qly02Eu7nVHeZQ==} + engines: {node: '>=18'} + + '@scalar/openapi-parser@0.10.16': + resolution: {integrity: sha512-UF+noQXEEQ52lm2Uum7mxkZdvsl8pMCIoTtN9jCTAfjutRwUPo7vfuic4JK+ChFLHDZyiYXYsbCwlP+aLk+4Xg==} + engines: {node: '>=18'} + + '@scalar/openapi-types@0.2.0': + resolution: {integrity: sha512-waiKk12cRCqyUCWTOX0K1WEVX46+hVUK+zRPzAahDJ7G0TApvbNkuy5wx7aoUyEk++HHde0XuQnshXnt8jsddA==} + engines: {node: '>=18'} + + '@scalar/postman-to-openapi@0.2.6': + resolution: {integrity: sha512-MZFkvw60XzdJGwS2Wm5Jtcw3ZwYKMeZ2jInWfy3i9gHteSRtFF54keoXuxlIeoyLWWWBUS2rP4NGzEPaT6cQnw==} + engines: {node: '>=18'} + + '@scalar/snippetz@0.2.19': + resolution: {integrity: sha512-fxC5mL3AZWiXAM21sMe1QU1/mu5KceN8ZmzFaP3xmdK26o/MkPKSLGVWW7w6OQkZi5hNloLHXXQiaI235qomEg==} + engines: {node: '>=18'} + + '@scalar/themes@0.10.0': + resolution: {integrity: sha512-r6dNrIILuBckVSXUysm2VCPpKG+Sh/+XYV3U8R18lpRga/0Qxq9VFMTxO8xMAyVupErxRlzrBoVlmnia8YruIw==} + engines: {node: '>=18'} + + '@scalar/types@0.1.8': + resolution: {integrity: sha512-VL1dcLB6w7V0htFxIgcdQeQhD5LFW1oqWk9ZWfzd9Ekl0a3bDGc81R5S3fk6qCHahPZR3cVPr4rHVQh0aX+FrQ==} + engines: {node: '>=18'} + + '@scalar/use-codemirror@0.11.94': + resolution: {integrity: sha512-+qd5+rIdtRpGsWHu4QDdeid5NiSLxViy2Of5Th5BN2a/U9A1dFa61D62gufYfUCniTxJUSn1mVljcIyD5oBgrw==} + engines: {node: '>=18'} + + '@scalar/use-hooks@0.1.41': + resolution: {integrity: sha512-YoXf8BOHyBIpiRt2FXzjFrS9OJtGrtfmeWzx7o8zV84dRqMs9/0mP9KXeyQHa0LlHckJ2Ubyl+jQI2Bis59zYQ==} + engines: {node: '>=18'} + + '@scalar/use-toasts@0.7.9': + resolution: {integrity: sha512-EcUDJY8VozLS9sfoQKvvipStQJ9RuH/nKOzf0BBr+mZDmumi1WFZ1iIJnHVXIN3iSLcSAr5ej6rOqa6jIv4bCQ==} + engines: {node: '>=18'} + + '@scalar/use-tooltip@1.0.6': + resolution: {integrity: sha512-f0gadIaUnILfi9qYAk7g+fNTsvLGXnam8oOUTxovavC1ocYuGTEykdz3g2MTqnAqRS8OkAB64h9mHf0FBfg6mg==} + engines: {node: '>=18'} + '@scarf/scarf@1.4.0': resolution: {integrity: sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==} @@ -6292,6 +6492,11 @@ packages: '@tanstack/virtual-core@3.13.6': resolution: {integrity: sha512-cnQUeWnhNP8tJ4WsGcYiX24Gjkc9ALstLbHcBj1t3E7EimN6n6kHH+DPV4PpDnuw00NApQp+ViojMj1GRdwYQg==} + '@tanstack/vue-virtual@3.13.6': + resolution: {integrity: sha512-GYdZ3SJBQPzgxhuCE2fvpiH46qzHiVx5XzBSdtESgiqh4poj8UgckjGWYEhxaBbcVt1oLzh1m3Ql4TyH32TOzQ==} + peerDependencies: + vue: ^2.7.0 || ^3.0.0 + '@testing-library/dom@10.4.0': resolution: {integrity: sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==} engines: {node: '>=18'} @@ -6621,6 +6826,9 @@ packages: '@types/uuid@9.0.8': resolution: {integrity: sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==} + '@types/web-bluetooth@0.0.20': + resolution: {integrity: sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==} + '@types/ws@7.4.7': resolution: {integrity: sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww==} @@ -6736,6 +6944,20 @@ packages: '@ungap/structured-clone@1.2.1': resolution: {integrity: sha512-fEzPV3hSkSMltkw152tJKNARhOupqbH96MZWyRjNaYZOMIzbrTeQDG+MTc6Mr2pgzFQzFxAfmhGDNP5QK++2ZA==} + '@unhead/dom@1.11.20': + resolution: {integrity: sha512-jgfGYdOH+xHJF/j8gudjsYu3oIjFyXhCWcgKaw3vQnT616gSqyqnGQGOItL+BQtQZACKNISwIfx5PuOtztMKLA==} + + '@unhead/schema@1.11.20': + resolution: {integrity: sha512-0zWykKAaJdm+/Y7yi/Yds20PrUK7XabLe9c3IRcjnwYmSWY6z0Cr19VIs3ozCj8P+GhR+/TI2mwtGlueCEYouA==} + + '@unhead/shared@1.11.20': + resolution: {integrity: sha512-1MOrBkGgkUXS+sOKz/DBh4U20DNoITlJwpmvSInxEUNhghSNb56S0RnaHRq0iHkhrO/cDgz2zvfdlRpoPLGI3w==} + + '@unhead/vue@1.11.20': + resolution: {integrity: sha512-sqQaLbwqY9TvLEGeq8Fd7+F2TIuV3nZ5ihVISHjWpAM3y7DwNWRU7NmT9+yYT+2/jw1Vjwdkv5/HvDnvCLrgmg==} + peerDependencies: + vue: '>=2.7 || >=3' + '@unrs/resolver-binding-darwin-arm64@1.6.3': resolution: {integrity: sha512-+BbDAtwT4AVUyGIfC6SimaA6Mi/tEJCf5OYV5XQg7WIOW0vyD15aVgDLvsQscIZxgz42xB6DDqR7Kv6NBQJrEg==} cpu: [arm64] @@ -6907,6 +7129,97 @@ packages: '@vitest/utils@3.1.2': resolution: {integrity: sha512-5GGd0ytZ7BH3H6JTj9Kw7Prn1Nbg0wZVrIvou+UWxm54d+WoXXgAgjFJ8wn3LdagWLFSEfpPeyYrByZaGEZHLg==} + '@vue/compiler-core@3.5.13': + resolution: {integrity: sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q==} + + '@vue/compiler-dom@3.5.13': + resolution: {integrity: sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA==} + + '@vue/compiler-sfc@3.5.13': + resolution: {integrity: sha512-6VdaljMpD82w6c2749Zhf5T9u5uLBWKnVue6XWxprDobftnletJ8+oel7sexFfM3qIxNmVE7LSFGTpv6obNyaQ==} + + '@vue/compiler-ssr@3.5.13': + resolution: {integrity: sha512-wMH6vrYHxQl/IybKJagqbquvxpWCuVYpoUJfCqFZwa/JY1GdATAQ+TgVtgrwwMZ0D07QhA99rs/EAAWfvG6KpA==} + + '@vue/devtools-api@6.6.4': + resolution: {integrity: sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==} + + '@vue/reactivity@3.5.13': + resolution: {integrity: sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg==} + + '@vue/runtime-core@3.5.13': + resolution: {integrity: sha512-Fj4YRQ3Az0WTZw1sFe+QDb0aXCerigEpw418pw1HBUKFtnQHWzwojaukAs2X/c9DQz4MQ4bsXTGlcpGxU/RCIw==} + + '@vue/runtime-dom@3.5.13': + resolution: {integrity: sha512-dLaj94s93NYLqjLiyFzVs9X6dWhTdAlEAciC3Moq7gzAc13VJUdCnjjRurNM6uTLFATRHexHCTu/Xp3eW6yoog==} + + '@vue/server-renderer@3.5.13': + resolution: {integrity: sha512-wAi4IRJV/2SAW3htkTlB+dHeRmpTiVIK1OGLWV1yeStVSebSQQOwGwIq0D3ZIoBj2C2qpgz5+vX9iEBkTdk5YA==} + peerDependencies: + vue: 3.5.13 + + '@vue/shared@3.5.13': + resolution: {integrity: sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==} + + '@vueuse/core@10.11.1': + resolution: {integrity: sha512-guoy26JQktXPcz+0n3GukWIy/JDNKti9v6VEMu6kV2sYBsWuGiTU8OWdg+ADfUbHg3/3DlqySDe7JmdHrktiww==} + + '@vueuse/core@11.3.0': + resolution: {integrity: sha512-7OC4Rl1f9G8IT6rUfi9JrKiXy4bfmHhZ5x2Ceojy0jnd3mHNEvV4JaRygH362ror6/NZ+Nl+n13LPzGiPN8cKA==} + + '@vueuse/integrations@11.3.0': + resolution: {integrity: sha512-5fzRl0apQWrDezmobchoiGTkGw238VWESxZHazfhP3RM7pDSiyXy18QbfYkILoYNTd23HPAfQTJpkUc5QbkwTw==} + peerDependencies: + async-validator: ^4 + axios: ^1.7.4 + change-case: ^5 + drauu: ^0.4 + focus-trap: ^7 + fuse.js: ^7 + idb-keyval: ^6 + jwt-decode: ^4 + nprogress: ^0.2 + qrcode: ^1.5 + sortablejs: ^1 + universal-cookie: ^7 + peerDependenciesMeta: + async-validator: + optional: true + axios: + optional: true + change-case: + optional: true + drauu: + optional: true + focus-trap: + optional: true + fuse.js: + optional: true + idb-keyval: + optional: true + jwt-decode: + optional: true + nprogress: + optional: true + qrcode: + optional: true + sortablejs: + optional: true + universal-cookie: + optional: true + + '@vueuse/metadata@10.11.1': + resolution: {integrity: sha512-IGa5FXd003Ug1qAZmyE8wF3sJ81xGLSqTqtQ6jaVfkeZ4i5kS2mwQF61yhVqojRnenVew5PldLyRgvdl4YYuSw==} + + '@vueuse/metadata@11.3.0': + resolution: {integrity: sha512-pwDnDspTqtTo2HwfLw4Rp6yywuuBdYnPYDq+mO38ZYKGebCUQC/nVj/PXSiK9HX5otxLz8Fn7ECPbjiRz2CC3g==} + + '@vueuse/shared@10.11.1': + resolution: {integrity: sha512-LHpC8711VFZlDaYUXEBbFBCQ7GS3dVU9mjOhhMhXP6txTV4EhYQg/KGnQuvt/sPAtoUKq7VVUnL6mVtFoL42sA==} + + '@vueuse/shared@11.3.0': + resolution: {integrity: sha512-P8gSSWQeucH5821ek2mn/ciCk+MS/zoRKqdQIM3bHq6p7GXDAJLmnRRKmF5F65sAVJIfzQlwR3aDzwCn10s8hA==} + '@wagmi/connectors@5.7.12': resolution: {integrity: sha512-pLFuZ1PsLkNyY11mx0+IOrMM7xACWCBRxaulfX17osqixkDFeOAyqFGBjh/XxkvRyrDJUdO4F+QHEeSoOiPpgg==} peerDependencies: @@ -7202,6 +7515,14 @@ packages: resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} engines: {node: '>=8'} + ajv-draft-04@1.0.0: + resolution: {integrity: sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==} + peerDependencies: + ajv: ^8.5.0 + peerDependenciesMeta: + ajv: + optional: true + ajv-formats@2.1.1: resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} peerDependencies: @@ -7210,6 +7531,14 @@ packages: ajv: optional: true + ajv-formats@3.0.1: + resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + ajv-keywords@3.5.2: resolution: {integrity: sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==} peerDependencies: @@ -8069,6 +8398,9 @@ packages: react: ^18 || ^19 || ^19.0.0-rc react-dom: ^18 || ^19 || ^19.0.0-rc + codemirror@6.0.1: + resolution: {integrity: sha512-J8j+nZ+CdWmIeFIGXEFbFPtpiYacFMDR8GlHK3IyHQJMCaVRfGx9NT+Hxivv1ckLWPvNdZqndbr/7lVhrf/Svg==} + color-convert@1.9.3: resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} @@ -8273,6 +8605,9 @@ packages: create-hmac@1.1.7: resolution: {integrity: sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==} + crelt@1.0.6: + resolution: {integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==} + cross-fetch@3.2.0: resolution: {integrity: sha512-Q+xVJLoGOeIMXZmbUK4HYk+69cQH6LudR0Vu/pRm2YlU/hDV9CiS0gKUMaWY5f2NeUH9C1nV3bsTlCo0FsTV1Q==} @@ -8359,6 +8694,14 @@ packages: csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + cva@1.0.0-beta.2: + resolution: {integrity: sha512-dqcOFe247I5pKxfuzqfq3seLL5iMYsTgo40Uw7+pKZAntPgFtR7Tmy59P5IVIq/XgB0NQWoIvYDt9TwHkuK8Cg==} + peerDependencies: + typescript: '>= 4.5.5 < 6' + peerDependenciesMeta: + typescript: + optional: true + d3-array@3.2.4: resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==} engines: {node: '>=12'} @@ -9570,6 +9913,9 @@ packages: resolution: {integrity: sha512-Ik/6OCk9RQQ0T5Xw+hKNLWrjSMtv51dD4GRmJjbD5a58TIEpI5a5iXagKVl3Z5UuyslMCA8Xwnu76jQob62Yhg==} engines: {node: '>=10'} + focus-trap@7.6.4: + resolution: {integrity: sha512-xx560wGBk7seZ6y933idtjJQc1l+ck+pI3sKvhKozdBV1dRZoKhkW5xoCaFv9tQiX5RH1xfSxjuNu6g+lmN/gw==} + follow-redirects@1.15.9: resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==} engines: {node: '>=4.0'} @@ -9727,6 +10073,10 @@ packages: resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} engines: {node: '>=6'} + get-own-enumerable-keys@1.0.0: + resolution: {integrity: sha512-PKsK2FSrQCyxcGHsGrLDcK0lx+0Ke+6e8KFFozA9/fIQLhQzPaRvJFdcz7+Axg3jUH/Mq+NI4xa5u/UT2tQskA==} + engines: {node: '>=14.16'} + get-package-type@0.1.0: resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} engines: {node: '>=8.0.0'} @@ -9920,9 +10270,45 @@ packages: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} + hast-util-embedded@3.0.0: + resolution: {integrity: sha512-naH8sld4Pe2ep03qqULEtvYr7EjrLK2QHY8KJR6RJkTUjPGObe1vnx585uzem2hGra+s1q08DZZpfgDVYRbaXA==} + + hast-util-format@1.1.0: + resolution: {integrity: sha512-yY1UDz6bC9rDvCWHpx12aIBGRG7krurX0p0Fm6pT547LwDIZZiNr8a+IHDogorAdreULSEzP82Nlv5SZkHZcjA==} + + hast-util-from-html@2.0.3: + resolution: {integrity: sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw==} + + hast-util-from-parse5@8.0.3: + resolution: {integrity: sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==} + + hast-util-has-property@3.0.0: + resolution: {integrity: sha512-MNilsvEKLFpV604hwfhVStK0usFY/QmM5zX16bo7EjnAEGofr5YyI37kzopBlZJkHD4t887i+q/C8/tr5Q94cA==} + + hast-util-is-body-ok-link@3.0.1: + resolution: {integrity: sha512-0qpnzOBLztXHbHQenVB8uNuxTnm/QBFUOmdOSsEn7GnBtyY07+ENTWVFBAnXd/zEgd9/SUG3lRY7hSIBWRgGpQ==} + + hast-util-is-element@3.0.0: + resolution: {integrity: sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==} + + hast-util-minify-whitespace@1.0.1: + resolution: {integrity: sha512-L96fPOVpnclQE0xzdWb/D12VT5FabA7SnZOUMtL1DbXmYiHJMXZvFkIZfiMmTCNJHUeO2K9UYNXoVyfz+QHuOw==} + hast-util-parse-selector@2.2.5: resolution: {integrity: sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ==} + hast-util-parse-selector@4.0.0: + resolution: {integrity: sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==} + + hast-util-phrasing@3.0.1: + resolution: {integrity: sha512-6h60VfI3uBQUxHqTyMymMZnEbNl1XmEGtOxxKYL7stY2o601COo62AWAYBQR9lZbYXYSBoxag8UpPRXK+9fqSQ==} + + hast-util-raw@9.1.0: + resolution: {integrity: sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==} + + hast-util-sanitize@5.0.2: + resolution: {integrity: sha512-3yTWghByc50aGS7JlGhk61SPenfE/p1oaFeNwkOOyrscaOkMGrcW9+Cy/QAIOBpZxP1yqDIzFMR0+Np0i0+usg==} + hast-util-to-estree@2.3.3: resolution: {integrity: sha512-ihhPIUPxN0v0w6M5+IiAZZrn0LH2uZomeWwhn7uP7avZC6TE7lIiEh2yBMPr5+zi1aUCXq6VoYRgs2Bw9xmycQ==} @@ -9932,6 +10318,12 @@ packages: hast-util-to-jsx-runtime@2.3.2: resolution: {integrity: sha512-1ngXYb+V9UT5h+PxNRa1O1FYguZK/XL+gkeqvp7EdHlB9oHUG0eYRo/vY5inBdcqo3RkPMC58/H94HvkbfGdyg==} + hast-util-to-parse5@8.0.0: + resolution: {integrity: sha512-3KKrV5ZVI8if87DVSi1vDeByYrkGzg4mEfeu4alwgmmIeARiBLKCZS2uw5Gb6nU9x9Yufyj3iudm6i7nl52PFw==} + + hast-util-to-text@4.0.2: + resolution: {integrity: sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==} + hast-util-whitespace@2.0.1: resolution: {integrity: sha512-nAxA0v8+vXSBDt3AnRUNjyRIQ0rD+ntpbAp4LnPkumc5M9yUbSMa4XDU9Q6etY4f1Wp4bNgvc1yjiZtsTTrSng==} @@ -9941,6 +10333,9 @@ packages: hastscript@6.0.0: resolution: {integrity: sha512-nDM6bvd7lIqDUiYEiu5Sl/+6ReP0BMk/2f4U/Rooccxkj0P5nm+acM5PrGJ/t5I8qPGiqZSE6hVAwZEdZIvP4w==} + hastscript@9.0.1: + resolution: {integrity: sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==} + he@1.2.0: resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} hasBin: true @@ -9973,6 +10368,13 @@ packages: highlight.js@10.7.3: resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==} + highlight.js@11.11.1: + resolution: {integrity: sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==} + engines: {node: '>=12.0.0'} + + highlightjs-curl@1.3.0: + resolution: {integrity: sha512-50UEfZq1KR0Lfk2Tr6xb/MUIZH3h10oNC0OTy9g7WELcs5Fgy/mKN1vEhuKTkKbdo8vr5F9GXstu2eLhApfQ3A==} + highlightjs-vue@1.0.0: resolution: {integrity: sha512-PDEfEF102G23vHmPhLyPboFCD+BkMGu+GuJe2d9/eH4FsCwvgBpnc9n0pGE+ffKdph38s6foEZiEjdgHdzp+IA==} @@ -9982,6 +10384,9 @@ packages: hoist-non-react-statics@3.3.2: resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} + hookable@5.5.3: + resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==} + hosted-git-info@7.0.2: resolution: {integrity: sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==} engines: {node: ^16.14.0 || >=18.0.0} @@ -10015,6 +10420,9 @@ packages: webpack: optional: true + html-whitespace-sensitive-tag-names@3.0.1: + resolution: {integrity: sha512-q+310vW8zmymYHALr1da4HyXUQ0zgiIwIicEfotYPWGN0OJVEN/58IJ3A4GBYcEq3LGAZqKb+ugvP0GNB9CEAA==} + htmlparser2@3.10.1: resolution: {integrity: sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==} @@ -10197,6 +10605,10 @@ packages: iron-webcrypto@1.2.1: resolution: {integrity: sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==} + is-absolute-url@4.0.1: + resolution: {integrity: sha512-/51/TKE88Lmm7Gc4/8btclNXWS+g50wXhYJq8HWIBAGUBnoAdRu1aXeh364t/O7wXDAcTJDP8PNuNKWUDWie+A==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + is-alphabetical@1.0.4: resolution: {integrity: sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==} @@ -10362,6 +10774,10 @@ packages: resolution: {integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==} engines: {node: '>=8'} + is-obj@3.0.0: + resolution: {integrity: sha512-IlsXEHOjtKhpN8r/tRFj2nDyTmHvcfNeu/nrRIcXE17ROeatXchkojffa1SpdqW4cr/Fj6QkEf/Gn4zf6KKvEQ==} + engines: {node: '>=12'} + is-path-cwd@2.2.0: resolution: {integrity: sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==} engines: {node: '>=6'} @@ -10396,6 +10812,10 @@ packages: resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} engines: {node: '>= 0.4'} + is-regexp@3.1.0: + resolution: {integrity: sha512-rbku49cWloU5bSMI+zaRaXdQHXnthP6DZ/vLnfdSKyL4zUzuWnomtOEiZZOd+ioQ+avFo/qau3KPTc7Fjy1uPA==} + engines: {node: '>=12'} + is-retry-allowed@1.2.0: resolution: {integrity: sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==} engines: {node: '>=0.10.0'} @@ -10714,6 +11134,10 @@ packages: resolution: {integrity: sha512-x7fpwxOkbhFCaJDJ8vb1fBY3DdSa4AlITaz+HHILQJzdPMnHEFjxPwVUi1ALIbcIxDE0PNe/0i7frnY8QnBQog==} engines: {node: '>=7.10.1'} + json-stringify-deterministic@1.0.12: + resolution: {integrity: sha512-q3PN0lbUdv0pmurkBNdJH3pfFvOTL/Zp0lquqpvcjfKzt6Y0j49EPHAmVHCAS4Ceq/Y+PejWTzyiVpoY71+D6g==} + engines: {node: '>= 4'} + json-stringify-safe@5.0.1: resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} @@ -10735,10 +11159,20 @@ packages: jsonfile@6.1.0: resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} + jsonpointer@5.0.1: + resolution: {integrity: sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==} + engines: {node: '>=0.10.0'} + jsx-ast-utils@3.3.5: resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} engines: {node: '>=4.0'} + just-clone@6.2.0: + resolution: {integrity: sha512-1IynUYEc/HAwxhi3WDpIpxJbZpMCvvrrmZVqvj9EhpvbH8lls7HhdhiByjL7DkAaWlLIzpC0Xc/VPvy/UxLNjA==} + + just-curry-it@5.3.0: + resolution: {integrity: sha512-silMIRiFjUWlfaDhkgSzpuAyQ6EX/o09Eu8ZBfmFwQMbax7+LQzeIU2CBrICT6Ne4l86ITCGvUCBpCubWYy0Yw==} + jwt-decode@3.1.2: resolution: {integrity: sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A==} @@ -10772,8 +11206,8 @@ packages: '@types/node': '>=18' typescript: '>=5.0.4' - ky@1.8.0: - resolution: {integrity: sha512-DoKGmG27nT8t/1F9gV8vNzggJ3mLAyD49J8tTMWHeZvS8qLc7GlyTieicYtFzvDznMe/q2u38peOjkWc5/pjvw==} + ky@1.8.1: + resolution: {integrity: sha512-7Bp3TpsE+L+TARSnnDpk3xg8Idi8RwSLdj6CMbNWoOARIrGrbuLGusV0dYwbZOm4bB3jHNxSw8Wk/ByDqJEnDw==} engines: {node: '>=18'} language-subtag-registry@0.3.23: @@ -10791,6 +11225,10 @@ packages: resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} engines: {node: '>=6'} + leven@4.0.0: + resolution: {integrity: sha512-puehA3YKku3osqPlNuzGDUHq8WpwXupUg1V6NXdV38G+gr+gkBwFC8g1b/+YcIvp8gnqVIus+eJCH/eGsRmJNw==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + levn@0.4.1: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} @@ -11058,6 +11496,9 @@ packages: lowlight@1.20.0: resolution: {integrity: sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==} + lowlight@3.3.0: + resolution: {integrity: sha512-0JNhgFoPvP6U6lE/UdVsSq99tn6DhjjpAj5MxG49ewd2mOBVtwWYIT8ClyABhq198aXXODMU6Ox8DrGy/CpTZQ==} + lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} @@ -11349,6 +11790,9 @@ packages: engines: {node: '>=18.18'} hasBin: true + microdiff@1.5.0: + resolution: {integrity: sha512-Drq+/THMvDdzRYrK0oxJmOKiC24ayUV8ahrt8l3oRK51PWt6gdtrIGrlIH3pT/lFh1z93FbAcidtsHcWbnRz8Q==} + micromark-core-commonmark@1.1.0: resolution: {integrity: sha512-BgHO1aRbolh2hcrzL2d1La37V0Aoz73ymF8rAcKnohLy93titmv62E0gP8Hrx9PKcKrqCZ1BbLGbP3bEhoXYlw==} @@ -11791,6 +12235,11 @@ packages: engines: {node: ^18 || >=20} hasBin: true + nanoid@5.1.5: + resolution: {integrity: sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw==} + engines: {node: ^18 || >=20} + hasBin: true + nanospinner@1.2.2: resolution: {integrity: sha512-Zt/AmG6qRU3e+WnzGGLuMCEAO/dAu45stNbHY223tUxldaDAeE+FxSPsd9Q+j+paejmm0ZbrNVs5Sraqy3dRxA==} @@ -12389,6 +12838,9 @@ packages: package-manager-detector@0.2.11: resolution: {integrity: sha512-BEnLolu+yuz22S56CU1SUKq3XC3PkwD5wv4ikR4MfGvnRVcmzXR9DwSlW2fEamyTPyXHomBJRzgapeuBvRNzJQ==} + packrup@0.1.2: + resolution: {integrity: sha512-ZcKU7zrr5GlonoS9cxxrb5HVswGnyj6jQvwFBa6p5VFw7G71VAHcUKL5wyZSU/ECtPM/9gacWxy2KFQKt1gMNA==} + pako@0.2.9: resolution: {integrity: sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==} @@ -12430,6 +12882,10 @@ packages: resolution: {integrity: sha512-SgOTCX/EZXtZxBE5eJ97P4yGM5n37BwRU+YMsH4vNzFqJV/oWFXXCmwFlgWUM4PrakybVOueJJ6pwHqSVhTFDw==} engines: {node: '>=16'} + parse-ms@3.0.0: + resolution: {integrity: sha512-Tpb8Z7r7XbbtBTrM9UhpkzzaMrqA2VXMT3YChzYltwV3P3pM6t8wl7TvpMnSTosz1aQAdVib7kdoys7vYOPerw==} + engines: {node: '>=12'} + parse-ms@4.0.0: resolution: {integrity: sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==} engines: {node: '>=18'} @@ -12438,6 +12894,9 @@ packages: resolution: {integrity: sha512-Nt/a5SfCLiTnQAjx3fHlqp8hRgTL3z7kTQZzvIMS9uCAepnCyjpdEc6M/sz69WqMBdaDBw9sF1F1UaHROYzGkQ==} engines: {node: '>=10'} + parse5@7.2.1: + resolution: {integrity: sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==} + parseurl@1.3.3: resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} engines: {node: '>= 0.8'} @@ -12790,6 +13249,10 @@ packages: resolution: {integrity: sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==} engines: {node: '>=6'} + pretty-bytes@6.1.1: + resolution: {integrity: sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ==} + engines: {node: ^14.13.1 || >=16.0.0} + pretty-error@4.0.0: resolution: {integrity: sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw==} @@ -12801,6 +13264,10 @@ packages: resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + pretty-ms@8.0.0: + resolution: {integrity: sha512-ASJqOugUF1bbzI35STMBUpZqdfYKlJugy6JBziGi2EE+AL5JPJGSzvpeVXojxrr0ViUYoToUjb5kjSEGf7Y83Q==} + engines: {node: '>=14.16'} + pretty-ms@9.2.0: resolution: {integrity: sha512-4yf0QO/sllf/1zbZWYnvWw3NxCQwLXKzIj0G849LSufP15BXKM0rbD2Z3wVnkMfjdn/CB0Dpp444gYAACdsplg==} engines: {node: '>=18'} @@ -12862,6 +13329,9 @@ packages: property-information@6.5.0: resolution: {integrity: sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==} + property-information@7.0.0: + resolution: {integrity: sha512-7D/qOz/+Y4X/rzSB6jKxKUsQnphO046ei8qxG59mtM3RG3DHgTK81HrxrmoDVINJb8NKT5ZsRbwHvQ6B68Iyhg==} + proto-list@1.2.4: resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==} @@ -12955,6 +13425,11 @@ packages: resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==} engines: {node: '>=10'} + radix-vue@1.9.17: + resolution: {integrity: sha512-mVCu7I2vXt1L2IUYHTt0sZMz7s1K2ZtqKeTIxG3yC5mMFfLBG4FtE1FDeRMpDd+Hhg/ybi9+iXmAP1ISREndoQ==} + peerDependencies: + vue: '>= 3.2.0' + radix3@1.1.2: resolution: {integrity: sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==} @@ -13391,6 +13866,24 @@ packages: resolution: {integrity: sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ==} hasBin: true + rehype-external-links@3.0.0: + resolution: {integrity: sha512-yp+e5N9V3C6bwBeAC4n796kc86M4gJCdlVhiMTxIrJG5UHDMh+PJANf9heqORJbt1nrCbDwIlAZKjANIaVBbvw==} + + rehype-format@5.0.1: + resolution: {integrity: sha512-zvmVru9uB0josBVpr946OR8ui7nJEdzZobwLOOqHb/OOD88W0Vk2SqLwoVOj0fM6IPCCO6TaV9CvQvJMWwukFQ==} + + rehype-parse@9.0.1: + resolution: {integrity: sha512-ksCzCD0Fgfh7trPDxr2rSylbwq9iYDkSn8TCDmEJ49ljEUBxDVCzCHv7QNzZOfODanX4+bWQ4WZqLCRWYLfhag==} + + rehype-raw@7.0.0: + resolution: {integrity: sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==} + + rehype-sanitize@6.0.0: + resolution: {integrity: sha512-CsnhKNsyI8Tub6L4sm5ZFsme4puGfc6pYylvXo1AeqaGbjOYyzNv3qZPwvs0oMJ39eryyeOdmxwUIo94IpEhqg==} + + rehype-stringify@10.0.1: + resolution: {integrity: sha512-k9ecfXHmIPuFVI61B9DeLPN0qFHfawM6RsuX48hoqlaKSF61RskNjSm1lI8PhBEM0MRdLxVVm4WmTqJQccH9mA==} + reinterval@1.1.0: resolution: {integrity: sha512-QIRet3SYrGp0HUHO88jVskiG6seqUGC5iAG7AwI/BV4ypGcuqk9Du6YQBUOUqm9c8pw1eyLoIaONifRua1lsEQ==} @@ -14074,6 +14567,10 @@ packages: stringify-entities@4.0.4: resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} + stringify-object@5.0.0: + resolution: {integrity: sha512-zaJYxz2FtcMb4f+g60KsRNFOpVMUyuJgA51Zi5Z1DOTC3S59+OQiVOzE9GZt0x72uBGWKsQIuBKeF9iusmKFsg==} + engines: {node: '>=14.16'} + strip-ansi@5.2.0: resolution: {integrity: sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==} engines: {node: '>=6'} @@ -14141,6 +14638,9 @@ packages: peerDependencies: webpack: ^5.0.0 + style-mod@4.1.2: + resolution: {integrity: sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==} + style-to-object@0.4.4: resolution: {integrity: sha512-HYNoHZa2GorYNyqiCaBgsxvcJIn7OHq6inEga+E6Ke3m5JkoqpQbnFssk4jwe+K7AhGa2fcha4wSOf1Kn01dMg==} @@ -14381,6 +14881,9 @@ packages: resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==} engines: {node: '>=14.0.0'} + tippy.js@6.3.7: + resolution: {integrity: sha512-E1d3oP2emgJ9dRQZdf3Kkn0qJgI6ZLpyS5z6ZkY1DF3kaQaBsGZsndEpHwx+eC+tYM41HaSNvNtLx8tU57FzTQ==} + tmp@0.0.33: resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} engines: {node: '>=0.6.0'} @@ -14450,6 +14953,10 @@ packages: resolution: {integrity: sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==} engines: {node: '>=6.10'} + ts-deepmerge@7.0.2: + resolution: {integrity: sha512-akcpDTPuez4xzULo5NwuoKwYRtjQJ9eoNfBACiBMaXwNAx7B1PKfe5wqUFJuW5uKzQ68YjDFwPaWHDG1KnFGsA==} + engines: {node: '>=14.13.1'} + ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} @@ -14680,6 +15187,9 @@ packages: unenv@1.10.0: resolution: {integrity: sha512-wY5bskBQFL9n3Eca5XnhH6KbUo/tfvkwm9OpcdCvLaeA7piBNbavbOKJySEwQ1V0RH6HvNlSAFRTpvTqgKRQXQ==} + unhead@1.11.20: + resolution: {integrity: sha512-3AsNQC0pjwlLqEYHLjtichGWankK8yqmocReITecmpB1H0aOabeESueyy+8X1gyJx4ftZVwo9hqQ4O3fPWffCA==} + unicode-canonical-property-names-ecmascript@2.0.1: resolution: {integrity: sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==} engines: {node: '>=4'} @@ -14724,6 +15234,9 @@ packages: resolution: {integrity: sha512-VGXBUVwxKMBUznyffQweQABPRRW1vHZAbadFZud4pLFAqRGvv/96vafgjWFqzourzr8YonlQiPgH0YCJfawoGQ==} engines: {node: '>=12'} + unist-util-find-after@5.0.0: + resolution: {integrity: sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==} + unist-util-generated@2.0.1: resolution: {integrity: sha512-qF72kLmPxAw0oN2fwpWIqbXAVyEqUzDHMsbtPvOudIlUzXYFIeQIuxXQCRCFh22B7cixvU0MG7m3MW8FTq/S+A==} @@ -15014,6 +15527,9 @@ packages: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} + vfile-location@5.0.3: + resolution: {integrity: sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==} + vfile-message@3.1.4: resolution: {integrity: sha512-fa0Z6P8HUrQN4BZaX05SIVXic+7kE3b05PWAtPuYP9QLHsLKYR7/AlLW3NtOrpXRLeawpDLMsVkmk5DG0NXgWw==} @@ -15139,6 +15655,36 @@ packages: vscode-textmate@8.0.0: resolution: {integrity: sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==} + vue-demi@0.14.10: + resolution: {integrity: sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==} + engines: {node: '>=12'} + hasBin: true + peerDependencies: + '@vue/composition-api': ^1.0.0-rc.1 + vue: ^3.0.0-0 || ^2.6.0 + peerDependenciesMeta: + '@vue/composition-api': + optional: true + + vue-router@4.5.0: + resolution: {integrity: sha512-HDuk+PuH5monfNuY+ct49mNmkCRK4xJAV9Ts4z9UFc4rzdDnxQLyCMGGc8pKhZhHTVzfanpNwB/lwqevcBwI4w==} + peerDependencies: + vue: ^3.2.0 + + vue-sonner@1.3.2: + resolution: {integrity: sha512-UbZ48E9VIya3ToiRHAZUbodKute/z/M1iT8/3fU8zEbwBRE11AKuHikssv18LMk2gTTr6eMQT4qf6JoLHWuj/A==} + + vue@3.5.13: + resolution: {integrity: sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + w3c-keyname@2.2.8: + resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==} + wagmi@2.14.16: resolution: {integrity: sha512-njOPvB8L0+jt3m1FTJiVF44T1u+kcjLtVWKvwI0mZnIesZTQZ/xDF0M/NHj3Uljyn3qJw3pyHjJe31NC+VVHMA==} peerDependencies: @@ -15166,6 +15712,9 @@ packages: wcwidth@1.0.1: resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} + web-namespaces@2.0.1: + resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==} + web-streams-polyfill@3.3.3: resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==} engines: {node: '>= 8'} @@ -15231,6 +15780,10 @@ packages: resolution: {integrity: sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==} engines: {node: '>=12'} + whatwg-mimetype@4.0.0: + resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==} + engines: {node: '>=18'} + whatwg-url-without-unicode@8.0.0-3: resolution: {integrity: sha512-HoKuzZrUlgpz35YO27XgD28uh/WJH4B0+3ttFqRo//lmq+9T/mIOJ6kqmINI9HpUpz1imRC/nR/lxKpJiv0uig==} engines: {node: '>=10'} @@ -15496,6 +16049,9 @@ packages: zenscroll@4.0.2: resolution: {integrity: sha512-jEA1znR7b4C/NnaycInCU6h/d15ZzCd1jmsruqOKnZP6WXQSMH3W2GL+OXbkruslU4h+Tzuos0HdswzRUk/Vgg==} + zhead@2.2.4: + resolution: {integrity: sha512-8F0OI5dpWIA5IGG5NHUg9staDwz/ZPxZtvGVf01j7vHqSyZ0raHY+78atOVxRqb73AotX22uV1pXt3gYSstGag==} + zod-validation-error@3.4.0: resolution: {integrity: sha512-ZOPR9SVY6Pb2qqO5XHt+MkkTRxGXb4EVtnjc9JpXUOtUB1T9Ru7mZOT361AN3MsetVe7R0a1KZshJDZdgp9miQ==} engines: {node: '>=18.0.0'} @@ -17766,6 +18322,105 @@ snapshots: '@cloudflare/workers-types@4.20250421.0': {} + '@codemirror/autocomplete@6.18.6': + dependencies: + '@codemirror/language': 6.11.0 + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.36.5 + '@lezer/common': 1.2.3 + + '@codemirror/commands@6.8.1': + dependencies: + '@codemirror/language': 6.11.0 + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.36.5 + '@lezer/common': 1.2.3 + + '@codemirror/lang-css@6.3.1': + dependencies: + '@codemirror/autocomplete': 6.18.6 + '@codemirror/language': 6.11.0 + '@codemirror/state': 6.5.2 + '@lezer/common': 1.2.3 + '@lezer/css': 1.1.11 + + '@codemirror/lang-html@6.4.9': + dependencies: + '@codemirror/autocomplete': 6.18.6 + '@codemirror/lang-css': 6.3.1 + '@codemirror/lang-javascript': 6.2.3 + '@codemirror/language': 6.11.0 + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.36.5 + '@lezer/common': 1.2.3 + '@lezer/css': 1.1.11 + '@lezer/html': 1.3.10 + + '@codemirror/lang-javascript@6.2.3': + dependencies: + '@codemirror/autocomplete': 6.18.6 + '@codemirror/language': 6.11.0 + '@codemirror/lint': 6.8.5 + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.36.5 + '@lezer/common': 1.2.3 + '@lezer/javascript': 1.5.0 + + '@codemirror/lang-json@6.0.1': + dependencies: + '@codemirror/language': 6.11.0 + '@lezer/json': 1.0.3 + + '@codemirror/lang-xml@6.1.0': + dependencies: + '@codemirror/autocomplete': 6.18.6 + '@codemirror/language': 6.11.0 + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.36.5 + '@lezer/common': 1.2.3 + '@lezer/xml': 1.0.6 + + '@codemirror/lang-yaml@6.1.2': + dependencies: + '@codemirror/autocomplete': 6.18.6 + '@codemirror/language': 6.11.0 + '@codemirror/state': 6.5.2 + '@lezer/common': 1.2.3 + '@lezer/highlight': 1.2.1 + '@lezer/lr': 1.4.2 + '@lezer/yaml': 1.0.3 + + '@codemirror/language@6.11.0': + dependencies: + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.36.5 + '@lezer/common': 1.2.3 + '@lezer/highlight': 1.2.1 + '@lezer/lr': 1.4.2 + style-mod: 4.1.2 + + '@codemirror/lint@6.8.5': + dependencies: + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.36.5 + crelt: 1.0.6 + + '@codemirror/search@6.5.10': + dependencies: + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.36.5 + crelt: 1.0.6 + + '@codemirror/state@6.5.2': + dependencies: + '@marijn/find-cluster-break': 1.0.2 + + '@codemirror/view@6.36.5': + dependencies: + '@codemirror/state': 6.5.2 + style-mod: 4.1.2 + w3c-keyname: 2.2.8 + '@codspeed/core@4.0.1': dependencies: axios: 1.8.4 @@ -18803,6 +19458,15 @@ snapshots: '@floating-ui/utils@0.2.9': {} + '@floating-ui/vue@1.1.6(vue@3.5.13(typescript@5.8.3))': + dependencies: + '@floating-ui/dom': 1.6.13 + '@floating-ui/utils': 0.2.9 + vue-demi: 0.14.10(vue@3.5.13(typescript@5.8.3)) + transitivePeerDependencies: + - '@vue/composition-api' + - vue + '@gerrit0/mini-shiki@1.27.2': dependencies: '@shikijs/engine-oniguruma': 1.29.1 @@ -18819,6 +19483,15 @@ snapshots: react-dom: 19.1.0(react@19.1.0) use-sync-external-store: 1.5.0(react@19.1.0) + '@headlessui/tailwindcss@0.2.2(tailwindcss@3.4.17)': + dependencies: + tailwindcss: 3.4.17 + + '@headlessui/vue@1.7.23(vue@3.5.13(typescript@5.8.3))': + dependencies: + '@tanstack/vue-virtual': 3.13.6(vue@3.5.13(typescript@5.8.3)) + vue: 3.5.13(typescript@5.8.3) + '@heroicons/react@2.2.0(react@19.1.0)': dependencies: react: 19.1.0 @@ -18870,6 +19543,30 @@ snapshots: '@humanwhocodes/retry@0.4.2': {} + '@hyperjump/browser@1.3.0': + dependencies: + '@hyperjump/json-pointer': 1.1.0 + '@hyperjump/uri': 1.3.1 + content-type: 1.0.5 + just-curry-it: 5.3.0 + + '@hyperjump/json-pointer@1.1.0': {} + + '@hyperjump/json-schema@1.12.1(@hyperjump/browser@1.3.0)': + dependencies: + '@hyperjump/browser': 1.3.0 + '@hyperjump/json-pointer': 1.1.0 + '@hyperjump/pact': 1.4.0 + '@hyperjump/uri': 1.3.1 + content-type: 1.0.5 + json-stringify-deterministic: 1.0.12 + just-curry-it: 5.3.0 + uuid: 9.0.1 + + '@hyperjump/pact@1.4.0': {} + + '@hyperjump/uri@1.3.1': {} + '@img/sharp-darwin-arm64@0.33.5': optionalDependencies: '@img/sharp-libvips-darwin-arm64': 1.0.4 @@ -19139,6 +19836,14 @@ snapshots: optionalDependencies: '@types/node': 22.14.1 + '@internationalized/date@3.8.0': + dependencies: + '@swc/helpers': 0.5.17 + + '@internationalized/number@3.6.1': + dependencies: + '@swc/helpers': 0.5.17 + '@ioredis/commands@1.2.0': {} '@isaacs/cliui@8.0.2': @@ -19248,6 +19953,52 @@ snapshots: '@jsdevtools/ono@7.1.3': {} + '@lezer/common@1.2.3': {} + + '@lezer/css@1.1.11': + dependencies: + '@lezer/common': 1.2.3 + '@lezer/highlight': 1.2.1 + '@lezer/lr': 1.4.2 + + '@lezer/highlight@1.2.1': + dependencies: + '@lezer/common': 1.2.3 + + '@lezer/html@1.3.10': + dependencies: + '@lezer/common': 1.2.3 + '@lezer/highlight': 1.2.1 + '@lezer/lr': 1.4.2 + + '@lezer/javascript@1.5.0': + dependencies: + '@lezer/common': 1.2.3 + '@lezer/highlight': 1.2.1 + '@lezer/lr': 1.4.2 + + '@lezer/json@1.0.3': + dependencies: + '@lezer/common': 1.2.3 + '@lezer/highlight': 1.2.1 + '@lezer/lr': 1.4.2 + + '@lezer/lr@1.4.2': + dependencies: + '@lezer/common': 1.2.3 + + '@lezer/xml@1.0.6': + dependencies: + '@lezer/common': 1.2.3 + '@lezer/highlight': 1.2.1 + '@lezer/lr': 1.4.2 + + '@lezer/yaml@1.0.3': + dependencies: + '@lezer/common': 1.2.3 + '@lezer/highlight': 1.2.1 + '@lezer/lr': 1.4.2 + '@lit-labs/ssr-dom-shim@1.3.0': {} '@lit/reactive-element@1.6.3': @@ -19304,6 +20055,8 @@ snapshots: - encoding - supports-color + '@marijn/find-cluster-break@1.0.2': {} + '@marsidev/react-turnstile@0.4.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: react: 19.1.0 @@ -19543,8 +20296,8 @@ snapshots: '@mobile-wallet-protocol/client@1.0.0(dgnjcbusqvetbc3a6onowh5cfm)': dependencies: '@noble/ciphers': 0.5.3 - '@noble/curves': 1.8.2 - '@noble/hashes': 1.7.2 + '@noble/curves': 1.8.1 + '@noble/hashes': 1.7.1 '@react-native-async-storage/async-storage': 2.1.2(react-native@0.78.1(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@19.1.2)(bufferutil@4.0.9)(react@19.1.0)(utf-8-validate@5.0.10)) eventemitter3: 5.0.1 expo: 52.0.46(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(bufferutil@4.0.9)(encoding@0.1.13)(graphql@16.10.0)(react-native@0.78.1(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@19.1.2)(bufferutil@4.0.9)(react@19.1.0)(utf-8-validate@5.0.10))(react@19.1.0)(utf-8-validate@5.0.10) @@ -21332,6 +22085,12 @@ snapshots: - utf-8-validate - zod + '@replit/codemirror-css-color-picker@6.3.0(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.36.5)': + dependencies: + '@codemirror/language': 6.11.0 + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.36.5 + '@resvg/resvg-wasm@2.4.0': {} '@rollup/plugin-commonjs@28.0.1(rollup@4.35.0)': @@ -21505,6 +22264,293 @@ snapshots: '@safe-global/safe-gateway-typescript-sdk@3.22.9': {} + '@scalar/api-client@2.3.22(@hyperjump/browser@1.3.0)(axios@1.8.4)(idb-keyval@6.2.1)(nprogress@0.2.0)(qrcode@1.5.4)(tailwindcss@3.4.17)(typescript@5.8.3)': + dependencies: + '@headlessui/tailwindcss': 0.2.2(tailwindcss@3.4.17) + '@headlessui/vue': 1.7.23(vue@3.5.13(typescript@5.8.3)) + '@scalar/components': 0.13.49(typescript@5.8.3) + '@scalar/draggable': 0.1.11(typescript@5.8.3) + '@scalar/icons': 0.1.3(typescript@5.8.3) + '@scalar/import': 0.3.16(@hyperjump/browser@1.3.0) + '@scalar/oas-utils': 0.2.133(@hyperjump/browser@1.3.0) + '@scalar/object-utils': 1.1.13 + '@scalar/openapi-parser': 0.10.16 + '@scalar/openapi-types': 0.2.0 + '@scalar/postman-to-openapi': 0.2.6(@hyperjump/browser@1.3.0) + '@scalar/snippetz': 0.2.19 + '@scalar/themes': 0.10.0 + '@scalar/types': 0.1.8 + '@scalar/use-codemirror': 0.11.94(typescript@5.8.3) + '@scalar/use-hooks': 0.1.41(typescript@5.8.3) + '@scalar/use-toasts': 0.7.9(typescript@5.8.3) + '@scalar/use-tooltip': 1.0.6(typescript@5.8.3) + '@vueuse/core': 10.11.1(vue@3.5.13(typescript@5.8.3)) + '@vueuse/integrations': 11.3.0(axios@1.8.4)(focus-trap@7.6.4)(fuse.js@7.1.0)(idb-keyval@6.2.1)(nprogress@0.2.0)(qrcode@1.5.4)(vue@3.5.13(typescript@5.8.3)) + focus-trap: 7.6.4 + fuse.js: 7.1.0 + microdiff: 1.5.0 + nanoid: 5.1.5 + pretty-bytes: 6.1.1 + pretty-ms: 8.0.0 + shell-quote: 1.8.2 + type-fest: 4.40.0 + vue: 3.5.13(typescript@5.8.3) + vue-router: 4.5.0(vue@3.5.13(typescript@5.8.3)) + whatwg-mimetype: 4.0.0 + yaml: 2.7.1 + zod: 3.24.3 + transitivePeerDependencies: + - '@hyperjump/browser' + - '@vue/composition-api' + - async-validator + - axios + - change-case + - drauu + - idb-keyval + - jwt-decode + - nprogress + - qrcode + - sortablejs + - supports-color + - tailwindcss + - typescript + - universal-cookie + + '@scalar/api-reference-react@0.6.19(@hyperjump/browser@1.3.0)(axios@1.8.4)(idb-keyval@6.2.1)(nprogress@0.2.0)(qrcode@1.5.4)(react@19.1.0)(tailwindcss@3.4.17)(typescript@5.8.3)': + dependencies: + '@scalar/api-reference': 1.28.22(@hyperjump/browser@1.3.0)(axios@1.8.4)(idb-keyval@6.2.1)(nprogress@0.2.0)(qrcode@1.5.4)(tailwindcss@3.4.17)(typescript@5.8.3) + '@scalar/types': 0.1.8 + react: 19.1.0 + transitivePeerDependencies: + - '@hyperjump/browser' + - '@vue/composition-api' + - async-validator + - axios + - change-case + - drauu + - idb-keyval + - jwt-decode + - nprogress + - qrcode + - sortablejs + - supports-color + - tailwindcss + - typescript + - universal-cookie + + '@scalar/api-reference@1.28.22(@hyperjump/browser@1.3.0)(axios@1.8.4)(idb-keyval@6.2.1)(nprogress@0.2.0)(qrcode@1.5.4)(tailwindcss@3.4.17)(typescript@5.8.3)': + dependencies: + '@floating-ui/vue': 1.1.6(vue@3.5.13(typescript@5.8.3)) + '@headlessui/vue': 1.7.23(vue@3.5.13(typescript@5.8.3)) + '@scalar/api-client': 2.3.22(@hyperjump/browser@1.3.0)(axios@1.8.4)(idb-keyval@6.2.1)(nprogress@0.2.0)(qrcode@1.5.4)(tailwindcss@3.4.17)(typescript@5.8.3) + '@scalar/code-highlight': 0.0.27 + '@scalar/components': 0.13.49(typescript@5.8.3) + '@scalar/oas-utils': 0.2.133(@hyperjump/browser@1.3.0) + '@scalar/openapi-parser': 0.10.16 + '@scalar/openapi-types': 0.2.0 + '@scalar/snippetz': 0.2.19 + '@scalar/themes': 0.10.0 + '@scalar/types': 0.1.8 + '@scalar/use-hooks': 0.1.41(typescript@5.8.3) + '@scalar/use-toasts': 0.7.9(typescript@5.8.3) + '@unhead/vue': 1.11.20(vue@3.5.13(typescript@5.8.3)) + '@vueuse/core': 10.11.1(vue@3.5.13(typescript@5.8.3)) + flatted: 3.3.3 + fuse.js: 7.1.0 + github-slugger: 2.0.0 + nanoid: 5.1.5 + vue: 3.5.13(typescript@5.8.3) + zod: 3.24.3 + transitivePeerDependencies: + - '@hyperjump/browser' + - '@vue/composition-api' + - async-validator + - axios + - change-case + - drauu + - idb-keyval + - jwt-decode + - nprogress + - qrcode + - sortablejs + - supports-color + - tailwindcss + - typescript + - universal-cookie + + '@scalar/code-highlight@0.0.27': + dependencies: + hast-util-to-text: 4.0.2 + highlight.js: 11.11.1 + highlightjs-curl: 1.3.0 + highlightjs-vue: 1.0.0 + lowlight: 3.3.0 + rehype-external-links: 3.0.0 + rehype-format: 5.0.1 + rehype-parse: 9.0.1 + rehype-raw: 7.0.0 + rehype-sanitize: 6.0.0 + rehype-stringify: 10.0.1 + remark-gfm: 4.0.1 + remark-parse: 11.0.0 + remark-rehype: 11.1.1 + remark-stringify: 11.0.0 + unified: 11.0.5 + unist-util-visit: 5.0.0 + transitivePeerDependencies: + - supports-color + + '@scalar/components@0.13.49(typescript@5.8.3)': + dependencies: + '@floating-ui/utils': 0.2.9 + '@floating-ui/vue': 1.1.6(vue@3.5.13(typescript@5.8.3)) + '@headlessui/vue': 1.7.23(vue@3.5.13(typescript@5.8.3)) + '@scalar/code-highlight': 0.0.27 + '@scalar/themes': 0.10.0 + '@scalar/use-hooks': 0.1.41(typescript@5.8.3) + '@scalar/use-toasts': 0.7.9(typescript@5.8.3) + '@vueuse/core': 10.11.1(vue@3.5.13(typescript@5.8.3)) + cva: 1.0.0-beta.2(typescript@5.8.3) + nanoid: 5.1.5 + pretty-bytes: 6.1.1 + radix-vue: 1.9.17(vue@3.5.13(typescript@5.8.3)) + tailwind-merge: 2.6.0 + vue: 3.5.13(typescript@5.8.3) + transitivePeerDependencies: + - '@vue/composition-api' + - supports-color + - typescript + + '@scalar/draggable@0.1.11(typescript@5.8.3)': + dependencies: + vue: 3.5.13(typescript@5.8.3) + transitivePeerDependencies: + - typescript + + '@scalar/icons@0.1.3(typescript@5.8.3)': + dependencies: + vue: 3.5.13(typescript@5.8.3) + transitivePeerDependencies: + - typescript + + '@scalar/import@0.3.16(@hyperjump/browser@1.3.0)': + dependencies: + '@scalar/oas-utils': 0.2.133(@hyperjump/browser@1.3.0) + '@scalar/openapi-parser': 0.10.16 + yaml: 2.7.1 + transitivePeerDependencies: + - '@hyperjump/browser' + + '@scalar/oas-utils@0.2.133(@hyperjump/browser@1.3.0)': + dependencies: + '@hyperjump/json-schema': 1.12.1(@hyperjump/browser@1.3.0) + '@scalar/object-utils': 1.1.13 + '@scalar/openapi-types': 0.2.0 + '@scalar/themes': 0.10.0 + '@scalar/types': 0.1.8 + flatted: 3.3.3 + microdiff: 1.5.0 + nanoid: 5.1.5 + type-fest: 4.40.0 + yaml: 2.7.1 + zod: 3.24.3 + transitivePeerDependencies: + - '@hyperjump/browser' + + '@scalar/object-utils@1.1.13': + dependencies: + flatted: 3.3.3 + just-clone: 6.2.0 + ts-deepmerge: 7.0.2 + + '@scalar/openapi-parser@0.10.16': + dependencies: + ajv: 8.17.1 + ajv-draft-04: 1.0.0(ajv@8.17.1) + ajv-formats: 3.0.1(ajv@8.17.1) + jsonpointer: 5.0.1 + leven: 4.0.0 + yaml: 2.7.1 + + '@scalar/openapi-types@0.2.0': + dependencies: + zod: 3.24.3 + + '@scalar/postman-to-openapi@0.2.6(@hyperjump/browser@1.3.0)': + dependencies: + '@scalar/oas-utils': 0.2.133(@hyperjump/browser@1.3.0) + '@scalar/openapi-types': 0.2.0 + transitivePeerDependencies: + - '@hyperjump/browser' + + '@scalar/snippetz@0.2.19': + dependencies: + stringify-object: 5.0.0 + + '@scalar/themes@0.10.0': + dependencies: + '@scalar/types': 0.1.8 + + '@scalar/types@0.1.8': + dependencies: + '@scalar/openapi-types': 0.2.0 + '@unhead/schema': 1.11.20 + nanoid: 5.1.5 + type-fest: 4.40.0 + zod: 3.24.3 + + '@scalar/use-codemirror@0.11.94(typescript@5.8.3)': + dependencies: + '@codemirror/autocomplete': 6.18.6 + '@codemirror/commands': 6.8.1 + '@codemirror/lang-css': 6.3.1 + '@codemirror/lang-html': 6.4.9 + '@codemirror/lang-json': 6.0.1 + '@codemirror/lang-xml': 6.1.0 + '@codemirror/lang-yaml': 6.1.2 + '@codemirror/language': 6.11.0 + '@codemirror/lint': 6.8.5 + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.36.5 + '@lezer/common': 1.2.3 + '@lezer/highlight': 1.2.1 + '@lezer/lr': 1.4.2 + '@replit/codemirror-css-color-picker': 6.3.0(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.36.5) + '@scalar/components': 0.13.49(typescript@5.8.3) + codemirror: 6.0.1 + style-mod: 4.1.2 + vue: 3.5.13(typescript@5.8.3) + transitivePeerDependencies: + - '@vue/composition-api' + - supports-color + - typescript + + '@scalar/use-hooks@0.1.41(typescript@5.8.3)': + dependencies: + '@scalar/themes': 0.10.0 + '@scalar/use-toasts': 0.7.9(typescript@5.8.3) + '@vueuse/core': 10.11.1(vue@3.5.13(typescript@5.8.3)) + vue: 3.5.13(typescript@5.8.3) + zod: 3.24.3 + transitivePeerDependencies: + - '@vue/composition-api' + - typescript + + '@scalar/use-toasts@0.7.9(typescript@5.8.3)': + dependencies: + nanoid: 5.1.5 + vue: 3.5.13(typescript@5.8.3) + vue-sonner: 1.3.2 + transitivePeerDependencies: + - typescript + + '@scalar/use-tooltip@1.0.6(typescript@5.8.3)': + dependencies: + tippy.js: 6.3.7 + vue: 3.5.13(typescript@5.8.3) + transitivePeerDependencies: + - typescript + '@scarf/scarf@1.4.0': {} '@scure/base@1.1.9': {} @@ -23250,6 +24296,11 @@ snapshots: '@tanstack/virtual-core@3.13.6': {} + '@tanstack/vue-virtual@3.13.6(vue@3.5.13(typescript@5.8.3))': + dependencies: + '@tanstack/virtual-core': 3.13.6 + vue: 3.5.13(typescript@5.8.3) + '@testing-library/dom@10.4.0': dependencies: '@babel/code-frame': 7.26.2 @@ -23605,6 +24656,8 @@ snapshots: '@types/uuid@9.0.8': {} + '@types/web-bluetooth@0.0.20': {} + '@types/ws@7.4.7': dependencies: '@types/node': 22.14.1 @@ -23819,6 +24872,29 @@ snapshots: '@ungap/structured-clone@1.2.1': {} + '@unhead/dom@1.11.20': + dependencies: + '@unhead/schema': 1.11.20 + '@unhead/shared': 1.11.20 + + '@unhead/schema@1.11.20': + dependencies: + hookable: 5.5.3 + zhead: 2.2.4 + + '@unhead/shared@1.11.20': + dependencies: + '@unhead/schema': 1.11.20 + packrup: 0.1.2 + + '@unhead/vue@1.11.20(vue@3.5.13(typescript@5.8.3))': + dependencies: + '@unhead/schema': 1.11.20 + '@unhead/shared': 1.11.20 + hookable: 5.5.3 + unhead: 1.11.20 + vue: 3.5.13(typescript@5.8.3) + '@unrs/resolver-binding-darwin-arm64@1.6.3': optional: true @@ -24015,6 +25091,116 @@ snapshots: loupe: 3.1.3 tinyrainbow: 2.0.0 + '@vue/compiler-core@3.5.13': + dependencies: + '@babel/parser': 7.27.0 + '@vue/shared': 3.5.13 + entities: 4.5.0 + estree-walker: 2.0.2 + source-map-js: 1.2.1 + + '@vue/compiler-dom@3.5.13': + dependencies: + '@vue/compiler-core': 3.5.13 + '@vue/shared': 3.5.13 + + '@vue/compiler-sfc@3.5.13': + dependencies: + '@babel/parser': 7.27.0 + '@vue/compiler-core': 3.5.13 + '@vue/compiler-dom': 3.5.13 + '@vue/compiler-ssr': 3.5.13 + '@vue/shared': 3.5.13 + estree-walker: 2.0.2 + magic-string: 0.30.17 + postcss: 8.5.3 + source-map-js: 1.2.1 + + '@vue/compiler-ssr@3.5.13': + dependencies: + '@vue/compiler-dom': 3.5.13 + '@vue/shared': 3.5.13 + + '@vue/devtools-api@6.6.4': {} + + '@vue/reactivity@3.5.13': + dependencies: + '@vue/shared': 3.5.13 + + '@vue/runtime-core@3.5.13': + dependencies: + '@vue/reactivity': 3.5.13 + '@vue/shared': 3.5.13 + + '@vue/runtime-dom@3.5.13': + dependencies: + '@vue/reactivity': 3.5.13 + '@vue/runtime-core': 3.5.13 + '@vue/shared': 3.5.13 + csstype: 3.1.3 + + '@vue/server-renderer@3.5.13(vue@3.5.13(typescript@5.8.3))': + dependencies: + '@vue/compiler-ssr': 3.5.13 + '@vue/shared': 3.5.13 + vue: 3.5.13(typescript@5.8.3) + + '@vue/shared@3.5.13': {} + + '@vueuse/core@10.11.1(vue@3.5.13(typescript@5.8.3))': + dependencies: + '@types/web-bluetooth': 0.0.20 + '@vueuse/metadata': 10.11.1 + '@vueuse/shared': 10.11.1(vue@3.5.13(typescript@5.8.3)) + vue-demi: 0.14.10(vue@3.5.13(typescript@5.8.3)) + transitivePeerDependencies: + - '@vue/composition-api' + - vue + + '@vueuse/core@11.3.0(vue@3.5.13(typescript@5.8.3))': + dependencies: + '@types/web-bluetooth': 0.0.20 + '@vueuse/metadata': 11.3.0 + '@vueuse/shared': 11.3.0(vue@3.5.13(typescript@5.8.3)) + vue-demi: 0.14.10(vue@3.5.13(typescript@5.8.3)) + transitivePeerDependencies: + - '@vue/composition-api' + - vue + + '@vueuse/integrations@11.3.0(axios@1.8.4)(focus-trap@7.6.4)(fuse.js@7.1.0)(idb-keyval@6.2.1)(nprogress@0.2.0)(qrcode@1.5.4)(vue@3.5.13(typescript@5.8.3))': + dependencies: + '@vueuse/core': 11.3.0(vue@3.5.13(typescript@5.8.3)) + '@vueuse/shared': 11.3.0(vue@3.5.13(typescript@5.8.3)) + vue-demi: 0.14.10(vue@3.5.13(typescript@5.8.3)) + optionalDependencies: + axios: 1.8.4 + focus-trap: 7.6.4 + fuse.js: 7.1.0 + idb-keyval: 6.2.1 + nprogress: 0.2.0 + qrcode: 1.5.4 + transitivePeerDependencies: + - '@vue/composition-api' + - vue + + '@vueuse/metadata@10.11.1': {} + + '@vueuse/metadata@11.3.0': {} + + '@vueuse/shared@10.11.1(vue@3.5.13(typescript@5.8.3))': + dependencies: + vue-demi: 0.14.10(vue@3.5.13(typescript@5.8.3)) + transitivePeerDependencies: + - '@vue/composition-api' + - vue + + '@vueuse/shared@11.3.0(vue@3.5.13(typescript@5.8.3))': + dependencies: + vue-demi: 0.14.10(vue@3.5.13(typescript@5.8.3)) + transitivePeerDependencies: + - '@vue/composition-api' + - vue + '@wagmi/connectors@5.7.12(@types/react@19.1.2)(@wagmi/core@2.16.7(@tanstack/query-core@5.74.4)(@types/react@19.1.2)(react@19.1.0)(typescript@5.8.3)(use-sync-external-store@1.4.0(react@19.1.0))(viem@2.27.2(bufferutil@4.0.9)(typescript@5.8.3)(utf-8-validate@5.0.10)(zod@3.24.3)))(aws4fetch@1.0.20)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.6.1)(react@19.1.0)(typescript@5.8.3)(utf-8-validate@5.0.10)(viem@2.27.2(bufferutil@4.0.9)(typescript@5.8.3)(utf-8-validate@5.0.10)(zod@3.24.3))(zod@3.24.3)': dependencies: '@coinbase/wallet-sdk': 4.3.0 @@ -24860,10 +26046,18 @@ snapshots: clean-stack: 2.2.0 indent-string: 4.0.0 + ajv-draft-04@1.0.0(ajv@8.17.1): + optionalDependencies: + ajv: 8.17.1 + ajv-formats@2.1.1(ajv@8.17.1): optionalDependencies: ajv: 8.17.1 + ajv-formats@3.0.1(ajv@8.17.1): + optionalDependencies: + ajv: 8.17.1 + ajv-keywords@3.5.2(ajv@6.12.6): dependencies: ajv: 6.12.6 @@ -25864,6 +27058,16 @@ snapshots: - '@types/react' - '@types/react-dom' + codemirror@6.0.1: + dependencies: + '@codemirror/autocomplete': 6.18.6 + '@codemirror/commands': 6.8.1 + '@codemirror/language': 6.11.0 + '@codemirror/lint': 6.8.5 + '@codemirror/search': 6.5.10 + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.36.5 + color-convert@1.9.3: dependencies: color-name: 1.1.3 @@ -26079,6 +27283,8 @@ snapshots: safe-buffer: 5.2.1 sha.js: 2.4.11 + crelt@1.0.6: {} + cross-fetch@3.2.0(encoding@0.1.13): dependencies: node-fetch: 2.7.0(encoding@0.1.13) @@ -26188,6 +27394,12 @@ snapshots: csstype@3.1.3: {} + cva@1.0.0-beta.2(typescript@5.8.3): + dependencies: + clsx: 2.1.1 + optionalDependencies: + typescript: 5.8.3 + d3-array@3.2.4: dependencies: internmap: 2.0.3 @@ -26872,8 +28084,8 @@ snapshots: '@typescript-eslint/parser': 7.14.1(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3) eslint: 9.24.0(jiti@2.4.2) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0)(eslint@9.24.0(jiti@2.4.2)) - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.14.1(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.24.0(jiti@2.4.2)) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.14.1(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.24.0(jiti@2.4.2)))(eslint@9.24.0(jiti@2.4.2)) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.14.1(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.14.1(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.24.0(jiti@2.4.2)))(eslint@9.24.0(jiti@2.4.2)))(eslint@9.24.0(jiti@2.4.2)) eslint-plugin-jsx-a11y: 6.10.2(eslint@9.24.0(jiti@2.4.2)) eslint-plugin-react: 7.37.5(eslint@9.24.0(jiti@2.4.2)) eslint-plugin-react-hooks: 5.2.0(eslint@9.24.0(jiti@2.4.2)) @@ -26892,33 +28104,33 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0)(eslint@8.57.0): + eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.14.1(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.24.0(jiti@2.4.2)))(eslint@9.24.0(jiti@2.4.2)): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.4.0(supports-color@8.1.1) - eslint: 8.57.0 + eslint: 9.24.0(jiti@2.4.2) get-tsconfig: 4.10.0 is-bun-module: 2.0.0 stable-hash: 0.0.5 tinyglobby: 0.2.13 unrs-resolver: 1.6.3 optionalDependencies: - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.0) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.14.1(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.14.1(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.24.0(jiti@2.4.2)))(eslint@9.24.0(jiti@2.4.2)))(eslint@9.24.0(jiti@2.4.2)) transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0)(eslint@9.24.0(jiti@2.4.2)): + eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0)(eslint@8.57.0): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.4.0(supports-color@8.1.1) - eslint: 9.24.0(jiti@2.4.2) + eslint: 8.57.0 get-tsconfig: 4.10.0 is-bun-module: 2.0.0 stable-hash: 0.0.5 tinyglobby: 0.2.13 unrs-resolver: 1.6.3 optionalDependencies: - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.14.1(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.24.0(jiti@2.4.2)) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.0) transitivePeerDependencies: - supports-color @@ -26954,14 +28166,14 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@7.14.1(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0)(eslint@9.24.0(jiti@2.4.2)))(eslint@9.24.0(jiti@2.4.2)): + eslint-module-utils@2.12.0(@typescript-eslint/parser@7.14.1(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.14.1(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.24.0(jiti@2.4.2)))(eslint@9.24.0(jiti@2.4.2)))(eslint@9.24.0(jiti@2.4.2)): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 7.14.1(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3) eslint: 9.24.0(jiti@2.4.2) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0)(eslint@9.24.0(jiti@2.4.2)) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.14.1(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.24.0(jiti@2.4.2)))(eslint@9.24.0(jiti@2.4.2)) transitivePeerDependencies: - supports-color @@ -26994,7 +28206,7 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.14.1(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.24.0(jiti@2.4.2)): + eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.14.1(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.14.1(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.24.0(jiti@2.4.2)))(eslint@9.24.0(jiti@2.4.2)))(eslint@9.24.0(jiti@2.4.2)): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.8 @@ -27005,7 +28217,7 @@ snapshots: doctrine: 2.1.0 eslint: 9.24.0(jiti@2.4.2) eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@7.14.1(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0)(eslint@9.24.0(jiti@2.4.2)))(eslint@9.24.0(jiti@2.4.2)) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@7.14.1(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.14.1(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.24.0(jiti@2.4.2)))(eslint@9.24.0(jiti@2.4.2)))(eslint@9.24.0(jiti@2.4.2)) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -27855,6 +29067,10 @@ snapshots: dependencies: tslib: 2.8.1 + focus-trap@7.6.4: + dependencies: + tabbable: 6.2.0 + follow-redirects@1.15.9: {} fontfaceobserver@2.3.0: {} @@ -28023,6 +29239,8 @@ snapshots: get-nonce@1.0.1: {} + get-own-enumerable-keys@1.0.0: {} + get-package-type@0.1.0: {} get-port@6.1.2: {} @@ -28256,8 +29474,97 @@ snapshots: dependencies: function-bind: 1.1.2 + hast-util-embedded@3.0.0: + dependencies: + '@types/hast': 3.0.4 + hast-util-is-element: 3.0.0 + + hast-util-format@1.1.0: + dependencies: + '@types/hast': 3.0.4 + hast-util-embedded: 3.0.0 + hast-util-minify-whitespace: 1.0.1 + hast-util-phrasing: 3.0.1 + hast-util-whitespace: 3.0.0 + html-whitespace-sensitive-tag-names: 3.0.1 + unist-util-visit-parents: 6.0.1 + + hast-util-from-html@2.0.3: + dependencies: + '@types/hast': 3.0.4 + devlop: 1.1.0 + hast-util-from-parse5: 8.0.3 + parse5: 7.2.1 + vfile: 6.0.3 + vfile-message: 4.0.2 + + hast-util-from-parse5@8.0.3: + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + devlop: 1.1.0 + hastscript: 9.0.1 + property-information: 7.0.0 + vfile: 6.0.3 + vfile-location: 5.0.3 + web-namespaces: 2.0.1 + + hast-util-has-property@3.0.0: + dependencies: + '@types/hast': 3.0.4 + + hast-util-is-body-ok-link@3.0.1: + dependencies: + '@types/hast': 3.0.4 + + hast-util-is-element@3.0.0: + dependencies: + '@types/hast': 3.0.4 + + hast-util-minify-whitespace@1.0.1: + dependencies: + '@types/hast': 3.0.4 + hast-util-embedded: 3.0.0 + hast-util-is-element: 3.0.0 + hast-util-whitespace: 3.0.0 + unist-util-is: 6.0.0 + hast-util-parse-selector@2.2.5: {} + hast-util-parse-selector@4.0.0: + dependencies: + '@types/hast': 3.0.4 + + hast-util-phrasing@3.0.1: + dependencies: + '@types/hast': 3.0.4 + hast-util-embedded: 3.0.0 + hast-util-has-property: 3.0.0 + hast-util-is-body-ok-link: 3.0.1 + hast-util-is-element: 3.0.0 + + hast-util-raw@9.1.0: + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + '@ungap/structured-clone': 1.2.1 + hast-util-from-parse5: 8.0.3 + hast-util-to-parse5: 8.0.0 + html-void-elements: 3.0.0 + mdast-util-to-hast: 13.2.0 + parse5: 7.2.1 + unist-util-position: 5.0.0 + unist-util-visit: 5.0.0 + vfile: 6.0.3 + web-namespaces: 2.0.1 + zwitch: 2.0.4 + + hast-util-sanitize@5.0.2: + dependencies: + '@types/hast': 3.0.4 + '@ungap/structured-clone': 1.2.1 + unist-util-position: 5.0.0 + hast-util-to-estree@2.3.3: dependencies: '@types/estree': 1.0.7 @@ -28312,6 +29619,23 @@ snapshots: transitivePeerDependencies: - supports-color + hast-util-to-parse5@8.0.0: + dependencies: + '@types/hast': 3.0.4 + comma-separated-tokens: 2.0.3 + devlop: 1.1.0 + property-information: 6.5.0 + space-separated-tokens: 2.0.2 + web-namespaces: 2.0.1 + zwitch: 2.0.4 + + hast-util-to-text@4.0.2: + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + hast-util-is-element: 3.0.0 + unist-util-find-after: 5.0.0 + hast-util-whitespace@2.0.1: {} hast-util-whitespace@3.0.0: @@ -28326,6 +29650,14 @@ snapshots: property-information: 5.6.0 space-separated-tokens: 1.1.5 + hastscript@9.0.1: + dependencies: + '@types/hast': 3.0.4 + comma-separated-tokens: 2.0.3 + hast-util-parse-selector: 4.0.0 + property-information: 7.0.0 + space-separated-tokens: 2.0.2 + he@1.2.0: {} headers-polyfill@4.0.3: {} @@ -28350,6 +29682,10 @@ snapshots: highlight.js@10.7.3: {} + highlight.js@11.11.1: {} + + highlightjs-curl@1.3.0: {} + highlightjs-vue@1.0.0: {} hmac-drbg@1.0.1: @@ -28362,6 +29698,8 @@ snapshots: dependencies: react-is: 16.13.1 + hookable@5.5.3: {} + hosted-git-info@7.0.2: dependencies: lru-cache: 10.4.3 @@ -28394,6 +29732,8 @@ snapshots: optionalDependencies: webpack: 5.99.6(esbuild@0.25.2) + html-whitespace-sensitive-tag-names@3.0.1: {} + htmlparser2@3.10.1: dependencies: domelementtype: 1.3.1 @@ -28592,6 +29932,8 @@ snapshots: iron-webcrypto@1.2.1: {} + is-absolute-url@4.0.1: {} + is-alphabetical@1.0.4: {} is-alphabetical@2.0.1: {} @@ -28739,6 +30081,8 @@ snapshots: is-obj@2.0.0: {} + is-obj@3.0.0: {} + is-path-cwd@2.2.0: {} is-path-inside@3.0.3: {} @@ -28768,6 +30112,8 @@ snapshots: has-tostringtag: 1.0.2 hasown: 2.0.2 + is-regexp@3.1.0: {} + is-retry-allowed@1.2.0: {} is-set@2.0.3: {} @@ -29137,6 +30483,8 @@ snapshots: json-stream-stringify@3.1.6: {} + json-stringify-deterministic@1.0.12: {} + json-stringify-safe@5.0.1: {} json5@1.0.2: @@ -29157,6 +30505,8 @@ snapshots: optionalDependencies: graceful-fs: 4.2.11 + jsonpointer@5.0.1: {} + jsx-ast-utils@3.3.5: dependencies: array-includes: 3.1.8 @@ -29164,6 +30514,10 @@ snapshots: object.assign: 4.1.7 object.values: 1.2.1 + just-clone@6.2.0: {} + + just-curry-it@5.3.0: {} + jwt-decode@3.1.2: {} keccak@3.0.4: @@ -29203,7 +30557,7 @@ snapshots: zod: 3.24.3 zod-validation-error: 3.4.0(zod@3.24.3) - ky@1.8.0: {} + ky@1.8.1: {} language-subtag-registry@0.3.23: {} @@ -29217,6 +30571,8 @@ snapshots: leven@3.1.0: {} + leven@4.0.0: {} + levn@0.4.1: dependencies: prelude-ls: 1.2.1 @@ -29459,6 +30815,12 @@ snapshots: fault: 1.0.4 highlight.js: 10.7.3 + lowlight@3.3.0: + dependencies: + '@types/hast': 3.0.4 + devlop: 1.1.0 + highlight.js: 11.11.1 + lru-cache@10.4.3: {} lru-cache@11.0.2: {} @@ -30068,6 +31430,8 @@ snapshots: - supports-color - utf-8-validate + microdiff@1.5.0: {} + micromark-core-commonmark@1.1.0: dependencies: decode-named-character-reference: 1.1.0 @@ -30817,6 +32181,8 @@ snapshots: nanoid@5.1.2: {} + nanoid@5.1.5: {} + nanospinner@1.2.2: dependencies: picocolors: 1.1.1 @@ -31425,6 +32791,8 @@ snapshots: dependencies: quansync: 0.2.10 + packrup@0.1.2: {} + pako@0.2.9: {} pako@1.0.11: {} @@ -31493,12 +32861,18 @@ snapshots: lines-and-columns: 2.0.4 type-fest: 3.13.1 + parse-ms@3.0.0: {} + parse-ms@4.0.0: {} parse-png@2.1.0: dependencies: pngjs: 3.4.0 + parse5@7.2.1: + dependencies: + entities: 4.5.0 + parseurl@1.3.3: {} pascal-case@3.1.2: @@ -31810,6 +33184,8 @@ snapshots: pretty-bytes@5.6.0: {} + pretty-bytes@6.1.1: {} + pretty-error@4.0.0: dependencies: lodash: 4.17.21 @@ -31827,6 +33203,10 @@ snapshots: ansi-styles: 5.2.0 react-is: 18.3.1 + pretty-ms@8.0.0: + dependencies: + parse-ms: 3.0.0 + pretty-ms@9.2.0: dependencies: parse-ms: 4.0.0 @@ -31877,6 +33257,8 @@ snapshots: property-information@6.5.0: {} + property-information@7.0.0: {} + proto-list@1.2.4: {} proxy-agent@6.4.0: @@ -31994,6 +33376,23 @@ snapshots: quick-lru@5.1.1: {} + radix-vue@1.9.17(vue@3.5.13(typescript@5.8.3)): + dependencies: + '@floating-ui/dom': 1.6.13 + '@floating-ui/vue': 1.1.6(vue@3.5.13(typescript@5.8.3)) + '@internationalized/date': 3.8.0 + '@internationalized/number': 3.6.1 + '@tanstack/vue-virtual': 3.13.6(vue@3.5.13(typescript@5.8.3)) + '@vueuse/core': 10.11.1(vue@3.5.13(typescript@5.8.3)) + '@vueuse/shared': 10.11.1(vue@3.5.13(typescript@5.8.3)) + aria-hidden: 1.2.4 + defu: 6.1.4 + fast-deep-equal: 3.1.3 + nanoid: 5.1.5 + vue: 3.5.13(typescript@5.8.3) + transitivePeerDependencies: + - '@vue/composition-api' + radix3@1.1.2: {} ramda-adjunct@5.1.0(ramda@0.30.1): @@ -32549,6 +33948,43 @@ snapshots: dependencies: jsesc: 3.0.2 + rehype-external-links@3.0.0: + dependencies: + '@types/hast': 3.0.4 + '@ungap/structured-clone': 1.2.1 + hast-util-is-element: 3.0.0 + is-absolute-url: 4.0.1 + space-separated-tokens: 2.0.2 + unist-util-visit: 5.0.0 + + rehype-format@5.0.1: + dependencies: + '@types/hast': 3.0.4 + hast-util-format: 1.1.0 + + rehype-parse@9.0.1: + dependencies: + '@types/hast': 3.0.4 + hast-util-from-html: 2.0.3 + unified: 11.0.5 + + rehype-raw@7.0.0: + dependencies: + '@types/hast': 3.0.4 + hast-util-raw: 9.1.0 + vfile: 6.0.3 + + rehype-sanitize@6.0.0: + dependencies: + '@types/hast': 3.0.4 + hast-util-sanitize: 5.0.2 + + rehype-stringify@10.0.1: + dependencies: + '@types/hast': 3.0.4 + hast-util-to-html: 9.0.4 + unified: 11.0.5 + reinterval@1.1.0: {} relateurl@0.2.7: {} @@ -33449,6 +34885,12 @@ snapshots: character-entities-html4: 2.1.0 character-entities-legacy: 3.0.0 + stringify-object@5.0.0: + dependencies: + get-own-enumerable-keys: 1.0.0 + is-obj: 3.0.0 + is-regexp: 3.1.0 + strip-ansi@5.2.0: dependencies: ansi-regex: 4.1.1 @@ -33500,6 +34942,8 @@ snapshots: dependencies: webpack: 5.99.6(esbuild@0.25.2) + style-mod@4.1.2: {} + style-to-object@0.4.4: dependencies: inline-style-parser: 0.1.1 @@ -33834,6 +35278,10 @@ snapshots: tinyspy@3.0.2: {} + tippy.js@6.3.7: + dependencies: + '@popperjs/core': 2.11.8 + tmp@0.0.33: dependencies: os-tmpdir: 1.0.2 @@ -33891,6 +35339,8 @@ snapshots: ts-dedent@2.2.0: {} + ts-deepmerge@7.0.2: {} + ts-interface-checker@0.1.13: {} ts-mixer@6.0.4: {} @@ -34113,6 +35563,13 @@ snapshots: node-fetch-native: 1.6.6 pathe: 1.1.2 + unhead@1.11.20: + dependencies: + '@unhead/dom': 1.11.20 + '@unhead/schema': 1.11.20 + '@unhead/shared': 1.11.20 + hookable: 5.5.3 + unicode-canonical-property-names-ecmascript@2.0.1: {} unicode-match-property-ecmascript@2.0.0: @@ -34192,6 +35649,11 @@ snapshots: dependencies: crypto-random-string: 4.0.0 + unist-util-find-after@5.0.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.0 + unist-util-generated@2.0.1: {} unist-util-inspect@8.1.0: @@ -34459,6 +35921,11 @@ snapshots: vary@1.1.2: {} + vfile-location@5.0.3: + dependencies: + '@types/unist': 3.0.3 + vfile: 6.0.3 + vfile-message@3.1.4: dependencies: '@types/unist': 2.0.11 @@ -34675,6 +36142,29 @@ snapshots: vscode-textmate@8.0.0: {} + vue-demi@0.14.10(vue@3.5.13(typescript@5.8.3)): + dependencies: + vue: 3.5.13(typescript@5.8.3) + + vue-router@4.5.0(vue@3.5.13(typescript@5.8.3)): + dependencies: + '@vue/devtools-api': 6.6.4 + vue: 3.5.13(typescript@5.8.3) + + vue-sonner@1.3.2: {} + + vue@3.5.13(typescript@5.8.3): + dependencies: + '@vue/compiler-dom': 3.5.13 + '@vue/compiler-sfc': 3.5.13 + '@vue/runtime-dom': 3.5.13 + '@vue/server-renderer': 3.5.13(vue@3.5.13(typescript@5.8.3)) + '@vue/shared': 3.5.13 + optionalDependencies: + typescript: 5.8.3 + + w3c-keyname@2.2.8: {} + wagmi@2.14.16(@tanstack/query-core@5.74.4)(@tanstack/react-query@5.74.4(react@19.1.0))(@types/react@19.1.2)(aws4fetch@1.0.20)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.6.1)(react@19.1.0)(typescript@5.8.3)(utf-8-validate@5.0.10)(viem@2.27.2(bufferutil@4.0.9)(typescript@5.8.3)(utf-8-validate@5.0.10)(zod@3.24.3))(zod@3.24.3): dependencies: '@tanstack/react-query': 5.74.4(react@19.1.0) @@ -34730,6 +36220,8 @@ snapshots: dependencies: defaults: 1.0.4 + web-namespaces@2.0.1: {} + web-streams-polyfill@3.3.3: {} web-tree-sitter@0.24.5: @@ -34848,6 +36340,8 @@ snapshots: whatwg-mimetype@3.0.0: {} + whatwg-mimetype@4.0.0: {} + whatwg-url-without-unicode@8.0.0-3: dependencies: buffer: 5.7.1 @@ -35119,6 +36613,8 @@ snapshots: zenscroll@4.0.2: {} + zhead@2.2.4: {} + zod-validation-error@3.4.0(zod@3.24.3): dependencies: zod: 3.24.3 From 9ce9bc8214a521bf8829e72f7934e8e1d64f13a8 Mon Sep 17 00:00:00 2001 From: Prithvish Baidya Date: Tue, 22 Apr 2025 03:47:04 +0530 Subject: [PATCH 027/101] updated vault-sdk --- packages/vault-sdk/package.json | 2 +- packages/vault-sdk/src/exports/thirdweb.ts | 40 +++ packages/vault-sdk/src/sdk.ts | 310 ++++++++++++++++++--- packages/vault-sdk/src/types.ts | 215 +++++++++++++- pnpm-lock.yaml | 12 +- 5 files changed, 514 insertions(+), 65 deletions(-) diff --git a/packages/vault-sdk/package.json b/packages/vault-sdk/package.json index fc3dfbdbafe..01fabd4b7f2 100644 --- a/packages/vault-sdk/package.json +++ b/packages/vault-sdk/package.json @@ -32,7 +32,7 @@ "@noble/curves": "1.8.1", "@noble/hashes": "1.7.1", "abitype": "1.0.8", - "ky": "^1.8.0" + "jose": "6.0.10" }, "devDependencies": { "rimraf": "6.0.1" diff --git a/packages/vault-sdk/src/exports/thirdweb.ts b/packages/vault-sdk/src/exports/thirdweb.ts index 43504d626cd..a66ce947db2 100644 --- a/packages/vault-sdk/src/exports/thirdweb.ts +++ b/packages/vault-sdk/src/exports/thirdweb.ts @@ -18,3 +18,43 @@ export { ParseTransactionError, parseTransaction, } from "../transaction-parser.js"; + +export type { + AccessTokenData, + Auth, + Authorization, + CheckedSignTypedDataPayload, + CreateAccessTokenPayload, + CreateEoaPayload, + CreateServiceAccountPayload, + EncryptedPayload, + GenericPayload, + GetAccessTokensData, + GetAccessTokensOptions, + GetServiceAccountPayload, + ListAccessTokensPayload, + ListEoaPayload, + Payload, + PingPayload, + PolicyComponent, + RevokeAccessTokenPayload, + RotateServiceAccountPayload, + SignAuthorizationData, + SignAuthorizationOptions, + SignMessagePayload, + SignTransactionPayload, + SignTypedDataPayload, + SignAuthorizationPayload, + SignAuthorizationRules, + SignStructuredMessageData, + SignStructuredMessageOptions, + SignStructuredMessagePayload, + SignedAuthorization, + StructuredMessageInput, + UnencryptedErrorResponse, + UserOperationV06Input, + UserOperationV06Rules, + UserOperationV07Input, + UserOperationV07Rules, + VaultError, +} from "../types.js"; diff --git a/packages/vault-sdk/src/sdk.ts b/packages/vault-sdk/src/sdk.ts index 9083e407744..214fca4ce71 100644 --- a/packages/vault-sdk/src/sdk.ts +++ b/packages/vault-sdk/src/sdk.ts @@ -1,6 +1,5 @@ import type { TypedData } from "abitype"; - -import ky from "ky"; +import * as jose from "jose"; import { x25519 } from "@noble/curves/ed25519"; import { randomBytes } from "@noble/hashes/utils"; import { sha256 } from "@noble/hashes/sha256"; @@ -24,6 +23,10 @@ import type { Prettify, UnencryptedErrorResponse, CheckedSignTypedDataPayload, + ListAccessTokensPayload, + SignStructuredMessagePayload, + SignAuthorizationPayload, + PolicyComponent, } from "./types.js"; function encryptForEnclave( @@ -105,29 +108,54 @@ export type VaultClient = { publicKey: Uint8Array; }; -export async function createVaultClient({ baseUrl }: { baseUrl: string }) { - const vaultApi = ky.create({ - prefixUrl: baseUrl, - headers: { - "Content-Type": "application/json", - }, - throwHttpErrors: false, - }); +export async function createVaultClient({ + baseUrl, +}: { + baseUrl: string; +}): Promise { + // Construct the full URL for the fetch call + const url = new URL("api/v1/enclave", baseUrl).toString(); type IntrospectionResponse = { publicKey: string; }; - const res = await vaultApi - .get("api/v1/enclave") - .json(); + try { + const response = await fetch(url, { + method: "GET", + headers: { + // Indicate we accept JSON responses + Accept: "application/json", + }, + }); - const publicKeyBytes = hexToBytes(res.publicKey); + // fetch doesn't throw on HTTP errors (like 4xx, 5xx) by default. + // Check if the request was successful (status in the range 200-299). + if (!response.ok) { + // You might want more sophisticated error handling here, + // potentially trying to parse an error message from the response body. + throw new Error( + `Failed to fetch enclave public key: ${response.status} ${response.statusText}`, + ); + } - return { - baseUrl: baseUrl, - publicKey: publicKeyBytes, - } as VaultClient; + // Parse the JSON response body + const data = (await response.json()) as IntrospectionResponse; + + if (!data.publicKey) { + throw new Error("Invalid response format: publicKey missing"); + } + + const publicKeyBytes = hexToBytes(data.publicKey); + + return { + baseUrl: baseUrl, // Store baseUrl + publicKey: publicKeyBytes, + }; + } catch (error) { + // Handle network errors or JSON parsing errors + throw new Error(`Failed to fetch enclave public key: ${error}`); // Re-throw or handle as appropriate for your application + } } // ========== Main API function ========== @@ -139,40 +167,84 @@ type SendRequestParams

= { async function sendRequest

({ request, client, -}: SendRequestParams

) { +}: SendRequestParams

): Promise> { const { encryptedPayload, ephemeralPrivateKey } = encryptForEnclave( request, client.publicKey, ); - const vaultApi = ky.create({ - prefixUrl: client.baseUrl, - headers: { - "Content-Type": "application/json", - }, - throwHttpErrors: false, - }); - - const res = await vaultApi - .post("api/v1/enclave", { - json: encryptedPayload, - }) - .json(); + // Construct the full URL using the client's baseUrl + const url = new URL("api/v1/enclave", client.baseUrl).toString(); - if (isErrorResponse(res)) { + try { + const response = await fetch(url, { + method: "POST", + headers: { + "Content-Type": "application/json", + Accept: "application/json", // Good practice to specify accept header + }, + // Stringify the JSON payload for the request body + body: JSON.stringify(encryptedPayload), + }); + + // IMPORTANT: Replicate ky's throwHttpErrors: false behavior. + // We proceed to parse the body regardless of response.ok status, + // as the body itself might contain the structured error (UnencryptedErrorResponse) + // or the encrypted success payload. + + let responseData: EncryptedPayload | UnencryptedErrorResponse; + try { + responseData = await response.json(); + } catch (parseError) { + // If JSON parsing fails (e.g., 500 error with HTML body), + // construct a generic error response. + return { + success: false, + data: null, + error: { + code: "FETCH_PARSE_ERROR", + message: `Failed to parse response: ${ + response.status + } ${response.statusText}. ${ + parseError instanceof Error + ? parseError.message + : String(parseError) + }`, + }, + } as Prettify; // Cast needed because error isn't strictly EncryptedError | UnencryptedError + } + + // Now check if the *parsed* response indicates an error + if (isErrorResponse(responseData)) { + console.error("🚨 Error response from enclave:", responseData); + return { + success: false, + data: null, + error: responseData.error, // Use the error from the response body + } as Prettify; + } + + // If it's not an error response, it must be the EncryptedPayload + const decryptedResponse = decryptFromEnclave( + responseData, // No need for 'as EncryptedPayload' if isErrorResponse is accurate + ephemeralPrivateKey, + ); + + // The decrypted response should match the expected success structure P["output"] + // which includes { success: true, data: ..., error: null } + return decryptedResponse as Prettify; + } catch (error) { + // Catch network errors during the fetch itself return { success: false, data: null, - error: res.error, - } as Prettify; + error: { + code: "FETCH_NETWORK_ERROR", + message: + error instanceof Error ? error.message : "Unknown network error", + }, + } as Prettify; // Cast needed } - - const decryptedResponse = decryptFromEnclave( - res, - ephemeralPrivateKey, - ) as Prettify; - - return decryptedResponse; } // ========== Generic Helper Params ========== @@ -328,3 +400,157 @@ export function revokeAccessToken({ client, }); } + +export function signAuthorization({ + client, + request: options, +}: PayloadParams) { + return sendRequest({ + request: { + operation: "eoa:signAuthorization", + ...options, + }, + client, + }); +} + +export function signStructuredMessage({ + client, + request: options, +}: PayloadParams) { + return sendRequest({ + request: { + operation: "eoa:signStructuredMessage", + ...options, + }, + client, + }); +} + +export function listAccessTokens({ + client, + request: options, +}: PayloadParams) { + return sendRequest({ + request: { + operation: "accessToken:list", + ...options, + }, + client, + }); +} + +const SIGNED_TOKEN_PREFIX = "vt_sat_"; +const DEFAULT_SIGNING_CONTEXT = "encryption"; // Default context for HKDF + +export interface CreateSignedAccessTokenParams { + /** The VaultClient instance (used to get the enclave public key). */ + vaultClient: VaultClient; + /** The base access token string obtained from the createAccessToken API call. */ + baseAccessToken: string; + /** The specific policies to embed in the signed token's claims. */ + additionalPolicies: PolicyComponent[]; + /** The desired expiration time for the signed token (Unix timestamp in seconds). */ + expiryTimestamp: number; + /** Optional: The context string for HKDF key derivation. Defaults to "encryption". MUST match server expectation. */ + signingContext?: string; +} + +/** + * Creates a client-side "signed access token". + * This involves encrypting the baseAccessToken and embedding it, along with + * additional policies and expiry, into a JWT signed using a key derived + * from the baseAccessToken itself. + * + * NOTE: This function is ASYNCHRONOUS because JWT signing using Web Crypto is async. + * + * @param params - The parameters for creating the signed token. + * @returns A Promise resolving to the signed access token string (prefixed). + * @throws If any cryptographic operation or JWT signing fails. + */ +export async function createSignedAccessToken({ + vaultClient, + baseAccessToken, + additionalPolicies, + expiryTimestamp, + signingContext = DEFAULT_SIGNING_CONTEXT, +}: CreateSignedAccessTokenParams): Promise { + if (!vaultClient || !vaultClient.publicKey) { + throw new Error("Vault client or enclave public key is missing."); + } + if (!baseAccessToken) { + throw new Error("Base access token is required."); + } + if (!additionalPolicies) { + throw new Error("Additional policies are required."); + } + if (typeof expiryTimestamp !== "number" || expiryTimestamp <= 0) { + throw new Error("Valid expiry timestamp is required."); + } + + try { + const enclavePublicKey = vaultClient.publicKey; + const contextBytes = new TextEncoder().encode(signingContext); + + // 1. Generate client ephemeral keys for this operation + const clientEphemeralPrivateKey = randomBytes(32); + const clientEphemeralPublicKey = x25519.getPublicKey( + clientEphemeralPrivateKey, + ); + + // 2. Derive shared secret (Client Ephemeral Private Key + Enclave Public Key) + const sharedSecret = x25519.getSharedSecret( + clientEphemeralPrivateKey, + enclavePublicKey, + ); + + // 3. Derive encryption key using HKDF + const encryptionKey = hkdf( + sha256, + sharedSecret, + undefined, // No salt + contextBytes, // Use the provided or default context + 32, // 32 bytes key length + ); + + // 4. Encrypt the base access token string + const nonce = randomBytes(24); // 24-byte nonce for XChaCha20 + const cipher = xchacha20poly1305(encryptionKey, nonce); + const baseTokenBytes = new TextEncoder().encode(baseAccessToken); + const ciphertext = cipher.encrypt(baseTokenBytes); + + // 5. Prepare JWT Signing Key (SHA-256 hash of the base token) + const secretBytes = new TextEncoder().encode(baseAccessToken); + const hashBuffer = await crypto.subtle.digest("SHA-256", secretBytes); + const jwtSigningKey = new Uint8Array(hashBuffer); + + // 6. Prepare JWT Claims + const claims = { + // Match Rust SignedTokenClaims struct (ensure names match server expectation) + exp: expiryTimestamp, + iat: Math.floor(Date.now() / 1000), + encrypted_token: bytesToHex(ciphertext), + nonce: bytesToHex(nonce), + ephemeral_public_key: bytesToHex(clientEphemeralPublicKey), + policies: additionalPolicies, + }; + + // 7. Sign the JWT using HS256 + const jwt = await new jose.SignJWT(claims) + .setProtectedHeader({ alg: "HS256" }) + // .setIssuedAt(claims.iat) // Included in claims + // .setExpirationTime(claims.exp) // Included in claims + .sign(jwtSigningKey); + + // 8. Prepend the prefix + const signedAccessToken = `${SIGNED_TOKEN_PREFIX}${jwt}`; + return signedAccessToken; + } catch (error) { + console.error("Error during signed access token creation:", error); + throw new Error( + `Failed to create signed access token: ${ + error instanceof Error ? error.message : String(error) + }`, + ); + } +} diff --git a/packages/vault-sdk/src/types.ts b/packages/vault-sdk/src/types.ts index ba05ee80ea0..1bfbcc3c48c 100644 --- a/packages/vault-sdk/src/types.ts +++ b/packages/vault-sdk/src/types.ts @@ -39,6 +39,11 @@ type RotationCodeAuth = { }; // ========== Base Types ========== +// Represents Address, Bytes, U256, I256 as strings for broad compatibility +type Bytes = string; // e.g., "0x..." +// type HexString = string; // Generic hex string +type BigNumberString = string; // String representation of U256/I256 + type UnencryptedError = { message: string; status: number; @@ -115,21 +120,27 @@ type PingOptions = { message: string; }; -// type Transaction = { -// to: string; -// value: string; -// gasLimit: string; -// gasPrice: string; -// nonce: string; -// chainId: number; -// data: string; -// }; - type SignTransactionOptions = { transaction: EthereumTypedTransaction; from: string; }; +export type SignAuthorizationOptions = { + from: Address; + authorization: Authorization; // Use the defined type +}; + +export type SignStructuredMessageOptions = { + from: Address; + structuredMessage: StructuredMessageInput; // Use the defined type + chainId?: number; // Keep as number for ChainId +}; + +export type GetAccessTokensOptions = { + page?: number; + pageSize?: number; +}; + type SignMessageOptions = { message: string; from: string; @@ -153,6 +164,49 @@ type CheckedSignTypedDataOptions< // biome-ignore lint/suspicious/noExplicitAny: type SignedTypedDataOptions = CheckedSignTypedDataOptions; +// ========== User Operation Types ========== + +// Corresponds to Rust UserOperationV06Input +export type UserOperationV06Input = { + sender: Address; + nonce: BigNumberString; // U256 + initCode?: Bytes; // Optional due to #[serde(default)] + callData: Bytes; + callGasLimit: BigNumberString; // U256 + verificationGasLimit: BigNumberString; // U256 + preVerificationGas: BigNumberString; // U256 + maxFeePerGas: BigNumberString; // U256 + maxPriorityFeePerGas: BigNumberString; // U256 + paymasterAndData?: Bytes; // Optional due to #[serde(default)] + signature?: Bytes; // Optional due to #[serde(default)] + entrypoint?: Address; // Optional due to #[serde(default = ...)] +}; + +// Corresponds to Rust UserOperationV07Input +export type UserOperationV07Input = { + sender: Address; + nonce: BigNumberString; // U256 + factory?: Address; // Optional due to #[serde(default)] + factoryData?: Bytes; // Optional due to #[serde(default)] + callData: Bytes; + callGasLimit: BigNumberString; // U256 + verificationGasLimit: BigNumberString; // U256 + preVerificationGas: BigNumberString; // U256 + maxFeePerGas: BigNumberString; // U256 + maxPriorityFeePerGas: BigNumberString; // U256 + paymaster?: Address; // Optional due to #[serde(default)] + paymasterData?: Bytes; // Optional due to #[serde(default)] - Assuming default is empty bytes + paymasterVerificationGasLimit?: BigNumberString; // U256 - Assuming default is 0 + paymasterPostOpGasLimit?: BigNumberString; // U256 - Assuming default is 0 + signature?: Bytes; // Optional due to #[serde(default)] + entrypoint?: Address; // Optional due to #[serde(default = ...)] +}; + +// Corresponds to Rust StructuredMessageInput enum +export type StructuredMessageInput = + | { userOpV06: UserOperationV06Input } + | { userOpV07: UserOperationV07Input }; + // ========== Policy Types ========== type RegexRule = { pattern: string; @@ -162,7 +216,7 @@ type NumberRuleOp = "greaterThan" | "lessThan" | "equalTo"; type NumberRule = { op: NumberRuleOp; - value: number | bigint; + value: number | BigNumberString; }; type Rule = NumberRule | RegexRule; @@ -172,7 +226,52 @@ type MetadataRule = { rule: Rule; }; -type PolicyComponent = +// ========== Policy Rule Structs ========== + +// Corresponds to Rust UserOperationV06Rules +export type UserOperationV06Rules = { + sender?: Rule; + nonce?: Rule; + initCode?: Rule; + callData?: Rule; + callGasLimit?: Rule; + verificationGasLimit?: Rule; + preVerificationGas?: Rule; + maxFeePerGas?: Rule; + maxPriorityFeePerGas?: Rule; + paymasterAndData?: Rule; + chainId?: Rule; + entrypoint?: Rule; // Optional, but Rust has a default func +}; + +// Corresponds to Rust UserOperationV07Rules +export type UserOperationV07Rules = { + sender?: Rule; + nonce?: Rule; + factory?: Rule; + factoryData?: Rule; + callData?: Rule; + callGasLimit?: Rule; + verificationGasLimit?: Rule; + preVerificationGas?: Rule; + maxFeePerGas?: Rule; + maxPriorityFeePerGas?: Rule; + paymaster?: Rule; + paymasterVerificationGasLimit?: Rule; + paymasterPostOpGasLimit?: Rule; + paymasterData?: Rule; + chainId?: Rule; + entrypoint?: Rule; // Optional, but Rust has a default func +}; + +// Corresponds to Rust SignAuthorizationRules +export type SignAuthorizationRules = { + chainId?: Rule; + nonce?: Rule; + address?: Rule; +}; + +export type PolicyComponent = | { type: "eoa:create"; requiredMetadataPatterns?: MetadataRule[]; @@ -199,6 +298,22 @@ type PolicyComponent = type: "eoa:signTypedData"; allowlist?: Address[]; metadataPatterns?: MetadataRule[]; + } + | { + type: "eoa:signAuthorization"; + allowlist?: Address[]; + metadataPatterns?: MetadataRule[]; + payloadPatterns?: SignAuthorizationRules; + } + | { + type: "eoa:signStructuredMessage"; + allowlist?: Address[]; + metadataPatterns?: MetadataRule[]; + // Define how UserOp rules are applied, e.g., separate for v6/v7 + structuredPatterns: { + userOpV06?: UserOperationV06Rules; + userOpV07?: UserOperationV07Rules; + }; }; type OwnerType = string; // Define based on your eoa models @@ -213,6 +328,23 @@ type RevokeAccessTokenOptions = { id: string; // UUID }; +// ========== Authorization Types ========== + +// Corresponds to Rust Authorization struct +export type Authorization = { + chainId: BigNumberString; // U256 + address: Address; + nonce: number; // u64 +}; + +// Corresponds to Rust SignedAuthorization struct +// Merges Authorization fields due to #[serde(flatten)] +export type SignedAuthorization = Authorization & { + yParity: number; // U8 (typically 0 or 1) + r: BigNumberString; // U256 + s: BigNumberString; // U256 +}; + // ========== Response Data Types ========== type PingData = { timestamp: number; @@ -291,6 +423,38 @@ type RevokeAccessTokenData = { revokedAt?: string; // ISO date string }; +// Update SignAuthorizationData to use the defined SignedAuthorization type +export type SignAuthorizationData = { + signedAuthorization: SignedAuthorization; // Use the defined type +}; + +// Add SignStructuredMessageData (as defined previously) +export type SignStructuredMessageData = { + signature: Bytes; + message: string; // This likely represents the UserOp hash in Rust +}; + +// Add AccessTokenData (as defined previously, ensure OwnerType/MetadataValue are correct) +export type AccessTokenData = { + id: string; // UUID + issuerId: string; // UUID + issuerType: OwnerType; + policies: PolicyComponent[]; + expiresAt: string; // ISO date string + metadata: Record; + createdAt: string; // ISO date string + updatedAt: string; // ISO date string + revokedAt?: string | null; // ISO date string or null +}; + +// Add GetAccessTokensData (as defined previously) +export type GetAccessTokensData = { + items: AccessTokenData[]; + page: number; + pageSize: number; + totalRecords: number; // Rust uses u64, TS uses number +}; + // ========== Operation Payloads ========== export type PingPayload = GenericPayload<{ operation: "ping"; @@ -341,6 +505,21 @@ export type SignTransactionPayload = GenericPayload<{ data: SignTransactionData; }>; +export type SignAuthorizationPayload = GenericPayload<{ + operation: "eoa:signAuthorization"; + auth: Auth; // Assuming Auth is defined as before + options: SignAuthorizationOptions; + data: SignAuthorizationData; +}>; + +// Add SignStructuredMessagePayload (using defined types) +export type SignStructuredMessagePayload = GenericPayload<{ + operation: "eoa:signStructuredMessage"; + auth: Auth; // Assuming Auth is defined as before + options: SignStructuredMessageOptions; + data: SignStructuredMessageData; +}>; + export type CheckedSignTypedDataPayload< Types extends TypedData, PrimaryType extends keyof Types | "EIP712Domain" = keyof Types, @@ -372,6 +551,13 @@ export type CreateAccessTokenPayload = GenericPayload<{ data: CreateAccessTokenData; }>; +// Add ListAccessTokensPayload (using defined types) +export type ListAccessTokensPayload = GenericPayload<{ + operation: "accessToken:list"; + auth: Auth; + data: GetAccessTokensData; +}>; + export type RevokeAccessTokenPayload = GenericPayload<{ operation: "accessToken:revoke"; auth: Auth; @@ -391,4 +577,7 @@ export type Payload = | SignMessagePayload | SignTypedDataPayload | CreateAccessTokenPayload - | RevokeAccessTokenPayload; + | RevokeAccessTokenPayload + | SignAuthorizationPayload + | SignStructuredMessagePayload + | ListAccessTokensPayload; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 215be2f30fd..6659180f4cf 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1315,9 +1315,9 @@ importers: abitype: specifier: 1.0.8 version: 1.0.8(typescript@5.8.3)(zod@3.24.3) - ky: - specifier: ^1.8.0 - version: 1.8.1 + jose: + specifier: 6.0.10 + version: 6.0.10 typescript: specifier: '>=5.0.4' version: 5.8.3 @@ -11206,10 +11206,6 @@ packages: '@types/node': '>=18' typescript: '>=5.0.4' - ky@1.8.1: - resolution: {integrity: sha512-7Bp3TpsE+L+TARSnnDpk3xg8Idi8RwSLdj6CMbNWoOARIrGrbuLGusV0dYwbZOm4bB3jHNxSw8Wk/ByDqJEnDw==} - engines: {node: '>=18'} - language-subtag-registry@0.3.23: resolution: {integrity: sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==} @@ -30557,8 +30553,6 @@ snapshots: zod: 3.24.3 zod-validation-error: 3.4.0(zod@3.24.3) - ky@1.8.1: {} - language-subtag-registry@0.3.23: {} language-tags@1.0.9: From 8e08edf948b9c110f643f3a805fb33dc3cc3e475 Mon Sep 17 00:00:00 2001 From: Prithvish Baidya Date: Tue, 22 Apr 2025 03:56:35 +0530 Subject: [PATCH 028/101] default sign structured policy + list real access tokens --- .../create-server-wallet.client.tsx | 27 +++++++ .../components/list-access-tokens.client.tsx | 77 +++++++++++++------ 2 files changed, 80 insertions(+), 24 deletions(-) diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/create-server-wallet.client.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/create-server-wallet.client.tsx index 4c8d588aeb4..373637813b5 100644 --- a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/create-server-wallet.client.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/create-server-wallet.client.tsx @@ -250,6 +250,33 @@ export default function CreateServerWallet(props: { }, ], }, + { + type: "eoa:signStructuredMessage", + structuredPatterns: { + userOpV06: {}, + userOpV07: {}, + }, + metadataPatterns: [ + { + key: "projectId", + rule: { + pattern: props.project.id, + }, + }, + { + key: "teamId", + rule: { + pattern: props.project.teamId, + }, + }, + { + key: "type", + rule: { + pattern: "server-wallet", + }, + }, + ], + }, ], metadata: { projectId: props.project.id, diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/list-access-tokens.client.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/list-access-tokens.client.tsx index 50bf407db74..8b808e0ebe9 100644 --- a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/list-access-tokens.client.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/list-access-tokens.client.tsx @@ -17,6 +17,7 @@ import { Loader2, Trash2 } from "lucide-react"; import { useState } from "react"; import { toast } from "sonner"; import { THIRDWEB_VAULT_URL } from "../../../../../../../@/constants/env"; +import { listAccessTokens } from "@thirdweb-dev/vault-sdk/dist/types/sdk"; export default function ListAccessTokensButton(props: { projectId: string; @@ -29,9 +30,7 @@ export default function ListAccessTokensButton(props: { // TODO allow passing permissions to the access token const createAccessTokenMutation = useMutation({ - mutationFn: async (args: { - adminKey: string; - }) => { + mutationFn: async (args: { adminKey: string }) => { const vaultClient = await createVaultClient({ baseUrl: THIRDWEB_VAULT_URL, }); @@ -160,6 +159,33 @@ export default function ListAccessTokensButton(props: { }, ], }, + { + type: "eoa:signStructuredMessage", + structuredPatterns: { + userOpV06: {}, + userOpV07: {}, + }, + metadataPatterns: [ + { + key: "projectId", + rule: { + pattern: props.projectId, + }, + }, + { + key: "teamId", + rule: { + pattern: props.teamId, + }, + }, + { + key: "type", + rule: { + pattern: "server-wallet", + }, + }, + ], + }, ], metadata: { projectId: props.projectId, @@ -192,10 +218,7 @@ export default function ListAccessTokensButton(props: { }); const revokeAccessTokenMutation = useMutation({ - mutationFn: async (args: { - adminKey: string; - accessTokenId: string; - }) => { + mutationFn: async (args: { adminKey: string; accessTokenId: string }) => { setDeletingTokenId(args.accessTokenId); const vaultClient = await createVaultClient({ baseUrl: THIRDWEB_VAULT_URL, @@ -237,25 +260,31 @@ export default function ListAccessTokensButton(props: { const listAccessTokensQuery = useQuery({ queryKey: ["list-access-tokens", maskSecret(adminKey)], queryFn: async () => { - // TODO (engine-cloud): need the command in vault - await new Promise((resolve) => setTimeout(resolve, 1000)); - // Return stub data for now - return { - accessTokens: [ - { - key: "stub_1234567890abcdef", - id: "token_1", - }, - { - key: "stub_1234567890abcdef", - id: "token_2", - }, - { - key: "stub_1234567890abcdef", - id: "token_3", + const vaultClient = await createVaultClient({ + baseUrl: THIRDWEB_VAULT_URL, + }); + const listResult = await listAccessTokens({ + client: vaultClient, + request: { + auth: { + adminKey, }, - ], + options: {}, + }, + }); + + if (!listResult.success) { + throw new Error( + `Failed to list access tokens: ${listResult.error.message}`, + ); + } + return { + accessTokens: listResult.data.items.map((t) => ({ + key: t.id, // todo: the actual user-facing key is not returned by this yet, fix this + id: t.id, + })), }; + // Return stub data for now }, enabled: !!adminKey, }); From 8d37481587eb720aa676b92ee5a9823244a2527f Mon Sep 17 00:00:00 2001 From: Prithvish Baidya Date: Tue, 22 Apr 2025 04:05:02 +0530 Subject: [PATCH 029/101] fix export for new vault SDK methods --- .../components/list-access-tokens.client.tsx | 7 +++++-- packages/vault-sdk/src/exports/thirdweb.ts | 4 ++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/list-access-tokens.client.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/list-access-tokens.client.tsx index 8b808e0ebe9..59791fcbcc8 100644 --- a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/list-access-tokens.client.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/list-access-tokens.client.tsx @@ -11,13 +11,16 @@ import { } from "@/components/ui/dialog"; import { Input } from "@/components/ui/input"; import { useMutation, useQuery } from "@tanstack/react-query"; -import { createAccessToken, revokeAccessToken } from "@thirdweb-dev/vault-sdk"; +import { + createAccessToken, + revokeAccessToken, + listAccessTokens, +} from "@thirdweb-dev/vault-sdk"; import { createVaultClient } from "@thirdweb-dev/vault-sdk"; import { Loader2, Trash2 } from "lucide-react"; import { useState } from "react"; import { toast } from "sonner"; import { THIRDWEB_VAULT_URL } from "../../../../../../../@/constants/env"; -import { listAccessTokens } from "@thirdweb-dev/vault-sdk/dist/types/sdk"; export default function ListAccessTokensButton(props: { projectId: string; diff --git a/packages/vault-sdk/src/exports/thirdweb.ts b/packages/vault-sdk/src/exports/thirdweb.ts index a66ce947db2..b97c7cf264f 100644 --- a/packages/vault-sdk/src/exports/thirdweb.ts +++ b/packages/vault-sdk/src/exports/thirdweb.ts @@ -12,6 +12,10 @@ export { signMessage, signTransaction, signTypedData, + listAccessTokens, + createSignedAccessToken, + signAuthorization, + signStructuredMessage, } from "../sdk.js"; export { From f6b05c238a28342830a774cd6188750ed9c26143 Mon Sep 17 00:00:00 2001 From: Joaquim Verges Date: Tue, 22 Apr 2025 11:37:47 +1200 Subject: [PATCH 030/101] fix import --- .../src/app/(app)/team/[team_slug]/(team)/~/usage/page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/usage/page.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/usage/page.tsx index 84c54d049e3..fae6dd5f430 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/usage/page.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/usage/page.tsx @@ -3,7 +3,7 @@ import { getTeamBySlug } from "@/api/team"; import { getTeamSubscriptions } from "@/api/team-subscription"; import { fetchRPCUsage } from "@/api/usage/rpc"; import { getThirdwebClient } from "@/constants/thirdweb.server"; -import { normalizeTimeISOString } from "lib/time"; +import { normalizeTimeISOString } from "@/lib/time"; import { redirect } from "next/navigation"; import { getValidAccount } from "../../../../../account/settings/getAccount"; import { getAuthToken } from "../../../../../api/lib/getAuthToken"; From e84ccc4a0229f84bedbdd9d892125c1d83656e30 Mon Sep 17 00:00:00 2001 From: Prithvish Baidya Date: Tue, 22 Apr 2025 05:27:53 +0530 Subject: [PATCH 031/101] fix typo --- packages/vault-sdk/src/types.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/vault-sdk/src/types.ts b/packages/vault-sdk/src/types.ts index 1bfbcc3c48c..177cb26c9a6 100644 --- a/packages/vault-sdk/src/types.ts +++ b/packages/vault-sdk/src/types.ts @@ -204,8 +204,8 @@ export type UserOperationV07Input = { // Corresponds to Rust StructuredMessageInput enum export type StructuredMessageInput = - | { userOpV06: UserOperationV06Input } - | { userOpV07: UserOperationV07Input }; + | { useropV06: UserOperationV06Input } + | { useropV07: UserOperationV07Input }; // ========== Policy Types ========== type RegexRule = { @@ -311,8 +311,8 @@ export type PolicyComponent = metadataPatterns?: MetadataRule[]; // Define how UserOp rules are applied, e.g., separate for v6/v7 structuredPatterns: { - userOpV06?: UserOperationV06Rules; - userOpV07?: UserOperationV07Rules; + useropV06?: UserOperationV06Rules; + useropV07?: UserOperationV07Rules; }; }; From d94bac543c346c9f4795f285a9daf00009087a17 Mon Sep 17 00:00:00 2001 From: Prithvish Baidya Date: Tue, 22 Apr 2025 05:34:29 +0530 Subject: [PATCH 032/101] fix more typos --- .../server-wallets/components/create-server-wallet.client.tsx | 4 ++-- .../server-wallets/components/list-access-tokens.client.tsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/create-server-wallet.client.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/create-server-wallet.client.tsx index 373637813b5..d7407c4c49a 100644 --- a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/create-server-wallet.client.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/create-server-wallet.client.tsx @@ -253,8 +253,8 @@ export default function CreateServerWallet(props: { { type: "eoa:signStructuredMessage", structuredPatterns: { - userOpV06: {}, - userOpV07: {}, + useropV06: {}, + useropV07: {}, }, metadataPatterns: [ { diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/list-access-tokens.client.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/list-access-tokens.client.tsx index 59791fcbcc8..08d1de16e50 100644 --- a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/list-access-tokens.client.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/list-access-tokens.client.tsx @@ -165,8 +165,8 @@ export default function ListAccessTokensButton(props: { { type: "eoa:signStructuredMessage", structuredPatterns: { - userOpV06: {}, - userOpV07: {}, + useropV06: {}, + useropV07: {}, }, metadataPatterns: [ { From a397a03accc49c5fed82f19dfe9078955c47232c Mon Sep 17 00:00:00 2001 From: Joaquim Verges Date: Tue, 22 Apr 2025 13:41:52 +1200 Subject: [PATCH 033/101] add base server url --- .../[project_slug]/transactions/explorer/page.tsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/explorer/page.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/explorer/page.tsx index 2eb65330ae5..f746c7aa9b2 100644 --- a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/explorer/page.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/explorer/page.tsx @@ -9,6 +9,12 @@ export default function TransactionsExplorerPage() {

From ed4bc80a98e81164b8be8f1286fadc0a54951fe8 Mon Sep 17 00:00:00 2001 From: Joaquim Verges Date: Tue, 22 Apr 2025 13:44:21 +1200 Subject: [PATCH 034/101] fix readme --- .changeset/big-cases-wish.md | 5 ++ packages/vault-sdk/README.md | 130 +------------------------------- packages/vault-sdk/package.json | 7 +- 3 files changed, 9 insertions(+), 133 deletions(-) create mode 100644 .changeset/big-cases-wish.md diff --git a/.changeset/big-cases-wish.md b/.changeset/big-cases-wish.md new file mode 100644 index 00000000000..36fc7f749a5 --- /dev/null +++ b/.changeset/big-cases-wish.md @@ -0,0 +1,5 @@ +--- +"@thirdweb-dev/vault-sdk": patch +--- + +Introducing vault sdk diff --git a/packages/vault-sdk/README.md b/packages/vault-sdk/README.md index d2eb074e6cb..06852393924 100644 --- a/packages/vault-sdk/README.md +++ b/packages/vault-sdk/README.md @@ -1,129 +1,3 @@ -# React Native Adapter +# Vault SDK -This package is required to run the thirdweb connect SDK in React Native. - -## Instructions - -### 1. Install the packages - -Using your favorite pacakge manager, install all the require dependencies - -```shell -npx expo install thirdweb @thirdweb-dev/react-native-adapter -``` - -Since react native requires installing native dependencies directly, you also have to install these required peer dependencies: - -```shell -npx expo install react-native-get-random-values @react-native-community/netinfo expo-application @react-native-async-storage/async-storage expo-web-browser expo-linking react-native-aes-gcm-crypto react-native-quick-crypto@0.7.0-rc.6 amazon-cognito-identity-js @coinbase/wallet-mobile-sdk react-native-mmkv react-native-svg @react-native-clipboard/clipboard -``` - -Here's an explanation of each peer dependency and why its needed: - -``` -// needed for wallet connect -react-native-get-random-values -@react-native-community/netinfo -expo-application - -// needed wallet connect + in-app wallet -@react-native-async-storage/async-storage - -// needed for inapp wallet -expo-web-browser // for oauth flows -amazon-cognito-identity-js // for authentication -react-native-aes-gcm-crypto // for encryption -react-native-quick-crypto@0.7.0-rc.6 //for fast hashing - -// needed for the prebuilt UIs -react-native-svg -@react-native-clipboard/clipboard -``` - -### 2. Edit your `metro.config.js` - -If you don't already have a `metro.config.file.js` in your project, you can create one by running: - -```shell -npx expo customize metro.config.js -``` - -Then, you need to add 2 properties to the metro resolver: `unstable_enablePackageExports` and `unstable_conditionNames`. This is to tell metro to resolve named `exports` properly. - -```js -// file: metro.config.js - -// Learn more https://docs.expo.io/guides/customizing-metro -const { getDefaultConfig } = require("expo/metro-config"); - -/** @type {import('expo/metro-config').MetroConfig} */ -const config = getDefaultConfig(__dirname); - -// ADD THESE 2 PROPERTIES -config.resolver.unstable_enablePackageExports = true; -config.resolver.unstable_conditionNames = [ - "react-native", - "browser", - "require", -]; - -module.exports = config; -``` - -### 3. Import `@thirdweb-dev/react-native-adapter` at the top of your `App.tsx` - -This will polyfill all the required functionality needed. - -```js -// this needs to be imported before anything else -import "@thirdweb-dev/react-native-adapter"; -// the rest of your app -``` - -If you're using `expo-router`, you need to polyfill before the router entry: - -1. create a `app/index.ts` - -This will be the new entrypoint to your app, ensuring the polyfills happen before any routing. - -```ts -// file: app/index.ts - -// this needs to be imported before expo-router -import "@thirdweb-dev/react-native-adapter"; -import "expo-router/entry"; -``` - -2. Change your main entrypoint in `package.json` - -Now you can replace `expo-router/entry` with `./app/index` as your main entrypoint. - -``` -// file: package.json - -"main": "./app/index", -``` - -### Additional notes - -1. `react-native-aes-gcm-crypto` requires `minSDK 26` for android, you can edit this in your `build.gradle` file -2. You will get some warnings about unresolved exports, this is normal and will get better as the libraries get updated. - - -### Use the `thirdweb` package in React Native - -Once all the setup above is all done, you can use the most of functionality in the `thirdweb` package out of the box, without having to do any react native specific code. - -This means that you can follow all the React documentation and expect it all to be exactly the same. - -Examples: - -```tsx -import { ThirdwebProvider } form "thirdweb/react"; -``` - -### Resources - -- [Full working demo](https://github.com/thirdweb-dev/expo-starter) -- [React docs](https://portal.thirdweb.com/typescript/v5/react) -- [TypeScript docs](https://portal.thirdweb.com/typescript/v5) +This package contains utilities to interact with Vault, thirdweb's key management servive. diff --git a/packages/vault-sdk/package.json b/packages/vault-sdk/package.json index 01fabd4b7f2..2bc74c5942f 100644 --- a/packages/vault-sdk/package.json +++ b/packages/vault-sdk/package.json @@ -1,6 +1,6 @@ { "name": "@thirdweb-dev/vault-sdk", - "version": "1.5.4", + "version": "0.0.1", "repository": { "type": "git", "url": "git+https://github.com/thirdweb-dev/js.git#main" @@ -23,10 +23,7 @@ }, "./package.json": "./package.json" }, - "files": [ - "dist/*", - "src/*" - ], + "files": ["dist/*", "src/*"], "dependencies": { "@noble/ciphers": "^1.2.1", "@noble/curves": "1.8.1", From 39691724e6a111f43ac3e68d64fdbc7c3df80f6f Mon Sep 17 00:00:00 2001 From: Joaquim Verges Date: Tue, 22 Apr 2025 14:31:23 +1200 Subject: [PATCH 035/101] rename transactions tab to engine --- .../components/ProjectSidebarLayout.tsx | 18 +++++++++--------- .../analytics/analytics-page.tsx | 0 .../analytics/filter.tsx | 0 .../analytics/getTransactionAnalyticsFilter.ts | 0 .../analytics/summary.tsx | 0 .../analytics/tx-chart/tx-chart-ui.tsx | 2 +- .../analytics/tx-chart/tx-chart.tsx | 0 .../analytics/tx-table/tx-table-ui.tsx | 0 .../analytics/tx-table/tx-table.tsx | 0 .../analytics/tx-table/types.ts | 0 .../{transactions => engine}/explorer/page.tsx | 0 .../{transactions => engine}/layout.tsx | 14 +++++++------- .../{transactions => engine}/page.tsx | 0 .../components/create-server-wallet.client.tsx | 0 .../components/key-management.tsx | 0 .../components/list-access-tokens.client.tsx | 0 .../components/rotate-admin-key.client.tsx | 0 .../components/send-dummy-tx.client.tsx | 2 +- .../server-wallets/components/try-it-out.tsx | 0 .../server-wallets/page.tsx | 0 .../server-wallets/wallet-table/types.ts | 0 .../wallet-table/wallet-table-ui.client.tsx | 0 .../wallet-table/wallet-table.tsx | 0 23 files changed, 18 insertions(+), 18 deletions(-) rename apps/dashboard/src/app/team/[team_slug]/[project_slug]/{transactions => engine}/analytics/analytics-page.tsx (100%) rename apps/dashboard/src/app/team/[team_slug]/[project_slug]/{transactions => engine}/analytics/filter.tsx (100%) rename apps/dashboard/src/app/team/[team_slug]/[project_slug]/{transactions => engine}/analytics/getTransactionAnalyticsFilter.ts (100%) rename apps/dashboard/src/app/team/[team_slug]/[project_slug]/{transactions => engine}/analytics/summary.tsx (100%) rename apps/dashboard/src/app/team/[team_slug]/[project_slug]/{transactions => engine}/analytics/tx-chart/tx-chart-ui.tsx (98%) rename apps/dashboard/src/app/team/[team_slug]/[project_slug]/{transactions => engine}/analytics/tx-chart/tx-chart.tsx (100%) rename apps/dashboard/src/app/team/[team_slug]/[project_slug]/{transactions => engine}/analytics/tx-table/tx-table-ui.tsx (100%) rename apps/dashboard/src/app/team/[team_slug]/[project_slug]/{transactions => engine}/analytics/tx-table/tx-table.tsx (100%) rename apps/dashboard/src/app/team/[team_slug]/[project_slug]/{transactions => engine}/analytics/tx-table/types.ts (100%) rename apps/dashboard/src/app/team/[team_slug]/[project_slug]/{transactions => engine}/explorer/page.tsx (100%) rename apps/dashboard/src/app/team/[team_slug]/[project_slug]/{transactions => engine}/layout.tsx (91%) rename apps/dashboard/src/app/team/[team_slug]/[project_slug]/{transactions => engine}/page.tsx (100%) rename apps/dashboard/src/app/team/[team_slug]/[project_slug]/{transactions => engine}/server-wallets/components/create-server-wallet.client.tsx (100%) rename apps/dashboard/src/app/team/[team_slug]/[project_slug]/{transactions => engine}/server-wallets/components/key-management.tsx (100%) rename apps/dashboard/src/app/team/[team_slug]/[project_slug]/{transactions => engine}/server-wallets/components/list-access-tokens.client.tsx (100%) rename apps/dashboard/src/app/team/[team_slug]/[project_slug]/{transactions => engine}/server-wallets/components/rotate-admin-key.client.tsx (100%) rename apps/dashboard/src/app/team/[team_slug]/[project_slug]/{transactions => engine}/server-wallets/components/send-dummy-tx.client.tsx (99%) rename apps/dashboard/src/app/team/[team_slug]/[project_slug]/{transactions => engine}/server-wallets/components/try-it-out.tsx (100%) rename apps/dashboard/src/app/team/[team_slug]/[project_slug]/{transactions => engine}/server-wallets/page.tsx (100%) rename apps/dashboard/src/app/team/[team_slug]/[project_slug]/{transactions => engine}/server-wallets/wallet-table/types.ts (100%) rename apps/dashboard/src/app/team/[team_slug]/[project_slug]/{transactions => engine}/server-wallets/wallet-table/wallet-table-ui.client.tsx (100%) rename apps/dashboard/src/app/team/[team_slug]/[project_slug]/{transactions => engine}/server-wallets/wallet-table/wallet-table.tsx (100%) diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/components/ProjectSidebarLayout.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/components/ProjectSidebarLayout.tsx index d199634f071..7e4292d9b77 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/components/ProjectSidebarLayout.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/components/ProjectSidebarLayout.tsx @@ -1,7 +1,6 @@ "use client"; import { FullWidthSidebarLayout } from "@/components/blocks/SidebarLayout"; import { - ArrowRightLeftIcon, BookTextIcon, BoxIcon, HomeIcon, @@ -9,6 +8,7 @@ import { WalletIcon, } from "lucide-react"; import { ContractIcon } from "../../../../(dashboard)/(chain)/components/server/icons/ContractIcon"; +import { EngineIcon } from "../../../../(dashboard)/(chain)/components/server/icons/EngineIcon"; import { InsightIcon } from "../../../../(dashboard)/(chain)/components/server/icons/InsightIcon"; import { PayIcon } from "../../../../(dashboard)/(chain)/components/server/icons/PayIcon"; import { SmartAccountIcon } from "../../../../(dashboard)/(chain)/components/server/icons/SmartAccountIcon"; @@ -61,10 +61,10 @@ export function ProjectSidebarLayout(props: { tracking: tracking("contracts"), }, { - href: `${layoutPath}/nebula`, - label: "Nebula", - icon: NebulaIcon, - tracking: tracking("nebula"), + href: `${layoutPath}/engine`, + label: "Engine", + icon: EngineIcon, + tracking: tracking("engine"), }, { href: `${layoutPath}/insight`, @@ -73,10 +73,10 @@ export function ProjectSidebarLayout(props: { tracking: tracking("insight"), }, { - href: `${layoutPath}/transactions`, - label: "Transactions", - icon: ArrowRightLeftIcon, - tracking: tracking("transactions"), + href: `${layoutPath}/nebula`, + label: "Nebula", + icon: NebulaIcon, + tracking: tracking("nebula"), }, ]} footerSidebarLinks={[ diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/analytics/analytics-page.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/analytics/analytics-page.tsx similarity index 100% rename from apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/analytics/analytics-page.tsx rename to apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/analytics/analytics-page.tsx diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/analytics/filter.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/analytics/filter.tsx similarity index 100% rename from apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/analytics/filter.tsx rename to apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/analytics/filter.tsx diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/analytics/getTransactionAnalyticsFilter.ts b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/analytics/getTransactionAnalyticsFilter.ts similarity index 100% rename from apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/analytics/getTransactionAnalyticsFilter.ts rename to apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/analytics/getTransactionAnalyticsFilter.ts diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/analytics/summary.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/analytics/summary.tsx similarity index 100% rename from apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/analytics/summary.tsx rename to apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/analytics/summary.tsx diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/analytics/tx-chart/tx-chart-ui.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/analytics/tx-chart/tx-chart-ui.tsx similarity index 98% rename from apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/analytics/tx-chart/tx-chart-ui.tsx rename to apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/analytics/tx-chart/tx-chart-ui.tsx index ce330fa7a33..eba18df1505 100644 --- a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/analytics/tx-chart/tx-chart-ui.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/analytics/tx-chart/tx-chart-ui.tsx @@ -173,7 +173,7 @@ function EmptyChartContent(props: { variant="primary" onClick={() => { router.push( - `/team/${props.team_slug}/${props.project_slug}/transactions/server-wallets`, + `/team/${props.team_slug}/${props.project_slug}/engine/server-wallets`, ); }} > diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/analytics/tx-chart/tx-chart.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/analytics/tx-chart/tx-chart.tsx similarity index 100% rename from apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/analytics/tx-chart/tx-chart.tsx rename to apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/analytics/tx-chart/tx-chart.tsx diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/analytics/tx-table/tx-table-ui.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/analytics/tx-table/tx-table-ui.tsx similarity index 100% rename from apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/analytics/tx-table/tx-table-ui.tsx rename to apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/analytics/tx-table/tx-table-ui.tsx diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/analytics/tx-table/tx-table.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/analytics/tx-table/tx-table.tsx similarity index 100% rename from apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/analytics/tx-table/tx-table.tsx rename to apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/analytics/tx-table/tx-table.tsx diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/analytics/tx-table/types.ts b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/analytics/tx-table/types.ts similarity index 100% rename from apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/analytics/tx-table/types.ts rename to apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/analytics/tx-table/types.ts diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/explorer/page.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/explorer/page.tsx similarity index 100% rename from apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/explorer/page.tsx rename to apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/explorer/page.tsx diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/layout.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/layout.tsx similarity index 91% rename from apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/layout.tsx rename to apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/layout.tsx index 95d6e7ab6da..a460042de9b 100644 --- a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/layout.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/layout.tsx @@ -1,11 +1,11 @@ import { getProject } from "@/api/projects"; import { getTeamBySlug } from "@/api/team"; +import { Badge } from "@/components/ui/badge"; import { TabPathLinks } from "@/components/ui/tabs"; +import { THIRDWEB_ENGINE_CLOUD_URL } from "@/constants/env"; import { CloudIcon } from "lucide-react"; import Link from "next/link"; import { redirect } from "next/navigation"; -import { Badge } from "@/components/ui/badge"; -import { THIRDWEB_ENGINE_CLOUD_URL } from "@/constants/env"; export default async function Page(props: { params: Promise<{ team_slug: string; project_slug: string }>; @@ -43,7 +43,7 @@ function TransactionsLayout(props: { clientId: string; children: React.ReactNode; }) { - const smartWalletsLayoutSlug = `/team/${props.teamSlug}/${props.projectSlug}/transactions`; + const engineLayoutSlug = `/team/${props.teamSlug}/${props.projectSlug}/engine`; return (
@@ -53,7 +53,7 @@ function TransactionsLayout(props: {

- Transactions + Engine

diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/page.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/page.tsx similarity index 100% rename from apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/page.tsx rename to apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/page.tsx diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/create-server-wallet.client.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/server-wallets/components/create-server-wallet.client.tsx similarity index 100% rename from apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/create-server-wallet.client.tsx rename to apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/server-wallets/components/create-server-wallet.client.tsx diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/key-management.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/server-wallets/components/key-management.tsx similarity index 100% rename from apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/key-management.tsx rename to apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/server-wallets/components/key-management.tsx diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/list-access-tokens.client.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/server-wallets/components/list-access-tokens.client.tsx similarity index 100% rename from apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/list-access-tokens.client.tsx rename to apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/server-wallets/components/list-access-tokens.client.tsx diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/rotate-admin-key.client.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/server-wallets/components/rotate-admin-key.client.tsx similarity index 100% rename from apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/rotate-admin-key.client.tsx rename to apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/server-wallets/components/rotate-admin-key.client.tsx diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/send-dummy-tx.client.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/server-wallets/components/send-dummy-tx.client.tsx similarity index 99% rename from apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/send-dummy-tx.client.tsx rename to apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/server-wallets/components/send-dummy-tx.client.tsx index cea81613714..636c2193205 100644 --- a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/send-dummy-tx.client.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/server-wallets/components/send-dummy-tx.client.tsx @@ -126,7 +126,7 @@ export default function SendDummyTx(props: { onClick={() => { handleCloseModal(); router.push( - `/team/${props.team_slug}/${props.project_slug}/transactions`, + `/team/${props.team_slug}/${props.project_slug}/engine`, ); }} variant={"primary"} diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/try-it-out.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/server-wallets/components/try-it-out.tsx similarity index 100% rename from apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/components/try-it-out.tsx rename to apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/server-wallets/components/try-it-out.tsx diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/page.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/server-wallets/page.tsx similarity index 100% rename from apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/page.tsx rename to apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/server-wallets/page.tsx diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/wallet-table/types.ts b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/server-wallets/wallet-table/types.ts similarity index 100% rename from apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/wallet-table/types.ts rename to apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/server-wallets/wallet-table/types.ts diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/wallet-table/wallet-table-ui.client.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/server-wallets/wallet-table/wallet-table-ui.client.tsx similarity index 100% rename from apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/wallet-table/wallet-table-ui.client.tsx rename to apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/server-wallets/wallet-table/wallet-table-ui.client.tsx diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/wallet-table/wallet-table.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/server-wallets/wallet-table/wallet-table.tsx similarity index 100% rename from apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/server-wallets/wallet-table/wallet-table.tsx rename to apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/server-wallets/wallet-table/wallet-table.tsx From 62a5b5964844a0c19fbe2623a436a08a03e61dc4 Mon Sep 17 00:00:00 2001 From: Joaquim Verges Date: Tue, 22 Apr 2025 15:38:38 +1200 Subject: [PATCH 036/101] add test transaction component --- .../engine/analytics/analytics-page.tsx | 4 + .../engine/analytics/send-test-tx.client.tsx | 250 ++++++++++++++++++ .../[project_slug]/engine/page.tsx | 29 ++ .../components/list-access-tokens.client.tsx | 4 +- .../components/rotate-admin-key.client.tsx | 2 +- .../server-wallets/components/try-it-out.tsx | 29 +- .../src/components/buttons/MismatchButton.tsx | 2 +- 7 files changed, 302 insertions(+), 18 deletions(-) create mode 100644 apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/analytics/send-test-tx.client.tsx diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/analytics/analytics-page.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/analytics/analytics-page.tsx index fd102169f59..ea42446d852 100644 --- a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/analytics/analytics-page.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/analytics/analytics-page.tsx @@ -2,6 +2,8 @@ import { ResponsiveSearchParamsProvider } from "responsive-rsc"; import { TransactionAnalyticsFilter } from "./filter"; import { TransactionsChartCard } from "./tx-chart/tx-chart"; import { TransactionsTable } from "./tx-table/tx-table"; +import { SendTestTransaction } from "./send-test-tx.client"; +import { Wallet } from "../server-wallets/wallet-table/types"; export function TransactionsAnalyticsPageContent(props: { searchParams: { @@ -13,6 +15,7 @@ export function TransactionsAnalyticsPageContent(props: { clientId: string; project_slug: string; team_slug: string; + wallets?: Wallet[]; }) { return ( @@ -29,6 +32,7 @@ export function TransactionsAnalyticsPageContent(props: { project_slug={props.project_slug} team_slug={props.team_slug} /> +
diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/analytics/send-test-tx.client.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/analytics/send-test-tx.client.tsx new file mode 100644 index 00000000000..759e236cc0e --- /dev/null +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/analytics/send-test-tx.client.tsx @@ -0,0 +1,250 @@ +"use client"; +import { WalletAvatar } from "@/components/blocks/wallet-address"; +import { Input } from "@/components/ui/input"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { THIRDWEB_ENGINE_CLOUD_URL } from "@/constants/env"; +import { useThirdwebClient } from "@/constants/thirdweb.client"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { useMutation } from "@tanstack/react-query"; +import { ChevronDown, Loader2 } from "lucide-react"; +import { useState } from "react"; +import { useForm } from "react-hook-form"; +import { toast } from "sonner"; +import { shortenAddress } from "thirdweb/utils"; +import * as z from "zod"; +import { SingleNetworkSelector } from "../../../../../../@/components/blocks/NetworkSelectors"; +import { Button } from "../../../../../../@/components/ui/button"; +import type { Wallet } from "../server-wallets/wallet-table/types"; + +const formSchema = z.object({ + projectSecretKey: z.string().min(1, "Project secret key is required"), + accessToken: z.string().min(1, "Access token is required"), + walletIndex: z.string(), + chainId: z.number(), +}); + +type FormValues = z.infer; + +export function SendTestTransaction(props: { + wallets?: Wallet[]; +}) { + const [isOpen, setIsOpen] = useState(false); + const thirdwebClient = useThirdwebClient(); + + const form = useForm({ + resolver: zodResolver(formSchema), + defaultValues: { + projectSecretKey: "", + accessToken: "", + walletIndex: "0", + chainId: 84532, + }, + }); + + const selectedWalletIndex = Number(form.watch("walletIndex")); + const selectedWallet = props.wallets?.[selectedWalletIndex]; + + const sendDummyTxMutation = useMutation({ + mutationFn: async (args: { + walletAddress: string; + accessToken: string; + chainId: number; + }) => { + const response = await fetch( + `${THIRDWEB_ENGINE_CLOUD_URL}/write/transaction`, + { + method: "POST", + headers: { + "Content-Type": "application/json", + "x-secret-key": form.getValues("projectSecretKey"), + }, + body: JSON.stringify({ + executionOptions: { + type: "AA", + signerAddress: args.walletAddress, + }, + transactionParams: [ + { + to: args.walletAddress, + value: "0", + }, + ], + vaultAccessToken: args.accessToken, + chainId: args.chainId.toString(), + }), + }, + ); + const result = await response.json(); + if (!response.ok) { + const errorMsg = result?.error?.message || "Failed to send transaction"; + throw new Error(errorMsg); + } + return result; + }, + onSuccess: () => { + toast.success("Test transaction sent successfully!"); + }, + onError: (error) => { + toast.error(error.message); + }, + }); + + const isLoading = sendDummyTxMutation.isPending; + + // Early return in render phase + if (!props.wallets || props.wallets.length === 0 || !selectedWallet) { + return null; + } + + const onSubmit = (data: FormValues) => { + sendDummyTxMutation.mutate({ + walletAddress: selectedWallet.address, + accessToken: data.accessToken, + chainId: data.chainId, + }); + }; + + return ( +
+ {/* Trigger Area */} +
setIsOpen(!isOpen)} + onKeyDown={(e) => { + if (e.key === "Enter" || e.key === " ") { + setIsOpen(!isOpen); + } + }} + > +

+ Send Test Transaction +

+ +
+ + {/* Content Area (conditional) */} + {isOpen && ( +
+

+ 🔐 This action requires a project secret key and a vault access + token. +

+ {/* Responsive container */} +
+
+
+

Project Secret Key

+ +
+
+
+
+

Vault Access Token

+ +
+
+
+ {/* Wallet Selector */} +
+
+
+

Signer

+ +
+
+

Network

+ { + form.setValue("chainId", chainId); + }} + /> +
+
+
+
+ +
+
+ )} +
+ ); +} diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/page.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/page.tsx index ec24c198d29..35038e9983b 100644 --- a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/page.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/page.tsx @@ -4,6 +4,9 @@ import { notFound, redirect } from "next/navigation"; import { getAuthToken } from "../../../../api/lib/getAuthToken"; import { TransactionsAnalyticsPageContent } from "./analytics/analytics-page"; import { TransactionAnalyticsSummary } from "./analytics/summary"; +import { createVaultClient, listEoas } from "@thirdweb-dev/vault-sdk"; +import { THIRDWEB_VAULT_URL } from "../../../../../@/constants/env"; +import { Wallet } from "./server-wallets/wallet-table/types"; export default async function TransactionsAnalyticsPage(props: { params: Promise<{ team_slug: string; project_slug: string }>; @@ -36,6 +39,31 @@ export default async function TransactionsAnalyticsPage(props: { redirect(`/team/${params.team_slug}`); } + const projectEngineCloudService = project.services.find( + (service) => service.name === "engineCloud", + ); + + const vaultClient = await createVaultClient({ + baseUrl: THIRDWEB_VAULT_URL, + }); + + const managementAccessToken = + projectEngineCloudService?.managementAccessToken; + + const eoas = managementAccessToken + ? await listEoas({ + client: vaultClient, + request: { + auth: { + accessToken: managementAccessToken, + }, + options: {}, + }, + }) + : { data: { items: [] }, error: null, success: true }; + + const wallets = eoas.data?.items as Wallet[] | undefined; + return (
); diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/server-wallets/components/list-access-tokens.client.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/server-wallets/components/list-access-tokens.client.tsx index 08d1de16e50..a26355575d6 100644 --- a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/server-wallets/components/list-access-tokens.client.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/server-wallets/components/list-access-tokens.client.tsx @@ -13,8 +13,8 @@ import { Input } from "@/components/ui/input"; import { useMutation, useQuery } from "@tanstack/react-query"; import { createAccessToken, - revokeAccessToken, listAccessTokens, + revokeAccessToken, } from "@thirdweb-dev/vault-sdk"; import { createVaultClient } from "@thirdweb-dev/vault-sdk"; import { Loader2, Trash2 } from "lucide-react"; @@ -396,7 +396,7 @@ export default function ListAccessTokensButton(props: {

- This action requires your Vault admin key. + 🔐 This action requires your Vault admin key.

- This action requires your Vault rotation code. + 🔐 This action requires your Vault rotation code.

{props.wallet && ( - + )}
diff --git a/apps/dashboard/src/components/buttons/MismatchButton.tsx b/apps/dashboard/src/components/buttons/MismatchButton.tsx index 69e3f9364a0..b24c58e8244 100644 --- a/apps/dashboard/src/components/buttons/MismatchButton.tsx +++ b/apps/dashboard/src/components/buttons/MismatchButton.tsx @@ -515,7 +515,7 @@ const MismatchNotice: React.FC<{

- This action requires you to connect to the{" "} + 🔐 This action requires you to connect to the{" "} {txChain?.name || `Chain ID #${txChainId}`} {" "} From 71a9431ddaa8f859a40f8283540721fd913dace3 Mon Sep 17 00:00:00 2001 From: Joaquim Verges Date: Tue, 22 Apr 2025 15:40:58 +1200 Subject: [PATCH 037/101] lint --- .../engine/analytics/analytics-page.tsx | 4 ++-- .../engine/analytics/tx-table/tx-table.tsx | 2 +- .../[project_slug]/engine/page.tsx | 6 ++--- .../components/key-management.tsx | 2 -- packages/vault-sdk/src/sdk.ts | 24 +++++++++---------- packages/vault-sdk/src/transaction-parser.ts | 10 ++++---- 6 files changed, 23 insertions(+), 25 deletions(-) diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/analytics/analytics-page.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/analytics/analytics-page.tsx index ea42446d852..a091c828a00 100644 --- a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/analytics/analytics-page.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/analytics/analytics-page.tsx @@ -1,9 +1,9 @@ import { ResponsiveSearchParamsProvider } from "responsive-rsc"; +import type { Wallet } from "../server-wallets/wallet-table/types"; import { TransactionAnalyticsFilter } from "./filter"; +import { SendTestTransaction } from "./send-test-tx.client"; import { TransactionsChartCard } from "./tx-chart/tx-chart"; import { TransactionsTable } from "./tx-table/tx-table"; -import { SendTestTransaction } from "./send-test-tx.client"; -import { Wallet } from "../server-wallets/wallet-table/types"; export function TransactionsAnalyticsPageContent(props: { searchParams: { diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/analytics/tx-table/tx-table.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/analytics/tx-table/tx-table.tsx index 45e48935658..bd7e33c7819 100644 --- a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/analytics/tx-table/tx-table.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/analytics/tx-table/tx-table.tsx @@ -1,7 +1,7 @@ "use client"; -import { TransactionsTableUI } from "./tx-table-ui"; import { engineCloudProxy } from "@/actions/proxies"; +import { TransactionsTableUI } from "./tx-table-ui"; import type { TransactionsResponse } from "./types"; export function TransactionsTable(props: { teamId: string; clientId: string }) { diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/page.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/page.tsx index 35038e9983b..02fb5355bd6 100644 --- a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/page.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/page.tsx @@ -1,12 +1,12 @@ import { getProject } from "@/api/projects"; import { getTeamBySlug } from "@/api/team"; +import { createVaultClient, listEoas } from "@thirdweb-dev/vault-sdk"; import { notFound, redirect } from "next/navigation"; +import { THIRDWEB_VAULT_URL } from "../../../../../@/constants/env"; import { getAuthToken } from "../../../../api/lib/getAuthToken"; import { TransactionsAnalyticsPageContent } from "./analytics/analytics-page"; import { TransactionAnalyticsSummary } from "./analytics/summary"; -import { createVaultClient, listEoas } from "@thirdweb-dev/vault-sdk"; -import { THIRDWEB_VAULT_URL } from "../../../../../@/constants/env"; -import { Wallet } from "./server-wallets/wallet-table/types"; +import type { Wallet } from "./server-wallets/wallet-table/types"; export default async function TransactionsAnalyticsPage(props: { params: Promise<{ team_slug: string; project_slug: string }>; diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/server-wallets/components/key-management.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/server-wallets/components/key-management.tsx index eab3a6f7eda..6df89e96d18 100644 --- a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/server-wallets/components/key-management.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/server-wallets/components/key-management.tsx @@ -1,5 +1,3 @@ -import { Button } from "@/components/ui/button"; -import { RefreshCcwIcon } from "lucide-react"; import ListAccessTokensButton from "./list-access-tokens.client"; import RotateAdminKeyButton from "./rotate-admin-key.client"; diff --git a/packages/vault-sdk/src/sdk.ts b/packages/vault-sdk/src/sdk.ts index 214fca4ce71..d35715a5993 100644 --- a/packages/vault-sdk/src/sdk.ts +++ b/packages/vault-sdk/src/sdk.ts @@ -1,32 +1,32 @@ -import type { TypedData } from "abitype"; -import * as jose from "jose"; +import { xchacha20poly1305 } from "@noble/ciphers/chacha"; import { x25519 } from "@noble/curves/ed25519"; -import { randomBytes } from "@noble/hashes/utils"; -import { sha256 } from "@noble/hashes/sha256"; import { hkdf } from "@noble/hashes/hkdf"; -import { xchacha20poly1305 } from "@noble/ciphers/chacha"; -import { hexToBytes, bytesToHex } from "@noble/hashes/utils"; +import { sha256 } from "@noble/hashes/sha256"; +import { randomBytes } from "@noble/hashes/utils"; +import { bytesToHex, hexToBytes } from "@noble/hashes/utils"; +import type { TypedData } from "abitype"; +import * as jose from "jose"; import type { + CheckedSignTypedDataPayload, CreateAccessTokenPayload, CreateEoaPayload, CreateServiceAccountPayload, EncryptedPayload, GetServiceAccountPayload, + ListAccessTokensPayload, ListEoaPayload, Payload, PingPayload, + PolicyComponent, + Prettify, RevokeAccessTokenPayload, RotateServiceAccountPayload, + SignAuthorizationPayload, SignMessagePayload, + SignStructuredMessagePayload, SignTransactionPayload, - Prettify, UnencryptedErrorResponse, - CheckedSignTypedDataPayload, - ListAccessTokensPayload, - SignStructuredMessagePayload, - SignAuthorizationPayload, - PolicyComponent, } from "./types.js"; function encryptForEnclave( diff --git a/packages/vault-sdk/src/transaction-parser.ts b/packages/vault-sdk/src/transaction-parser.ts index b1e9ecdbca1..81720920531 100644 --- a/packages/vault-sdk/src/transaction-parser.ts +++ b/packages/vault-sdk/src/transaction-parser.ts @@ -1,15 +1,15 @@ // Update type definitions for our standardized types import type { - Address, AccessList, - TxLegacy, - TxEip2930, + Address, + EthereumTypedTransaction, + SignedAuthorization, TxEip1559, + TxEip2930, TxEip4844, TxEip4844WithSidecar, TxEip7702, - EthereumTypedTransaction, - SignedAuthorization, + TxLegacy, } from "./transaction-types.js"; // Custom error class for transaction parsing errors From 2ad7e7c223876eff69f255679caa5d3b545f6cac Mon Sep 17 00:00:00 2001 From: Joaquim Verges Date: Tue, 22 Apr 2025 19:53:07 +1200 Subject: [PATCH 038/101] fix build --- .../components/send-dummy-tx.client.tsx | 207 ------------------ .../wallet-table/wallet-table-ui.client.tsx | 7 +- 2 files changed, 4 insertions(+), 210 deletions(-) delete mode 100644 apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/server-wallets/components/send-dummy-tx.client.tsx diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/server-wallets/components/send-dummy-tx.client.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/server-wallets/components/send-dummy-tx.client.tsx deleted file mode 100644 index 636c2193205..00000000000 --- a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/server-wallets/components/send-dummy-tx.client.tsx +++ /dev/null @@ -1,207 +0,0 @@ -"use client"; -import { WalletAvatar } from "@/components/blocks/wallet-address"; -import { Spinner } from "@/components/ui/Spinner/Spinner"; -import {} from "@/components/ui/alert"; -import { Button } from "@/components/ui/button"; -import { CodeClient } from "@/components/ui/code/code.client"; -import { - Dialog, - DialogContent, - DialogHeader, - DialogTitle, -} from "@/components/ui/dialog"; -import { Input } from "@/components/ui/input"; -import { THIRDWEB_ENGINE_CLOUD_URL } from "@/constants/env"; -import { useThirdwebClient } from "@/constants/thirdweb.client"; -import { useMutation } from "@tanstack/react-query"; -import { Loader2 } from "lucide-react"; -import { useState } from "react"; -import { toast } from "sonner"; -import { shortenAddress } from "thirdweb/utils"; -import { useDashboardRouter } from "../../../../../../../@/lib/DashboardRouter"; -import type { Wallet } from "../wallet-table/types"; - -export default function SendDummyTx(props: { - authToken: string; - wallet: Wallet; - team_slug: string; - project_slug: string; -}) { - const router = useDashboardRouter(); - const thirdwebClient = useThirdwebClient(); - const [modalOpen, setModalOpen] = useState(false); - const [accessToken, setAccessToken] = useState(""); - const [projectSecretKey, setProjectSecretKey] = useState(""); - const sendDummyTxMutation = useMutation({ - mutationFn: async (args: { - walletAddress: string; - accessToken: string; - }) => { - const response = await fetch( - `${THIRDWEB_ENGINE_CLOUD_URL}/write/contract`, - { - method: "POST", - headers: { - "Content-Type": "application/json", - "x-secret-key": projectSecretKey, - }, - body: JSON.stringify({ - executionOptions: { - type: "AA", - signerAddress: args.walletAddress, - }, - transactionParams: [ - { - contractAddress: "0x036CbD53842c5426634e7929541eC2318f3dCF7e", - method: "approve", - params: [args.walletAddress, "0"], - }, - ], - vaultAccessToken: args.accessToken, - chainId: "84532", - }), - }, - ); - return response.json(); - }, - onError: (error) => { - toast.error(error.message); - }, - }); - - const handleCloseModal = () => { - setModalOpen(false); - }; - - const isLoading = sendDummyTxMutation.isPending; - - return ( - <> - - -

- - - Send Test Transaction - - {sendDummyTxMutation.isPending ? ( -
- -

- Sending transaction... -

-
- ) : sendDummyTxMutation.data ? ( -
-
-
-
-

- Transaction Result -

-
- -
-
-
-
- -
- -
-
- ) : ( -
-
-
-

Sending from

-
- - - {shortenAddress(props.wallet.address)} - -
-
-

- This action requries a project secret key and a Vault access - token. -

- setProjectSecretKey(e.target.value)} - /> - setAccessToken(e.target.value)} - /> -
- - -
-
-
- )} -
-
- - ); -} diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/server-wallets/wallet-table/wallet-table-ui.client.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/server-wallets/wallet-table/wallet-table-ui.client.tsx index 2b6f6259aaf..d2fb75d2dc7 100644 --- a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/server-wallets/wallet-table/wallet-table-ui.client.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/server-wallets/wallet-table/wallet-table-ui.client.tsx @@ -2,6 +2,7 @@ import type { Project } from "@/api/projects"; import { WalletAddress } from "@/components/blocks/wallet-address"; +import { Spinner } from "@/components/ui/Spinner/Spinner"; import { Table, TableBody, @@ -12,6 +13,7 @@ import { TableRow, } from "@/components/ui/table"; import { ToolTipLabel } from "@/components/ui/tooltip"; +import { getThirdwebClient } from "@/constants/thirdweb.server"; import { useQuery } from "@tanstack/react-query"; import { formatDistanceToNowStrict } from "date-fns"; import { format } from "date-fns/format"; @@ -19,11 +21,10 @@ import { DEFAULT_ACCOUNT_FACTORY_V0_7, predictSmartAccountAddress, } from "thirdweb/wallets/smart"; -import { Spinner } from "../../../../../../../@/components/ui/Spinner/Spinner"; -import { getThirdwebClient } from "../../../../../../../@/constants/thirdweb.server"; import { useV5DashboardChain } from "../../../../../../../lib/v5-adapter"; import CreateServerWallet from "../components/create-server-wallet.client"; import type { Wallet } from "./types"; + export function ServerWalletsTableUI({ wallets, project, @@ -102,7 +103,7 @@ function SmartAccountCell({ wallet }: { wallet: Wallet }) { queryKey: ["smart-account-address", wallet.address], queryFn: async () => { const smartAccountAddress = await predictSmartAccountAddress({ - client: getThirdwebClient(), + client: getThirdwebClient(undefined), adminAddress: wallet.address, chain, factoryAddress: DEFAULT_ACCOUNT_FACTORY_V0_7, From ad0bbd6ee2f457ca769002a15964f4f29a310a88 Mon Sep 17 00:00:00 2001 From: Joaquim Verges Date: Tue, 22 Apr 2025 20:45:08 +1200 Subject: [PATCH 039/101] access token management --- .../engine/analytics/analytics-page.tsx | 1 + .../engine/analytics/summary.tsx | 15 +- .../engine/analytics/tx-chart/tx-chart-ui.tsx | 52 ++++--- .../engine/analytics/tx-chart/tx-chart.tsx | 8 +- .../engine/analytics/tx-table/tx-table-ui.tsx | 3 +- .../engine/analytics/tx-table/tx-table.tsx | 1 + .../create-server-wallet.client.tsx | 18 ++- .../components/list-access-tokens.client.tsx | 146 +++++++++++++----- 8 files changed, 173 insertions(+), 71 deletions(-) diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/analytics/analytics-page.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/analytics/analytics-page.tsx index a091c828a00..ee95ed60332 100644 --- a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/analytics/analytics-page.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/analytics/analytics-page.tsx @@ -31,6 +31,7 @@ export function TransactionsAnalyticsPageContent(props: { clientId={props.clientId} project_slug={props.project_slug} team_slug={props.team_slug} + wallets={props.wallets ?? []} /> diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/analytics/summary.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/analytics/summary.tsx index 608816aad44..936b9599b54 100644 --- a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/analytics/summary.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/analytics/summary.tsx @@ -167,11 +167,16 @@ async function AsyncTransactionsAnalyticsSummary(props: { teamId: string; clientId: string; }) { - const data = await getTransactionAnalyticsSummary({ - teamId: props.teamId, - clientId: props.clientId, - }); - return ; + try { + const data = await getTransactionAnalyticsSummary({ + teamId: props.teamId, + clientId: props.clientId, + }); + return ; + } catch (error) { + console.error("Failed to fetch transaction summary:", error); + return ; + } } // Main component: Shows loading state (Suspense fallback) while fetching data diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/analytics/tx-chart/tx-chart-ui.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/analytics/tx-chart/tx-chart-ui.tsx index eba18df1505..7a1f4196a3f 100644 --- a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/analytics/tx-chart/tx-chart-ui.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/analytics/tx-chart/tx-chart-ui.tsx @@ -10,21 +10,19 @@ import { useAllChainsData } from "hooks/chains/allChains"; import { formatTickerNumber } from "lib/format-utils"; import { useMemo } from "react"; import type { TransactionStats } from "types/analytics"; +import type { Wallet } from "../../server-wallets/wallet-table/types"; type ChartData = Record & { time: string; }; // TODO: write a story for this component when its finalized - -// This is copy of SponsoredTransactionsChartCard component -// TODO - update the name of the component to something more relevant export function TransactionsChartCardUI(props: { - // TODO - update this userOpStats: TransactionStats[]; isPending: boolean; project_slug: string; team_slug: string; + wallets: Wallet[]; }) { const { userOpStats } = props; const topChainsToShow = 10; @@ -150,6 +148,7 @@ export function TransactionsChartCardUI(props: { } /> @@ -160,26 +159,39 @@ export function TransactionsChartCardUI(props: { function EmptyChartContent(props: { project_slug: string; team_slug: string; + wallets: Wallet[]; }) { const router = useDashboardRouter(); return (
- {/* TODO - update this */} - - Create a server wallet and send your first transaction to get started - -
- -
+ {props.wallets.length === 0 ? ( + <> + + Create a server wallet and send your first transaction to get + started + +
+ +
+ + ) : ( +

+ + + + + Waiting for transactions... +

+ )}
); } diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/analytics/tx-chart/tx-chart.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/analytics/tx-chart/tx-chart.tsx index 281580323ad..87f475cd9d6 100644 --- a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/analytics/tx-chart/tx-chart.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/analytics/tx-chart/tx-chart.tsx @@ -2,6 +2,7 @@ import { ResponsiveSuspense } from "responsive-rsc"; import { THIRDWEB_ENGINE_CLOUD_URL } from "../../../../../../../@/constants/env"; import type { TransactionStats } from "../../../../../../../types/analytics"; import { getAuthToken } from "../../../../../../api/lib/getAuthToken"; +import type { Wallet } from "../../server-wallets/wallet-table/types"; import { getTxAnalyticsFiltersFromSearchParams } from "../getTransactionAnalyticsFilter"; import { TransactionsChartCardUI } from "./tx-chart-ui"; @@ -13,6 +14,7 @@ async function AsyncTransactionsChartCard(props: { clientId: string; project_slug: string; team_slug: string; + wallets: Wallet[]; }) { const data = await getTransactionsChart({ teamId: props.teamId, @@ -28,6 +30,7 @@ async function AsyncTransactionsChartCard(props: { userOpStats={data} project_slug={props.project_slug} team_slug={props.team_slug} + wallets={props.wallets} /> ); } @@ -42,6 +45,7 @@ export function TransactionsChartCard(props: { clientId: string; project_slug: string; team_slug: string; + wallets: Wallet[]; }) { const { range, interval } = getTxAnalyticsFiltersFromSearchParams( props.searchParams, @@ -57,6 +61,7 @@ export function TransactionsChartCard(props: { userOpStats={[]} project_slug={props.project_slug} team_slug={props.team_slug} + wallets={[]} /> } > @@ -68,6 +73,7 @@ export function TransactionsChartCard(props: { clientId={props.clientId} project_slug={props.project_slug} team_slug={props.team_slug} + wallets={props.wallets} /> ); @@ -115,7 +121,7 @@ async function getTransactionsChart({ // TODO - need to handle this error state, like we do with the connect charts throw new Error( - `Error fetching transactions chart data: ${response.status} ${response.statusText}`, + `Error fetching transactions chart data: ${response.status} ${response.statusText} - ${await response.text().catch(() => "Unknown error")}`, ); } diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/analytics/tx-table/tx-table-ui.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/analytics/tx-table/tx-table-ui.tsx index dcbeea3e0af..1a927653c04 100644 --- a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/analytics/tx-table/tx-table-ui.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/analytics/tx-table/tx-table-ui.tsx @@ -44,6 +44,7 @@ import type { // TODO - add Status selector dropdown here export function TransactionsTableUI(props: { getData: (params: { page: number }) => Promise; + clientId: string; }) { const [autoUpdate, setAutoUpdate] = useState(true); const [status, setStatus] = useState( @@ -53,7 +54,7 @@ export function TransactionsTableUI(props: { const pageSize = 10; const transactionsQuery = useQuery({ - queryKey: ["transactions", page], + queryKey: ["transactions", props.clientId, page], queryFn: () => props.getData({ page }), refetchInterval: autoUpdate ? 4_000 : false, placeholderData: keepPreviousData, diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/analytics/tx-table/tx-table.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/analytics/tx-table/tx-table.tsx index bd7e33c7819..001b90b8d1d 100644 --- a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/analytics/tx-table/tx-table.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/analytics/tx-table/tx-table.tsx @@ -14,6 +14,7 @@ export function TransactionsTable(props: { teamId: string; clientId: string }) { page, }); }} + clientId={props.clientId} /> ); } diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/server-wallets/components/create-server-wallet.client.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/server-wallets/components/create-server-wallet.client.tsx index d7407c4c49a..766f6ae4361 100644 --- a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/server-wallets/components/create-server-wallet.client.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/server-wallets/components/create-server-wallet.client.tsx @@ -26,6 +26,12 @@ import { Loader2 } from "lucide-react"; import { useState } from "react"; import { toast } from "sonner"; +export const SERVER_WALLET_ACCESS_TOKEN_PURPOSE = + "Project Wide Server Wallet Access Token"; + +export const SERVER_WALLET_MANAGEMENT_ACCESS_TOKEN_PURPOSE = + "Dashboard Server Wallet Management Token"; + export default function CreateServerWallet(props: { project: Project; managementAccessToken: string | undefined; @@ -117,7 +123,7 @@ export default function CreateServerWallet(props: { metadata: { projectId: props.project.id, teamId: props.project.teamId, - purpose: "Thirdweb Project Server Wallet Access Token", + purpose: SERVER_WALLET_MANAGEMENT_ACCESS_TOKEN_PURPOSE, }, }, auth: { @@ -281,7 +287,7 @@ export default function CreateServerWallet(props: { metadata: { projectId: props.project.id, teamId: props.project.teamId, - purpose: "Thirdweb Project Server Wallet Access Token", + purpose: SERVER_WALLET_ACCESS_TOKEN_PURPOSE, }, }, auth: { @@ -415,9 +421,7 @@ export default function CreateServerWallet(props: { {initialiseProjectWithVaultMutation.isPending ? ( <> - - Generating your wallet management keys - + Generating your Vault management keys
@@ -430,6 +434,10 @@ export default function CreateServerWallet(props: {
Vault Management Keys +

+ These keys are used for end-to-end encryption and are required + to interact with Vault, thirdweb's key management system. +

diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/server-wallets/components/list-access-tokens.client.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/server-wallets/components/list-access-tokens.client.tsx index a26355575d6..1f9e4ea18ff 100644 --- a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/server-wallets/components/list-access-tokens.client.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/server-wallets/components/list-access-tokens.client.tsx @@ -1,6 +1,4 @@ "use client"; - -import { CopyTextButton } from "@/components/ui/CopyTextButton"; import { Spinner } from "@/components/ui/Spinner/Spinner"; import { Button } from "@/components/ui/button"; import { @@ -20,7 +18,22 @@ import { createVaultClient } from "@thirdweb-dev/vault-sdk"; import { Loader2, Trash2 } from "lucide-react"; import { useState } from "react"; import { toast } from "sonner"; +import { CopyTextButton } from "../../../../../../../@/components/ui/CopyTextButton"; +import { + Alert, + AlertDescription, + AlertTitle, +} from "../../../../../../../@/components/ui/alert"; +import { Badge } from "../../../../../../../@/components/ui/badge"; +import { + Checkbox, + CheckboxWithLabel, +} from "../../../../../../../@/components/ui/checkbox"; import { THIRDWEB_VAULT_URL } from "../../../../../../../@/constants/env"; +import { + SERVER_WALLET_ACCESS_TOKEN_PURPOSE, + SERVER_WALLET_MANAGEMENT_ACCESS_TOKEN_PURPOSE, +} from "./create-server-wallet.client"; export default function ListAccessTokensButton(props: { projectId: string; @@ -30,7 +43,8 @@ export default function ListAccessTokensButton(props: { const [typedAdminKey, setTypedAdminKey] = useState(""); const [adminKey, setAdminKey] = useState(""); const [deletingTokenId, setDeletingTokenId] = useState(null); - + const [newAccessToken, setNewAccessToken] = useState(null); + const [keysConfirmed, setKeysConfirmed] = useState(false); // TODO allow passing permissions to the access token const createAccessTokenMutation = useMutation({ mutationFn: async (args: { adminKey: string }) => { @@ -193,7 +207,7 @@ export default function ListAccessTokensButton(props: { metadata: { projectId: props.projectId, teamId: props.teamId, - purpose: "Thirdweb Project Server Wallet Access Token", + purpose: SERVER_WALLET_ACCESS_TOKEN_PURPOSE, }, }, auth: { @@ -215,7 +229,8 @@ export default function ListAccessTokensButton(props: { onError: (error) => { toast.error(error.message); }, - onSuccess: () => { + onSuccess: (data) => { + setNewAccessToken(data.userAccessToken.id); listAccessTokensQuery.refetch(); }, }); @@ -261,7 +276,7 @@ export default function ListAccessTokensButton(props: { // Stub data for now const listAccessTokensQuery = useQuery({ - queryKey: ["list-access-tokens", maskSecret(adminKey)], + queryKey: ["list-access-tokensq", maskSecret(adminKey)], queryFn: async () => { const vaultClient = await createVaultClient({ baseUrl: THIRDWEB_VAULT_URL, @@ -282,10 +297,17 @@ export default function ListAccessTokensButton(props: { ); } return { - accessTokens: listResult.data.items.map((t) => ({ - key: t.id, // todo: the actual user-facing key is not returned by this yet, fix this - id: t.id, - })), + accessTokens: listResult.data.items + .filter( + (t) => + t.metadata?.purpose?.toString() !== + SERVER_WALLET_MANAGEMENT_ACCESS_TOKEN_PURPOSE, + ) + .map((t) => ({ + key: t.metadata?.purpose?.toString() ?? t.id, + id: t.id, + policies: t.policies || [], + })), }; // Return stub data for now }, @@ -296,6 +318,8 @@ export default function ListAccessTokensButton(props: { setModalOpen(false); setAdminKey(""); setTypedAdminKey(""); + setNewAccessToken(null); + setKeysConfirmed(false); }; const isLoading = listAccessTokensQuery.isLoading; @@ -317,7 +341,53 @@ export default function ListAccessTokensButton(props: { Vault Access Tokens - {listAccessTokensQuery.isLoading ? ( + {/* If new access token, show copy button */} + {newAccessToken ? ( +
+
+

+ Here's your new access token. Store it securely as it will not + be displayed again. +

+ +

+ This access token is used to sign transactions and messages + from your backend. Can be revoked and recreated with your + admin key. +

+ + Secure your keys + + These keys will not be displayed again. Store them securely + as they provide access to your server wallets. + +
+ + setKeysConfirmed(!!v)} + /> + I confirm that I've securely stored these keys + + +
+
+ +
+
+ ) : listAccessTokensQuery.isLoading ? (
Loading access tokens @@ -330,17 +400,20 @@ export default function ListAccessTokensButton(props: {
{listAccessTokensQuery.data.accessTokens.map((token) => (
- +
+

{token.key}

+
+ {token.policies.map((policy) => ( + + {policy.type} + + ))} +
+
))} -

@@ -387,9 +441,23 @@ export default function ListAccessTokensButton(props: {

- +
) : ( From 9426108fb780b5a5118facb8109effd8c9e80870 Mon Sep 17 00:00:00 2001 From: Joaquim Verges Date: Wed, 23 Apr 2025 10:46:54 +1200 Subject: [PATCH 040/101] handle access keys --- .../engine/analytics/send-test-tx.client.tsx | 9 +++-- .../components/list-access-tokens.client.tsx | 40 ++++++++++++------- .../components/rotate-admin-key.client.tsx | 7 ++-- .../src/components/buttons/MismatchButton.tsx | 2 +- 4 files changed, 36 insertions(+), 22 deletions(-) diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/analytics/send-test-tx.client.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/analytics/send-test-tx.client.tsx index 759e236cc0e..3e4b7368912 100644 --- a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/analytics/send-test-tx.client.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/analytics/send-test-tx.client.tsx @@ -12,7 +12,7 @@ import { THIRDWEB_ENGINE_CLOUD_URL } from "@/constants/env"; import { useThirdwebClient } from "@/constants/thirdweb.client"; import { zodResolver } from "@hookform/resolvers/zod"; import { useMutation } from "@tanstack/react-query"; -import { ChevronDown, Loader2 } from "lucide-react"; +import { ChevronDown, Loader2, LockIcon } from "lucide-react"; import { useState } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; @@ -138,9 +138,9 @@ export function SendTestTransaction(props: { onSubmit={form.handleSubmit(onSubmit)} className="space-y-4 px-3 pb-3" > -

- 🔐 This action requires a project secret key and a vault access - token. +

+ This action requires a project + secret key and a vault access token.

{/* Responsive container */}
@@ -217,6 +217,7 @@ export function SendTestTransaction(props: {

Network

{ form.setValue("chainId", chainId); diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/server-wallets/components/list-access-tokens.client.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/server-wallets/components/list-access-tokens.client.tsx index 1f9e4ea18ff..36d7968462a 100644 --- a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/server-wallets/components/list-access-tokens.client.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/server-wallets/components/list-access-tokens.client.tsx @@ -15,7 +15,7 @@ import { revokeAccessToken, } from "@thirdweb-dev/vault-sdk"; import { createVaultClient } from "@thirdweb-dev/vault-sdk"; -import { Loader2, Trash2 } from "lucide-react"; +import { Loader2, LockIcon, Trash2 } from "lucide-react"; import { useState } from "react"; import { toast } from "sonner"; import { CopyTextButton } from "../../../../../../../@/components/ui/CopyTextButton"; @@ -30,6 +30,7 @@ import { CheckboxWithLabel, } from "../../../../../../../@/components/ui/checkbox"; import { THIRDWEB_VAULT_URL } from "../../../../../../../@/constants/env"; +import { toDateTimeLocal } from "../../../../../../../utils/date-utils"; import { SERVER_WALLET_ACCESS_TOKEN_PURPOSE, SERVER_WALLET_MANAGEMENT_ACCESS_TOKEN_PURPOSE, @@ -230,7 +231,7 @@ export default function ListAccessTokensButton(props: { toast.error(error.message); }, onSuccess: (data) => { - setNewAccessToken(data.userAccessToken.id); + setNewAccessToken(data.userAccessToken.accessToken); listAccessTokensQuery.refetch(); }, }); @@ -303,11 +304,7 @@ export default function ListAccessTokensButton(props: { t.metadata?.purpose?.toString() !== SERVER_WALLET_MANAGEMENT_ACCESS_TOKEN_PURPOSE, ) - .map((t) => ({ - key: t.metadata?.purpose?.toString() ?? t.id, - id: t.id, - policies: t.policies || [], - })), + .filter((t) => !t.revokedAt), }; // Return stub data for now }, @@ -379,11 +376,14 @@ export default function ListAccessTokensButton(props: {
@@ -400,15 +400,26 @@ export default function ListAccessTokensButton(props: {
{listAccessTokensQuery.data.accessTokens.map((token) => (
-
-

{token.key}

+
+

+ {token.metadata?.purpose || + "Unnamed Access Token"} +

{token.policies.map((policy) => ( - + {policy.type} ))}
+

+ Created on:{" "} + {toDateTimeLocal(new Date(token.createdAt))} +

); diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/server-wallets/components/create-server-wallet.client.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/server-wallets/components/create-server-wallet.client.tsx index 766f6ae4361..4a7797dd8c0 100644 --- a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/server-wallets/components/create-server-wallet.client.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/server-wallets/components/create-server-wallet.client.tsx @@ -11,26 +11,19 @@ import { DialogHeader, DialogTitle, } from "@/components/ui/dialog"; -import { THIRDWEB_VAULT_URL } from "@/constants/env"; import { useDashboardRouter } from "@/lib/DashboardRouter"; import { cn } from "@/lib/utils"; -import { updateProjectClient } from "@3rdweb-sdk/react/hooks/useApi"; import { useMutation } from "@tanstack/react-query"; -import { - createAccessToken, - createEoa, - createServiceAccount, - createVaultClient, -} from "@thirdweb-dev/vault-sdk"; +import { createEoa, createServiceAccount } from "@thirdweb-dev/vault-sdk"; import { Loader2 } from "lucide-react"; import { useState } from "react"; import { toast } from "sonner"; - -export const SERVER_WALLET_ACCESS_TOKEN_PURPOSE = - "Project Wide Server Wallet Access Token"; - -export const SERVER_WALLET_MANAGEMENT_ACCESS_TOKEN_PURPOSE = - "Dashboard Server Wallet Management Token"; +import { + createManagementAccessToken, + createWalletAccessToken, + initVaultClient, + maskSecret, +} from "../lib/vault-utils"; export default function CreateServerWallet(props: { project: Project; @@ -44,9 +37,7 @@ export default function CreateServerWallet(props: { mutationFn: async () => { setModalOpen(true); - const vaultClient = await createVaultClient({ - baseUrl: THIRDWEB_VAULT_URL, - }); + const vaultClient = await initVaultClient(); const serviceAccount = await createServiceAccount({ client: vaultClient, @@ -65,235 +56,16 @@ export default function CreateServerWallet(props: { throw new Error("Failed to create service account"); } - const managementAccessTokenPromise = createAccessToken({ - client: vaultClient, - request: { - options: { - expiresAt: new Date( - Date.now() + 60 * 60 * 1000 * 24 * 365 * 1000, - ).toISOString(), // 100 years from now - policies: [ - { - type: "eoa:read", - metadataPatterns: [ - { - key: "projectId", - rule: { - pattern: props.project.id, - }, - }, - { - key: "teamId", - rule: { - pattern: props.project.teamId, - }, - }, - { - key: "type", - rule: { - pattern: "server-wallet", - }, - }, - ], - }, - { - type: "eoa:create", - requiredMetadataPatterns: [ - { - key: "projectId", - rule: { - pattern: props.project.id, - }, - }, - { - key: "teamId", - rule: { - pattern: props.project.teamId, - }, - }, - { - key: "type", - rule: { - pattern: "server-wallet", - }, - }, - ], - }, - ], - metadata: { - projectId: props.project.id, - teamId: props.project.teamId, - purpose: SERVER_WALLET_MANAGEMENT_ACCESS_TOKEN_PURPOSE, - }, - }, - auth: { - adminKey: serviceAccount.data.adminKey, - }, - }, + const managementAccessTokenPromise = createManagementAccessToken({ + project: props.project, + adminKey: serviceAccount.data.adminKey, + vaultClient, }); - const userAccesTokenPromise = createAccessToken({ - client: vaultClient, - request: { - options: { - expiresAt: new Date( - Date.now() + 60 * 60 * 1000 * 24 * 365 * 1000, - ).toISOString(), // 100 years from now - policies: [ - { - type: "eoa:read", - metadataPatterns: [ - { - key: "projectId", - rule: { - pattern: props.project.id, - }, - }, - { - key: "teamId", - rule: { - pattern: props.project.teamId, - }, - }, - { - key: "type", - rule: { - pattern: "server-wallet", - }, - }, - ], - }, - { - type: "eoa:create", - requiredMetadataPatterns: [ - { - 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", - }, - }, - ], - }, - { - type: "eoa:signTransaction", - payloadPatterns: {}, - 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:signStructuredMessage", - structuredPatterns: { - useropV06: {}, - useropV07: {}, - }, - metadataPatterns: [ - { - key: "projectId", - rule: { - pattern: props.project.id, - }, - }, - { - key: "teamId", - rule: { - pattern: props.project.teamId, - }, - }, - { - key: "type", - rule: { - pattern: "server-wallet", - }, - }, - ], - }, - ], - metadata: { - projectId: props.project.id, - teamId: props.project.teamId, - purpose: SERVER_WALLET_ACCESS_TOKEN_PURPOSE, - }, - }, - auth: { - adminKey: serviceAccount.data.adminKey, - }, - }, + const userAccesTokenPromise = createWalletAccessToken({ + project: props.project, + adminKey: serviceAccount.data.adminKey, + vaultClient, }); const [userAccessTokenRes, managementAccessTokenRes] = await Promise.all([ @@ -305,25 +77,6 @@ export default function CreateServerWallet(props: { throw new Error("Failed to create access token"); } - // store the management access token in the project - await updateProjectClient( - { - projectId: props.project.id, - teamId: props.project.teamId, - }, - { - services: [ - ...props.project.services, - { - name: "engineCloud", - managementAccessToken: managementAccessTokenRes.data.accessToken, - maskedAdminKey: maskSecret(serviceAccount.data.adminKey), - actions: [], - }, - ], - }, - ); - return { serviceAccount: serviceAccount.data, userAccessToken: userAccessTokenRes.data, @@ -342,9 +95,7 @@ export default function CreateServerWallet(props: { }: { managementAccessToken: string; }) => { - const vaultClient = await createVaultClient({ - baseUrl: THIRDWEB_VAULT_URL, - }); + const vaultClient = await initVaultClient(); const eoa = await createEoa({ request: { @@ -388,6 +139,20 @@ export default function CreateServerWallet(props: { } }; + const handleCopyAllKeys = () => { + if (!initialiseProjectWithVaultMutation.data) { + return; + } + navigator.clipboard.writeText( + ` +Vault Admin Key: ${initialiseProjectWithVaultMutation.data.serviceAccount.adminKey} +Rotation Code: ${initialiseProjectWithVaultMutation.data.serviceAccount.rotationCode} +Vault Access Token: ${initialiseProjectWithVaultMutation.data.userAccessToken.accessToken} + `, + ); + toast.success("All keys copied to clipboard"); + }; + const handleCloseModal = () => { if (!keysConfirmed) { return; @@ -533,6 +298,10 @@ export default function CreateServerWallet(props: {
+ +
) : null; diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/server-wallets/components/list-access-tokens.client.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/server-wallets/components/list-access-tokens.client.tsx index 36d7968462a..ce271691786 100644 --- a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/server-wallets/components/list-access-tokens.client.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/server-wallets/components/list-access-tokens.client.tsx @@ -9,15 +9,11 @@ import { } from "@/components/ui/dialog"; import { Input } from "@/components/ui/input"; import { useMutation, useQuery } from "@tanstack/react-query"; -import { - createAccessToken, - listAccessTokens, - revokeAccessToken, -} from "@thirdweb-dev/vault-sdk"; -import { createVaultClient } from "@thirdweb-dev/vault-sdk"; +import { listAccessTokens, revokeAccessToken } from "@thirdweb-dev/vault-sdk"; import { Loader2, LockIcon, Trash2 } from "lucide-react"; import { useState } from "react"; import { toast } from "sonner"; +import type { Project } from "../../../../../../../@/api/projects"; import { CopyTextButton } from "../../../../../../../@/components/ui/CopyTextButton"; import { Alert, @@ -29,16 +25,15 @@ import { Checkbox, CheckboxWithLabel, } from "../../../../../../../@/components/ui/checkbox"; -import { THIRDWEB_VAULT_URL } from "../../../../../../../@/constants/env"; import { toDateTimeLocal } from "../../../../../../../utils/date-utils"; import { - SERVER_WALLET_ACCESS_TOKEN_PURPOSE, SERVER_WALLET_MANAGEMENT_ACCESS_TOKEN_PURPOSE, -} from "./create-server-wallet.client"; + createWalletAccessToken, + initVaultClient, +} from "../lib/vault-utils"; export default function ListAccessTokensButton(props: { - projectId: string; - teamId: string; + project: Project; }) { const [modalOpen, setModalOpen] = useState(false); const [typedAdminKey, setTypedAdminKey] = useState(""); @@ -49,172 +44,12 @@ export default function ListAccessTokensButton(props: { // TODO allow passing permissions to the access token const createAccessTokenMutation = useMutation({ mutationFn: async (args: { adminKey: string }) => { - const vaultClient = await createVaultClient({ - baseUrl: THIRDWEB_VAULT_URL, - }); + const vaultClient = await initVaultClient(); - const userAccessTokenRes = await createAccessToken({ - client: vaultClient, - request: { - options: { - expiresAt: new Date( - Date.now() + 60 * 60 * 1000 * 24 * 365 * 1000, - ).toISOString(), // 100 years from now - policies: [ - { - type: "eoa:read", - metadataPatterns: [ - { - key: "projectId", - rule: { - pattern: props.projectId, - }, - }, - { - key: "teamId", - rule: { - pattern: props.teamId, - }, - }, - { - key: "type", - rule: { - pattern: "server-wallet", - }, - }, - ], - }, - { - type: "eoa:create", - requiredMetadataPatterns: [ - { - key: "projectId", - rule: { - pattern: props.projectId, - }, - }, - { - key: "teamId", - rule: { - pattern: props.teamId, - }, - }, - { - key: "type", - rule: { - pattern: "server-wallet", - }, - }, - ], - }, - { - type: "eoa:signMessage", - metadataPatterns: [ - { - key: "projectId", - rule: { - pattern: props.projectId, - }, - }, - { - key: "teamId", - rule: { - pattern: props.teamId, - }, - }, - { - key: "type", - rule: { - pattern: "server-wallet", - }, - }, - ], - }, - { - type: "eoa:signTransaction", - payloadPatterns: {}, - metadataPatterns: [ - { - key: "projectId", - rule: { - pattern: props.projectId, - }, - }, - { - key: "teamId", - rule: { - pattern: props.teamId, - }, - }, - { - key: "type", - rule: { - pattern: "server-wallet", - }, - }, - ], - }, - { - type: "eoa:signTypedData", - metadataPatterns: [ - { - key: "projectId", - rule: { - pattern: props.projectId, - }, - }, - { - key: "teamId", - rule: { - pattern: props.teamId, - }, - }, - { - key: "type", - rule: { - pattern: "server-wallet", - }, - }, - ], - }, - { - type: "eoa:signStructuredMessage", - structuredPatterns: { - useropV06: {}, - useropV07: {}, - }, - metadataPatterns: [ - { - key: "projectId", - rule: { - pattern: props.projectId, - }, - }, - { - key: "teamId", - rule: { - pattern: props.teamId, - }, - }, - { - key: "type", - rule: { - pattern: "server-wallet", - }, - }, - ], - }, - ], - metadata: { - projectId: props.projectId, - teamId: props.teamId, - purpose: SERVER_WALLET_ACCESS_TOKEN_PURPOSE, - }, - }, - auth: { - adminKey: args.adminKey, - }, - }, + const userAccessTokenRes = await createWalletAccessToken({ + project: props.project, + adminKey: args.adminKey, + vaultClient, }); if (!userAccessTokenRes.success) { @@ -239,9 +74,7 @@ export default function ListAccessTokensButton(props: { const revokeAccessTokenMutation = useMutation({ mutationFn: async (args: { adminKey: string; accessTokenId: string }) => { setDeletingTokenId(args.accessTokenId); - const vaultClient = await createVaultClient({ - baseUrl: THIRDWEB_VAULT_URL, - }); + const vaultClient = await initVaultClient(); const revokeAccessTokenRes = await revokeAccessToken({ client: vaultClient, @@ -279,9 +112,7 @@ export default function ListAccessTokensButton(props: { const listAccessTokensQuery = useQuery({ queryKey: ["list-access-tokensq", maskSecret(adminKey)], queryFn: async () => { - const vaultClient = await createVaultClient({ - baseUrl: THIRDWEB_VAULT_URL, - }); + const vaultClient = await initVaultClient(); const listResult = await listAccessTokens({ client: vaultClient, request: { @@ -335,7 +166,7 @@ export default function ListAccessTokensButton(props: { - + Vault Access Tokens {/* If new access token, show copy button */} @@ -472,8 +303,8 @@ export default function ListAccessTokensButton(props: {
) : ( -
-
+
+

This action requires your Vault admin key. @@ -489,34 +320,34 @@ export default function ListAccessTokensButton(props: { } }} /> -

- - -
+
+
+ +
)} diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/server-wallets/components/rotate-admin-key.client.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/server-wallets/components/rotate-admin-key.client.tsx index caeadf2c687..4010b2f537c 100644 --- a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/server-wallets/components/rotate-admin-key.client.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/server-wallets/components/rotate-admin-key.client.tsx @@ -1,5 +1,6 @@ "use client"; +import type { Project } from "@/api/projects"; import { CopyTextButton } from "@/components/ui/CopyTextButton"; import { Spinner } from "@/components/ui/Spinner/Spinner"; import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; @@ -15,16 +16,17 @@ import { import { Input } from "@/components/ui/input"; import { cn } from "@/lib/utils"; import { useMutation } from "@tanstack/react-query"; -import { - createVaultClient, - rotateServiceAccount, -} from "@thirdweb-dev/vault-sdk"; +import { rotateServiceAccount } from "@thirdweb-dev/vault-sdk"; import { Loader2, LockIcon, RefreshCcwIcon } from "lucide-react"; import { useState } from "react"; import { toast } from "sonner"; -import { THIRDWEB_VAULT_URL } from "../../../../../../../@/constants/env"; +import { + createManagementAccessToken, + initVaultClient, + maskSecret, +} from "../lib/vault-utils"; -export default function RotateAdminKeyButton() { +export default function RotateAdminKeyButton(props: { project: Project }) { const [modalOpen, setModalOpen] = useState(false); const [rotationCode, setRotationCode] = useState(""); const [keysConfirmed, setKeysConfirmed] = useState(false); @@ -36,12 +38,10 @@ export default function RotateAdminKeyButton() { } await new Promise((resolve) => setTimeout(resolve, 1000)); - const client = await createVaultClient({ - baseUrl: THIRDWEB_VAULT_URL, - }); + const vaultClient = await initVaultClient(); const rotateServiceAccountRes = await rotateServiceAccount({ - client, + client: vaultClient, request: { auth: { rotationCode, @@ -53,6 +53,17 @@ export default function RotateAdminKeyButton() { throw new Error(rotateServiceAccountRes.error.message); } + // need to recreate the management access token with the new admin key + const res = await createManagementAccessToken({ + project: props.project, + adminKey: rotateServiceAccountRes.data.newAdminKey, + vaultClient, + }); + + if (res.error) { + throw new Error(res.error.message); + } + return { success: true, adminKey: rotateServiceAccountRes.data.newAdminKey, @@ -182,15 +193,18 @@ export default function RotateAdminKeyButton() {
) : ( <> - + Rotate your Vault admin key This action will generate a new Vault admin key and rotation - code. This will invalidate all existing access tokens. + code.{" "} +

+ This will invalidate all existing access tokens. +

-
-
+
+

This action requires your Vault rotation code. @@ -206,33 +220,31 @@ export default function RotateAdminKeyButton() { } }} /> -

- - -
+
+
+ +
@@ -242,7 +254,3 @@ export default function RotateAdminKeyButton() { ); } - -function maskSecret(secret: string) { - return `${secret.substring(0, 11)}...${secret.substring(secret.length - 5)}`; -} diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/server-wallets/components/try-it-out.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/server-wallets/components/try-it-out.tsx index d4034245188..3cfb1a2b922 100644 --- a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/server-wallets/components/try-it-out.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/server-wallets/components/try-it-out.tsx @@ -43,7 +43,7 @@ export function TryItOut(props: { {props.wallet && (
diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/layout.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/layout.tsx index 277301459f4..c90b9b040b8 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/layout.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/layout.tsx @@ -82,13 +82,17 @@ function TransactionsLayout(props: { path: `${engineLayoutSlug}`, exactMatch: true, }, + { + name: "Explorer", + path: `${engineLayoutSlug}/explorer`, + }, { name: "Server Wallets", path: `${engineLayoutSlug}/server-wallets`, }, { - name: "Explorer", - path: `${engineLayoutSlug}/explorer`, + name: "Vault", + path: `${engineLayoutSlug}/vault`, }, ]} /> diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/server-wallets/components/create-server-wallet.client.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/server-wallets/components/create-server-wallet.client.tsx index 0c705e94b79..1aa7c07034b 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/server-wallets/components/create-server-wallet.client.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/server-wallets/components/create-server-wallet.client.tsx @@ -128,10 +128,7 @@ export default function CreateServerWallet(props: { const handleCreateServerWallet = async () => { if (!props.managementAccessToken) { - const initResult = await initialiseProjectWithVaultMutation.mutateAsync(); - await createEoaMutation.mutateAsync({ - managementAccessToken: initResult.managementAccessToken.accessToken, - }); + router.push("vault"); } else { await createEoaMutation.mutateAsync({ managementAccessToken: props.managementAccessToken, @@ -173,9 +170,7 @@ Vault Access Token: ${initialiseProjectWithVaultMutation.data.userAccessToken.ac className="flex flex-row items-center gap-2" > {isLoading && } - {props.managementAccessToken - ? "Create Server Wallet" - : "Create Vault Admin Keys"} + {props.managementAccessToken ? "Create Server Wallet" : "Get Started"} diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/server-wallets/components/key-management.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/server-wallets/components/key-management.tsx deleted file mode 100644 index 7110dabfeee..00000000000 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/server-wallets/components/key-management.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import type { Project } from "@/api/projects"; -import ListAccessTokensButton from "./list-access-tokens.client"; -import RotateAdminKeyButton from "./rotate-admin-key.client"; - -export function KeyManagement({ - maskedAdminKey, - project, -}: { maskedAdminKey?: string; project: Project }) { - return maskedAdminKey ? ( -
-
-
-
-

- Key Management -

-

- Manage your admin key and access tokens. -

-
-
-
-
-
-

Vault Admin Key

-

{maskedAdminKey}

-
- -
-
- -
-
- ) : null; -} diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/server-wallets/page.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/server-wallets/page.tsx index 1555cf58466..7346c7c4f51 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/server-wallets/page.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/server-wallets/page.tsx @@ -3,8 +3,6 @@ import { createVaultClient, listEoas } from "@thirdweb-dev/vault-sdk"; import { notFound } from "next/navigation"; import { THIRDWEB_VAULT_URL } from "../../../../../../../@/constants/env"; import { getAuthToken } from "../../../../../api/lib/getAuthToken"; -import { KeyManagement } from "./components/key-management"; -import { TryItOut } from "./components/try-it-out"; import type { Wallet } from "./wallet-table/types"; import { ServerWalletsTable } from "./wallet-table/wallet-table"; @@ -31,7 +29,6 @@ export default async function TransactionsServerWalletsPage(props: { const managementAccessToken = projectEngineCloudService?.managementAccessToken; - const maskedAdminKey = projectEngineCloudService?.maskedAdminKey; const eoas = managementAccessToken ? await listEoas({ @@ -45,8 +42,6 @@ export default async function TransactionsServerWalletsPage(props: { }) : { data: { items: [] }, error: null, success: true }; - const wallet = eoas.data?.items[0] as Wallet | undefined; - return ( <> {eoas.error ? ( @@ -58,16 +53,6 @@ export default async function TransactionsServerWalletsPage(props: { project={project} managementAccessToken={managementAccessToken ?? undefined} /> - -
)} diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/vault/components/create-vault-account.client.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/vault/components/create-vault-account.client.tsx new file mode 100644 index 00000000000..295c24989d7 --- /dev/null +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/vault/components/create-vault-account.client.tsx @@ -0,0 +1,267 @@ +"use client"; +import type { Project } from "@/api/projects"; +import { CopyTextButton } from "@/components/ui/CopyTextButton"; +import { Spinner } from "@/components/ui/Spinner/Spinner"; +import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; +import { Button } from "@/components/ui/button"; +import { Checkbox, CheckboxWithLabel } from "@/components/ui/checkbox"; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { cn } from "@/lib/utils"; +import { useMutation } from "@tanstack/react-query"; +import { createServiceAccount } from "@thirdweb-dev/vault-sdk"; +import { Loader2 } from "lucide-react"; +import { useState } from "react"; +import { toast } from "sonner"; +import { + createManagementAccessToken, + createWalletAccessToken, + initVaultClient, + maskSecret, +} from "../../lib/vault.client"; + +export default function CreateVaultAccountButton(props: { + project: Project; +}) { + const [modalOpen, setModalOpen] = useState(false); + const [keysConfirmed, setKeysConfirmed] = useState(false); + + const initialiseProjectWithVaultMutation = useMutation({ + mutationFn: async () => { + setModalOpen(true); + + const vaultClient = await initVaultClient(); + + const serviceAccount = await createServiceAccount({ + client: vaultClient, + request: { + options: { + metadata: { + projectId: props.project.id, + teamId: props.project.teamId, + purpose: "Thirdweb Project Server Wallet Service Account", + }, + }, + }, + }); + + if (!serviceAccount.success) { + throw new Error("Failed to create service account"); + } + + const managementAccessTokenPromise = createManagementAccessToken({ + project: props.project, + adminKey: serviceAccount.data.adminKey, + vaultClient, + }); + + const userAccesTokenPromise = createWalletAccessToken({ + project: props.project, + adminKey: serviceAccount.data.adminKey, + vaultClient, + }); + + const [userAccessTokenRes, managementAccessTokenRes] = await Promise.all([ + userAccesTokenPromise, + managementAccessTokenPromise, + ]); + + if (!managementAccessTokenRes.success || !userAccessTokenRes.success) { + throw new Error("Failed to create access token"); + } + + return { + serviceAccount: serviceAccount.data, + userAccessToken: userAccessTokenRes.data, + managementAccessToken: managementAccessTokenRes.data, + }; + }, + onError: (error) => { + toast.error(error.message); + setModalOpen(false); + }, + }); + + const handleCreateServerWallet = async () => { + await initialiseProjectWithVaultMutation.mutateAsync(); + }; + + const handleCopyAllKeys = () => { + if (!initialiseProjectWithVaultMutation.data) { + return; + } + navigator.clipboard.writeText( + ` +Vault Admin Key: ${initialiseProjectWithVaultMutation.data.serviceAccount.adminKey} +Rotation Code: ${initialiseProjectWithVaultMutation.data.serviceAccount.rotationCode} +Vault Access Token: ${initialiseProjectWithVaultMutation.data.userAccessToken.accessToken} + `, + ); + toast.success("All keys copied to clipboard"); + }; + + const handleCloseModal = () => { + if (!keysConfirmed) { + return; + } + setModalOpen(false); + setKeysConfirmed(false); + }; + + const isLoading = initialiseProjectWithVaultMutation.isPending; + + return ( + <> + + + + + {initialiseProjectWithVaultMutation.isPending ? ( + <> + + Generating your Vault management keys + +
+ +

+ This may take a few seconds. +

+
+ + ) : initialiseProjectWithVaultMutation.data ? ( +
+ + Vault Management Keys +

+ These keys are used for end-to-end encryption and are required + to interact with Vault, thirdweb's key management system. +

+
+ +
+
+
+

+ Vault Admin Key +

+
+ +

+ This key is used to create or revoke your access tokens. +

+
+
+ +
+

Rotation Code

+
+ +

+ This code is used to revoke and recreate your Vault + admin key if needed. Keep it safe. +

+
+
+ +
+

+ Vault Access Token +

+
+ +

+ This access token is used to sign transactions and + messages from your backend. Can be revoked and recreated + with your admin key. +

+
+
+
+ + Secure your keys + + These keys will not be displayed again. Store them securely + as they provide access to your server wallets. + +
+ + setKeysConfirmed(!!v)} + /> + I confirm that I've securely stored these keys + + +
+ +
+ + + +
+
+ ) : null} + +
+ + ); +} diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/vault/components/key-management.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/vault/components/key-management.tsx new file mode 100644 index 00000000000..0bcabf9cc10 --- /dev/null +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/vault/components/key-management.tsx @@ -0,0 +1,94 @@ +import type { Project } from "@/api/projects"; +import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; +import { InfoIcon } from "lucide-react"; +import Link from "next/link"; +import RotateAdminKeyButton from "../../server-wallets/components/rotate-admin-key.client"; +import CreateVaultAccountButton from "./create-vault-account.client"; +import ListAccessTokensButton from "./list-access-tokens.client"; + +export function KeyManagement({ + maskedAdminKey, + project, +}: { maskedAdminKey?: string; project: Project }) { + return ( +
+
+
+

Vault

+

+ Secure, non-custodial key management system for your server wallets.{" "} + + Learn more. + +

+
+ {!maskedAdminKey ? ( +
+
+ + + + What is Vault? + + + Vault is thirdweb's non-custodial key management system for + your server wallets that allows you to: +
    +
  • Create multiple server wallets.
  • +
  • Create Vault access tokens.
  • +
  • Sign transactions using a Vault access token.
  • +
+ Your keys are stored in a hardware enclave, and all requests + are end-to-end encrypted.{" "} + + Learn more about Vault security model. + +
+ Creating server wallets and access tokens requires a Vault + admin account. Create one below to get started. + +
+ +
+
+ +
+
+ ) : ( +
+ )} +
+ {maskedAdminKey ? ( +
+
+

Admin Key

+

+ This key is used to create new server wallets and access tokens. +
We do not store this key. If you lose it, you can rotate it + to create a new one. Doing so will invalidate all existing access + tokens. +

+
+
+
+

+ {maskedAdminKey} +

+
+ +
+
+
+ +
+
+ ) : null} +
+ ); +} diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/server-wallets/components/list-access-tokens.client.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/vault/components/list-access-tokens.client.tsx similarity index 99% rename from apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/server-wallets/components/list-access-tokens.client.tsx rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/vault/components/list-access-tokens.client.tsx index ce3eb8412bb..3d496c8667e 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/server-wallets/components/list-access-tokens.client.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/vault/components/list-access-tokens.client.tsx @@ -155,7 +155,7 @@ export default function ListAccessTokensButton(props: { return ( <> - - - - {initialiseProjectWithVaultMutation.isPending ? ( - <> - - Generating your Vault management keys - -
- -

- This may take a few seconds. -

-
- - ) : initialiseProjectWithVaultMutation.data ? ( -
- - Vault Management Keys -

- These keys are used for end-to-end encryption and are required - to interact with Vault, thirdweb's key management system. -

-
- -
-
-
-

- Vault Admin Key -

-
- -

- This key is used to create or revoke your access tokens. -

-
-
- -
-

Rotation Code

-
- -

- This code is used to revoke and recreate your Vault - admin key if needed. Keep it safe. -

-
-
- -
-

- Vault Access Token -

-
- -

- This access token is used to sign transactions and - messages from your backend. Can be revoked and recreated - with your admin key. -

-
-
-
- - Secure your keys - - These keys will not be displayed again. Store them securely - as they provide access to your server wallets. - -
- - setKeysConfirmed(!!v)} - /> - I confirm that I've securely stored these keys - - -
- -
- - - -
-
- ) : null} - -
); } diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/server-wallets/components/rotate-admin-key.client.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/server-wallets/components/rotate-admin-key.client.tsx index 08d6776e9af..a04b39282b3 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/server-wallets/components/rotate-admin-key.client.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/server-wallets/components/rotate-admin-key.client.tsx @@ -13,32 +13,39 @@ import { DialogHeader, DialogTitle, } from "@/components/ui/dialog"; -import { Input } from "@/components/ui/input"; import { cn } from "@/lib/utils"; import { useMutation } from "@tanstack/react-query"; import { rotateServiceAccount } from "@thirdweb-dev/vault-sdk"; -import { Loader2, LockIcon, RefreshCcwIcon } from "lucide-react"; +import { Loader2, RefreshCcwIcon } from "lucide-react"; import { useState } from "react"; import { toast } from "sonner"; import { createManagementAccessToken, + createWalletAccessToken, initVaultClient, maskSecret, } from "../../lib/vault.client"; +import { useDashboardRouter } from "@/lib/DashboardRouter"; export default function RotateAdminKeyButton(props: { project: Project }) { const [modalOpen, setModalOpen] = useState(false); - const [rotationCode, setRotationCode] = useState(""); const [keysConfirmed, setKeysConfirmed] = useState(false); + const router = useDashboardRouter(); const rotateAdminKeyMutation = useMutation({ mutationFn: async () => { - if (!rotationCode) { - throw new Error("Rotation code is required"); - } await new Promise((resolve) => setTimeout(resolve, 1000)); const vaultClient = await initVaultClient(); + const rotationCode = props.project.services.find( + (service) => service.name === "engineCloud", + )?.rotationCode; + + console.log("DEBUG", rotationCode); + + if (!rotationCode) { + throw new Error("Rotation code not found"); + } const rotateServiceAccountRes = await rotateServiceAccount({ client: vaultClient, @@ -54,20 +61,32 @@ export default function RotateAdminKeyButton(props: { project: Project }) { } // need to recreate the management access token with the new admin key - const res = await createManagementAccessToken({ + const managementAccessTokenPromise = createManagementAccessToken({ + project: props.project, + adminKey: rotateServiceAccountRes.data.newAdminKey, + rotationCode: rotateServiceAccountRes.data.newRotationCode, + vaultClient, + }); + + const userAccesTokenPromise = createWalletAccessToken({ project: props.project, adminKey: rotateServiceAccountRes.data.newAdminKey, vaultClient, }); - if (res.error) { - throw new Error(res.error.message); + const [userAccessTokenRes, managementAccessTokenRes] = await Promise.all([ + userAccesTokenPromise, + managementAccessTokenPromise, + ]); + + if (!managementAccessTokenRes.success || !userAccessTokenRes.success) { + throw new Error("Failed to create access token"); } return { success: true, adminKey: rotateServiceAccountRes.data.newAdminKey, - rotationKey: rotateServiceAccountRes.data.newRotationCode, + userAccessToken: userAccessTokenRes.data, }; }, onError: (error) => { @@ -80,8 +99,10 @@ export default function RotateAdminKeyButton(props: { project: Project }) { return; } setModalOpen(false); - setRotationCode(""); setKeysConfirmed(false); + rotateAdminKeyMutation.reset(); + // invalidate the page to force a reload of the project data + router.refresh(); }; const isLoading = rotateAdminKeyMutation.isPending; @@ -143,26 +164,6 @@ export default function RotateAdminKeyButton(props: { project: Project }) {

- -
-

- New Rotation Key -

-
- -

- This key is used to rotate your admin key in the future. -

-
-
Secure your keys @@ -198,43 +199,28 @@ export default function RotateAdminKeyButton(props: { project: Project }) { This action will generate a new Vault admin key and rotation code.{" "} -

- This will invalidate all existing access tokens. -

-

- This action requires your - Vault rotation code. +

+ This will invalidate your current admin key and all existing + access tokens.

- setRotationCode(e.target.value)} - onKeyDown={(e) => { - if (e.key === "Enter") { - rotateAdminKeyMutation.mutate(); - } - }} - />
-
-

Rotation Code

-
- -

- This code is used to revoke and recreate your Vault - admin key if needed. Keep it safe. -

-
-
-

Vault Access Token @@ -246,16 +245,12 @@ Vault Access Token: ${initialiseProjectWithVaultMutation.data.userAccessToken.ac

- -
diff --git a/packages/service-utils/src/core/api.ts b/packages/service-utils/src/core/api.ts index 288b45a9ef4..256fccc087e 100644 --- a/packages/service-utils/src/core/api.ts +++ b/packages/service-utils/src/core/api.ts @@ -200,6 +200,7 @@ export type ProjectService = actions: never[]; maskedAdminKey?: string | null; managementAccessToken?: string | null; + rotationCode?: string | null; } | ProjectBundlerService | ProjectEmbeddedWalletsService; diff --git a/packages/service-utils/src/index.ts b/packages/service-utils/src/index.ts index 07b03d88fcd..5595ad0f6bb 100644 --- a/packages/service-utils/src/index.ts +++ b/packages/service-utils/src/index.ts @@ -16,10 +16,7 @@ export type { export { fetchTeamAndProject } from "./core/api.js"; -export { - authorizeBundleId, - authorizeDomain, -} from "./core/authorize/client.js"; +export { authorizeBundleId, authorizeDomain } from "./core/authorize/client.js"; export { rateLimitSlidingWindow } from "./core/rateLimit/strategies/sliding-window.js"; export { rateLimit } from "./core/rateLimit/index.js"; From 3725074145f16d6d1e873a75d117dd9a58c0c97e Mon Sep 17 00:00:00 2001 From: Joaquim Verges Date: Tue, 29 Apr 2025 13:00:58 +1200 Subject: [PATCH 061/101] list tokens UI --- .../vault/components/key-management.tsx | 44 +- .../components/list-access-tokens.client.tsx | 459 +++++++++--------- 2 files changed, 264 insertions(+), 239 deletions(-) diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/vault/components/key-management.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/vault/components/key-management.tsx index 0bcabf9cc10..ec18f635f3c 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/vault/components/key-management.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/vault/components/key-management.tsx @@ -4,7 +4,7 @@ import { InfoIcon } from "lucide-react"; import Link from "next/link"; import RotateAdminKeyButton from "../../server-wallets/components/rotate-admin-key.client"; import CreateVaultAccountButton from "./create-vault-account.client"; -import ListAccessTokensButton from "./list-access-tokens.client"; +import ListAccessTokens from "./list-access-tokens.client"; export function KeyManagement({ maskedAdminKey, @@ -65,29 +65,31 @@ export function KeyManagement({ )}
{maskedAdminKey ? ( -
-
-

Admin Key

-

- This key is used to create new server wallets and access tokens. -
We do not store this key. If you lose it, you can rotate it - to create a new one. Doing so will invalidate all existing access - tokens. -

-
-
-
-

- {maskedAdminKey} -

+ <> +
+
+

+ Admin Key +

+

+ This key is used to create new server wallets and access tokens. +
We do not store this key. If you lose it, you can rotate + it to create a new one. Doing so will invalidate all existing + access tokens. +

+
+
+
+

+ {maskedAdminKey} +

+
+
-
-
- -
-
+ + ) : null}
); diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/vault/components/list-access-tokens.client.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/vault/components/list-access-tokens.client.tsx index 3d496c8667e..ae3f81d970c 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/vault/components/list-access-tokens.client.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/vault/components/list-access-tokens.client.tsx @@ -1,6 +1,9 @@ "use client"; -import { Spinner } from "@/components/ui/Spinner/Spinner"; +import type { Project } from "@/api/projects"; +import { CopyTextButton } from "@/components/ui/CopyTextButton"; +import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; import { Button } from "@/components/ui/button"; +import { Checkbox, CheckboxWithLabel } from "@/components/ui/checkbox"; import { Dialog, DialogContent, @@ -8,31 +11,20 @@ import { DialogTitle, } from "@/components/ui/dialog"; import { Input } from "@/components/ui/input"; +import { Skeleton } from "@/components/ui/skeleton"; import { useMutation, useQuery } from "@tanstack/react-query"; import { listAccessTokens, revokeAccessToken } from "@thirdweb-dev/vault-sdk"; import { Loader2, LockIcon, Trash2 } from "lucide-react"; import { useState } from "react"; import { toast } from "sonner"; -import type { Project } from "../../../../../../../../@/api/projects"; -import { CopyTextButton } from "../../../../../../../../@/components/ui/CopyTextButton"; -import { - Alert, - AlertDescription, - AlertTitle, -} from "../../../../../../../../@/components/ui/alert"; -import { Badge } from "../../../../../../../../@/components/ui/badge"; -import { - Checkbox, - CheckboxWithLabel, -} from "../../../../../../../../@/components/ui/checkbox"; -import { toDateTimeLocal } from "../../../../../../../../utils/date-utils"; +import { toDateTimeLocal } from "utils/date-utils"; import { SERVER_WALLET_MANAGEMENT_ACCESS_TOKEN_PURPOSE, createWalletAccessToken, initVaultClient, } from "../../lib/vault.client"; -export default function ListAccessTokensButton(props: { +export default function ListAccessTokens(props: { project: Project; }) { const [modalOpen, setModalOpen] = useState(false); @@ -108,17 +100,31 @@ export default function ListAccessTokensButton(props: { }, }); - // Stub data for now + const managementAccessToken = props.project.services.find( + (s) => s.name === "engineCloud", + )?.managementAccessToken; + const listAccessTokensQuery = useQuery({ - queryKey: ["list-access-tokensq", maskSecret(adminKey)], + queryKey: [ + "list-access-tokens", + maskSecret(managementAccessToken || ""), + maskSecret(adminKey), + ], queryFn: async () => { + if (!managementAccessToken) { + throw new Error("Management access token not found"); + } const vaultClient = await initVaultClient(); const listResult = await listAccessTokens({ client: vaultClient, request: { - auth: { - adminKey, - }, + auth: adminKey + ? { + adminKey, + } + : { + accessToken: managementAccessToken, + }, options: {}, }, }); @@ -139,221 +145,238 @@ export default function ListAccessTokensButton(props: { }; // Return stub data for now }, - enabled: !!adminKey, + enabled: !!managementAccessToken, }); const handleCloseModal = () => { setModalOpen(false); - setAdminKey(""); - setTypedAdminKey(""); - setNewAccessToken(null); - setKeysConfirmed(false); }; - const isLoading = listAccessTokensQuery.isLoading; - return ( - <> - - - - - - Vault Access Tokens - - {/* If new access token, show copy button */} - {newAccessToken ? ( -
-
-

- Here's your new access token. Store it securely as it will not - be displayed again. -

- -

- This access token is used to sign transactions and messages - from your backend. Can be revoked and recreated with your - admin key. -

- - Secure your keys - - These keys will not be displayed again. Store them securely - as they provide access to your server wallets. - -
- - setKeysConfirmed(!!v)} - /> - I confirm that I've securely stored these keys - - -
-
- -
-
- ) : listAccessTokensQuery.isLoading ? ( -
- - Loading access tokens -
- ) : listAccessTokensQuery.data ? ( -
-
-
-
-
- {listAccessTokensQuery.data.accessTokens.map((token) => ( -
-
-

- {token.metadata?.purpose || - "Unnamed Access Token"} -

-
- {token.policies.map((policy) => ( - - {policy.type} - - ))} -
-

- Created on:{" "} - {toDateTimeLocal(new Date(token.createdAt))} -

-
- +
+
+ {listAccessTokensQuery.isLoading ? ( +
+ + + +
+ ) : listAccessTokensQuery.data ? ( +
+
+
+
+
+ {listAccessTokensQuery.data.accessTokens.map((token) => ( +
+
+

+ {token.metadata?.purpose || "Unnamed Access Token"} +

+
+ {token.accessToken.includes("**") ? ( +
+

+ {token.accessToken} +

+
) : ( - + )} - + +
+ {/* TODO (cloud): show policies and let you edit them */} +

+ Created on:{" "} + {toDateTimeLocal(new Date(token.createdAt))} +

- ))} -
+
+ ))}
-

- These access tokens can be used for all server wallets - created within this project (more granular permissions - coming soon). -

+
+
+ ) : ( +
+

+ No access tokens found. +

+
+ )} -
- - +
+ +
+ + + + + Unlock Vault + + {/* If new access token, show copy button */} + {newAccessToken ? ( +
+
+

+ Here's your new access token. Store it securely as it will + not be displayed again. +

+ +

+ This access token is used to sign transactions and messages + from your backend. Can be revoked and recreated with your + admin key. +

+ + Secure your keys + + These keys will not be displayed again. Store them + securely as they provide access to your server wallets. + +
+ + setKeysConfirmed(!!v)} + /> + I confirm that I've securely stored these keys + + +
+
+ +
-
- ) : ( -
-
-

- This action requires your - Vault admin key. -

- setTypedAdminKey(e.target.value)} - onKeyDown={(e) => { - if (e.key === "Enter") { + ) : ( +
+
+

+ This action requires your + Vault admin key. +

+ setTypedAdminKey(e.target.value)} + onKeyDown={(e) => { + if (e.key === "Enter") { + setAdminKey(typedAdminKey); + } + }} + /> +
+
+ +
-
- - + handleCloseModal(); + }} + disabled={!typedAdminKey || listAccessTokensQuery.isLoading} + > + Unlock Vault + +
-
- )} - -
- + )} + +
+
+
); } From 6d481e7a4419c12d44c3ed43f2d3c0d01d5680ad Mon Sep 17 00:00:00 2001 From: Prithvish Baidya Date: Tue, 29 Apr 2025 07:59:56 +0530 Subject: [PATCH 062/101] dashboard auth for test --- .../engine/analytics/send-test-tx.client.tsx | 71 ++++++++----------- 1 file changed, 28 insertions(+), 43 deletions(-) diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/analytics/send-test-tx.client.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/analytics/send-test-tx.client.tsx index 931828642a1..dcd194b0d28 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/analytics/send-test-tx.client.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/analytics/send-test-tx.client.tsx @@ -11,7 +11,6 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select"; -import { THIRDWEB_ENGINE_CLOUD_URL } from "@/constants/env"; import { useThirdwebClient } from "@/constants/thirdweb.client"; import { zodResolver } from "@hookform/resolvers/zod"; import { useMutation, useQueryClient } from "@tanstack/react-query"; @@ -22,9 +21,9 @@ import { toast } from "sonner"; import { shortenAddress } from "thirdweb/utils"; import * as z from "zod"; import type { Wallet } from "../server-wallets/wallet-table/types"; +import { engineCloudProxy } from "@/actions/proxies"; const formSchema = z.object({ - projectSecretKey: z.string().min(1, "Project secret key is required"), accessToken: z.string().min(1, "Access token is required"), walletIndex: z.string(), chainId: z.number(), @@ -44,7 +43,6 @@ export function SendTestTransaction(props: { const form = useForm({ resolver: zodResolver(formSchema), defaultValues: { - projectSecretKey: "", accessToken: "", walletIndex: "0", chainId: 84532, @@ -60,36 +58,36 @@ export function SendTestTransaction(props: { accessToken: string; chainId: number; }) => { - const response = await fetch( - `${THIRDWEB_ENGINE_CLOUD_URL}/write/transaction`, - { - method: "POST", - headers: { - "Content-Type": "application/json", - "x-secret-key": form.getValues("projectSecretKey"), - "x-vault-access-token": args.accessToken, + const response = await engineCloudProxy({ + pathname: "/write/transaction", + method: "POST", + headers: { + "Content-Type": "application/json", + "x-team-id": props.project.teamId, + "x-client-id": props.project.publishableKey, + "x-vault-access-token": args.accessToken, + }, + body: JSON.stringify({ + executionOptions: { + type: "AA", + signerAddress: args.walletAddress, + chainId: args.chainId.toString(), }, - body: JSON.stringify({ - executionOptions: { - type: "AA", - signerAddress: args.walletAddress, - chainId: args.chainId.toString(), + params: [ + { + to: args.walletAddress, + value: "0", }, - params: [ - { - to: args.walletAddress, - value: "0", - }, - ], - }), - }, - ); - const result = await response.json(); + ], + }), + }); + if (!response.ok) { - const errorMsg = result?.error?.message || "Failed to send transaction"; + const errorMsg = response.error ?? "Failed to send transaction"; throw new Error(errorMsg); } - return result; + + return response.data; }, onSuccess: () => { toast.success("Test transaction sent successfully!"); @@ -146,24 +144,11 @@ export function SendTestTransaction(props: { className="space-y-4 px-3 pb-3" >

- This action requires a project - secret key and a vault access token. + This action requires your vault + access token.

{/* Responsive container */}
-
-
-

Project Secret Key

- -
-

Vault Access Token

From ba6476066d00d6480d1a18b0751c0f2eae91d741 Mon Sep 17 00:00:00 2001 From: Joaquim Verges Date: Tue, 29 Apr 2025 15:35:26 +1200 Subject: [PATCH 063/101] add legacy badges and upsells --- apps/dashboard/src/@/components/ui/button.tsx | 2 +- apps/dashboard/src/@/components/ui/tabs.tsx | 6 +-- .../(app)/team/[team_slug]/(team)/layout.tsx | 8 +++- .../(team)/~/engine/(general)/_components.tsx | 6 +-- .../(team)/~/engine/(general)/layout.tsx | 20 +++++++++- .../overview/engine-instances-table.tsx | 9 ++--- .../alerts/components/ManageEngineAlerts.tsx | 2 +- .../components/EngineSystemMetrics.tsx | 2 +- .../[project_slug]/engine/layout.tsx | 40 ++++++++++--------- .../features/alert-notifications/page.mdx | 6 +-- .../features/keypair-authentication/page.mdx | 2 +- 11 files changed, 65 insertions(+), 38 deletions(-) diff --git a/apps/dashboard/src/@/components/ui/button.tsx b/apps/dashboard/src/@/components/ui/button.tsx index 201ea8d6361..fb9036e9a76 100644 --- a/apps/dashboard/src/@/components/ui/button.tsx +++ b/apps/dashboard/src/@/components/ui/button.tsx @@ -21,7 +21,7 @@ const buttonVariants = cva( ghost: "hover:bg-accent text-semibold hover:text-accent-foreground", link: "text-primary underline-offset-4 hover:underline text-semibold", upsell: - "bg-gradient-to-r from-purple-500 to-pink-500 text-white hover:from-purple-600 hover:to-pink-600 shadow-lg hover:shadow-xl transform hover:-translate-y-0.5 transition-all duration-200", + "bg-green-600 text-white hover:bg-green-700 shadow-lg hover:shadow-xl transform hover:-translate-y-0.5 transition-all duration-200", }, size: { default: "h-10 px-4 py-2", diff --git a/apps/dashboard/src/@/components/ui/tabs.tsx b/apps/dashboard/src/@/components/ui/tabs.tsx index c672d2589e9..c2ff5d947a3 100644 --- a/apps/dashboard/src/@/components/ui/tabs.tsx +++ b/apps/dashboard/src/@/components/ui/tabs.tsx @@ -10,7 +10,7 @@ import { Button } from "./button"; import { ToolTipLabel } from "./tooltip"; export type TabLink = { - name: string; + name: React.ReactNode; href: string; isActive: boolean; isDisabled?: boolean; @@ -43,7 +43,7 @@ export function TabLinks(props: { return ( + setSelectedTab("cloud-hosted")} > - Cloud-hosted + Managed +
@@ -97,8 +103,6 @@ function TransactionsLayout(props: { ]} />
- - {/* content */}
{props.children}
diff --git a/apps/portal/src/app/engine/features/alert-notifications/page.mdx b/apps/portal/src/app/engine/features/alert-notifications/page.mdx index bf8d3270c9f..589f2163f6a 100644 --- a/apps/portal/src/app/engine/features/alert-notifications/page.mdx +++ b/apps/portal/src/app/engine/features/alert-notifications/page.mdx @@ -4,12 +4,12 @@ import { createMetadata, DocImage, OpenSourceCard } from "@doc"; export const metadata = createMetadata({ title: "Alert Notifications | thirdweb Engine", - description: "Get notified of issues or events on your cloud-hosted Engine.", + description: "Get notified of issues or events on your managed Engine.", }); # Alert Notifications -All cloud-hosted Engine instances (running `v2.0.10` or later) can now configure alert notifications to be notified when an issue is detected on your Engine instance. +All managed Engine instances (running `v2.0.10` or later) can now configure alert notifications to be notified when an issue is detected on your Engine instance. ## Add an alert @@ -30,7 +30,7 @@ All cloud-hosted Engine instances (running `v2.0.10` or later) can now configure #### Why is my Create Alert button is disabled? -At this time only the original admin (the account which deployed the cloud-hosted Engine) can view and manage alerts. This is usually the first admin address listed on the Admins tab. We plan to expand alert management to all admins in the near future. +At this time only the original admin (the account which deployed the managed Engine) can view and manage alerts. This is usually the first admin address listed on the Admins tab. We plan to expand alert management to all admins in the near future. ## View alert history diff --git a/apps/portal/src/app/engine/features/keypair-authentication/page.mdx b/apps/portal/src/app/engine/features/keypair-authentication/page.mdx index 08faad50221..06a502ce4d2 100644 --- a/apps/portal/src/app/engine/features/keypair-authentication/page.mdx +++ b/apps/portal/src/app/engine/features/keypair-authentication/page.mdx @@ -16,7 +16,7 @@ Engine supports keypair authentication allowing your app to generate short-lived Keypair authentication is an advanced feature and must be explicitly enabled. -[Contact us](https://thirdweb.com/contact-us) to enable this on your Cloud-hosted Engine instance. +[Contact us](https://thirdweb.com/contact-us) to enable this on your managed Engine instance. **Don't need short-lived access tokens?** Utilize [Access Tokens](/engine/features/access-tokens) which are valid until deleted. From 76ce72c528c81d94f348d8d8365f4ce6d1d2f225 Mon Sep 17 00:00:00 2001 From: Joaquim Verges Date: Tue, 29 Apr 2025 15:48:18 +1200 Subject: [PATCH 064/101] label for server wallets --- .../create-server-wallet.client.tsx | 69 ++++++++++++++++++- .../server-wallets/wallet-table/types.ts | 2 +- .../wallet-table/wallet-table-ui.client.tsx | 33 +++++++-- 3 files changed, 93 insertions(+), 11 deletions(-) diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/server-wallets/components/create-server-wallet.client.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/server-wallets/components/create-server-wallet.client.tsx index c126a6aeb71..b7c50685105 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/server-wallets/components/create-server-wallet.client.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/server-wallets/components/create-server-wallet.client.tsx @@ -1,10 +1,19 @@ "use client"; import type { Project } from "@/api/projects"; import { Button } from "@/components/ui/button"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { Input } from "@/components/ui/input"; import { useDashboardRouter } from "@/lib/DashboardRouter"; import { useMutation } from "@tanstack/react-query"; import { createEoa } from "@thirdweb-dev/vault-sdk"; import { Loader2 } from "lucide-react"; +import { useState } from "react"; import { toast } from "sonner"; import { initVaultClient } from "../../lib/vault.client"; @@ -13,11 +22,16 @@ export default function CreateServerWallet(props: { managementAccessToken: string | undefined; }) { const router = useDashboardRouter(); + const [label, setLabel] = useState(""); + const [modalOpen, setModalOpen] = useState(false); + const createEoaMutation = useMutation({ mutationFn: async ({ managementAccessToken, + label, }: { managementAccessToken: string; + label: string; }) => { const vaultClient = await initVaultClient(); @@ -28,6 +42,7 @@ export default function CreateServerWallet(props: { projectId: props.project.id, teamId: props.project.teamId, type: "server-wallet", + label, }, }, auth: { @@ -42,6 +57,7 @@ export default function CreateServerWallet(props: { } router.refresh(); + setModalOpen(false); return eoa; }, @@ -56,6 +72,7 @@ export default function CreateServerWallet(props: { } else { await createEoaMutation.mutateAsync({ managementAccessToken: props.managementAccessToken, + label, }); } }; @@ -66,13 +83,59 @@ export default function CreateServerWallet(props: { <> + + + + + Create server wallet + + Enter a label for your server wallet. + + +
+
+ setLabel(e.target.value)} + className="w-full" + /> +
+
+
+ + +
+
+
); } diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/server-wallets/wallet-table/types.ts b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/server-wallets/wallet-table/types.ts index 872d858d3ce..2b957b5220c 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/server-wallets/wallet-table/types.ts +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/server-wallets/wallet-table/types.ts @@ -4,7 +4,7 @@ export type Wallet = { metadata: { type: string; projectId: string; - name?: string; + label?: string; }; createdAt: string; updatedAt: string; diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/server-wallets/wallet-table/wallet-table-ui.client.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/server-wallets/wallet-table/wallet-table-ui.client.tsx index 281e6ea64f4..60a77a71fa8 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/server-wallets/wallet-table/wallet-table-ui.client.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/server-wallets/wallet-table/wallet-table-ui.client.tsx @@ -17,10 +17,13 @@ import { getThirdwebClient } from "@/constants/thirdweb.server"; import { useQuery } from "@tanstack/react-query"; import { formatDistanceToNowStrict } from "date-fns"; import { format } from "date-fns/format"; +import { useState } from "react"; import { DEFAULT_ACCOUNT_FACTORY_V0_7, predictSmartAccountAddress, } from "thirdweb/wallets/smart"; +import { Badge } from "../../../../../../../../@/components/ui/badge"; +import { Switch } from "../../../../../../../../@/components/ui/switch"; import { useV5DashboardChain } from "../../../../../../../../lib/v5-adapter"; import CreateServerWallet from "../components/create-server-wallet.client"; import type { Wallet } from "./types"; @@ -34,6 +37,7 @@ export function ServerWalletsTableUI({ project: Project; managementAccessToken: string | undefined; }) { + const [showSigners, setShowSigners] = useState(false); return (
@@ -47,6 +51,16 @@ export function ServerWalletsTableUI({

+
+ + setShowSigners(!showSigners)} + /> +
- Signer - Smart Account + Address + Label Created At Updated At @@ -74,11 +88,13 @@ export function ServerWalletsTableUI({ wallets.map((wallet) => ( - - - - + {showSigners ? ( + + ) : ( + + )} + {wallet.metadata.label || "none"} @@ -115,7 +131,10 @@ function SmartAccountCell({ wallet }: { wallet: Wallet }) { return (
{smartAccountAddressQuery.data ? ( - +
+ + Smart Account +
) : ( )} From cd568da18a4599fcf2f0a90786c7321f27e0d633 Mon Sep 17 00:00:00 2001 From: Joaquim Verges Date: Tue, 29 Apr 2025 16:05:32 +1200 Subject: [PATCH 065/101] add diff lang examples --- .../[project_slug]/engine/layout.tsx | 2 +- .../server-wallets/components/try-it-out.tsx | 276 ++++++++++++++++-- .../engine/server-wallets/page.tsx | 6 + 3 files changed, 254 insertions(+), 30 deletions(-) diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/layout.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/layout.tsx index cde76f5d4f5..8e6cbbab04c 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/layout.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/layout.tsx @@ -89,7 +89,7 @@ function TransactionsLayout(props: { exactMatch: true, }, { - name: "Explorer", + name: "API Explorer", path: `${engineLayoutSlug}/explorer`, }, { diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/server-wallets/components/try-it-out.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/server-wallets/components/try-it-out.tsx index af67ba69fe4..0e031b0bc33 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/server-wallets/components/try-it-out.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/server-wallets/components/try-it-out.tsx @@ -1,7 +1,10 @@ -import { CodeServer } from "@/components/ui/code/code.server"; +"use client"; import { THIRDWEB_ENGINE_CLOUD_URL } from "@/constants/env"; import Link from "next/link"; +import { useState } from "react"; import { Button } from "../../../../../../../../@/components/ui/button"; +import { CodeClient } from "../../../../../../../../@/components/ui/code/code.client"; +import { TabButtons } from "../../../../../../../../@/components/ui/tabs"; import type { Wallet } from "../wallet-table/types"; export function TryItOut(props: { @@ -10,6 +13,8 @@ export function TryItOut(props: { team_slug: string; project_slug: string; }) { + const [activeTab, setActiveTab] = useState("curl"); + return (
@@ -25,11 +30,66 @@ export function TryItOut(props: {
- setActiveTab("curl"), + isActive: activeTab === "curl", + }, + { + name: "JavaScript", + onClick: () => setActiveTab("js"), + isActive: activeTab === "js", + }, + { + name: "Python", + onClick: () => setActiveTab("python"), + isActive: activeTab === "python", + }, + { + name: "Go", + onClick: () => setActiveTab("go"), + isActive: activeTab === "go", + }, + { + name: "C#", + onClick: () => setActiveTab("csharp"), + isActive: activeTab === "csharp", + }, + ]} /> + +
+ + {activeTab === "curl" && ( + + )} + {activeTab === "js" && ( + + )} + {activeTab === "python" && ( + + )} + {activeTab === "go" && ( + + )} + {activeTab === "csharp" && ( + + )} +
)} From 6aaee1a1f285ab344859fdd9dbf5a3ebd647223f Mon Sep 17 00:00:00 2001 From: Joaquim Verges Date: Tue, 29 Apr 2025 20:40:44 +1200 Subject: [PATCH 066/101] cleanup --- .gitignore | 3 +- .../create-vault-account.client.tsx | 3 +- .../vault/components/key-management.tsx | 2 +- .../components/list-access-tokens.client.tsx | 152 ++++++------------ .../components/rotate-admin-key.client.tsx | 72 ++++++++- 5 files changed, 123 insertions(+), 109 deletions(-) rename apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/{server-wallets => vault}/components/rotate-admin-key.client.tsx (76%) diff --git a/.gitignore b/.gitignore index fc96d534409..b9bd7c27d98 100644 --- a/.gitignore +++ b/.gitignore @@ -30,4 +30,5 @@ packages/*/typedoc/* storybook-static .aider* -tsconfig.tsbuildinfo \ No newline at end of file +tsconfig.tsbuildinfo +.cursor \ No newline at end of file diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/vault/components/create-vault-account.client.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/vault/components/create-vault-account.client.tsx index 867da679617..78af0672983 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/vault/components/create-vault-account.client.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/vault/components/create-vault-account.client.tsx @@ -100,7 +100,7 @@ export default function CreateVaultAccountButton(props: { project: Project }) { const blob = new Blob([fileContent], { type: "text/plain;charset=utf-8" }); const url = URL.createObjectURL(blob); const link = document.createElement("a"); - const filename = `${props.project.name}-${props.project.publishableKey}-vault-keys.txt`; + const filename = `${props.project.name}-vault-keys.txt`; link.href = url; link.download = filename; document.body.appendChild(link); // Required for Firefox @@ -122,6 +122,7 @@ export default function CreateVaultAccountButton(props: { project: Project }) { setModalOpen(false); setKeysConfirmed(false); setKeysDownloaded(false); + initialiseProjectWithVaultMutation.reset(); // invalidate the page to force a reload router.refresh(); }; diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/vault/components/key-management.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/vault/components/key-management.tsx index ec18f635f3c..3ca3bc1bca8 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/vault/components/key-management.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/vault/components/key-management.tsx @@ -2,7 +2,7 @@ import type { Project } from "@/api/projects"; import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; import { InfoIcon } from "lucide-react"; import Link from "next/link"; -import RotateAdminKeyButton from "../../server-wallets/components/rotate-admin-key.client"; +import RotateAdminKeyButton from "./rotate-admin-key.client"; import CreateVaultAccountButton from "./create-vault-account.client"; import ListAccessTokens from "./list-access-tokens.client"; diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/vault/components/list-access-tokens.client.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/vault/components/list-access-tokens.client.tsx index ae3f81d970c..bb5625734e1 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/vault/components/list-access-tokens.client.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/vault/components/list-access-tokens.client.tsx @@ -1,9 +1,9 @@ "use client"; import type { Project } from "@/api/projects"; import { CopyTextButton } from "@/components/ui/CopyTextButton"; -import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; +import {} from "@/components/ui/alert"; import { Button } from "@/components/ui/button"; -import { Checkbox, CheckboxWithLabel } from "@/components/ui/checkbox"; +import {} from "@/components/ui/checkbox"; import { Dialog, DialogContent, @@ -12,7 +12,7 @@ import { } from "@/components/ui/dialog"; import { Input } from "@/components/ui/input"; import { Skeleton } from "@/components/ui/skeleton"; -import { useMutation, useQuery } from "@tanstack/react-query"; +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { listAccessTokens, revokeAccessToken } from "@thirdweb-dev/vault-sdk"; import { Loader2, LockIcon, Trash2 } from "lucide-react"; import { useState } from "react"; @@ -31,8 +31,7 @@ export default function ListAccessTokens(props: { const [typedAdminKey, setTypedAdminKey] = useState(""); const [adminKey, setAdminKey] = useState(""); const [deletingTokenId, setDeletingTokenId] = useState(null); - const [newAccessToken, setNewAccessToken] = useState(null); - const [keysConfirmed, setKeysConfirmed] = useState(false); + const queryClient = useQueryClient(); // TODO allow passing permissions to the access token const createAccessTokenMutation = useMutation({ mutationFn: async (args: { adminKey: string }) => { @@ -57,9 +56,10 @@ export default function ListAccessTokens(props: { onError: (error) => { toast.error(error.message); }, - onSuccess: (data) => { - setNewAccessToken(data.userAccessToken.accessToken); - listAccessTokensQuery.refetch(); + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: ["list-access-tokens"], + }); }, }); @@ -95,7 +95,9 @@ export default function ListAccessTokens(props: { setDeletingTokenId(null); }, onSuccess: () => { - listAccessTokensQuery.refetch(); + queryClient.invalidateQueries({ + queryKey: ["list-access-tokens"], + }); setDeletingTokenId(null); }, }); @@ -171,6 +173,9 @@ export default function ListAccessTokens(props: { setModalOpen(true); } else { setAdminKey(""); + queryClient.invalidateQueries({ + queryKey: ["list-access-tokens"], + }); } }} variant={"primary"} @@ -281,98 +286,47 @@ export default function ListAccessTokens(props: { Unlock Vault - {/* If new access token, show copy button */} - {newAccessToken ? ( -
-
-

- Here's your new access token. Store it securely as it will - not be displayed again. -

- -

- This access token is used to sign transactions and messages - from your backend. Can be revoked and recreated with your - admin key. -

- - Secure your keys - - These keys will not be displayed again. Store them - securely as they provide access to your server wallets. - -
- - setKeysConfirmed(!!v)} - /> - I confirm that I've securely stored these keys - - -
-
- -
-
- ) : ( -
-
-

- This action requires your - Vault admin key. -

- setTypedAdminKey(e.target.value)} - onKeyDown={(e) => { - if (e.key === "Enter") { - setAdminKey(typedAdminKey); - } - }} - /> -
-
- - -
+ } + }} + />
- )} +
+ + +
+
diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/server-wallets/components/rotate-admin-key.client.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/vault/components/rotate-admin-key.client.tsx similarity index 76% rename from apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/server-wallets/components/rotate-admin-key.client.tsx rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/vault/components/rotate-admin-key.client.tsx index a04b39282b3..85bdf4706ac 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/server-wallets/components/rotate-admin-key.client.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/vault/components/rotate-admin-key.client.tsx @@ -13,6 +13,7 @@ import { DialogHeader, DialogTitle, } from "@/components/ui/dialog"; +import { useDashboardRouter } from "@/lib/DashboardRouter"; import { cn } from "@/lib/utils"; import { useMutation } from "@tanstack/react-query"; import { rotateServiceAccount } from "@thirdweb-dev/vault-sdk"; @@ -25,11 +26,11 @@ import { initVaultClient, maskSecret, } from "../../lib/vault.client"; -import { useDashboardRouter } from "@/lib/DashboardRouter"; export default function RotateAdminKeyButton(props: { project: Project }) { const [modalOpen, setModalOpen] = useState(false); const [keysConfirmed, setKeysConfirmed] = useState(false); + const [keysDownloaded, setKeysDownloaded] = useState(false); const router = useDashboardRouter(); const rotateAdminKeyMutation = useMutation({ @@ -41,8 +42,6 @@ export default function RotateAdminKeyButton(props: { project: Project }) { (service) => service.name === "engineCloud", )?.rotationCode; - console.log("DEBUG", rotationCode); - if (!rotationCode) { throw new Error("Rotation code not found"); } @@ -94,17 +93,50 @@ export default function RotateAdminKeyButton(props: { project: Project }) { }, }); + const handleDownloadKeys = () => { + if (!rotateAdminKeyMutation.data) { + return; + } + + const fileContent = `Project: ${props.project.name} (${props.project.publishableKey})\nVault Admin Key: ${rotateAdminKeyMutation.data.adminKey}\nVault Access Token: ${rotateAdminKeyMutation.data.userAccessToken.accessToken}\n`; + const blob = new Blob([fileContent], { type: "text/plain;charset=utf-8" }); + const url = URL.createObjectURL(blob); + const link = document.createElement("a"); + const filename = `${props.project.name}-vault-keys-rotated.txt`; + link.href = url; + link.download = filename; + document.body.appendChild(link); // Required for Firefox + link.click(); + + // Clean up + document.body.removeChild(link); + URL.revokeObjectURL(url); + + toast.success(`Keys downloaded as ${filename}`); + setKeysDownloaded(true); + }; + const handleCloseModal = () => { if (!keysConfirmed) { return; } + setModalOpen(false); setKeysConfirmed(false); + setKeysDownloaded(false); + // invalidate the page to force a reload rotateAdminKeyMutation.reset(); - // invalidate the page to force a reload of the project data router.refresh(); }; + const handlePrimaryButton = () => { + if (!keysDownloaded) { + handleDownloadKeys(); + return; + } + handleCloseModal(); + }; + const isLoading = rotateAdminKeyMutation.isPending; return ( @@ -164,6 +196,32 @@ export default function RotateAdminKeyButton(props: { project: Project }) {

+ +
+

+ New Vault Access Token +

+
+ +

+ This access token is used to sign transactions and + messages from your backend. Can be revoked and recreated + with your admin key. +

+
+
Secure your keys @@ -184,11 +242,11 @@ export default function RotateAdminKeyButton(props: { project: Project }) {
From 32f0dabb4ad5e0ff2caed834bda62de93c58383d Mon Sep 17 00:00:00 2001 From: Joaquim Verges Date: Tue, 29 Apr 2025 22:55:23 +1200 Subject: [PATCH 067/101] FTUX --- .../engine/analytics/analytics-page.tsx | 35 ++- .../engine/analytics/ftux.client.tsx | 116 +++++++++ .../engine/analytics/send-test-tx.client.tsx | 225 +++++++++--------- .../engine/analytics/summary.tsx | 36 +-- .../[project_slug]/engine/layout.tsx | 24 -- .../[project_slug]/engine/page.tsx | 33 ++- .../create-vault-account.client.tsx | 8 +- .../vault/components/key-management.tsx | 78 +++--- .../src/components/dashboard/StepsCard.tsx | 8 +- 9 files changed, 332 insertions(+), 231 deletions(-) create mode 100644 apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/analytics/ftux.client.tsx diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/analytics/analytics-page.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/analytics/analytics-page.tsx index 548b52fb5d6..b0373362a2d 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/analytics/analytics-page.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/analytics/analytics-page.tsx @@ -2,7 +2,6 @@ import type { Project } from "@/api/projects"; import { ResponsiveSearchParamsProvider } from "responsive-rsc"; import type { Wallet } from "../server-wallets/wallet-table/types"; import { TransactionAnalyticsFilter } from "./filter"; -import { SendTestTransaction } from "./send-test-tx.client"; import { TransactionsChartCard } from "./tx-chart/tx-chart"; import { TransactionsTable } from "./tx-table/tx-table"; @@ -13,29 +12,25 @@ export function TransactionsAnalyticsPageContent(props: { interval?: string | undefined | string[]; }; project: Project; + hasTransactions: boolean; wallets?: Wallet[]; - expandTestTx?: boolean; }) { return ( -
-
- -
-
-
- - - -
+
+ {props.hasTransactions && ( + <> +
+ +
+ + + )} +
); diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/analytics/ftux.client.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/analytics/ftux.client.tsx new file mode 100644 index 00000000000..e36ff03c6d1 --- /dev/null +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/analytics/ftux.client.tsx @@ -0,0 +1,116 @@ +"use client"; +import type { Project } from "@/api/projects"; +import { type Step, StepsCard } from "components/dashboard/StepsCard"; +import Link from "next/link"; +import { useMemo } from "react"; +import { Button } from "../../../../../../../@/components/ui/button"; +import CreateServerWallet from "../server-wallets/components/create-server-wallet.client"; +import type { Wallet } from "../server-wallets/wallet-table/types"; +import CreateVaultAccountButton from "../vault/components/create-vault-account.client"; +import { SendTestTransaction } from "./send-test-tx.client"; + +interface Props { + managementAccessToken: string | undefined; + project: Project; + wallets: Wallet[]; + hasTransactions: boolean; +} + +export const EngineChecklist: React.FC = (props) => { + const finalSteps = useMemo(() => { + const steps: Step[] = []; + steps.push({ + title: "Create a Vault Admin Account", + description: + "Your Vault admin account will be used to create server wallets and manage access tokens.", + children: , + completed: !!props.managementAccessToken, + showCompletedChildren: false, + }); + steps.push({ + title: "Create a Server Wallet", + description: + "Your server wallet will be used to send transactions to Engine.", + children: ( + + ), + completed: props.wallets.length > 0, + showIncompleteChildren: false, + showCompletedChildren: false, + }); + steps.push({ + title: "Send a Test Transaction", + description: "Send a test transaction to see Engine in action.", + children: ( + + ), + completed: props.hasTransactions, + showIncompleteChildren: true, + showCompletedChildren: false, + }); + return steps; + }, [ + props.managementAccessToken, + props.project, + props.wallets, + props.hasTransactions, + ]); + + if (finalSteps.length === 1) { + return null; + } + + return ( + + ); +}; + +function CreateVaultAccountStep(props: { project: Project }) { + return ( +
+

+ Let's get you set up with Vault. +

+
+

+ To use Engine, you will need to manage one or more server wallets. + Server wallets are secured and accessed through Vault, thirdweb's key + management system. +

+
+
+ + + + +
+
+ ); +} + +function CreateServerWalletStep(props: { + project: Project; + managementAccessToken: string | undefined; +}) { + return ( +
+

+ Now, let's create a server wallet. +

+
+

+ Your server wallet will be used to send transactions to the Engine. +

+
+
+ +
+
+ ); +} diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/analytics/send-test-tx.client.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/analytics/send-test-tx.client.tsx index dcd194b0d28..59590bb941d 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/analytics/send-test-tx.client.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/analytics/send-test-tx.client.tsx @@ -1,4 +1,5 @@ "use client"; +import { engineCloudProxy } from "@/actions/proxies"; import type { Project } from "@/api/projects"; import { SingleNetworkSelector } from "@/components/blocks/NetworkSelectors"; import { WalletAvatar } from "@/components/blocks/wallet-address"; @@ -12,16 +13,16 @@ import { SelectValue, } from "@/components/ui/select"; import { useThirdwebClient } from "@/constants/thirdweb.client"; +import { useDashboardRouter } from "@/lib/DashboardRouter"; import { zodResolver } from "@hookform/resolvers/zod"; import { useMutation, useQueryClient } from "@tanstack/react-query"; -import { ChevronDown, Loader2, LockIcon } from "lucide-react"; +import { Loader2, LockIcon } from "lucide-react"; import { useState } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; import { shortenAddress } from "thirdweb/utils"; import * as z from "zod"; import type { Wallet } from "../server-wallets/wallet-table/types"; -import { engineCloudProxy } from "@/actions/proxies"; const formSchema = z.object({ accessToken: z.string().min(1, "Access token is required"), @@ -36,9 +37,10 @@ export function SendTestTransaction(props: { project: Project; expanded?: boolean; }) { - const [isOpen, setIsOpen] = useState(props.expanded ?? false); const thirdwebClient = useThirdwebClient(); const queryClient = useQueryClient(); + const [hasSentTx, setHasSentTx] = useState(false); + const router = useDashboardRouter(); const form = useForm({ resolver: zodResolver(formSchema), @@ -113,132 +115,135 @@ export function SendTestTransaction(props: { queryClient.invalidateQueries({ queryKey: ["transactions", props.project.id], }); + setHasSentTx(true); }; return ( -
- {/* Trigger Area */} -
setIsOpen(!isOpen)} - onKeyDown={(e) => { - if (e.key === "Enter" || e.key === " ") { - setIsOpen(!isOpen); - } - }} - > -

- Send Test Transaction -

- -
- - {/* Content Area (conditional) */} - {isOpen && ( -
-

- This action requires your vault - access token. -

- {/* Responsive container */} -
-
-
-

Vault Access Token

- -
+
+

+ Engine is designed to scale, gas-free, fast, and secure.
+

+
+

+ Fire up many test transactions and watch the table below to see how it + performs! +

+
+ +

+ This action requires your vault + access token. +

+
+ {/* Responsive container */} +
+
+
+

Vault Access Token

+
- {/* Wallet Selector */} -
-
-
-

Signer

- form.setValue("walletIndex", value)} + > + + +
+ + + {shortenAddress(selectedWallet.address)} + + + {selectedWallet.metadata.label} + +
+
+
+ + {props.wallets.map((wallet, index) => ( +
- {shortenAddress(selectedWallet.address)} + {shortenAddress(wallet.address)} + + + {selectedWallet.metadata.label}
- - - - {props.wallets.map((wallet, index) => ( - -
- - - {shortenAddress(wallet.address)} - -
-
- ))} -
- -
-
-

Network

- { - form.setValue("chainId", chainId); - }} - /> -
+ + ))} + + +
+
+

Network

+ { + form.setValue("chainId", chainId); + }} + />
-
+
+
+
+ {hasSentTx && ( -
- - )} + )} + +
+
); } diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/analytics/summary.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/analytics/summary.tsx index 0000b1b2ef4..145f432f94b 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/analytics/summary.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/analytics/summary.tsx @@ -1,11 +1,7 @@ import { StatCard } from "components/analytics/stat"; // Assuming correct path import { ActivityIcon, CoinsIcon } from "lucide-react"; -import { Suspense } from "react"; import { toEther } from "thirdweb/utils"; -import { - type TransactionSummaryData, - getTransactionAnalyticsSummary, -} from "../lib/analytics"; +import type { TransactionSummaryData } from "../lib/analytics"; // Renders the UI based on fetched data or pending state function TransactionAnalyticsSummaryUI(props: { @@ -89,38 +85,12 @@ function TransactionAnalyticsSummaryUI(props: { ); } -// Fetches data and renders the UI component -async function AsyncTransactionsAnalyticsSummary(props: { - teamId: string; - clientId: string; -}) { - try { - const data = await getTransactionAnalyticsSummary({ - teamId: props.teamId, - clientId: props.clientId, - }); - return ; - } catch (error) { - console.error("Failed to fetch transaction summary:", error); - return ; - } -} - -// Main component: Shows loading state (Suspense fallback) while fetching data export function TransactionAnalyticsSummary(props: { teamId: string; clientId: string; + initialData: TransactionSummaryData | undefined; }) { return ( - - } - > - - + ); } diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/layout.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/layout.tsx index 8e6cbbab04c..eea3d9a80dd 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/layout.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/layout.tsx @@ -111,27 +111,3 @@ function TransactionsLayout(props: {
); } - -// TODO: seo and add metadata - -// const seo = { -// title: "TODO", -// desc: "TODO", -// }; - -// export const metadata: Metadata = { -// title: seo.title, -// description: seo.desc, -// openGraph: { -// title: seo.title, -// description: seo.desc, -// images: [ -// { -// url: `${getAbsoluteUrl()}/assets/og-image/TODO`, -// width: 1200, -// height: 630, -// alt: seo.title, -// }, -// ], -// }, -// }; diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/page.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/page.tsx index c5e4938466f..eb332671b6c 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/page.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/page.tsx @@ -5,7 +5,12 @@ import { createVaultClient, listEoas } from "@thirdweb-dev/vault-sdk"; import { notFound, redirect } from "next/navigation"; import { getAuthToken } from "../../../../api/lib/getAuthToken"; import { TransactionsAnalyticsPageContent } from "./analytics/analytics-page"; +import { EngineChecklist } from "./analytics/ftux.client"; import { TransactionAnalyticsSummary } from "./analytics/summary"; +import { + type TransactionSummaryData, + getTransactionAnalyticsSummary, +} from "./lib/analytics"; import type { Wallet } from "./server-wallets/wallet-table/types"; export default async function TransactionsAnalyticsPage(props: { @@ -14,7 +19,6 @@ export default async function TransactionsAnalyticsPage(props: { from?: string | string[] | undefined; to?: string | string[] | undefined; interval?: string | string[] | undefined; - expand_test_tx?: string | string[] | undefined; }>; }) { const [params, searchParams, authToken] = await Promise.all([ @@ -65,18 +69,37 @@ export default async function TransactionsAnalyticsPage(props: { const wallets = eoas.data?.items as Wallet[] | undefined; + let initialData: TransactionSummaryData | undefined; + if (wallets && wallets.length > 0) { + const summary = await getTransactionAnalyticsSummary({ + teamId: project.teamId, + clientId: project.publishableKey, + }).catch(() => undefined); + initialData = summary; + } + const hasTransactions = initialData ? initialData.totalCount > 0 : false; + return (
- + {hasTransactions && ( + + )}
); diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/vault/components/create-vault-account.client.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/vault/components/create-vault-account.client.tsx index 78af0672983..1e55b9e8ced 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/vault/components/create-vault-account.client.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/vault/components/create-vault-account.client.tsx @@ -15,7 +15,7 @@ import { useDashboardRouter } from "@/lib/DashboardRouter"; import { cn } from "@/lib/utils"; import { useMutation } from "@tanstack/react-query"; import { createServiceAccount } from "@thirdweb-dev/vault-sdk"; -import { Loader2 } from "lucide-react"; +import { Loader2, LockIcon } from "lucide-react"; import { useState } from "react"; import { toast } from "sonner"; import { @@ -145,7 +145,11 @@ export default function CreateVaultAccountButton(props: { project: Project }) { disabled={isLoading} className="flex flex-row items-center gap-2" > - {isLoading && } + {isLoading ? ( + + ) : ( + + )} {"Create Vault Admin Account"} diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/vault/components/key-management.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/vault/components/key-management.tsx index 3ca3bc1bca8..8e899096325 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/vault/components/key-management.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/vault/components/key-management.tsx @@ -2,9 +2,9 @@ import type { Project } from "@/api/projects"; import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; import { InfoIcon } from "lucide-react"; import Link from "next/link"; -import RotateAdminKeyButton from "./rotate-admin-key.client"; import CreateVaultAccountButton from "./create-vault-account.client"; import ListAccessTokens from "./list-access-tokens.client"; +import RotateAdminKeyButton from "./rotate-admin-key.client"; export function KeyManagement({ maskedAdminKey, @@ -26,40 +26,7 @@ export function KeyManagement({

{!maskedAdminKey ? ( -
-
- - - - What is Vault? - - - Vault is thirdweb's non-custodial key management system for - your server wallets that allows you to: -
    -
  • Create multiple server wallets.
  • -
  • Create Vault access tokens.
  • -
  • Sign transactions using a Vault access token.
  • -
- Your keys are stored in a hardware enclave, and all requests - are end-to-end encrypted.{" "} - - Learn more about Vault security model. - -
- Creating server wallets and access tokens requires a Vault - admin account. Create one below to get started. - -
- -
-
- -
-
+ ) : (
)} @@ -94,3 +61,44 @@ export function KeyManagement({
); } + +export async function CreateVaultAccountAlert(props: { + project: Project; +}) { + return ( +
+
+ + + + What is Vault? + + + Vault is thirdweb's non-custodial key management system for your + server wallets that allows you to: +
    +
  • Create multiple server wallets.
  • +
  • Create Vault access tokens.
  • +
  • Sign transactions using a Vault access token.
  • +
+ Your keys are stored in a hardware enclave, and all requests are + end-to-end encrypted.{" "} + + Learn more about Vault security model. + +
+ Creating server wallets and access tokens requires a Vault admin + account. Create one below to get started. + +
+ +
+
+ +
+
+ ); +} diff --git a/apps/dashboard/src/components/dashboard/StepsCard.tsx b/apps/dashboard/src/components/dashboard/StepsCard.tsx index 308377e3972..3e9542fd985 100644 --- a/apps/dashboard/src/components/dashboard/StepsCard.tsx +++ b/apps/dashboard/src/components/dashboard/StepsCard.tsx @@ -3,12 +3,13 @@ import { cn } from "@/lib/utils"; import { CheckIcon } from "lucide-react"; import { type JSX, useMemo } from "react"; -type Step = { +export type Step = { title: string | JSX.Element; description?: string; completed: boolean; children: React.ReactNode; showCompletedChildren?: boolean; + showIncompleteChildren?: boolean; }; interface StepsCardProps { @@ -57,8 +58,11 @@ export const StepsCard: React.FC = ({
{steps.map(({ children, ...step }, index) => { + const isActiveStep = index === lastStepCompleted + 1; const showChildren = - !step.completed || (step.completed && step.showCompletedChildren); + isActiveStep || + (!step.completed && step.showIncompleteChildren !== false) || + (step.completed && step.showCompletedChildren); return (
Date: Tue, 29 Apr 2025 23:02:14 +1200 Subject: [PATCH 068/101] lint --- packages/vault-sdk/package.json | 2 +- pnpm-lock.yaml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/vault-sdk/package.json b/packages/vault-sdk/package.json index bed65f5ba37..797408559bc 100644 --- a/packages/vault-sdk/package.json +++ b/packages/vault-sdk/package.json @@ -26,7 +26,7 @@ "files": ["dist/*", "src/*"], "dependencies": { "@noble/ciphers": "^1.2.1", - "@noble/curves": "1.8.1", + "@noble/curves": "1.8.2", "@noble/hashes": "1.7.2", "abitype": "1.0.8", "jose": "6.0.10" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index be02eae9b9f..e6a1db25a24 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1307,8 +1307,8 @@ importers: specifier: ^1.2.1 version: 1.2.1 '@noble/curves': - specifier: 1.8.1 - version: 1.8.1 + specifier: 1.8.2 + version: 1.8.2 '@noble/hashes': specifier: 1.7.2 version: 1.7.2 From 368c57db4450dd1206cbf1a5c51e67e4c3979cf4 Mon Sep 17 00:00:00 2001 From: Joaquim Verges Date: Tue, 29 Apr 2025 23:07:32 +1200 Subject: [PATCH 069/101] misc --- .../[team_slug]/[project_slug]/engine/analytics/ftux.client.tsx | 2 +- .../engine/server-wallets/components/try-it-out.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/analytics/ftux.client.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/analytics/ftux.client.tsx index e36ff03c6d1..058b8243730 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/analytics/ftux.client.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/analytics/ftux.client.tsx @@ -82,7 +82,7 @@ function CreateVaultAccountStep(props: { project: Project }) {

- + diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/server-wallets/components/try-it-out.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/server-wallets/components/try-it-out.tsx index 0e031b0bc33..e4828b37967 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/server-wallets/components/try-it-out.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/server-wallets/components/try-it-out.tsx @@ -24,7 +24,7 @@ export function TryItOut(props: { Usage from your backend

- Send transactions to the blockchain using a simple http API + Send transactions from your server wallets using a simple http API

From 19a0c143ed5ca98e6c9c3fae58b78c31d394e5da Mon Sep 17 00:00:00 2001 From: Prithvish Baidya Date: Wed, 30 Apr 2025 01:06:22 +0530 Subject: [PATCH 070/101] show access token for FTUX flow --- .../engine/analytics/ftux.client.tsx | 51 +++++++++++++++++-- .../create-vault-account.client.tsx | 7 ++- 2 files changed, 52 insertions(+), 6 deletions(-) diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/analytics/ftux.client.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/analytics/ftux.client.tsx index 058b8243730..fa614e60544 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/analytics/ftux.client.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/analytics/ftux.client.tsx @@ -2,12 +2,14 @@ import type { Project } from "@/api/projects"; import { type Step, StepsCard } from "components/dashboard/StepsCard"; import Link from "next/link"; -import { useMemo } from "react"; +import { useMemo, useState } from "react"; import { Button } from "../../../../../../../@/components/ui/button"; import CreateServerWallet from "../server-wallets/components/create-server-wallet.client"; import type { Wallet } from "../server-wallets/wallet-table/types"; import CreateVaultAccountButton from "../vault/components/create-vault-account.client"; import { SendTestTransaction } from "./send-test-tx.client"; +import { CopyTextButton } from "@/components/ui/CopyTextButton"; +import { maskSecret } from "../lib/vault.client"; interface Props { managementAccessToken: string | undefined; @@ -17,13 +19,20 @@ interface Props { } export const EngineChecklist: React.FC = (props) => { + const [userAccessToken, setUserAccessToken] = useState(); + const finalSteps = useMemo(() => { const steps: Step[] = []; steps.push({ title: "Create a Vault Admin Account", description: "Your Vault admin account will be used to create server wallets and manage access tokens.", - children: , + children: ( + setUserAccessToken(token)} + /> + ), completed: !!props.managementAccessToken, showCompletedChildren: false, }); @@ -45,7 +54,32 @@ export const EngineChecklist: React.FC = (props) => { title: "Send a Test Transaction", description: "Send a test transaction to see Engine in action.", children: ( - + <> +
+ {userAccessToken && ( +
+ +

+ This is the access token you just created. You need it to + authorize every wallet action. You can create more access + tokens with your admin key. Each access token can be scoped + and permissioned with flexible policies. You can copy this one + now to send a test transaction. +

+
+ )} +
+ + ), completed: props.hasTransactions, showIncompleteChildren: true, @@ -57,6 +91,7 @@ export const EngineChecklist: React.FC = (props) => { props.project, props.wallets, props.hasTransactions, + userAccessToken, ]); if (finalSteps.length === 1) { @@ -68,7 +103,10 @@ export const EngineChecklist: React.FC = (props) => { ); }; -function CreateVaultAccountStep(props: { project: Project }) { +function CreateVaultAccountStep(props: { + project: Project; + onUserAccessTokenCreated: (userAccessToken: string) => void; +}) { return (

@@ -85,7 +123,10 @@ function CreateVaultAccountStep(props: { project: Project }) { - +

); diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/vault/components/create-vault-account.client.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/vault/components/create-vault-account.client.tsx index 1e55b9e8ced..da4a63008fe 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/vault/components/create-vault-account.client.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/vault/components/create-vault-account.client.tsx @@ -25,7 +25,10 @@ import { maskSecret, } from "../../lib/vault.client"; -export default function CreateVaultAccountButton(props: { project: Project }) { +export default function CreateVaultAccountButton(props: { + project: Project; + onUserAccessTokenCreated?: (userAccessToken: string) => void; +}) { const [modalOpen, setModalOpen] = useState(false); const [keysConfirmed, setKeysConfirmed] = useState(false); const [keysDownloaded, setKeysDownloaded] = useState(false); @@ -76,6 +79,8 @@ export default function CreateVaultAccountButton(props: { project: Project }) { throw new Error("Failed to create access token"); } + props.onUserAccessTokenCreated?.(userAccessTokenRes.data.accessToken); + return { serviceAccount: serviceAccount.data, userAccessToken: userAccessTokenRes.data, From b20e94150968a506a4862496d59170b39a9957c4 Mon Sep 17 00:00:00 2001 From: Joaquim Verges Date: Wed, 30 Apr 2025 13:06:18 +1200 Subject: [PATCH 071/101] banner update --- .../(team)/~/engine/(general)/layout.tsx | 50 ++++++++++++++++--- .../engine/analytics/ftux.client.tsx | 7 +-- 2 files changed, 47 insertions(+), 10 deletions(-) diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(general)/layout.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(general)/layout.tsx index fd24d1387c6..6cf20c97870 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(general)/layout.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(general)/layout.tsx @@ -1,9 +1,10 @@ import type { SidebarLink } from "@/components/blocks/Sidebar"; import { SidebarLayout } from "@/components/blocks/SidebarLayout"; +import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; +import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; import { CloudIcon } from "lucide-react"; import Link from "next/link"; -import { Badge } from "../../../../../../../../@/components/ui/badge"; -import { Button } from "../../../../../../../../@/components/ui/button"; import { ImportEngineLink } from "./_components"; export default async function Layout(props: { @@ -42,17 +43,16 @@ export default async function Layout(props: {

- - -
+
+
+ +
{/* sidebar layout */} @@ -62,3 +62,39 @@ export default async function Layout(props: {
); } + +function EngineLegacyBannerUI(props: { + teamSlug: string; +}) { + return ( + + + Engine Cloud (Beta) + +
+

+ Try Engine Cloud (Beta) - now included for free in every thirdweb + project. +

+
+
    +
  • No recurring monthly cost, pay-per-request model
  • +
  • Powered by Vault: our new TEE based key management system
  • +
  • Improved performance and simplified transaction API
  • +
+
+
+ + + + + + +
+ + + ); +} diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/analytics/ftux.client.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/analytics/ftux.client.tsx index fa614e60544..23b2e0e65a2 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/analytics/ftux.client.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/analytics/ftux.client.tsx @@ -1,15 +1,15 @@ "use client"; import type { Project } from "@/api/projects"; +import { CopyTextButton } from "@/components/ui/CopyTextButton"; import { type Step, StepsCard } from "components/dashboard/StepsCard"; import Link from "next/link"; import { useMemo, useState } from "react"; import { Button } from "../../../../../../../@/components/ui/button"; +import { maskSecret } from "../lib/vault.client"; import CreateServerWallet from "../server-wallets/components/create-server-wallet.client"; import type { Wallet } from "../server-wallets/wallet-table/types"; import CreateVaultAccountButton from "../vault/components/create-vault-account.client"; import { SendTestTransaction } from "./send-test-tx.client"; -import { CopyTextButton } from "@/components/ui/CopyTextButton"; -import { maskSecret } from "../lib/vault.client"; interface Props { managementAccessToken: string | undefined; @@ -143,7 +143,8 @@ function CreateServerWalletStep(props: {

- Your server wallet will be used to send transactions to the Engine. + Server wallets are smart wallets, they don't require any gas funds to + send transactions.

From 8ea8f66e8a200cfe6629ad2ba16285b6ac72113f Mon Sep 17 00:00:00 2001 From: Joaquim Verges Date: Wed, 30 Apr 2025 13:58:36 +1200 Subject: [PATCH 072/101] addressed feedback --- .../engine/analytics/ftux.client.tsx | 91 +++++-------------- .../engine/analytics/send-test-tx.client.tsx | 83 ++++++++++++----- .../[project_slug]/engine/explorer/page.tsx | 1 + .../create-server-wallet.client.tsx | 13 ++- .../create-vault-account.client.tsx | 32 ++++--- .../components/rotate-admin-key.client.tsx | 32 ++++--- 6 files changed, 133 insertions(+), 119 deletions(-) diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/analytics/ftux.client.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/analytics/ftux.client.tsx index 23b2e0e65a2..49c4ef9f131 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/analytics/ftux.client.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/analytics/ftux.client.tsx @@ -1,11 +1,9 @@ "use client"; import type { Project } from "@/api/projects"; -import { CopyTextButton } from "@/components/ui/CopyTextButton"; import { type Step, StepsCard } from "components/dashboard/StepsCard"; import Link from "next/link"; import { useMemo, useState } from "react"; import { Button } from "../../../../../../../@/components/ui/button"; -import { maskSecret } from "../lib/vault.client"; import CreateServerWallet from "../server-wallets/components/create-server-wallet.client"; import type { Wallet } from "../server-wallets/wallet-table/types"; import CreateVaultAccountButton from "../vault/components/create-vault-account.client"; @@ -26,7 +24,7 @@ export const EngineChecklist: React.FC = (props) => { steps.push({ title: "Create a Vault Admin Account", description: - "Your Vault admin account will be used to create server wallets and manage access tokens.", + "Vault is thirdweb's key management system. It allows you to create secure server wallets and manage access tokens.", children: ( = (props) => { steps.push({ title: "Create a Server Wallet", description: - "Your server wallet will be used to send transactions to Engine.", + "Server wallets are smart wallets, they don't require any gas funds to send transactions.", children: ( = (props) => { }); steps.push({ title: "Send a Test Transaction", - description: "Send a test transaction to see Engine in action.", + description: + "Engine handles gas fees, and is designed for scale, speed and security. Send a test transaction to see it in action", children: ( - <> -
- {userAccessToken && ( -
- -

- This is the access token you just created. You need it to - authorize every wallet action. You can create more access - tokens with your admin key. Each access token can be scoped - and permissioned with flexible policies. You can copy this one - now to send a test transaction. -

-
- )} -
- - + ), completed: props.hasTransactions, - showIncompleteChildren: true, + showIncompleteChildren: false, showCompletedChildren: false, }); return steps; @@ -108,26 +86,14 @@ function CreateVaultAccountStep(props: { onUserAccessTokenCreated: (userAccessToken: string) => void; }) { return ( -
-

- Let's get you set up with Vault. -

-
-

- To use Engine, you will need to manage one or more server wallets. - Server wallets are secured and accessed through Vault, thirdweb's key - management system. -

-
-
- - - - -
+
+ + + +
); } @@ -137,22 +103,11 @@ function CreateServerWalletStep(props: { managementAccessToken: string | undefined; }) { return ( -
-

- Now, let's create a server wallet. -

-
-

- Server wallets are smart wallets, they don't require any gas funds to - send transactions. -

-
-
- -
+
+
); } diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/analytics/send-test-tx.client.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/analytics/send-test-tx.client.tsx index 59590bb941d..73b9ec8ef91 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/analytics/send-test-tx.client.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/analytics/send-test-tx.client.tsx @@ -20,6 +20,12 @@ import { Loader2, LockIcon } from "lucide-react"; import { useState } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; +import { + arbitrumSepolia, + baseSepolia, + optimismSepolia, + sepolia, +} from "thirdweb/chains"; import { shortenAddress } from "thirdweb/utils"; import * as z from "zod"; import type { Wallet } from "../server-wallets/wallet-table/types"; @@ -35,6 +41,7 @@ type FormValues = z.infer; export function SendTestTransaction(props: { wallets?: Wallet[]; project: Project; + userAccessToken?: string; expanded?: boolean; }) { const thirdwebClient = useThirdwebClient(); @@ -45,7 +52,7 @@ export function SendTestTransaction(props: { const form = useForm({ resolver: zodResolver(formSchema), defaultValues: { - accessToken: "", + accessToken: props.userAccessToken ?? "", walletIndex: "0", chainId: 84532, }, @@ -120,19 +127,10 @@ export function SendTestTransaction(props: { return (
-

- Engine is designed to scale, gas-free, fast, and secure.
-

-
-

- Fire up many test transactions and watch the table below to see how it - performs! -

-

- This action requires your vault - access token. + + Every wallet action requires your Vault access token.

{/* Responsive container */} @@ -142,12 +140,21 @@ export function SendTestTransaction(props: {

Vault Access Token

+ {props.userAccessToken && ( +
+

+ This is the project-wide access token you just created. You + can create more access tokens using your admin key, with + granular scopes and permissions. +

+
+ )}
@@ -205,6 +212,32 @@ export function SendTestTransaction(props: {

Network

{ @@ -216,20 +249,9 @@ export function SendTestTransaction(props: {
- {hasSentTx && ( - - )} + {hasSentTx && ( + + )}
diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/explorer/page.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/explorer/page.tsx index f746c7aa9b2..bb32eb032a3 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/explorer/page.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/explorer/page.tsx @@ -9,6 +9,7 @@ export default function TransactionsExplorerPage() { - {props.managementAccessToken ? "Create Server Wallet" : "Get Started"} + {isLoading ? ( + + ) : ( + + )} + {props.managementAccessToken + ? isLoading + ? "Creating..." + : "Create Server Wallet" + : "Get Started"} diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/vault/components/create-vault-account.client.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/vault/components/create-vault-account.client.tsx index da4a63008fe..d53bbfc5773 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/vault/components/create-vault-account.client.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/vault/components/create-vault-account.client.tsx @@ -15,7 +15,7 @@ import { useDashboardRouter } from "@/lib/DashboardRouter"; import { cn } from "@/lib/utils"; import { useMutation } from "@tanstack/react-query"; import { createServiceAccount } from "@thirdweb-dev/vault-sdk"; -import { Loader2, LockIcon } from "lucide-react"; +import { CheckIcon, DownloadIcon, Loader2, LockIcon } from "lucide-react"; import { useState } from "react"; import { toast } from "sonner"; import { @@ -132,14 +132,6 @@ export default function CreateVaultAccountButton(props: { router.refresh(); }; - const handlePrimaryButton = () => { - if (!keysDownloaded) { - handleDownloadKeys(); - return; - } - handleCloseModal(); - }; - const isLoading = initialiseProjectWithVaultMutation.isPending; return ( @@ -244,6 +236,22 @@ export default function CreateVaultAccountButton(props: { as they provide access to your server wallets.
+
+ + {keysDownloaded && ( + + + + )} +
+
diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/vault/components/rotate-admin-key.client.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/vault/components/rotate-admin-key.client.tsx index 85bdf4706ac..0af9050efa1 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/vault/components/rotate-admin-key.client.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/vault/components/rotate-admin-key.client.tsx @@ -17,7 +17,7 @@ import { useDashboardRouter } from "@/lib/DashboardRouter"; import { cn } from "@/lib/utils"; import { useMutation } from "@tanstack/react-query"; import { rotateServiceAccount } from "@thirdweb-dev/vault-sdk"; -import { Loader2, RefreshCcwIcon } from "lucide-react"; +import { CheckIcon, DownloadIcon, Loader2, RefreshCcwIcon } from "lucide-react"; import { useState } from "react"; import { toast } from "sonner"; import { @@ -129,14 +129,6 @@ export default function RotateAdminKeyButton(props: { project: Project }) { router.refresh(); }; - const handlePrimaryButton = () => { - if (!keysDownloaded) { - handleDownloadKeys(); - return; - } - handleCloseModal(); - }; - const isLoading = rotateAdminKeyMutation.isPending; return ( @@ -230,6 +222,22 @@ export default function RotateAdminKeyButton(props: { project: Project }) { as they provide access to your server wallets.
+
+ + {keysDownloaded && ( + + + + )} +
+
From 00576f470a45b1a4bb3a8e62c8680068343cf136 Mon Sep 17 00:00:00 2001 From: Joaquim Verges Date: Wed, 30 Apr 2025 14:11:45 +1200 Subject: [PATCH 073/101] polish --- .../create-vault-account.client.tsx | 2 +- .../components/list-access-tokens.client.tsx | 7 ++++++ .../components/rotate-admin-key.client.tsx | 24 +++++++++++++++---- 3 files changed, 27 insertions(+), 6 deletions(-) diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/vault/components/create-vault-account.client.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/vault/components/create-vault-account.client.tsx index d53bbfc5773..a5843351dd5 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/vault/components/create-vault-account.client.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/vault/components/create-vault-account.client.tsx @@ -101,7 +101,7 @@ export default function CreateVaultAccountButton(props: { return; } - const fileContent = `Project: ${props.project.name} (${props.project.publishableKey})\nVault Admin Key: ${initialiseProjectWithVaultMutation.data.serviceAccount.adminKey}\nVault Access Token: ${initialiseProjectWithVaultMutation.data.userAccessToken.accessToken}\n`; + const fileContent = `Project:\n${props.project.name} (${props.project.publishableKey})\n\nVault Admin Key:\n${initialiseProjectWithVaultMutation.data.serviceAccount.adminKey}\n\nVault Access Token:\n${initialiseProjectWithVaultMutation.data.userAccessToken.accessToken}\n`; const blob = new Blob([fileContent], { type: "text/plain;charset=utf-8" }); const url = URL.createObjectURL(blob); const link = document.createElement("a"); diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/vault/components/list-access-tokens.client.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/vault/components/list-access-tokens.client.tsx index bb5625734e1..af2f31558d1 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/vault/components/list-access-tokens.client.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/vault/components/list-access-tokens.client.tsx @@ -173,6 +173,7 @@ export default function ListAccessTokens(props: { setModalOpen(true); } else { setAdminKey(""); + setTypedAdminKey(""); queryClient.invalidateQueries({ queryKey: ["list-access-tokens"], }); @@ -192,6 +193,12 @@ export default function ListAccessTokens(props: {
+ ) : listAccessTokensQuery.error ? ( +
+

+ Failed to list access tokens. Check your admin key and try again. +

+
) : listAccessTokensQuery.data ? (
diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/vault/components/rotate-admin-key.client.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/vault/components/rotate-admin-key.client.tsx index 0af9050efa1..d1634fc02d5 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/vault/components/rotate-admin-key.client.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/vault/components/rotate-admin-key.client.tsx @@ -17,7 +17,13 @@ import { useDashboardRouter } from "@/lib/DashboardRouter"; import { cn } from "@/lib/utils"; import { useMutation } from "@tanstack/react-query"; import { rotateServiceAccount } from "@thirdweb-dev/vault-sdk"; -import { CheckIcon, DownloadIcon, Loader2, RefreshCcwIcon } from "lucide-react"; +import { + CheckIcon, + CircleAlertIcon, + DownloadIcon, + Loader2, + RefreshCcwIcon, +} from "lucide-react"; import { useState } from "react"; import { toast } from "sonner"; import { @@ -98,7 +104,7 @@ export default function RotateAdminKeyButton(props: { project: Project }) { return; } - const fileContent = `Project: ${props.project.name} (${props.project.publishableKey})\nVault Admin Key: ${rotateAdminKeyMutation.data.adminKey}\nVault Access Token: ${rotateAdminKeyMutation.data.userAccessToken.accessToken}\n`; + const fileContent = `Project:\n${props.project.name} (${props.project.publishableKey})\n\nVault Admin Key:\n${rotateAdminKeyMutation.data.adminKey}\n\nVault Access Token:\n${rotateAdminKeyMutation.data.userAccessToken.accessToken}\n`; const blob = new Blob([fileContent], { type: "text/plain;charset=utf-8" }); const url = URL.createObjectURL(blob); const link = document.createElement("a"); @@ -269,10 +275,18 @@ export default function RotateAdminKeyButton(props: { project: Project }) {
-

- This will invalidate your current admin key and all existing - access tokens. +

+ Revoke your current keys and generates new ones.

+ + + Important + + This action will invalidate your current admin key and all + existing access tokens. You will need to update your + backend to use these new access tokens. + +
From 7e00c49e80d1c5da15df4e92328cfa95a2097b0e Mon Sep 17 00:00:00 2001 From: Joaquim Verges Date: Wed, 30 Apr 2025 14:40:50 +1200 Subject: [PATCH 076/101] lint --- apps/dashboard/knip.json | 1 + apps/dashboard/src/@/components/blocks/wallet-address.tsx | 2 +- .../team/[team_slug]/(team)/~/engine/(general)/layout.tsx | 3 ++- .../[project_slug]/engine/vault/components/key-management.tsx | 2 +- 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/apps/dashboard/knip.json b/apps/dashboard/knip.json index 0a12e455fdb..77e776608e1 100644 --- a/apps/dashboard/knip.json +++ b/apps/dashboard/knip.json @@ -12,6 +12,7 @@ "ignoreDependencies": [ "@storybook/blocks", "@thirdweb-dev/service-utils", + "@thirdweb-dev/vault-sdk", "@types/color", "fast-xml-parser" ] diff --git a/apps/dashboard/src/@/components/blocks/wallet-address.tsx b/apps/dashboard/src/@/components/blocks/wallet-address.tsx index dfedc9d6bae..9ff08bdea9d 100644 --- a/apps/dashboard/src/@/components/blocks/wallet-address.tsx +++ b/apps/dashboard/src/@/components/blocks/wallet-address.tsx @@ -166,7 +166,7 @@ export function WalletAddress(props: { ); } -export function WalletAvatar(props: { +function WalletAvatar(props: { address: string; profiles: SocialProfile[]; thirdwebClient: ThirdwebClient; diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(general)/layout.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(general)/layout.tsx index 6cf20c97870..18f0eebd25e 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(general)/layout.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(general)/layout.tsx @@ -84,8 +84,9 @@ function EngineLegacyBannerUI(props: {
+ {/* TODO (cloud): add link to Engine Cloud blog post */} diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/vault/components/key-management.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/vault/components/key-management.tsx index 8e899096325..f547829d819 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/vault/components/key-management.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/vault/components/key-management.tsx @@ -62,7 +62,7 @@ export function KeyManagement({ ); } -export async function CreateVaultAccountAlert(props: { +async function CreateVaultAccountAlert(props: { project: Project; }) { return ( From 462e44fbd903352aeac7f6c2b96bfc4204d3dabc Mon Sep 17 00:00:00 2001 From: Joaquim Verges Date: Thu, 1 May 2025 08:02:36 +1200 Subject: [PATCH 077/101] [Dashboard] Refactor team engines UI (#6892) --- .../src/@3rdweb-sdk/react/hooks/useEngine.ts | 12 +- .../(app)/team/[team_slug]/(team)/layout.tsx | 10 -- .../components/ProjectSidebarLayout.tsx | 7 +- .../{ => cloud}/analytics/analytics-page.tsx | 0 .../engine/{ => cloud}/analytics/filter.tsx | 4 +- .../{ => cloud}/analytics/ftux.client.tsx | 2 +- .../analytics/send-test-tx.client.tsx | 0 .../engine/{ => cloud}/analytics/summary.tsx | 0 .../analytics/tx-chart/tx-chart-ui.tsx | 0 .../analytics/tx-chart/tx-chart.tsx | 0 .../analytics/tx-table/tx-table-ui.tsx | 2 +- .../analytics/tx-table/tx-table.tsx | 0 .../{ => cloud}/analytics/tx-table/types.ts | 0 .../engine/{ => cloud}/explorer/page.tsx | 0 .../engine/{ => cloud}/layout.tsx | 48 +++----- .../engine/{ => cloud}/lib/analytics.ts | 4 +- .../engine/{ => cloud}/lib/utils.ts | 0 .../engine/{ => cloud}/lib/vault.client.ts | 2 +- .../[project_slug]/engine/cloud/page.tsx | 103 ++++++++++++++++++ .../create-server-wallet.client.tsx | 0 .../server-wallets/components/try-it-out.tsx | 18 +-- .../{ => cloud}/server-wallets/page.tsx | 4 +- .../server-wallets/wallet-table/types.ts | 0 .../wallet-table/wallet-table-ui.client.tsx | 8 +- .../wallet-table/wallet-table.tsx | 0 .../engine/{ => cloud}/tx/[id]/layout.tsx | 2 +- .../engine/{ => cloud}/tx/[id]/page.tsx | 4 +- .../create-vault-account.client.tsx | 0 .../vault/components/key-management.tsx | 0 .../components/list-access-tokens.client.tsx | 0 .../components/rotate-admin-key.client.tsx | 0 .../engine/{ => cloud}/vault/page.tsx | 2 +- .../(general)/EngineFooterCard.stories.tsx | 1 + .../dedicated}/(general)/_components.tsx | 10 +- .../import/EngineImportPage.stories.tsx | 0 .../(general)/import/EngineImportPage.tsx | 5 +- .../dedicated}/(general)/import/page.tsx | 3 +- .../engine/dedicated}/(general)/layout.tsx | 36 ++++-- .../engine-instances-table.stories.tsx | 0 .../overview/engine-instances-table.tsx | 24 +++- .../(general)/overview/engine-list.tsx | 10 +- .../engine/dedicated}/(general)/page.tsx | 8 +- .../_components/EngineErrorPage.tsx | 0 .../_components/EnginePageLayout.tsx | 3 +- .../_components/EnsureEnginePermission.tsx | 5 +- .../[engineId]/_components/version.tsx | 0 .../components/access-tokens-table.tsx | 0 .../components/add-access-token-button.tsx | 0 .../components/add-keypair-button.tsx | 0 .../components/engine-access-tokens.tsx | 0 .../components/keypairs-table.tsx | 0 .../[engineId]/access-tokens/page.tsx | 1 + .../admins/components/add-admin-button.tsx | 0 .../admins/components/admins-table.tsx | 0 .../admins/components/engine-admins.tsx | 0 .../(instance)/[engineId]/admins/page.tsx | 1 + .../components/EngineAlertDialogForm.tsx | 0 .../alerts/components/EngineAlertsPage.tsx | 0 .../components/EngineDeleteAlertModal.tsx | 0 .../components/ManageEngineAlerts.stories.tsx | 0 .../alerts/components/ManageEngineAlerts.tsx | 0 .../components/RecentEngineAlerts.stories.tsx | 0 .../alerts/components/RecentEngineAlerts.tsx | 0 .../(instance)/[engineId]/alerts/page.tsx | 1 + .../components/circle-config.tsx | 0 .../configuration/components/cors.tsx | 0 .../components/engine-configuration.tsx | 9 +- .../components/engine-wallet-config.tsx | 4 +- .../configuration/components/ip-allowlist.tsx | 0 .../components/kms-aws-config.tsx | 0 .../components/kms-gcp-config.tsx | 0 .../configuration/components/local-config.tsx | 0 .../configuration/components/system.tsx | 8 +- .../[engineId]/configuration/page.tsx | 2 + .../add-contract-subscription-button.tsx | 0 .../contract-subscriptions-table.tsx | 0 .../engine-contract-subscription.tsx | 0 .../contract-subscriptions/page.tsx | 1 + .../explorer/components/engine-explorer.tsx | 0 .../explorer/components/swagger-ui.css | 0 .../(instance)/[engineId]/explorer/page.tsx | 1 + .../(instance)/[engineId]/layout.tsx | 10 +- .../components/EngineSystemMetrics.tsx | 10 +- .../metrics/components/ErrorRate.tsx | 0 .../metrics/components/Healthcheck.tsx | 0 .../metrics/components/StatusCodes.tsx | 0 .../(instance)/[engineId]/metrics/page.tsx | 2 + .../components/backend-wallets-table.tsx | 0 .../create-backend-wallet-button.tsx | 7 +- .../overview/components/engine-overview.tsx | 10 +- .../import-backend-wallet-button.tsx | 5 +- .../components/transaction-timeline.tsx | 0 .../components/transactions-table.tsx | 0 .../[engineId]/overview/components/utils.ts | 0 .../dedicated}/(instance)/[engineId]/page.tsx | 2 + .../components/add-relayer-button.tsx | 0 .../relayers/components/engine-relayer.tsx | 0 .../relayers/components/relayers-table.tsx | 0 .../(instance)/[engineId]/relayers/page.tsx | 1 + .../dedicated}/(instance)/[engineId]/types.ts | 1 + .../create-wallet-credential-button.tsx | 0 .../components/credential-form.tsx | 0 .../credential-type-fields/circle.tsx | 0 .../edit-wallet-credential-button.tsx | 0 .../wallet-credentials/components/types.ts | 0 .../components/wallet-credentials-table.tsx | 0 .../components/wallet-credentials.tsx | 0 .../[engineId]/wallet-credentials/page.tsx | 1 + .../components/add-webhook-button.tsx | 0 .../webhooks/components/engine-webhooks.tsx | 0 .../webhooks/components/webhooks-table.tsx | 0 .../(instance)/[engineId]/webhooks/page.tsx | 1 + .../_utils/getEngineAccessPermission.ts | 0 .../dedicated}/_utils/getEngineInstance.ts | 0 .../_utils/getEngineInstancePageMeta.ts | 3 +- .../dedicated}/_utils/getEngineInstances.ts | 0 .../[project_slug]/engine/loading.tsx | 5 + .../[project_slug]/engine/page.tsx | 93 +++------------- .../TeamAndProjectSelectorPopoverButton.tsx | 7 +- 119 files changed, 320 insertions(+), 203 deletions(-) rename apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/{ => cloud}/analytics/analytics-page.tsx (100%) rename apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/{ => cloud}/analytics/filter.tsx (87%) rename apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/{ => cloud}/analytics/ftux.client.tsx (97%) rename apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/{ => cloud}/analytics/send-test-tx.client.tsx (100%) rename apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/{ => cloud}/analytics/summary.tsx (100%) rename apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/{ => cloud}/analytics/tx-chart/tx-chart-ui.tsx (100%) rename apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/{ => cloud}/analytics/tx-chart/tx-chart.tsx (100%) rename apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/{ => cloud}/analytics/tx-table/tx-table-ui.tsx (99%) rename apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/{ => cloud}/analytics/tx-table/tx-table.tsx (100%) rename apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/{ => cloud}/analytics/tx-table/types.ts (100%) rename apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/{ => cloud}/explorer/page.tsx (100%) rename apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/{ => cloud}/layout.tsx (66%) rename apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/{ => cloud}/lib/analytics.ts (97%) rename apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/{ => cloud}/lib/utils.ts (100%) rename apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/{ => cloud}/lib/vault.client.ts (98%) create mode 100644 apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/page.tsx rename apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/{ => cloud}/server-wallets/components/create-server-wallet.client.tsx (100%) rename apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/{ => cloud}/server-wallets/components/try-it-out.tsx (93%) rename apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/{ => cloud}/server-wallets/page.tsx (92%) rename apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/{ => cloud}/server-wallets/wallet-table/types.ts (100%) rename apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/{ => cloud}/server-wallets/wallet-table/wallet-table-ui.client.tsx (94%) rename apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/{ => cloud}/server-wallets/wallet-table/wallet-table.tsx (100%) rename apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/{ => cloud}/tx/[id]/layout.tsx (97%) rename apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/{ => cloud}/tx/[id]/page.tsx (84%) rename apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/{ => cloud}/vault/components/create-vault-account.client.tsx (100%) rename apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/{ => cloud}/vault/components/key-management.tsx (100%) rename apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/{ => cloud}/vault/components/list-access-tokens.client.tsx (100%) rename apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/{ => cloud}/vault/components/rotate-admin-key.client.tsx (100%) rename apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/{ => cloud}/vault/page.tsx (92%) rename apps/dashboard/src/app/(app)/team/[team_slug]/{(team)/~/engine => [project_slug]/engine/dedicated}/(general)/EngineFooterCard.stories.tsx (95%) rename apps/dashboard/src/app/(app)/team/[team_slug]/{(team)/~/engine => [project_slug]/engine/dedicated}/(general)/_components.tsx (93%) rename apps/dashboard/src/app/(app)/team/[team_slug]/{(team)/~/engine => [project_slug]/engine/dedicated}/(general)/import/EngineImportPage.stories.tsx (100%) rename apps/dashboard/src/app/(app)/team/[team_slug]/{(team)/~/engine => [project_slug]/engine/dedicated}/(general)/import/EngineImportPage.tsx (97%) rename apps/dashboard/src/app/(app)/team/[team_slug]/{(team)/~/engine => [project_slug]/engine/dedicated}/(general)/import/page.tsx (85%) rename apps/dashboard/src/app/(app)/team/[team_slug]/{(team)/~/engine => [project_slug]/engine/dedicated}/(general)/layout.tsx (72%) rename apps/dashboard/src/app/(app)/team/[team_slug]/{(team)/~/engine => [project_slug]/engine/dedicated}/(general)/overview/engine-instances-table.stories.tsx (100%) rename apps/dashboard/src/app/(app)/team/[team_slug]/{(team)/~/engine => [project_slug]/engine/dedicated}/(general)/overview/engine-instances-table.tsx (97%) rename apps/dashboard/src/app/(app)/team/[team_slug]/{(team)/~/engine => [project_slug]/engine/dedicated}/(general)/overview/engine-list.tsx (68%) rename apps/dashboard/src/app/(app)/team/[team_slug]/{(team)/~/engine => [project_slug]/engine/dedicated}/(general)/page.tsx (79%) rename apps/dashboard/src/app/(app)/team/[team_slug]/{(team)/~/engine => [project_slug]/engine/dedicated}/(instance)/[engineId]/_components/EngineErrorPage.tsx (100%) rename apps/dashboard/src/app/(app)/team/[team_slug]/{(team)/~/engine => [project_slug]/engine/dedicated}/(instance)/[engineId]/_components/EnginePageLayout.tsx (93%) rename apps/dashboard/src/app/(app)/team/[team_slug]/{(team)/~/engine => [project_slug]/engine/dedicated}/(instance)/[engineId]/_components/EnsureEnginePermission.tsx (91%) rename apps/dashboard/src/app/(app)/team/[team_slug]/{(team)/~/engine => [project_slug]/engine/dedicated}/(instance)/[engineId]/_components/version.tsx (100%) rename apps/dashboard/src/app/(app)/team/[team_slug]/{(team)/~/engine => [project_slug]/engine/dedicated}/(instance)/[engineId]/access-tokens/components/access-tokens-table.tsx (100%) rename apps/dashboard/src/app/(app)/team/[team_slug]/{(team)/~/engine => [project_slug]/engine/dedicated}/(instance)/[engineId]/access-tokens/components/add-access-token-button.tsx (100%) rename apps/dashboard/src/app/(app)/team/[team_slug]/{(team)/~/engine => [project_slug]/engine/dedicated}/(instance)/[engineId]/access-tokens/components/add-keypair-button.tsx (100%) rename apps/dashboard/src/app/(app)/team/[team_slug]/{(team)/~/engine => [project_slug]/engine/dedicated}/(instance)/[engineId]/access-tokens/components/engine-access-tokens.tsx (100%) rename apps/dashboard/src/app/(app)/team/[team_slug]/{(team)/~/engine => [project_slug]/engine/dedicated}/(instance)/[engineId]/access-tokens/components/keypairs-table.tsx (100%) rename apps/dashboard/src/app/(app)/team/[team_slug]/{(team)/~/engine => [project_slug]/engine/dedicated}/(instance)/[engineId]/access-tokens/page.tsx (93%) rename apps/dashboard/src/app/(app)/team/[team_slug]/{(team)/~/engine => [project_slug]/engine/dedicated}/(instance)/[engineId]/admins/components/add-admin-button.tsx (100%) rename apps/dashboard/src/app/(app)/team/[team_slug]/{(team)/~/engine => [project_slug]/engine/dedicated}/(instance)/[engineId]/admins/components/admins-table.tsx (100%) rename apps/dashboard/src/app/(app)/team/[team_slug]/{(team)/~/engine => [project_slug]/engine/dedicated}/(instance)/[engineId]/admins/components/engine-admins.tsx (100%) rename apps/dashboard/src/app/(app)/team/[team_slug]/{(team)/~/engine => [project_slug]/engine/dedicated}/(instance)/[engineId]/admins/page.tsx (93%) rename apps/dashboard/src/app/(app)/team/[team_slug]/{(team)/~/engine => [project_slug]/engine/dedicated}/(instance)/[engineId]/alerts/components/EngineAlertDialogForm.tsx (100%) rename apps/dashboard/src/app/(app)/team/[team_slug]/{(team)/~/engine => [project_slug]/engine/dedicated}/(instance)/[engineId]/alerts/components/EngineAlertsPage.tsx (100%) rename apps/dashboard/src/app/(app)/team/[team_slug]/{(team)/~/engine => [project_slug]/engine/dedicated}/(instance)/[engineId]/alerts/components/EngineDeleteAlertModal.tsx (100%) rename apps/dashboard/src/app/(app)/team/[team_slug]/{(team)/~/engine => [project_slug]/engine/dedicated}/(instance)/[engineId]/alerts/components/ManageEngineAlerts.stories.tsx (100%) rename apps/dashboard/src/app/(app)/team/[team_slug]/{(team)/~/engine => [project_slug]/engine/dedicated}/(instance)/[engineId]/alerts/components/ManageEngineAlerts.tsx (100%) rename apps/dashboard/src/app/(app)/team/[team_slug]/{(team)/~/engine => [project_slug]/engine/dedicated}/(instance)/[engineId]/alerts/components/RecentEngineAlerts.stories.tsx (100%) rename apps/dashboard/src/app/(app)/team/[team_slug]/{(team)/~/engine => [project_slug]/engine/dedicated}/(instance)/[engineId]/alerts/components/RecentEngineAlerts.tsx (100%) rename apps/dashboard/src/app/(app)/team/[team_slug]/{(team)/~/engine => [project_slug]/engine/dedicated}/(instance)/[engineId]/alerts/page.tsx (93%) rename apps/dashboard/src/app/(app)/team/[team_slug]/{(team)/~/engine => [project_slug]/engine/dedicated}/(instance)/[engineId]/configuration/components/circle-config.tsx (100%) rename apps/dashboard/src/app/(app)/team/[team_slug]/{(team)/~/engine => [project_slug]/engine/dedicated}/(instance)/[engineId]/configuration/components/cors.tsx (100%) rename apps/dashboard/src/app/(app)/team/[team_slug]/{(team)/~/engine => [project_slug]/engine/dedicated}/(instance)/[engineId]/configuration/components/engine-configuration.tsx (82%) rename apps/dashboard/src/app/(app)/team/[team_slug]/{(team)/~/engine => [project_slug]/engine/dedicated}/(instance)/[engineId]/configuration/components/engine-wallet-config.tsx (96%) rename apps/dashboard/src/app/(app)/team/[team_slug]/{(team)/~/engine => [project_slug]/engine/dedicated}/(instance)/[engineId]/configuration/components/ip-allowlist.tsx (100%) rename apps/dashboard/src/app/(app)/team/[team_slug]/{(team)/~/engine => [project_slug]/engine/dedicated}/(instance)/[engineId]/configuration/components/kms-aws-config.tsx (100%) rename apps/dashboard/src/app/(app)/team/[team_slug]/{(team)/~/engine => [project_slug]/engine/dedicated}/(instance)/[engineId]/configuration/components/kms-gcp-config.tsx (100%) rename apps/dashboard/src/app/(app)/team/[team_slug]/{(team)/~/engine => [project_slug]/engine/dedicated}/(instance)/[engineId]/configuration/components/local-config.tsx (100%) rename apps/dashboard/src/app/(app)/team/[team_slug]/{(team)/~/engine => [project_slug]/engine/dedicated}/(instance)/[engineId]/configuration/components/system.tsx (84%) rename apps/dashboard/src/app/(app)/team/[team_slug]/{(team)/~/engine => [project_slug]/engine/dedicated}/(instance)/[engineId]/configuration/page.tsx (88%) rename apps/dashboard/src/app/(app)/team/[team_slug]/{(team)/~/engine => [project_slug]/engine/dedicated}/(instance)/[engineId]/contract-subscriptions/components/add-contract-subscription-button.tsx (100%) rename apps/dashboard/src/app/(app)/team/[team_slug]/{(team)/~/engine => [project_slug]/engine/dedicated}/(instance)/[engineId]/contract-subscriptions/components/contract-subscriptions-table.tsx (100%) rename apps/dashboard/src/app/(app)/team/[team_slug]/{(team)/~/engine => [project_slug]/engine/dedicated}/(instance)/[engineId]/contract-subscriptions/components/engine-contract-subscription.tsx (100%) rename apps/dashboard/src/app/(app)/team/[team_slug]/{(team)/~/engine => [project_slug]/engine/dedicated}/(instance)/[engineId]/contract-subscriptions/page.tsx (94%) rename apps/dashboard/src/app/(app)/team/[team_slug]/{(team)/~/engine => [project_slug]/engine/dedicated}/(instance)/[engineId]/explorer/components/engine-explorer.tsx (100%) rename apps/dashboard/src/app/(app)/team/[team_slug]/{(team)/~/engine => [project_slug]/engine/dedicated}/(instance)/[engineId]/explorer/components/swagger-ui.css (100%) rename apps/dashboard/src/app/(app)/team/[team_slug]/{(team)/~/engine => [project_slug]/engine/dedicated}/(instance)/[engineId]/explorer/page.tsx (93%) rename apps/dashboard/src/app/(app)/team/[team_slug]/{(team)/~/engine => [project_slug]/engine/dedicated}/(instance)/[engineId]/layout.tsx (91%) rename apps/dashboard/src/app/(app)/team/[team_slug]/{(team)/~/engine => [project_slug]/engine/dedicated}/(instance)/[engineId]/metrics/components/EngineSystemMetrics.tsx (95%) rename apps/dashboard/src/app/(app)/team/[team_slug]/{(team)/~/engine => [project_slug]/engine/dedicated}/(instance)/[engineId]/metrics/components/ErrorRate.tsx (100%) rename apps/dashboard/src/app/(app)/team/[team_slug]/{(team)/~/engine => [project_slug]/engine/dedicated}/(instance)/[engineId]/metrics/components/Healthcheck.tsx (100%) rename apps/dashboard/src/app/(app)/team/[team_slug]/{(team)/~/engine => [project_slug]/engine/dedicated}/(instance)/[engineId]/metrics/components/StatusCodes.tsx (100%) rename apps/dashboard/src/app/(app)/team/[team_slug]/{(team)/~/engine => [project_slug]/engine/dedicated}/(instance)/[engineId]/metrics/page.tsx (88%) rename apps/dashboard/src/app/(app)/team/[team_slug]/{(team)/~/engine => [project_slug]/engine/dedicated}/(instance)/[engineId]/overview/components/backend-wallets-table.tsx (100%) rename apps/dashboard/src/app/(app)/team/[team_slug]/{(team)/~/engine => [project_slug]/engine/dedicated}/(instance)/[engineId]/overview/components/create-backend-wallet-button.tsx (97%) rename apps/dashboard/src/app/(app)/team/[team_slug]/{(team)/~/engine => [project_slug]/engine/dedicated}/(instance)/[engineId]/overview/components/engine-overview.tsx (93%) rename apps/dashboard/src/app/(app)/team/[team_slug]/{(team)/~/engine => [project_slug]/engine/dedicated}/(instance)/[engineId]/overview/components/import-backend-wallet-button.tsx (98%) rename apps/dashboard/src/app/(app)/team/[team_slug]/{(team)/~/engine => [project_slug]/engine/dedicated}/(instance)/[engineId]/overview/components/transaction-timeline.tsx (100%) rename apps/dashboard/src/app/(app)/team/[team_slug]/{(team)/~/engine => [project_slug]/engine/dedicated}/(instance)/[engineId]/overview/components/transactions-table.tsx (100%) rename apps/dashboard/src/app/(app)/team/[team_slug]/{(team)/~/engine => [project_slug]/engine/dedicated}/(instance)/[engineId]/overview/components/utils.ts (100%) rename apps/dashboard/src/app/(app)/team/[team_slug]/{(team)/~/engine => [project_slug]/engine/dedicated}/(instance)/[engineId]/page.tsx (88%) rename apps/dashboard/src/app/(app)/team/[team_slug]/{(team)/~/engine => [project_slug]/engine/dedicated}/(instance)/[engineId]/relayers/components/add-relayer-button.tsx (100%) rename apps/dashboard/src/app/(app)/team/[team_slug]/{(team)/~/engine => [project_slug]/engine/dedicated}/(instance)/[engineId]/relayers/components/engine-relayer.tsx (100%) rename apps/dashboard/src/app/(app)/team/[team_slug]/{(team)/~/engine => [project_slug]/engine/dedicated}/(instance)/[engineId]/relayers/components/relayers-table.tsx (100%) rename apps/dashboard/src/app/(app)/team/[team_slug]/{(team)/~/engine => [project_slug]/engine/dedicated}/(instance)/[engineId]/relayers/page.tsx (93%) rename apps/dashboard/src/app/(app)/team/[team_slug]/{(team)/~/engine => [project_slug]/engine/dedicated}/(instance)/[engineId]/types.ts (81%) rename apps/dashboard/src/app/(app)/team/[team_slug]/{(team)/~/engine => [project_slug]/engine/dedicated}/(instance)/[engineId]/wallet-credentials/components/create-wallet-credential-button.tsx (100%) rename apps/dashboard/src/app/(app)/team/[team_slug]/{(team)/~/engine => [project_slug]/engine/dedicated}/(instance)/[engineId]/wallet-credentials/components/credential-form.tsx (100%) rename apps/dashboard/src/app/(app)/team/[team_slug]/{(team)/~/engine => [project_slug]/engine/dedicated}/(instance)/[engineId]/wallet-credentials/components/credential-type-fields/circle.tsx (100%) rename apps/dashboard/src/app/(app)/team/[team_slug]/{(team)/~/engine => [project_slug]/engine/dedicated}/(instance)/[engineId]/wallet-credentials/components/edit-wallet-credential-button.tsx (100%) rename apps/dashboard/src/app/(app)/team/[team_slug]/{(team)/~/engine => [project_slug]/engine/dedicated}/(instance)/[engineId]/wallet-credentials/components/types.ts (100%) rename apps/dashboard/src/app/(app)/team/[team_slug]/{(team)/~/engine => [project_slug]/engine/dedicated}/(instance)/[engineId]/wallet-credentials/components/wallet-credentials-table.tsx (100%) rename apps/dashboard/src/app/(app)/team/[team_slug]/{(team)/~/engine => [project_slug]/engine/dedicated}/(instance)/[engineId]/wallet-credentials/components/wallet-credentials.tsx (100%) rename apps/dashboard/src/app/(app)/team/[team_slug]/{(team)/~/engine => [project_slug]/engine/dedicated}/(instance)/[engineId]/wallet-credentials/page.tsx (93%) rename apps/dashboard/src/app/(app)/team/[team_slug]/{(team)/~/engine => [project_slug]/engine/dedicated}/(instance)/[engineId]/webhooks/components/add-webhook-button.tsx (100%) rename apps/dashboard/src/app/(app)/team/[team_slug]/{(team)/~/engine => [project_slug]/engine/dedicated}/(instance)/[engineId]/webhooks/components/engine-webhooks.tsx (100%) rename apps/dashboard/src/app/(app)/team/[team_slug]/{(team)/~/engine => [project_slug]/engine/dedicated}/(instance)/[engineId]/webhooks/components/webhooks-table.tsx (100%) rename apps/dashboard/src/app/(app)/team/[team_slug]/{(team)/~/engine => [project_slug]/engine/dedicated}/(instance)/[engineId]/webhooks/page.tsx (93%) rename apps/dashboard/src/app/(app)/team/[team_slug]/{(team)/~/engine => [project_slug]/engine/dedicated}/_utils/getEngineAccessPermission.ts (100%) rename apps/dashboard/src/app/(app)/team/[team_slug]/{(team)/~/engine => [project_slug]/engine/dedicated}/_utils/getEngineInstance.ts (100%) rename apps/dashboard/src/app/(app)/team/[team_slug]/{(team)/~/engine => [project_slug]/engine/dedicated}/_utils/getEngineInstancePageMeta.ts (86%) rename apps/dashboard/src/app/(app)/team/[team_slug]/{(team)/~/engine => [project_slug]/engine/dedicated}/_utils/getEngineInstances.ts (100%) create mode 100644 apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/loading.tsx diff --git a/apps/dashboard/src/@3rdweb-sdk/react/hooks/useEngine.ts b/apps/dashboard/src/@3rdweb-sdk/react/hooks/useEngine.ts index 26e4986bb27..ee8a12678d6 100644 --- a/apps/dashboard/src/@3rdweb-sdk/react/hooks/useEngine.ts +++ b/apps/dashboard/src/@3rdweb-sdk/react/hooks/useEngine.ts @@ -7,12 +7,12 @@ import { useQuery, useQueryClient, } from "@tanstack/react-query"; -import type { ResultItem } from "app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/metrics/components/StatusCodes"; import type { EngineBackendWalletType } from "lib/engine"; import { useState } from "react"; import { useActiveAccount } from "thirdweb/react"; import invariant from "tiny-invariant"; -import type { EngineStatus } from "../../../app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/overview/components/transactions-table"; +import type { ResultItem } from "../../../app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/metrics/components/StatusCodes"; +import type { EngineStatus } from "../../../app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/overview/components/transactions-table"; import { engineKeys } from "../cache-keys"; // Engine instances @@ -1641,7 +1641,11 @@ interface EngineResourceMetrics { }; } -export function useEngineSystemMetrics(engineId: string, teamIdOrSlug: string) { +export function useEngineSystemMetrics( + engineId: string, + teamIdOrSlug: string, + projectSlug: string, +) { const [enabled, setEnabled] = useState(true); return useQuery({ @@ -1649,7 +1653,7 @@ export function useEngineSystemMetrics(engineId: string, teamIdOrSlug: string) { queryFn: async () => { const res = await apiServerProxy({ method: "GET", - pathname: `/v1/teams/${teamIdOrSlug}/engine/${engineId}/metrics`, + pathname: `/v1/teams/${teamIdOrSlug}/${projectSlug}/engine/dedicated/${engineId}/metrics`, }); if (!res.ok) { diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/layout.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/layout.tsx index 1c987ac047e..7487db6348e 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/layout.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/layout.tsx @@ -5,7 +5,6 @@ import { TabPathLinks } from "@/components/ui/tabs"; import { getThirdwebClient } from "@/constants/thirdweb.server"; import { AnnouncementBanner } from "components/notices/AnnouncementBanner"; import { redirect } from "next/navigation"; -import { Badge } from "../../../../../@/components/ui/badge"; import { getValidAccount } from "../../../account/settings/getAccount"; import { getAuthToken, @@ -75,15 +74,6 @@ export default async function TeamLayout(props: { path: `/team/${params.team_slug}/~/analytics`, name: "Analytics", }, - { - path: `/team/${params.team_slug}/~/engine`, - name: ( - - Engines - Legacy - - ), - }, { path: `/team/${params.team_slug}/~/ecosystem`, name: "Ecosystems", diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/components/ProjectSidebarLayout.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/components/ProjectSidebarLayout.tsx index 0ad46e07e26..7e4292d9b77 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/components/ProjectSidebarLayout.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/components/ProjectSidebarLayout.tsx @@ -12,7 +12,6 @@ import { EngineIcon } from "../../../../(dashboard)/(chain)/components/server/ic import { InsightIcon } from "../../../../(dashboard)/(chain)/components/server/icons/InsightIcon"; import { PayIcon } from "../../../../(dashboard)/(chain)/components/server/icons/PayIcon"; import { SmartAccountIcon } from "../../../../(dashboard)/(chain)/components/server/icons/SmartAccountIcon"; -import { Badge } from "../../../../../../@/components/ui/badge"; import { NebulaIcon } from "../../../../../nebula-app/(app)/icons/NebulaIcon"; export function ProjectSidebarLayout(props: { @@ -63,11 +62,7 @@ export function ProjectSidebarLayout(props: { }, { href: `${layoutPath}/engine`, - label: ( - - Engine Beta - - ), + label: "Engine", icon: EngineIcon, tracking: tracking("engine"), }, diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/analytics/analytics-page.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/analytics-page.tsx similarity index 100% rename from apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/analytics/analytics-page.tsx rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/analytics-page.tsx diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/analytics/filter.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/filter.tsx similarity index 87% rename from apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/analytics/filter.tsx rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/filter.tsx index 8679439c57d..773264f8375 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/analytics/filter.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/filter.tsx @@ -5,8 +5,8 @@ import { useResponsiveSearchParams, useSetResponsiveSearchParams, } from "responsive-rsc"; -import { DateRangeSelector } from "../../../../../../../components/analytics/date-range-selector"; -import { IntervalSelector } from "../../../../../../../components/analytics/interval-selector"; +import { DateRangeSelector } from "../../../../../../../../components/analytics/date-range-selector"; +import { IntervalSelector } from "../../../../../../../../components/analytics/interval-selector"; import { getTxAnalyticsFiltersFromSearchParams } from "../lib/utils"; export function TransactionAnalyticsFilter() { diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/analytics/ftux.client.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/ftux.client.tsx similarity index 97% rename from apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/analytics/ftux.client.tsx rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/ftux.client.tsx index 49c4ef9f131..cb7e6eb2950 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/analytics/ftux.client.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/ftux.client.tsx @@ -3,7 +3,7 @@ import type { Project } from "@/api/projects"; import { type Step, StepsCard } from "components/dashboard/StepsCard"; import Link from "next/link"; import { useMemo, useState } from "react"; -import { Button } from "../../../../../../../@/components/ui/button"; +import { Button } from "../../../../../../../../@/components/ui/button"; import CreateServerWallet from "../server-wallets/components/create-server-wallet.client"; import type { Wallet } from "../server-wallets/wallet-table/types"; import CreateVaultAccountButton from "../vault/components/create-vault-account.client"; diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/analytics/send-test-tx.client.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/send-test-tx.client.tsx similarity index 100% rename from apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/analytics/send-test-tx.client.tsx rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/send-test-tx.client.tsx diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/analytics/summary.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/summary.tsx similarity index 100% rename from apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/analytics/summary.tsx rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/summary.tsx diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/analytics/tx-chart/tx-chart-ui.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/tx-chart/tx-chart-ui.tsx similarity index 100% rename from apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/analytics/tx-chart/tx-chart-ui.tsx rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/tx-chart/tx-chart-ui.tsx diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/analytics/tx-chart/tx-chart.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/tx-chart/tx-chart.tsx similarity index 100% rename from apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/analytics/tx-chart/tx-chart.tsx rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/tx-chart/tx-chart.tsx diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/analytics/tx-table/tx-table-ui.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/tx-table/tx-table-ui.tsx similarity index 99% rename from apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/analytics/tx-table/tx-table-ui.tsx rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/tx-table/tx-table-ui.tsx index 117ee68f629..cd8daf51e23 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/analytics/tx-table/tx-table-ui.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/tx-table/tx-table-ui.tsx @@ -37,7 +37,7 @@ import { useAllChainsData } from "hooks/chains/allChains"; import { ExternalLinkIcon, InfoIcon } from "lucide-react"; import Link from "next/link"; import { useState } from "react"; -import { ChainIconClient } from "../../../../../../../../components/icons/ChainIcon"; +import { ChainIconClient } from "../../../../../../../../../components/icons/ChainIcon"; import type { Wallet } from "../../server-wallets/wallet-table/types"; import type { Transaction, diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/analytics/tx-table/tx-table.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/tx-table/tx-table.tsx similarity index 100% rename from apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/analytics/tx-table/tx-table.tsx rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/tx-table/tx-table.tsx diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/analytics/tx-table/types.ts b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/tx-table/types.ts similarity index 100% rename from apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/analytics/tx-table/types.ts rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/tx-table/types.ts diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/explorer/page.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/explorer/page.tsx similarity index 100% rename from apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/explorer/page.tsx rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/explorer/page.tsx diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/layout.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/layout.tsx similarity index 66% rename from apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/layout.tsx rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/layout.tsx index eea3d9a80dd..2d7e709bdeb 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/layout.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/layout.tsx @@ -1,12 +1,9 @@ -import { getProject } from "@/api/projects"; -import { getTeamBySlug } from "@/api/team"; import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; import { TabPathLinks } from "@/components/ui/tabs"; import { THIRDWEB_ENGINE_CLOUD_URL } from "@/constants/env"; -import { CloudIcon } from "lucide-react"; import Link from "next/link"; -import { redirect } from "next/navigation"; -import { Button } from "../../../../../../@/components/ui/button"; +import { EngineIcon } from "../../../../../(dashboard)/(chain)/components/server/icons/EngineIcon"; export default async function Page(props: { params: Promise<{ team_slug: string; project_slug: string }>; @@ -14,25 +11,8 @@ export default async function Page(props: { }) { const { team_slug, project_slug } = await props.params; - const [team, project] = await Promise.all([ - getTeamBySlug(team_slug), - getProject(team_slug, project_slug), - ]); - - if (!team) { - redirect("/team"); - } - - if (!project) { - redirect(`/team/${team_slug}`); - } - return ( - + {props.children} ); @@ -41,10 +21,10 @@ export default async function Page(props: { function TransactionsLayout(props: { projectSlug: string; teamSlug: string; - clientId: string; children: React.ReactNode; }) { - const engineLayoutSlug = `/team/${props.teamSlug}/${props.projectSlug}/engine`; + const engineBaseSlug = `/team/${props.teamSlug}/${props.projectSlug}/engine`; + const engineLayoutSlug = `${engineBaseSlug}/cloud`; return (
@@ -54,8 +34,14 @@ function TransactionsLayout(props: {
-

- Engine +

+ Engine{" "} + + Cloud +

{THIRDWEB_ENGINE_CLOUD_URL} - - - Cloud -
- - + +
diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/lib/analytics.ts b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/lib/analytics.ts similarity index 97% rename from apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/lib/analytics.ts rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/lib/analytics.ts index df00470dc4c..05020cfeda5 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/lib/analytics.ts +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/lib/analytics.ts @@ -1,6 +1,6 @@ import { THIRDWEB_ENGINE_CLOUD_URL } from "@/constants/env"; -import type { TransactionStats } from "../../../../../../../types/analytics"; -import { getAuthToken } from "../../../../../api/lib/getAuthToken"; +import type { TransactionStats } from "../../../../../../../../types/analytics"; +import { getAuthToken } from "../../../../../../api/lib/getAuthToken"; // Define the structure of the data we expect back from our fetch function export type TransactionSummaryData = { diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/lib/utils.ts b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/lib/utils.ts similarity index 100% rename from apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/lib/utils.ts rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/lib/utils.ts diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/lib/vault.client.ts b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/lib/vault.client.ts similarity index 98% rename from apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/lib/vault.client.ts rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/lib/vault.client.ts index f66f308ab93..225f87bed3c 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/lib/vault.client.ts +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/lib/vault.client.ts @@ -7,7 +7,7 @@ import { createAccessToken, createVaultClient, } from "@thirdweb-dev/vault-sdk"; -import { updateProjectClient } from "../../../../../../../@3rdweb-sdk/react/hooks/useApi"; +import { updateProjectClient } from "../../../../../../../../@3rdweb-sdk/react/hooks/useApi"; const SERVER_WALLET_ACCESS_TOKEN_PURPOSE = "Access Token for All Server Wallets"; diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/page.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/page.tsx new file mode 100644 index 00000000000..cce8142a363 --- /dev/null +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/page.tsx @@ -0,0 +1,103 @@ +import { getProject } from "@/api/projects"; +import { THIRDWEB_VAULT_URL } from "@/constants/env"; +import { createVaultClient, listEoas } from "@thirdweb-dev/vault-sdk"; +import { notFound, redirect } from "next/navigation"; +import { getAuthToken } from "../../../../../api/lib/getAuthToken"; +import { TransactionsAnalyticsPageContent } from "./analytics/analytics-page"; +import { EngineChecklist } from "./analytics/ftux.client"; +import { TransactionAnalyticsSummary } from "./analytics/summary"; +import { + type TransactionSummaryData, + getTransactionAnalyticsSummary, +} from "./lib/analytics"; +import type { Wallet } from "./server-wallets/wallet-table/types"; + +export default async function TransactionsAnalyticsPage(props: { + params: Promise<{ team_slug: string; project_slug: string }>; + searchParams: Promise<{ + from?: string | string[] | undefined; + to?: string | string[] | undefined; + interval?: string | string[] | undefined; + }>; +}) { + const [params, searchParams, authToken] = await Promise.all([ + props.params, + props.searchParams, + getAuthToken(), + ]); + + if (!authToken) { + notFound(); + } + + const [vaultClient, project] = await Promise.all([ + createVaultClient({ + baseUrl: THIRDWEB_VAULT_URL, + }).catch(() => undefined), + getProject(params.team_slug, params.project_slug), + ]); + + if (!project) { + redirect(`/team/${params.team_slug}`); + } + + if (!vaultClient) { + return
Error: Failed to connect to Vault
; + } + + const projectEngineCloudService = project.services.find( + (service) => service.name === "engineCloud", + ); + + const managementAccessToken = + projectEngineCloudService?.managementAccessToken; + + const eoas = managementAccessToken + ? await listEoas({ + client: vaultClient, + request: { + auth: { + accessToken: managementAccessToken, + }, + options: {}, + }, + }) + : { data: { items: [] }, error: null, success: true }; + + const wallets = eoas.data?.items as Wallet[] | undefined; + + let initialData: TransactionSummaryData | undefined; + if (wallets && wallets.length > 0) { + const summary = await getTransactionAnalyticsSummary({ + teamId: project.teamId, + clientId: project.publishableKey, + }).catch(() => undefined); + initialData = summary; + } + const hasTransactions = initialData ? initialData.totalCount > 0 : false; + + return ( +
+ + {hasTransactions && ( + + )} +
+ +
+ ); +} diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/server-wallets/components/create-server-wallet.client.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/server-wallets/components/create-server-wallet.client.tsx similarity index 100% rename from apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/server-wallets/components/create-server-wallet.client.tsx rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/server-wallets/components/create-server-wallet.client.tsx diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/server-wallets/components/try-it-out.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/server-wallets/components/try-it-out.tsx similarity index 93% rename from apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/server-wallets/components/try-it-out.tsx rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/server-wallets/components/try-it-out.tsx index e4828b37967..f855c6ed92b 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/server-wallets/components/try-it-out.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/server-wallets/components/try-it-out.tsx @@ -1,10 +1,10 @@ "use client"; +import { Button } from "@/components/ui/button"; +import { CodeClient } from "@/components/ui/code/code.client"; +import { TabButtons } from "@/components/ui/tabs"; import { THIRDWEB_ENGINE_CLOUD_URL } from "@/constants/env"; import Link from "next/link"; import { useState } from "react"; -import { Button } from "../../../../../../../../@/components/ui/button"; -import { CodeClient } from "../../../../../../../../@/components/ui/code/code.client"; -import { TabButtons } from "../../../../../../../../@/components/ui/tabs"; import type { Wallet } from "../wallet-table/types"; export function TryItOut(props: { @@ -94,22 +94,12 @@ export function TryItOut(props: {
- {props.wallet && ( - - )}
diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/server-wallets/page.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/server-wallets/page.tsx similarity index 92% rename from apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/server-wallets/page.tsx rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/server-wallets/page.tsx index 368791001a8..5e853b4d840 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/server-wallets/page.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/server-wallets/page.tsx @@ -1,8 +1,8 @@ import { getProject } from "@/api/projects"; import { createVaultClient, listEoas } from "@thirdweb-dev/vault-sdk"; import { notFound } from "next/navigation"; -import { THIRDWEB_VAULT_URL } from "../../../../../../../@/constants/env"; -import { getAuthToken } from "../../../../../api/lib/getAuthToken"; +import { THIRDWEB_VAULT_URL } from "../../../../../../../../@/constants/env"; +import { getAuthToken } from "../../../../../../api/lib/getAuthToken"; import { TryItOut } from "./components/try-it-out"; import type { Wallet } from "./wallet-table/types"; import { ServerWalletsTable } from "./wallet-table/wallet-table"; diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/server-wallets/wallet-table/types.ts b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/server-wallets/wallet-table/types.ts similarity index 100% rename from apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/server-wallets/wallet-table/types.ts rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/server-wallets/wallet-table/types.ts diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/server-wallets/wallet-table/wallet-table-ui.client.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/server-wallets/wallet-table/wallet-table-ui.client.tsx similarity index 94% rename from apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/server-wallets/wallet-table/wallet-table-ui.client.tsx rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/server-wallets/wallet-table/wallet-table-ui.client.tsx index f017d1f283e..cc3a5b34c21 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/server-wallets/wallet-table/wallet-table-ui.client.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/server-wallets/wallet-table/wallet-table-ui.client.tsx @@ -2,6 +2,9 @@ import type { Project } from "@/api/projects"; import { WalletAddress } from "@/components/blocks/wallet-address"; +import { Badge } from "@/components/ui/badge"; +import { Skeleton } from "@/components/ui/skeleton"; +import { Switch } from "@/components/ui/switch"; import { Table, TableBody, @@ -21,10 +24,7 @@ import { DEFAULT_ACCOUNT_FACTORY_V0_7, predictSmartAccountAddress, } from "thirdweb/wallets/smart"; -import { Badge } from "../../../../../../../../@/components/ui/badge"; -import { Skeleton } from "../../../../../../../../@/components/ui/skeleton"; -import { Switch } from "../../../../../../../../@/components/ui/switch"; -import { useV5DashboardChain } from "../../../../../../../../lib/v5-adapter"; +import { useV5DashboardChain } from "../../../../../../../../../lib/v5-adapter"; import CreateServerWallet from "../components/create-server-wallet.client"; import type { Wallet } from "./types"; diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/server-wallets/wallet-table/wallet-table.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/server-wallets/wallet-table/wallet-table.tsx similarity index 100% rename from apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/server-wallets/wallet-table/wallet-table.tsx rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/server-wallets/wallet-table/wallet-table.tsx diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/tx/[id]/layout.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/tx/[id]/layout.tsx similarity index 97% rename from apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/tx/[id]/layout.tsx rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/tx/[id]/layout.tsx index f12a7f728bd..bfa7963dd88 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/tx/[id]/layout.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/tx/[id]/layout.tsx @@ -12,7 +12,7 @@ export default function TransactionLayout({
diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/tx/[id]/page.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/tx/[id]/page.tsx similarity index 84% rename from apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/tx/[id]/page.tsx rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/tx/[id]/page.tsx index 365e84a1215..68843aa1313 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/tx/[id]/page.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/tx/[id]/page.tsx @@ -1,6 +1,6 @@ import { notFound, redirect } from "next/navigation"; -import { getProject } from "../../../../../../../../@/api/projects"; -import { CodeServer } from "../../../../../../../../@/components/ui/code/code.server"; +import { getProject } from "../../../../../../../../../@/api/projects"; +import { CodeServer } from "../../../../../../../../../@/components/ui/code/code.server"; import { getSingleTransaction } from "../../lib/analytics"; export default async function TransactionPage({ diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/vault/components/create-vault-account.client.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/vault/components/create-vault-account.client.tsx similarity index 100% rename from apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/vault/components/create-vault-account.client.tsx rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/vault/components/create-vault-account.client.tsx diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/vault/components/key-management.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/vault/components/key-management.tsx similarity index 100% rename from apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/vault/components/key-management.tsx rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/vault/components/key-management.tsx diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/vault/components/list-access-tokens.client.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/vault/components/list-access-tokens.client.tsx similarity index 100% rename from apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/vault/components/list-access-tokens.client.tsx rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/vault/components/list-access-tokens.client.tsx diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/vault/components/rotate-admin-key.client.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/vault/components/rotate-admin-key.client.tsx similarity index 100% rename from apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/vault/components/rotate-admin-key.client.tsx rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/vault/components/rotate-admin-key.client.tsx diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/vault/page.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/vault/page.tsx similarity index 92% rename from apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/vault/page.tsx rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/vault/page.tsx index 149cb1dfa24..02857a44b72 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/vault/page.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/vault/page.tsx @@ -1,6 +1,6 @@ import { getProject } from "@/api/projects"; import { notFound } from "next/navigation"; -import { getAuthToken } from "../../../../../api/lib/getAuthToken"; +import { getAuthToken } from "../../../../../../api/lib/getAuthToken"; import { KeyManagement } from "./components/key-management"; export default async function VaultPage(props: { diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(general)/EngineFooterCard.stories.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(general)/EngineFooterCard.stories.tsx similarity index 95% rename from apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(general)/EngineFooterCard.stories.tsx rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(general)/EngineFooterCard.stories.tsx index 22a678e32f1..c4d3b3f55c2 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(general)/EngineFooterCard.stories.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(general)/EngineFooterCard.stories.tsx @@ -6,6 +6,7 @@ const meta = { component: EngineFooterCard, args: { team_slug: "demo-team", + project_slug: "demo-project", }, decorators: [ (Story) => ( diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(general)/_components.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(general)/_components.tsx similarity index 93% rename from apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(general)/_components.tsx rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(general)/_components.tsx index 8c6680521a8..0dc6e43b7a1 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(general)/_components.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(general)/_components.tsx @@ -27,8 +27,8 @@ export function ImportEngineLink(props: { ); } -function EngineInfoSection(props: { team_slug: string }) { - const engineLinkPrefix = `/team/${props.team_slug}/~/engine`; +function EngineInfoSection(props: { team_slug: string; project_slug: string }) { + const engineLinkPrefix = `/team/${props.team_slug}/${props.project_slug}/engine/dedicated`; return (
@@ -148,6 +148,7 @@ function CloudHostedEngineSection(props: { export function EngineFooterCard(props: { teamPlan: Team["billingPlan"]; team_slug: string; + project_slug: string; }) { return (
@@ -161,7 +162,10 @@ export function EngineFooterCard(props: { )} - +
); } diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(general)/import/EngineImportPage.stories.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(general)/import/EngineImportPage.stories.tsx similarity index 100% rename from apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(general)/import/EngineImportPage.stories.tsx rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(general)/import/EngineImportPage.stories.tsx diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(general)/import/EngineImportPage.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(general)/import/EngineImportPage.tsx similarity index 97% rename from apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(general)/import/EngineImportPage.tsx rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(general)/import/EngineImportPage.tsx index ea9f330bc2b..7854d32fc98 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(general)/import/EngineImportPage.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(general)/import/EngineImportPage.tsx @@ -55,6 +55,7 @@ async function importEngine({ export function EngineImportCard(props: { prefillImportUrl: string | undefined; teamSlug: string; + projectSlug: string; }) { const router = useDashboardRouter(); @@ -63,7 +64,9 @@ export function EngineImportCard(props: { prefillImportUrl={props.prefillImportUrl} importEngine={async (params) => { await importEngine({ ...params, teamIdOrSlug: props.teamSlug }); - router.push(`/team/${props.teamSlug}/~/engine`); + router.push( + `/team/${props.teamSlug}/${props.projectSlug}/engine/dedicated`, + ); }} /> ); diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(general)/import/page.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(general)/import/page.tsx similarity index 85% rename from apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(general)/import/page.tsx rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(general)/import/page.tsx index 89d16708101..bc9b371bdde 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(general)/import/page.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(general)/import/page.tsx @@ -1,7 +1,7 @@ import { EngineImportCard } from "./EngineImportPage"; export default async function Page(props: { - params: Promise<{ team_slug: string }>; + params: Promise<{ team_slug: string; project_slug: string }>; searchParams: Promise<{ importUrl?: string | string[]; }>; @@ -21,6 +21,7 @@ export default async function Page(props: {
); diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(general)/layout.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(general)/layout.tsx similarity index 72% rename from apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(general)/layout.tsx rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(general)/layout.tsx index 18f0eebd25e..645756a5376 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(general)/layout.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(general)/layout.tsx @@ -3,18 +3,20 @@ import { SidebarLayout } from "@/components/blocks/SidebarLayout"; import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; -import { CloudIcon } from "lucide-react"; +import { DatabaseIcon } from "lucide-react"; import Link from "next/link"; +import { EngineIcon } from "../../../../../../(dashboard)/(chain)/components/server/icons/EngineIcon"; import { ImportEngineLink } from "./_components"; export default async function Layout(props: { params: Promise<{ team_slug: string; + project_slug: string; }>; children: React.ReactNode; }) { const params = await props.params; - const linkPrefix = `/team/${params.team_slug}/~/engine`; + const linkPrefix = `/team/${params.team_slug}/${params.project_slug}/engine/dedicated`; const sidebarLinks: SidebarLink[] = [ { label: "Engine Instances", @@ -28,18 +30,22 @@ export default async function Layout(props: { ]; return ( -
+
{/* header */}

- Engines Legacy + Engine{" "} + + Dedicated +

- {/* TODO (cloud): add link to Engine Cloud blog post */} - The latest version of Engine has moved inside projects. Your - legacy engines will remain available here. + Manage your deployed Engine instances

@@ -51,7 +57,10 @@ export default async function Layout(props: {
- +
@@ -65,10 +74,11 @@ export default async function Layout(props: { function EngineLegacyBannerUI(props: { teamSlug: string; + projectSlug: string; }) { return ( - + Engine Cloud (Beta)
@@ -91,8 +101,12 @@ function EngineLegacyBannerUI(props: { > - - + +
diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(general)/overview/engine-instances-table.stories.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(general)/overview/engine-instances-table.stories.tsx similarity index 100% rename from apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(general)/overview/engine-instances-table.stories.tsx rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(general)/overview/engine-instances-table.stories.tsx diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(general)/overview/engine-instances-table.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(general)/overview/engine-instances-table.tsx similarity index 97% rename from apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(general)/overview/engine-instances-table.tsx rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(general)/overview/engine-instances-table.tsx index 71da032abfa..317b69fcfe3 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(general)/overview/engine-instances-table.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(general)/overview/engine-instances-table.tsx @@ -83,6 +83,7 @@ type RemovedEngineFromDashboard = ( export function EngineInstancesTable(props: { teamSlug: string; + projectSlug: string; instances: EngineInstance[]; engineLinkPrefix: string; teamPlan: Team["billingPlan"]; @@ -95,6 +96,7 @@ export function EngineInstancesTable(props: { instances={props.instances} engineLinkPrefix={props.engineLinkPrefix} teamSlug={props.teamSlug} + projectSlug={props.projectSlug} deleteCloudHostedEngine={async (params) => { await deleteCloudHostedEngine(params); router.refresh(); @@ -119,6 +121,7 @@ export function EngineInstancesTableUI(props: { removeEngineFromDashboard: RemovedEngineFromDashboard; teamPlan: Team["billingPlan"]; teamSlug: string; + projectSlug: string; }) { return (
@@ -127,14 +130,19 @@ export function EngineInstancesTableUI(props: { {props.instances.length === 0 ? ( - + ) : ( - Engine Instance - Actions + Engine Instance + Version + Actions @@ -177,7 +185,7 @@ function EngineInstanceRow(props: { >
-
+
+ + v2 + - + Import self-hosted Engine diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(general)/overview/engine-list.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(general)/overview/engine-list.tsx similarity index 68% rename from apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(general)/overview/engine-list.tsx rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(general)/overview/engine-list.tsx index 0c0a394cffa..7460dfb51e2 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(general)/overview/engine-list.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(general)/overview/engine-list.tsx @@ -5,22 +5,28 @@ import { EngineInstancesTable } from "./engine-instances-table"; export const EngineInstancesList = (props: { team_slug: string; + project_slug: string; instances: EngineInstance[]; teamPlan: Team["billingPlan"]; }) => { - const engineLinkPrefix = `/team/${props.team_slug}/~/engine`; + const engineLinkPrefix = `/team/${props.team_slug}/${props.project_slug}/engine/dedicated`; return (
- +
); }; diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(general)/page.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(general)/page.tsx similarity index 79% rename from apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(general)/page.tsx rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(general)/page.tsx index ee1946e0cd5..02e221729f7 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(general)/page.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(general)/page.tsx @@ -8,6 +8,7 @@ import { EngineInstancesList } from "./overview/engine-list"; export default async function Page(props: { params: Promise<{ team_slug: string; + project_slug: string; }>; searchParams: Promise<{ importUrl?: string; @@ -20,7 +21,7 @@ export default async function Page(props: { if (searchParams.importUrl) { redirect( - `/team/${params.team_slug}/~/engine/import?importUrl=${searchParams.importUrl}`, + `/team/${params.team_slug}/${params.project_slug}/engine/dedicated/import?importUrl=${searchParams.importUrl}`, ); } @@ -30,7 +31,9 @@ export default async function Page(props: { ]); if (!authToken) { - loginRedirect(`/team/${params.team_slug}/~/engine`); + loginRedirect( + `/team/${params.team_slug}/${params.project_slug}/engine/dedicated`, + ); } if (!team) { @@ -45,6 +48,7 @@ export default async function Page(props: { return ( diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/_components/EngineErrorPage.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/_components/EngineErrorPage.tsx similarity index 100% rename from apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/_components/EngineErrorPage.tsx rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/_components/EngineErrorPage.tsx diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/_components/EnginePageLayout.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/_components/EnginePageLayout.tsx similarity index 93% rename from apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/_components/EnginePageLayout.tsx rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/_components/EnginePageLayout.tsx index b8c7de2ef93..53c85d51209 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/_components/EnginePageLayout.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/_components/EnginePageLayout.tsx @@ -51,9 +51,10 @@ const sidebarLinkMeta: Array<{ pathId: string; label: string }> = [ export function EngineSidebarLayout(props: { engineId: string; teamSlug: string; + projectSlug: string; children: React.ReactNode; }) { - const rootPath = `/team/${props.teamSlug}/~/engine`; + const rootPath = `/team/${props.teamSlug}/${props.projectSlug}/engine/dedicated`; const links: SidebarLink[] = sidebarLinkMeta.map((linkMeta) => { return { diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/_components/EnsureEnginePermission.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/_components/EnsureEnginePermission.tsx similarity index 91% rename from apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/_components/EnsureEnginePermission.tsx rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/_components/EnsureEnginePermission.tsx index 1e3bfc6bd63..674f7afb817 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/_components/EnsureEnginePermission.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/_components/EnsureEnginePermission.tsx @@ -1,9 +1,9 @@ "use client"; +import { GenericLoadingPage } from "@/components/blocks/skeletons/GenericLoadingPage"; import type { EngineInstance } from "@3rdweb-sdk/react/hooks/useEngine"; import { useQuery } from "@tanstack/react-query"; import type React from "react"; -import GenericLoadingPage from "../../../../ecosystem/loading"; import { getEngineAccessPermission } from "../../../_utils/getEngineAccessPermission"; import { EngineErrorPage } from "./EngineErrorPage"; @@ -13,10 +13,11 @@ export function EnsureEnginePermission(props: { authToken: string; children: React.ReactNode; teamSlug: string; + projectSlug: string; instance: EngineInstance; }) { const { instance } = props; - const rootPath = `/team/${props.teamSlug}/~/engine`; + const rootPath = `/team/${props.teamSlug}/${props.projectSlug}/engine/dedicated`; const permissionQuery = useQuery({ queryKey: [ diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/_components/version.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/_components/version.tsx similarity index 100% rename from apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/_components/version.tsx rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/_components/version.tsx diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/access-tokens/components/access-tokens-table.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/access-tokens/components/access-tokens-table.tsx similarity index 100% rename from apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/access-tokens/components/access-tokens-table.tsx rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/access-tokens/components/access-tokens-table.tsx diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/access-tokens/components/add-access-token-button.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/access-tokens/components/add-access-token-button.tsx similarity index 100% rename from apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/access-tokens/components/add-access-token-button.tsx rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/access-tokens/components/add-access-token-button.tsx diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/access-tokens/components/add-keypair-button.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/access-tokens/components/add-keypair-button.tsx similarity index 100% rename from apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/access-tokens/components/add-keypair-button.tsx rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/access-tokens/components/add-keypair-button.tsx diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/access-tokens/components/engine-access-tokens.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/access-tokens/components/engine-access-tokens.tsx similarity index 100% rename from apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/access-tokens/components/engine-access-tokens.tsx rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/access-tokens/components/engine-access-tokens.tsx diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/access-tokens/components/keypairs-table.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/access-tokens/components/keypairs-table.tsx similarity index 100% rename from apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/access-tokens/components/keypairs-table.tsx rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/access-tokens/components/keypairs-table.tsx diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/access-tokens/page.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/access-tokens/page.tsx similarity index 93% rename from apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/access-tokens/page.tsx rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/access-tokens/page.tsx index f1dd2edd5d3..5d584ca1846 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/access-tokens/page.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/access-tokens/page.tsx @@ -7,6 +7,7 @@ export default async function Page(props: EngineInstancePageProps) { const { instance, authToken } = await engineInstancePageHandler({ engineId: params.engineId, teamSlug: params.team_slug, + projectSlug: params.project_slug, }); return ( diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/admins/components/add-admin-button.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/admins/components/add-admin-button.tsx similarity index 100% rename from apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/admins/components/add-admin-button.tsx rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/admins/components/add-admin-button.tsx diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/admins/components/admins-table.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/admins/components/admins-table.tsx similarity index 100% rename from apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/admins/components/admins-table.tsx rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/admins/components/admins-table.tsx diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/admins/components/engine-admins.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/admins/components/engine-admins.tsx similarity index 100% rename from apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/admins/components/engine-admins.tsx rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/admins/components/engine-admins.tsx diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/admins/page.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/admins/page.tsx similarity index 93% rename from apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/admins/page.tsx rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/admins/page.tsx index e3d235d774b..b62cfee33f2 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/admins/page.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/admins/page.tsx @@ -7,6 +7,7 @@ export default async function Page(props: EngineInstancePageProps) { const { instance, authToken } = await engineInstancePageHandler({ engineId: params.engineId, teamSlug: params.team_slug, + projectSlug: params.project_slug, }); return ; diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/alerts/components/EngineAlertDialogForm.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/alerts/components/EngineAlertDialogForm.tsx similarity index 100% rename from apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/alerts/components/EngineAlertDialogForm.tsx rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/alerts/components/EngineAlertDialogForm.tsx diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/alerts/components/EngineAlertsPage.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/alerts/components/EngineAlertsPage.tsx similarity index 100% rename from apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/alerts/components/EngineAlertsPage.tsx rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/alerts/components/EngineAlertsPage.tsx diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/alerts/components/EngineDeleteAlertModal.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/alerts/components/EngineDeleteAlertModal.tsx similarity index 100% rename from apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/alerts/components/EngineDeleteAlertModal.tsx rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/alerts/components/EngineDeleteAlertModal.tsx diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/alerts/components/ManageEngineAlerts.stories.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/alerts/components/ManageEngineAlerts.stories.tsx similarity index 100% rename from apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/alerts/components/ManageEngineAlerts.stories.tsx rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/alerts/components/ManageEngineAlerts.stories.tsx diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/alerts/components/ManageEngineAlerts.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/alerts/components/ManageEngineAlerts.tsx similarity index 100% rename from apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/alerts/components/ManageEngineAlerts.tsx rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/alerts/components/ManageEngineAlerts.tsx diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/alerts/components/RecentEngineAlerts.stories.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/alerts/components/RecentEngineAlerts.stories.tsx similarity index 100% rename from apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/alerts/components/RecentEngineAlerts.stories.tsx rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/alerts/components/RecentEngineAlerts.stories.tsx diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/alerts/components/RecentEngineAlerts.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/alerts/components/RecentEngineAlerts.tsx similarity index 100% rename from apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/alerts/components/RecentEngineAlerts.tsx rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/alerts/components/RecentEngineAlerts.tsx diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/alerts/page.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/alerts/page.tsx similarity index 93% rename from apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/alerts/page.tsx rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/alerts/page.tsx index 0a0329ac19a..f5465a0614e 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/alerts/page.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/alerts/page.tsx @@ -7,6 +7,7 @@ export default async function Page(props: EngineInstancePageProps) { const { instance } = await engineInstancePageHandler({ engineId: params.engineId, teamSlug: params.team_slug, + projectSlug: params.project_slug, }); return ( diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/configuration/components/circle-config.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/configuration/components/circle-config.tsx similarity index 100% rename from apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/configuration/components/circle-config.tsx rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/configuration/components/circle-config.tsx diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/configuration/components/cors.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/configuration/components/cors.tsx similarity index 100% rename from apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/configuration/components/cors.tsx rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/configuration/components/cors.tsx diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/configuration/components/engine-configuration.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/configuration/components/engine-configuration.tsx similarity index 82% rename from apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/configuration/components/engine-configuration.tsx rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/configuration/components/engine-configuration.tsx index 8c64552a3be..66c846fbc2e 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/configuration/components/engine-configuration.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/configuration/components/engine-configuration.tsx @@ -9,12 +9,14 @@ import { EngineSystem } from "./system"; interface EngineConfigurationProps { instance: EngineInstance; teamSlug: string; + projectSlug: string; authToken: string; } export const EngineConfiguration: React.FC = ({ instance, teamSlug, + projectSlug, authToken, }) => { return ( @@ -22,6 +24,7 @@ export const EngineConfiguration: React.FC = ({ @@ -29,7 +32,11 @@ export const EngineConfiguration: React.FC = ({ instanceUrl={instance.url} authToken={authToken} /> - +
); }; diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/configuration/components/engine-wallet-config.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/configuration/components/engine-wallet-config.tsx similarity index 96% rename from apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/configuration/components/engine-wallet-config.tsx rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/configuration/components/engine-wallet-config.tsx index 8bb3431cf02..3ec13885927 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/configuration/components/engine-wallet-config.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/configuration/components/engine-wallet-config.tsx @@ -23,12 +23,14 @@ import { LocalConfig } from "./local-config"; interface EngineWalletConfigProps { instance: EngineInstance; teamSlug: string; + projectSlug: string; authToken: string; } export const EngineWalletConfig: React.FC = ({ instance, teamSlug, + projectSlug, authToken, }) => { const { data: walletConfig } = useEngineWalletConfig({ @@ -72,7 +74,7 @@ export const EngineWalletConfig: React.FC = ({

Create backend wallets on the{" "} Overview diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/configuration/components/ip-allowlist.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/configuration/components/ip-allowlist.tsx similarity index 100% rename from apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/configuration/components/ip-allowlist.tsx rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/configuration/components/ip-allowlist.tsx diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/configuration/components/kms-aws-config.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/configuration/components/kms-aws-config.tsx similarity index 100% rename from apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/configuration/components/kms-aws-config.tsx rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/configuration/components/kms-aws-config.tsx diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/configuration/components/kms-gcp-config.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/configuration/components/kms-gcp-config.tsx similarity index 100% rename from apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/configuration/components/kms-gcp-config.tsx rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/configuration/components/kms-gcp-config.tsx diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/configuration/components/local-config.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/configuration/components/local-config.tsx similarity index 100% rename from apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/configuration/components/local-config.tsx rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/configuration/components/local-config.tsx diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/configuration/components/system.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/configuration/components/system.tsx similarity index 84% rename from apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/configuration/components/system.tsx rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/configuration/components/system.tsx index b348ce335ac..64705289f15 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/configuration/components/system.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/configuration/components/system.tsx @@ -7,14 +7,20 @@ import { interface EngineSystemProps { instance: EngineInstance; teamIdOrSlug: string; + projectSlug: string; } export const EngineSystem: React.FC = ({ instance, teamIdOrSlug, + projectSlug, }) => { const healthQuery = useEngineSystemHealth(instance.url); - const metricsQuery = useEngineSystemMetrics(instance.id, teamIdOrSlug); + const metricsQuery = useEngineSystemMetrics( + instance.id, + teamIdOrSlug, + projectSlug, + ); if (!healthQuery.data) { return null; } diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/configuration/page.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/configuration/page.tsx similarity index 88% rename from apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/configuration/page.tsx rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/configuration/page.tsx index 4480cb06e27..5d36e68e08b 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/configuration/page.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/configuration/page.tsx @@ -7,12 +7,14 @@ export default async function Page(props: EngineInstancePageProps) { const { instance, authToken } = await engineInstancePageHandler({ engineId: params.engineId, teamSlug: params.team_slug, + projectSlug: params.project_slug, }); return ( ); diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/contract-subscriptions/components/add-contract-subscription-button.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/contract-subscriptions/components/add-contract-subscription-button.tsx similarity index 100% rename from apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/contract-subscriptions/components/add-contract-subscription-button.tsx rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/contract-subscriptions/components/add-contract-subscription-button.tsx diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/contract-subscriptions/components/contract-subscriptions-table.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/contract-subscriptions/components/contract-subscriptions-table.tsx similarity index 100% rename from apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/contract-subscriptions/components/contract-subscriptions-table.tsx rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/contract-subscriptions/components/contract-subscriptions-table.tsx diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/contract-subscriptions/components/engine-contract-subscription.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/contract-subscriptions/components/engine-contract-subscription.tsx similarity index 100% rename from apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/contract-subscriptions/components/engine-contract-subscription.tsx rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/contract-subscriptions/components/engine-contract-subscription.tsx diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/contract-subscriptions/page.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/contract-subscriptions/page.tsx similarity index 94% rename from apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/contract-subscriptions/page.tsx rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/contract-subscriptions/page.tsx index 0e41b54826c..a7bbc8a3d67 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/contract-subscriptions/page.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/contract-subscriptions/page.tsx @@ -7,6 +7,7 @@ export default async function Page(props: EngineInstancePageProps) { const { instance, authToken } = await engineInstancePageHandler({ engineId: params.engineId, teamSlug: params.team_slug, + projectSlug: params.project_slug, }); return ( diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/explorer/components/engine-explorer.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/explorer/components/engine-explorer.tsx similarity index 100% rename from apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/explorer/components/engine-explorer.tsx rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/explorer/components/engine-explorer.tsx diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/explorer/components/swagger-ui.css b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/explorer/components/swagger-ui.css similarity index 100% rename from apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/explorer/components/swagger-ui.css rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/explorer/components/swagger-ui.css diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/explorer/page.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/explorer/page.tsx similarity index 93% rename from apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/explorer/page.tsx rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/explorer/page.tsx index 691b755db0f..4f265324ab9 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/explorer/page.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/explorer/page.tsx @@ -7,6 +7,7 @@ export default async function Page(props: EngineInstancePageProps) { const { instance, authToken } = await engineInstancePageHandler({ engineId: params.engineId, teamSlug: params.team_slug, + projectSlug: params.project_slug, }); return ; diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/layout.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/layout.tsx similarity index 91% rename from apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/layout.tsx rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/layout.tsx index 99850d1ad6d..6ed59afe4ce 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/layout.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/layout.tsx @@ -20,6 +20,7 @@ import { EngineVersionBadge } from "./_components/version"; export default async function Layout(props: { params: Promise<{ team_slug: string; + project_slug: string; engineId: string; }>; children: React.ReactNode; @@ -29,7 +30,9 @@ export default async function Layout(props: { const authToken = await getAuthToken(); if (!authToken) { - loginRedirect(`/team/${params.team_slug}/~/engine/${params.engineId}`); + loginRedirect( + `/team/${params.team_slug}/${params.project_slug}/engine/dedicated/${params.engineId}`, + ); } const instance = await getEngineInstance({ @@ -39,13 +42,14 @@ export default async function Layout(props: { accountId: account.id, }); - const engineRootLayoutPath = `/team/${params.team_slug}/~/engine`; + const engineRootLayoutPath = `/team/${params.team_slug}/${params.project_slug}/engine/dedicated`; if (!instance) { return ( Engine Instance Not Found @@ -66,9 +70,11 @@ export default async function Layout(props: { = ({ instance, teamSlug, + projectSlug, authToken, }) => { - const systemMetricsQuery = useEngineSystemMetrics(instance.id, teamSlug); + const systemMetricsQuery = useEngineSystemMetrics( + instance.id, + teamSlug, + projectSlug, + ); const queueMetricsQuery = useEngineQueueMetrics({ authToken, instanceUrl: instance.url, @@ -44,7 +50,7 @@ export const EngineSystemMetrics: React.FC = ({ Upgrade to a{" "} ); diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/overview/components/backend-wallets-table.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/overview/components/backend-wallets-table.tsx similarity index 100% rename from apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/overview/components/backend-wallets-table.tsx rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/overview/components/backend-wallets-table.tsx diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/overview/components/create-backend-wallet-button.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/overview/components/create-backend-wallet-button.tsx similarity index 97% rename from apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/overview/components/create-backend-wallet-button.tsx rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/overview/components/create-backend-wallet-button.tsx index 5c5c19764dd..a9383b3a093 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/overview/components/create-backend-wallet-button.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/overview/components/create-backend-wallet-button.tsx @@ -43,12 +43,13 @@ interface CreateBackendWalletButtonProps { instance: EngineInstance; walletConfig: WalletConfigResponse; teamSlug: string; + projectSlug: string; authToken: string; } export const CreateBackendWalletButton: React.FC< CreateBackendWalletButtonProps -> = ({ instance, walletConfig, teamSlug, authToken }) => { +> = ({ instance, walletConfig, teamSlug, projectSlug, authToken }) => { const { isSupported: supportsMultipleWalletTypes } = useHasEngineFeature( instance.url, "HETEROGENEOUS_WALLET_TYPES", @@ -192,7 +193,7 @@ export const CreateBackendWalletButton: React.FC< Provide your credentials on the{" "} Configuration @@ -249,7 +250,7 @@ export const CreateBackendWalletButton: React.FC< The ID of the Circle credential to use for this wallet. You can find this in the{" "} Wallet Credentials diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/overview/components/engine-overview.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/overview/components/engine-overview.tsx similarity index 93% rename from apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/overview/components/engine-overview.tsx rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/overview/components/engine-overview.tsx index 5e53bdd1c20..3a57117e23f 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/overview/components/engine-overview.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/overview/components/engine-overview.tsx @@ -19,12 +19,14 @@ import { TransactionCharts, TransactionsTable } from "./transactions-table"; interface EngineOverviewProps { instance: EngineInstance; teamSlug: string; + projectSlug: string; authToken: string; } export const EngineOverview: React.FC = ({ instance, teamSlug, + projectSlug, authToken, }) => { const client = useThirdwebClient(); @@ -34,6 +36,7 @@ export const EngineOverview: React.FC = ({ @@ -52,10 +55,11 @@ export const EngineOverview: React.FC = ({ function BackendWalletsSection(props: { instance: EngineInstance; teamSlug: string; + projectSlug: string; authToken: string; client: ThirdwebClient; }) { - const { instance, teamSlug, authToken } = props; + const { instance, teamSlug, projectSlug, authToken } = props; const activeWalletChain = useActiveWalletChain(); const [_chainId, setChainId] = useState(); const chainId = _chainId || activeWalletChain?.id || 1; @@ -85,7 +89,7 @@ function BackendWalletsSection(props: {

Set up other wallet types from the{" "} Configuration {" "} @@ -108,12 +112,14 @@ function BackendWalletsSection(props: { instance={instance} walletConfig={walletConfig} teamSlug={teamSlug} + projectSlug={projectSlug} authToken={authToken} />

diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/overview/components/import-backend-wallet-button.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/overview/components/import-backend-wallet-button.tsx similarity index 98% rename from apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/overview/components/import-backend-wallet-button.tsx rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/overview/components/import-backend-wallet-button.tsx index cd6f8243187..f1b0a62e3e6 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/overview/components/import-backend-wallet-button.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/overview/components/import-backend-wallet-button.tsx @@ -43,12 +43,13 @@ interface ImportBackendWalletButtonProps { instance: EngineInstance; walletConfig: WalletConfigResponse; teamSlug: string; + projectSlug: string; authToken: string; } export const ImportBackendWalletButton: React.FC< ImportBackendWalletButtonProps -> = ({ instance, walletConfig, teamSlug, authToken }) => { +> = ({ instance, walletConfig, teamSlug, projectSlug, authToken }) => { const { isSupported: supportsMultipleWalletTypes } = useHasEngineFeature( instance.url, "HETEROGENEOUS_WALLET_TYPES", @@ -196,7 +197,7 @@ export const ImportBackendWalletButton: React.FC< Provide your credentials on the{" "} Configuration diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/overview/components/transaction-timeline.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/overview/components/transaction-timeline.tsx similarity index 100% rename from apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/overview/components/transaction-timeline.tsx rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/overview/components/transaction-timeline.tsx diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/overview/components/transactions-table.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/overview/components/transactions-table.tsx similarity index 100% rename from apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/overview/components/transactions-table.tsx rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/overview/components/transactions-table.tsx diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/overview/components/utils.ts b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/overview/components/utils.ts similarity index 100% rename from apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/overview/components/utils.ts rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/overview/components/utils.ts diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/page.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/page.tsx similarity index 88% rename from apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/page.tsx rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/page.tsx index bd6ec62012c..b3bde12f86a 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/page.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/page.tsx @@ -7,12 +7,14 @@ export default async function Page(props: EngineInstancePageProps) { const { instance, authToken } = await engineInstancePageHandler({ engineId: params.engineId, teamSlug: params.team_slug, + projectSlug: params.project_slug, }); return ( ); diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/relayers/components/add-relayer-button.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/relayers/components/add-relayer-button.tsx similarity index 100% rename from apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/relayers/components/add-relayer-button.tsx rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/relayers/components/add-relayer-button.tsx diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/relayers/components/engine-relayer.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/relayers/components/engine-relayer.tsx similarity index 100% rename from apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/relayers/components/engine-relayer.tsx rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/relayers/components/engine-relayer.tsx diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/relayers/components/relayers-table.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/relayers/components/relayers-table.tsx similarity index 100% rename from apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/relayers/components/relayers-table.tsx rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/relayers/components/relayers-table.tsx diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/relayers/page.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/relayers/page.tsx similarity index 93% rename from apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/relayers/page.tsx rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/relayers/page.tsx index 3450919eb7e..c619554fda7 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/relayers/page.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/relayers/page.tsx @@ -7,6 +7,7 @@ export default async function Page(props: EngineInstancePageProps) { const { instance, authToken } = await engineInstancePageHandler({ engineId: params.engineId, teamSlug: params.team_slug, + projectSlug: params.project_slug, }); return ; diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/types.ts b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/types.ts similarity index 81% rename from apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/types.ts rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/types.ts index 5f004e8d3f8..40e7e65acc9 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/types.ts +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/types.ts @@ -1,6 +1,7 @@ export type EngineInstancePageProps = { params: Promise<{ team_slug: string; + project_slug: string; engineId: string; }>; }; diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/wallet-credentials/components/create-wallet-credential-button.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/wallet-credentials/components/create-wallet-credential-button.tsx similarity index 100% rename from apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/wallet-credentials/components/create-wallet-credential-button.tsx rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/wallet-credentials/components/create-wallet-credential-button.tsx diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/wallet-credentials/components/credential-form.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/wallet-credentials/components/credential-form.tsx similarity index 100% rename from apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/wallet-credentials/components/credential-form.tsx rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/wallet-credentials/components/credential-form.tsx diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/wallet-credentials/components/credential-type-fields/circle.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/wallet-credentials/components/credential-type-fields/circle.tsx similarity index 100% rename from apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/wallet-credentials/components/credential-type-fields/circle.tsx rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/wallet-credentials/components/credential-type-fields/circle.tsx diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/wallet-credentials/components/edit-wallet-credential-button.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/wallet-credentials/components/edit-wallet-credential-button.tsx similarity index 100% rename from apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/wallet-credentials/components/edit-wallet-credential-button.tsx rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/wallet-credentials/components/edit-wallet-credential-button.tsx diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/wallet-credentials/components/types.ts b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/wallet-credentials/components/types.ts similarity index 100% rename from apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/wallet-credentials/components/types.ts rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/wallet-credentials/components/types.ts diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/wallet-credentials/components/wallet-credentials-table.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/wallet-credentials/components/wallet-credentials-table.tsx similarity index 100% rename from apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/wallet-credentials/components/wallet-credentials-table.tsx rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/wallet-credentials/components/wallet-credentials-table.tsx diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/wallet-credentials/components/wallet-credentials.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/wallet-credentials/components/wallet-credentials.tsx similarity index 100% rename from apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/wallet-credentials/components/wallet-credentials.tsx rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/wallet-credentials/components/wallet-credentials.tsx diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/wallet-credentials/page.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/wallet-credentials/page.tsx similarity index 93% rename from apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/wallet-credentials/page.tsx rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/wallet-credentials/page.tsx index 21470354729..6b27e92d629 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/wallet-credentials/page.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/wallet-credentials/page.tsx @@ -7,6 +7,7 @@ export default async function Page(props: EngineInstancePageProps) { const { instance, authToken } = await engineInstancePageHandler({ engineId: params.engineId, teamSlug: params.team_slug, + projectSlug: params.project_slug, }); return ( diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/webhooks/components/add-webhook-button.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/webhooks/components/add-webhook-button.tsx similarity index 100% rename from apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/webhooks/components/add-webhook-button.tsx rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/webhooks/components/add-webhook-button.tsx diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/webhooks/components/engine-webhooks.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/webhooks/components/engine-webhooks.tsx similarity index 100% rename from apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/webhooks/components/engine-webhooks.tsx rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/webhooks/components/engine-webhooks.tsx diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/webhooks/components/webhooks-table.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/webhooks/components/webhooks-table.tsx similarity index 100% rename from apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/webhooks/components/webhooks-table.tsx rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/webhooks/components/webhooks-table.tsx diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/webhooks/page.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/webhooks/page.tsx similarity index 93% rename from apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/webhooks/page.tsx rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/webhooks/page.tsx index 4f6faccb348..d7afe756224 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/webhooks/page.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(instance)/[engineId]/webhooks/page.tsx @@ -7,6 +7,7 @@ export default async function Page(props: EngineInstancePageProps) { const { instance, authToken } = await engineInstancePageHandler({ engineId: params.engineId, teamSlug: params.team_slug, + projectSlug: params.project_slug, }); return ; diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/_utils/getEngineAccessPermission.ts b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/_utils/getEngineAccessPermission.ts similarity index 100% rename from apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/_utils/getEngineAccessPermission.ts rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/_utils/getEngineAccessPermission.ts diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/_utils/getEngineInstance.ts b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/_utils/getEngineInstance.ts similarity index 100% rename from apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/_utils/getEngineInstance.ts rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/_utils/getEngineInstance.ts diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/_utils/getEngineInstancePageMeta.ts b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/_utils/getEngineInstancePageMeta.ts similarity index 86% rename from apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/_utils/getEngineInstancePageMeta.ts rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/_utils/getEngineInstancePageMeta.ts index 5b3a787d2d2..c5221e9bb24 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/_utils/getEngineInstancePageMeta.ts +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/_utils/getEngineInstancePageMeta.ts @@ -6,9 +6,10 @@ import { getEngineInstance } from "./getEngineInstance"; export async function engineInstancePageHandler(params: { teamSlug: string; + projectSlug: string; engineId: string; }) { - const pagePath = `/team/${params.teamSlug}/~/engine/${params.engineId}/access-tokens`; + const pagePath = `/team/${params.teamSlug}/${params.projectSlug}/engine/dedicated/${params.engineId}/access-tokens`; const [authToken, account] = await Promise.all([ getAuthToken(), diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/_utils/getEngineInstances.ts b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/_utils/getEngineInstances.ts similarity index 100% rename from apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/engine/_utils/getEngineInstances.ts rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/_utils/getEngineInstances.ts diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/loading.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/loading.tsx new file mode 100644 index 00000000000..ddbf6bd8ca7 --- /dev/null +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/loading.tsx @@ -0,0 +1,5 @@ +import { GenericLoadingPage } from "@/components/blocks/skeletons/GenericLoadingPage"; + +export default function Loading() { + return ; +} diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/page.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/page.tsx index eb332671b6c..3f64aa75892 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/page.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/page.tsx @@ -1,17 +1,7 @@ import { getProject } from "@/api/projects"; -import { getTeamBySlug } from "@/api/team"; -import { THIRDWEB_VAULT_URL } from "@/constants/env"; -import { createVaultClient, listEoas } from "@thirdweb-dev/vault-sdk"; -import { notFound, redirect } from "next/navigation"; +import { redirect } from "next/navigation"; import { getAuthToken } from "../../../../api/lib/getAuthToken"; -import { TransactionsAnalyticsPageContent } from "./analytics/analytics-page"; -import { EngineChecklist } from "./analytics/ftux.client"; -import { TransactionAnalyticsSummary } from "./analytics/summary"; -import { - type TransactionSummaryData, - getTransactionAnalyticsSummary, -} from "./lib/analytics"; -import type { Wallet } from "./server-wallets/wallet-table/types"; +import { getEngineInstances } from "./dedicated/_utils/getEngineInstances"; export default async function TransactionsAnalyticsPage(props: { params: Promise<{ team_slug: string; project_slug: string }>; @@ -21,86 +11,39 @@ export default async function TransactionsAnalyticsPage(props: { interval?: string | string[] | undefined; }>; }) { - const [params, searchParams, authToken] = await Promise.all([ - props.params, - props.searchParams, - getAuthToken(), - ]); + const { team_slug, project_slug } = await props.params; + const authToken = await getAuthToken(); if (!authToken) { - notFound(); + redirect("/team"); } - const [team, project] = await Promise.all([ - getTeamBySlug(params.team_slug), - getProject(params.team_slug, params.project_slug), + const [project, engineInstances] = await Promise.all([ + getProject(team_slug, project_slug), + getEngineInstances({ authToken, teamIdOrSlug: team_slug }), ]); - if (!team) { - redirect("/team"); - } - if (!project) { - redirect(`/team/${params.team_slug}`); + redirect(`/team/${team_slug}`); } const projectEngineCloudService = project.services.find( (service) => service.name === "engineCloud", ); - const vaultClient = await createVaultClient({ - baseUrl: THIRDWEB_VAULT_URL, - }); - const managementAccessToken = projectEngineCloudService?.managementAccessToken; - const eoas = managementAccessToken - ? await listEoas({ - client: vaultClient, - request: { - auth: { - accessToken: managementAccessToken, - }, - options: {}, - }, - }) - : { data: { items: [] }, error: null, success: true }; - - const wallets = eoas.data?.items as Wallet[] | undefined; + // if we have a management access token, redirect to the engine cloud layout + if (managementAccessToken) { + redirect(`/team/${team_slug}/${project_slug}/engine/cloud`); + } - let initialData: TransactionSummaryData | undefined; - if (wallets && wallets.length > 0) { - const summary = await getTransactionAnalyticsSummary({ - teamId: project.teamId, - clientId: project.publishableKey, - }).catch(() => undefined); - initialData = summary; + // if we have any legacy engine instances, redirect to the legacy engine layout + if (engineInstances.data?.length && engineInstances.data.length > 0) { + redirect(`/team/${team_slug}/${project_slug}/engine/dedicated`); } - const hasTransactions = initialData ? initialData.totalCount > 0 : false; - return ( -
- - {hasTransactions && ( - - )} -
- -
- ); + // otherwise, redirect to the engine cloud layout + redirect(`/team/${team_slug}/${project_slug}/engine/cloud`); } diff --git a/apps/dashboard/src/app/(app)/team/components/TeamHeader/TeamAndProjectSelectorPopoverButton.tsx b/apps/dashboard/src/app/(app)/team/components/TeamHeader/TeamAndProjectSelectorPopoverButton.tsx index 1ac05bd0397..d38b57f9821 100644 --- a/apps/dashboard/src/app/(app)/team/components/TeamHeader/TeamAndProjectSelectorPopoverButton.tsx +++ b/apps/dashboard/src/app/(app)/team/components/TeamHeader/TeamAndProjectSelectorPopoverButton.tsx @@ -38,10 +38,15 @@ export function TeamAndProjectSelectorPopoverButton(props: TeamSwitcherProps) { return null; } - const projectsToShow = teamsAndProjects.find( + const teamProjects = teamsAndProjects.find( (x) => x.team.slug === projectsToShowOfTeam.slug, )?.projects; + // @TODO: HACK hide Engine projects from the list. + const projectsToShow = teamProjects?.filter( + (project) => !project.name.startsWith("Cloud-hosted Engine ("), + ); + return ( Date: Thu, 1 May 2025 12:24:04 +1200 Subject: [PATCH 078/101] fix links --- .../engine/cloud/analytics/analytics-page.tsx | 8 +++++++- .../engine/cloud/analytics/ftux.client.tsx | 7 +++++++ .../engine/cloud/analytics/tx-chart/tx-chart-ui.tsx | 12 ++++++++++-- .../engine/cloud/analytics/tx-chart/tx-chart.tsx | 5 +++++ .../engine/cloud/analytics/tx-table/tx-table-ui.tsx | 5 ++++- .../engine/cloud/analytics/tx-table/tx-table.tsx | 2 ++ .../[team_slug]/[project_slug]/engine/cloud/page.tsx | 2 ++ .../components/create-server-wallet.client.tsx | 9 +++++++-- .../engine/cloud/server-wallets/page.tsx | 1 + .../wallet-table/wallet-table-ui.client.tsx | 3 +++ .../server-wallets/wallet-table/wallet-table.tsx | 3 +++ .../[project_slug]/engine/cloud/tx/[id]/page.tsx | 4 ++-- 12 files changed, 53 insertions(+), 8 deletions(-) diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/analytics-page.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/analytics-page.tsx index b0373362a2d..767efedcae8 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/analytics-page.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/analytics-page.tsx @@ -14,6 +14,7 @@ export function TransactionsAnalyticsPageContent(props: { project: Project; hasTransactions: boolean; wallets?: Wallet[]; + teamSlug: string; }) { return ( @@ -27,10 +28,15 @@ export function TransactionsAnalyticsPageContent(props: { searchParams={props.searchParams} project={props.project} wallets={props.wallets ?? []} + teamSlug={props.teamSlug} /> )} - +
); diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/ftux.client.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/ftux.client.tsx index cb7e6eb2950..9b0d7e74610 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/ftux.client.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/ftux.client.tsx @@ -14,6 +14,7 @@ interface Props { project: Project; wallets: Wallet[]; hasTransactions: boolean; + teamSlug: string; } export const EngineChecklist: React.FC = (props) => { @@ -28,6 +29,7 @@ export const EngineChecklist: React.FC = (props) => { children: ( setUserAccessToken(token)} /> ), @@ -41,6 +43,7 @@ export const EngineChecklist: React.FC = (props) => { children: ( ), @@ -70,6 +73,7 @@ export const EngineChecklist: React.FC = (props) => { props.wallets, props.hasTransactions, userAccessToken, + props.teamSlug, ]); if (finalSteps.length === 1) { @@ -83,6 +87,7 @@ export const EngineChecklist: React.FC = (props) => { function CreateVaultAccountStep(props: { project: Project; + teamSlug: string; onUserAccessTokenCreated: (userAccessToken: string) => void; }) { return ( @@ -100,12 +105,14 @@ function CreateVaultAccountStep(props: { function CreateServerWalletStep(props: { project: Project; + teamSlug: string; managementAccessToken: string | undefined; }) { return (
diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/tx-chart/tx-chart-ui.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/tx-chart/tx-chart-ui.tsx index b3d9fbcb60b..cbacf041f37 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/tx-chart/tx-chart-ui.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/tx-chart/tx-chart-ui.tsx @@ -24,6 +24,7 @@ export function TransactionsChartCardUI(props: { isPending: boolean; project: Project; wallets: Wallet[]; + teamSlug: string; }) { const { userOpStats } = props; const topChainsToShow = 10; @@ -146,7 +147,11 @@ export function TransactionsChartCardUI(props: { }} toolTipValueFormatter={(value) => formatTickerNumber(Number(value))} emptyChartState={ - + } /> ); @@ -155,6 +160,7 @@ export function TransactionsChartCardUI(props: { // TODO - update the title and doc links function EmptyChartContent(props: { project: Project; + teamSlug: string; wallets: Wallet[]; }) { const router = useDashboardRouter(); @@ -177,7 +183,9 @@ function EmptyChartContent(props: { + ) : ( + + )} +
+ ) : ( +
Not available yet
+ )} +
+ + +
+
+ Network +
+
+ {chain ? ( +
+ + {chain.name} +
+ ) : ( +
Chain ID: {chainId || "Unknown"}
+ )} +
+
+ + + + + Transaction Parameters + + + {transaction.transactionParams && + transaction.transactionParams.length > 0 ? ( + + ) : ( +

+ No transaction parameters available +

+ )} +
+
+ {errorMessage && ( + + + + Error Details + + + +
+ {errorMessage} +
+
+
+ )} + + + Sender Information + + +
+
+ Sender Address +
+
+ +
+
+ +
+
+ Signer Address +
+
+ +
+
+
+
+ + + Timing Information + + +
+
+ Created At +
+
+ {createdAt ? ( +

+ {formatDistanceToNowStrict(new Date(createdAt), { + addSuffix: true, + })}{" "} + ({format(new Date(createdAt), "PP pp z")}) +

+ ) : ( + "N/A" + )} +
+
+ +
+
+ Confirmed At +
+
+ {confirmedAt ? ( +

+ {formatDistanceToNowStrict(new Date(confirmedAt), { + addSuffix: true, + })}{" "} + ({format(new Date(confirmedAt), "PP pp z")}) +

+ ) : ( + "Pending" + )} +
+
+ + {confirmationTime && ( +
+
+ Confirmation Time +
+
+ {Math.floor(confirmationTime / 1000)} seconds +
+
+ )} +
+
+ + + Gas Information + + +
+
+ Gas Used +
+
{gasUsed}
+
+ +
+
+ Gas Cost +
+
{gasCost}
+
+ + {transaction.confirmedAtBlockNumber && ( +
+
+ Block Number +
+
+ {transaction.confirmedAtBlockNumber} +
+
+ )} +
+
+ + + ); +} From 248816ed394f432e0834ad83bd772e62733f3e87 Mon Sep 17 00:00:00 2001 From: Joaquim Verges Date: Thu, 1 May 2025 13:35:06 +1200 Subject: [PATCH 080/101] lint --- .../engine/cloud/analytics/tx-table/types.ts | 25 +--------- .../cloud/tx/[id]/transaction-details-ui.tsx | 48 +++++++++---------- 2 files changed, 25 insertions(+), 48 deletions(-) diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/tx-table/types.ts b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/tx-table/types.ts index eebe8066ffe..319dff34b1d 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/tx-table/types.ts +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/tx-table/types.ts @@ -80,30 +80,7 @@ export type TransactionStatus = | "CONFIRMED" | "REVERTED"; -export type TransactionParam = { - to: string; - data: string; - value: string; -}; - -export type ExecutionParams = { - type: string; - signerAddress: string; - entrypointAddress: string; - smartAccountAddress: string; -}; - -export type ExecutionResult = { - status: TransactionStatus; - nonce?: string; - userOpHash?: string; - actualGasCost?: string; - actualGasUsed?: string; - onchainStatus?: string; - transactionHash?: string; -}; - -export type Pagination = { +type Pagination = { totalCount: string; page: number; limit: number; diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/tx/[id]/transaction-details-ui.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/tx/[id]/transaction-details-ui.tsx index b3c620bc3c1..739f9ba6886 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/tx/[id]/transaction-details-ui.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/tx/[id]/transaction-details-ui.tsx @@ -206,6 +206,30 @@ export function TransactionDetailsUI({ + + + Sender Information + + +
+
+ Sender Address +
+
+ +
+
+ +
+
+ Signer Address +
+
+ +
+
+
+
Transaction Parameters @@ -238,30 +262,6 @@ export function TransactionDetailsUI({ )} - - - Sender Information - - -
-
- Sender Address -
-
- -
-
- -
-
- Signer Address -
-
- -
-
-
-
Timing Information From 537d3abb689e111c0b89056ca5883d558ee261d6 Mon Sep 17 00:00:00 2001 From: Prithvish Baidya Date: Fri, 2 May 2025 10:26:56 +0530 Subject: [PATCH 081/101] add isRotated field to access token data --- packages/vault-sdk/src/types.ts | 41 +++++++++------------------------ 1 file changed, 11 insertions(+), 30 deletions(-) diff --git a/packages/vault-sdk/src/types.ts b/packages/vault-sdk/src/types.ts index 2d9b13587f8..5456b2a5d32 100644 --- a/packages/vault-sdk/src/types.ts +++ b/packages/vault-sdk/src/types.ts @@ -403,7 +403,7 @@ type SignTypedDataData = { signature: string; }; -type CreateAccessTokenData = { +export type AccessTokenData = { accessToken: string; id: string; // UUID issuerId: string; // UUID @@ -413,19 +413,15 @@ type CreateAccessTokenData = { metadata: Record; createdAt: string; // ISO date string updatedAt: string; // ISO date string + /** + * If nullish then token hasn't been explicitly revoked, otherwise the ISO date string of the revocation. + * Note that an access token will be "implicitly" revoked if the token issuer account gets rotated. Check the isRotated field for this + */ revokedAt?: string; // ISO date string -}; - -type RevokeAccessTokenData = { - id: string; // UUID - issuerId: string; // UUID - issuerType: OwnerType; - policies: PolicyComponent[]; - expiresAt: string; // ISO date string - metadata: Record; - createdAt: string; // ISO date string - updatedAt: string; // ISO date string - revokedAt?: string; // ISO date string + /** + * Reflects whether the issuer of this token has "rotated" their account, which implicitly revokes the token, but is not tracked by the revokedAt? field. + */ + isRotated: boolean; }; // Update SignAuthorizationData to use the defined SignedAuthorization type @@ -439,21 +435,6 @@ export type SignStructuredMessageData = { message: string; // This likely represents the UserOp hash in Rust }; -// Add AccessTokenData (as defined previously, ensure OwnerType/MetadataValue are correct) -export type AccessTokenData = { - id: string; // UUID - issuerId: string; // UUID - // Only revealed if revealSensitive is true for the policy being used to read, otherwise redacted/masked - accessToken: string; - issuerType: OwnerType; - policies: PolicyComponent[]; - expiresAt: string; // ISO date string - metadata: Record; - createdAt: string; // ISO date string - updatedAt: string; // ISO date string - revokedAt?: string | null; // ISO date string or null -}; - // Add GetAccessTokensData (as defined previously) export type GetAccessTokensData = { items: AccessTokenData[]; @@ -555,7 +536,7 @@ export type CreateAccessTokenPayload = GenericPayload<{ operation: "accessToken:create"; auth: Auth; options: CreateAccessTokenOptions; - data: CreateAccessTokenData; + data: AccessTokenData; }>; // Add ListAccessTokensPayload (using defined types) @@ -569,7 +550,7 @@ export type RevokeAccessTokenPayload = GenericPayload<{ operation: "accessToken:revoke"; auth: Auth; options: RevokeAccessTokenOptions; - data: RevokeAccessTokenData; + data: AccessTokenData; }>; // ========== Union of all payloads ========== From 3c33120432a1b069634862951810ce929a846c7f Mon Sep 17 00:00:00 2001 From: Prithvish Baidya Date: Fri, 2 May 2025 11:57:32 +0530 Subject: [PATCH 082/101] fix type --- .../[project_slug]/engine/cloud/analytics/tx-table/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/tx-table/types.ts b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/tx-table/types.ts index 319dff34b1d..8c2daacd52d 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/tx-table/types.ts +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/tx-table/types.ts @@ -81,7 +81,7 @@ export type TransactionStatus = | "REVERTED"; type Pagination = { - totalCount: string; + totalCount: number; page: number; limit: number; }; From e8a8c1361d90e17cd2f767e9511b5bf985221d5c Mon Sep 17 00:00:00 2001 From: Prithvish Baidya Date: Fri, 2 May 2025 12:11:20 +0530 Subject: [PATCH 083/101] fix more types --- .../[project_slug]/engine/cloud/lib/analytics.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/lib/analytics.ts b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/lib/analytics.ts index 05020cfeda5..45f5426ed58 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/lib/analytics.ts +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/lib/analytics.ts @@ -1,6 +1,7 @@ import { THIRDWEB_ENGINE_CLOUD_URL } from "@/constants/env"; import type { TransactionStats } from "../../../../../../../../types/analytics"; import { getAuthToken } from "../../../../../../api/lib/getAuthToken"; +import type { Address, Hex } from "thirdweb"; // Define the structure of the data we expect back from our fetch function export type TransactionSummaryData = { @@ -147,8 +148,8 @@ export async function getTransactionsChart({ } type TransactionParam = { - to: string; - data: string; + to: Address; + data: Hex; value: string; }; @@ -167,9 +168,9 @@ type Transaction = { id: string; batchIndex: number; chainId: string; - from: string; + from: Address; transactionParams: TransactionParam[]; - transactionHash: string | null; + transactionHash: Hex | null; confirmedAt: string | null; confirmedAtBlockNumber: number | null; enrichedData: unknown[]; From 7acf7783415aba1a64e9dae3995ac420850bf3aa Mon Sep 17 00:00:00 2001 From: Joaquim Verges Date: Fri, 2 May 2025 21:43:37 +1200 Subject: [PATCH 084/101] remove log --- .../[project_slug]/engine/cloud/lib/vault.client.ts | 7 ------- 1 file changed, 7 deletions(-) diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/lib/vault.client.ts b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/lib/vault.client.ts index 225f87bed3c..4c40deb0ca5 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/lib/vault.client.ts +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/lib/vault.client.ts @@ -289,13 +289,6 @@ export async function createManagementAccessToken(props: { }); if (res.success) { const data = res.data; - console.log({ - name: "engineCloud", - managementAccessToken: data.accessToken, - maskedAdminKey: maskSecret(props.adminKey), - rotationCode: props.rotationCode, - actions: [], - }); // store the management access token in the project await updateProjectClient( { From 44d7f6eabc1fcc84bee13ba6e5ddcad929e86899 Mon Sep 17 00:00:00 2001 From: Joaquim Verges Date: Fri, 2 May 2025 21:58:30 +1200 Subject: [PATCH 085/101] fix types --- .../cloud/analytics/send-test-tx.client.tsx | 3 +- .../engine/cloud/lib/analytics.ts | 56 ++----------------- 2 files changed, 7 insertions(+), 52 deletions(-) diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/send-test-tx.client.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/send-test-tx.client.tsx index 9f504e8376e..fff3fbe7412 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/send-test-tx.client.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/send-test-tx.client.tsx @@ -77,8 +77,7 @@ export function SendTestTransaction(props: { }, body: JSON.stringify({ executionOptions: { - type: "AA", - signerAddress: args.walletAddress, + from: args.walletAddress, chainId: args.chainId.toString(), }, params: [ diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/lib/analytics.ts b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/lib/analytics.ts index 45f5426ed58..e926c2f55cb 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/lib/analytics.ts +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/lib/analytics.ts @@ -1,7 +1,10 @@ import { THIRDWEB_ENGINE_CLOUD_URL } from "@/constants/env"; import type { TransactionStats } from "../../../../../../../../types/analytics"; import { getAuthToken } from "../../../../../../api/lib/getAuthToken"; -import type { Address, Hex } from "thirdweb"; +import type { + Transaction, + TransactionsResponse, +} from "../analytics/tx-table/types"; // Define the structure of the data we expect back from our fetch function export type TransactionSummaryData = { @@ -147,53 +150,6 @@ export async function getTransactionsChart({ })); } -type TransactionParam = { - to: Address; - data: Hex; - value: string; -}; - -type ExecutionParams = { - type: string; - signerAddress: string; - entrypointAddress: string; - smartAccountAddress: string; -}; - -type ExecutionResult = { - status: string; -}; - -type Transaction = { - id: string; - batchIndex: number; - chainId: string; - from: Address; - transactionParams: TransactionParam[]; - transactionHash: Hex | null; - confirmedAt: string | null; - confirmedAtBlockNumber: number | null; - enrichedData: unknown[]; - executionParams: ExecutionParams; - executionResult: ExecutionResult; - createdAt: string; - errorMessage: string | null; - cancelledAt: string | null; -}; - -type Pagination = { - totalCount: string; - page: number; - limit: number; -}; - -type TransactionsSearchResponse = { - result: { - transactions: Transaction[]; - pagination: Pagination; - }; -}; - export async function getSingleTransaction({ teamId, clientId, @@ -240,7 +196,7 @@ export async function getSingleTransaction({ ); } - const data = (await response.json()) as TransactionsSearchResponse; + const data = (await response.json()).result as TransactionsResponse; - return data.result.transactions[0]; + return data.transactions[0]; } From 634ab5d14a405167fad5b60efd0c1c4ef67a3ad6 Mon Sep 17 00:00:00 2001 From: Joaquim Verges Date: Sat, 3 May 2025 00:37:04 +1200 Subject: [PATCH 086/101] cache SA, filter rotated access tokens --- .../components/ProjectSidebarLayout.tsx | 7 +++- .../engine/cloud/analytics/analytics-page.tsx | 4 +- .../engine/cloud/analytics/ftux.client.tsx | 14 +++++++ .../cloud/analytics/send-test-tx.client.tsx | 25 ++++++++++-- .../[project_slug]/engine/cloud/page.tsx | 6 ++- .../create-server-wallet.client.tsx | 17 ++++++++ .../server-wallets/components/try-it-out.tsx | 20 ++++------ .../wallet-table/wallet-table-ui.client.tsx | 40 +++++++++++++++++-- .../components/list-access-tokens.client.tsx | 2 +- 9 files changed, 108 insertions(+), 27 deletions(-) diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/components/ProjectSidebarLayout.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/components/ProjectSidebarLayout.tsx index 7e4292d9b77..cecd6ec482d 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/components/ProjectSidebarLayout.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/components/ProjectSidebarLayout.tsx @@ -12,6 +12,7 @@ import { EngineIcon } from "../../../../(dashboard)/(chain)/components/server/ic import { InsightIcon } from "../../../../(dashboard)/(chain)/components/server/icons/InsightIcon"; import { PayIcon } from "../../../../(dashboard)/(chain)/components/server/icons/PayIcon"; import { SmartAccountIcon } from "../../../../(dashboard)/(chain)/components/server/icons/SmartAccountIcon"; +import { Badge } from "../../../../../../@/components/ui/badge"; import { NebulaIcon } from "../../../../../nebula-app/(app)/icons/NebulaIcon"; export function ProjectSidebarLayout(props: { @@ -62,7 +63,11 @@ export function ProjectSidebarLayout(props: { }, { href: `${layoutPath}/engine`, - label: "Engine", + label: ( + + Engine New + + ), icon: EngineIcon, tracking: tracking("engine"), }, diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/analytics-page.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/analytics-page.tsx index 767efedcae8..8a8dfd4c97d 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/analytics-page.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/analytics-page.tsx @@ -12,14 +12,14 @@ export function TransactionsAnalyticsPageContent(props: { interval?: string | undefined | string[]; }; project: Project; - hasTransactions: boolean; + showAnalytics: boolean; wallets?: Wallet[]; teamSlug: string; }) { return (
- {props.hasTransactions && ( + {props.showAnalytics && ( <>
diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/ftux.client.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/ftux.client.tsx index 9b0d7e74610..2fabb70b740 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/ftux.client.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/ftux.client.tsx @@ -15,6 +15,7 @@ interface Props { wallets: Wallet[]; hasTransactions: boolean; teamSlug: string; + testTxWithWallet?: string | undefined; } export const EngineChecklist: React.FC = (props) => { @@ -60,6 +61,7 @@ export const EngineChecklist: React.FC = (props) => { wallets={props.wallets} project={props.project} userAccessToken={userAccessToken} + teamSlug={props.teamSlug} /> ), completed: props.hasTransactions, @@ -76,6 +78,18 @@ export const EngineChecklist: React.FC = (props) => { props.teamSlug, ]); + if (props.testTxWithWallet) { + return ( + + ); + } + if (finalSteps.length === 1) { return null; } diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/send-test-tx.client.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/send-test-tx.client.tsx index fff3fbe7412..c0be7ec332e 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/send-test-tx.client.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/send-test-tx.client.tsx @@ -40,8 +40,10 @@ type FormValues = z.infer; export function SendTestTransaction(props: { wallets?: Wallet[]; project: Project; + teamSlug: string; userAccessToken?: string; expanded?: boolean; + walletId?: string; }) { const thirdwebClient = useThirdwebClient(); const queryClient = useQueryClient(); @@ -52,7 +54,13 @@ export function SendTestTransaction(props: { resolver: zodResolver(formSchema), defaultValues: { accessToken: props.userAccessToken ?? "", - walletIndex: "0", + walletIndex: + props.wallets && props.walletId + ? props.wallets + .findIndex((w) => w.id === props.walletId) + ?.toString() + .replace("-1", "0") + : "0", chainId: 84532, }, }); @@ -126,6 +134,9 @@ export function SendTestTransaction(props: { return (
+ {props.walletId && ( +

Send a test transaction

+ )}

Every wallet action requires your Vault access token. @@ -182,7 +193,7 @@ export function SendTestTransaction(props: {

- {selectedWallet.metadata.label} + {wallet.metadata.label}
@@ -251,10 +262,16 @@ export function SendTestTransaction(props: { variant="primary" className="w-full md:w-auto" onClick={() => { - router.refresh(); + if (props.walletId) { + router.replace( + `/team/${props.teamSlug}/${props.project.slug}/engine/cloud`, + ); + } else { + router.refresh(); + } }} > - Complete Setup + {props.walletId ? "Close" : "Complete Setup"} )}
diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/page.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/page.tsx index 2fbe2b21b2c..fb9877f228e 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/page.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/page.tsx @@ -18,6 +18,7 @@ export default async function TransactionsAnalyticsPage(props: { from?: string | string[] | undefined; to?: string | string[] | undefined; interval?: string | string[] | undefined; + testTxWithWallet?: string | string[] | undefined; }>; }) { const [params, searchParams, authToken] = await Promise.all([ @@ -84,8 +85,9 @@ export default async function TransactionsAnalyticsPage(props: { hasTransactions={hasTransactions} project={project} wallets={wallets ?? []} + testTxWithWallet={searchParams.testTxWithWallet as string | undefined} /> - {hasTransactions && ( + {hasTransactions && !searchParams.testTxWithWallet && (
diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/server-wallets/components/create-server-wallet.client.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/server-wallets/components/create-server-wallet.client.tsx index 323789e78b7..12471a64292 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/server-wallets/components/create-server-wallet.client.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/server-wallets/components/create-server-wallet.client.tsx @@ -15,6 +15,7 @@ import { createEoa } from "@thirdweb-dev/vault-sdk"; import { Loader2, WalletIcon } from "lucide-react"; import { useState } from "react"; import { toast } from "sonner"; +import { engineCloudProxy } from "../../../../../../../../../@/actions/proxies"; import { initVaultClient } from "../../lib/vault.client"; export default function CreateServerWallet(props: { @@ -57,6 +58,22 @@ export default function CreateServerWallet(props: { throw new Error("Failed to create eoa"); } + // no need to await this, it's not blocking + engineCloudProxy({ + pathname: "/cache/smart-account", + method: "POST", + headers: { + "Content-Type": "application/json", + "x-team-id": props.project.teamId, + "x-client-id": props.project.publishableKey, + }, + body: JSON.stringify({ + signerAddress: eoa.data.address, + }), + }).catch((err) => { + console.warn("failed to cache server wallet", err); + }); + router.refresh(); setModalOpen(false); diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/server-wallets/components/try-it-out.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/server-wallets/components/try-it-out.tsx index f855c6ed92b..2fbe7e700b8 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/server-wallets/components/try-it-out.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/server-wallets/components/try-it-out.tsx @@ -113,8 +113,7 @@ curl -X POST "${THIRDWEB_ENGINE_CLOUD_URL}/write/contract" \\ -H "x-vault-access-token: " \\ -d '{ "executionOptions": { - "type": "AA", - "signerAddress": "", + "from": "", "chainId": "84532" }, "params": [ @@ -138,8 +137,7 @@ const response = await fetch( }, body: JSON.stringify({ "executionOptions": { - "type": "AA", - "signerAddress": "", + "from": "", "chainId": "84532" }, "params": [ @@ -165,8 +163,7 @@ headers = { } payload = { "executionOptions": { - "type": "AA", - "signerAddress": "", + "from": "", "chainId": "84532" }, "params": [ @@ -203,9 +200,8 @@ func main() { type RequestBody struct { ExecutionOptions struct { - Type string \`json:"type"\` - SignerAddress string \`json:"signerAddress"\` - ChainId string \`json:"chainId"\` + From string \`json:"from"\` + ChainId string \`json:"chainId"\` } \`json:"executionOptions"\` Params []Param \`json:"params"\` } @@ -219,8 +215,7 @@ func main() { }, }, } - requestBody.ExecutionOptions.Type = "AA" - requestBody.ExecutionOptions.SignerAddress = "" + requestBody.ExecutionOptions.From = "" requestBody.ExecutionOptions.ChainId = "84532" jsonData, _ := json.Marshal(requestBody) @@ -263,8 +258,7 @@ class Program { executionOptions = new { - type = "AA", - signerAddress = "", + from = "", chainId = "84532" }, @params = new[] diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/server-wallets/wallet-table/wallet-table-ui.client.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/server-wallets/wallet-table/wallet-table-ui.client.tsx index 7cc9304fc2b..034df03d40d 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/server-wallets/wallet-table/wallet-table-ui.client.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/server-wallets/wallet-table/wallet-table-ui.client.tsx @@ -19,11 +19,14 @@ import { getThirdwebClient } from "@/constants/thirdweb.server"; import { useQuery } from "@tanstack/react-query"; import { formatDistanceToNowStrict } from "date-fns"; import { format } from "date-fns/format"; +import { SendIcon } from "lucide-react"; import { useState } from "react"; import { DEFAULT_ACCOUNT_FACTORY_V0_7, predictSmartAccountAddress, } from "thirdweb/wallets/smart"; +import { Button } from "../../../../../../../../../@/components/ui/button"; +import { useDashboardRouter } from "../../../../../../../../../@/lib/DashboardRouter"; import { useV5DashboardChain } from "../../../../../../../../../lib/v5-adapter"; import CreateServerWallet from "../components/create-server-wallet.client"; import type { Wallet } from "./types"; @@ -76,8 +79,9 @@ export function ServerWalletsTableUI({ Address Label - Created At - Updated At + Created At + Updated At + Send test tx @@ -98,12 +102,19 @@ export function ServerWalletsTableUI({ )} {wallet.metadata.label || "none"} - + - + + + + )) )} @@ -157,3 +168,24 @@ function WalletDateCell({ date }: { date: string }) { ); } + +function SendTestTransaction(props: { + wallet: Wallet; + teamSlug: string; + project: Project; +}) { + const router = useDashboardRouter(); + return ( + + ); +} diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/vault/components/list-access-tokens.client.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/vault/components/list-access-tokens.client.tsx index af2f31558d1..4cfc1b2d6cd 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/vault/components/list-access-tokens.client.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/vault/components/list-access-tokens.client.tsx @@ -143,7 +143,7 @@ export default function ListAccessTokens(props: { t.metadata?.purpose?.toString() !== SERVER_WALLET_MANAGEMENT_ACCESS_TOKEN_PURPOSE, ) - .filter((t) => !t.revokedAt), + .filter((t) => !t.revokedAt && !t.isRotated), }; // Return stub data for now }, From 824afaa6abdb66efcd1bc18038310770a988e41b Mon Sep 17 00:00:00 2001 From: Joaquim Verges Date: Mon, 5 May 2025 00:36:25 +1200 Subject: [PATCH 087/101] sdk support --- .../cloud/analytics/tx-table/tx-table-ui.tsx | 4 + .../engine/cloud/analytics/tx-table/types.ts | 3 +- .../TDoc/utils/getSidebarLinkgroups.ts | 2 + package.json | 10 +- packages/engine/CHANGELOG.md | 33 + packages/engine/README.md | 29 + packages/engine/biome.json | 7 + packages/engine/openapi-ts.config.ts | 7 + packages/engine/package.json | 57 ++ packages/engine/src/client/client.gen.ts | 28 + packages/engine/src/client/index.ts | 3 + packages/engine/src/client/sdk.gen.ts | 348 +++++++ packages/engine/src/client/types.gen.ts | 862 ++++++++++++++++++ packages/engine/src/configure.ts | 19 + packages/engine/src/exports/thirdweb.ts | 3 + packages/engine/tsconfig.base.json | 47 + packages/engine/tsconfig.build.json | 16 + packages/engine/tsconfig.json | 13 + packages/insight/package.json | 2 +- packages/insight/src/configure.ts | 2 +- packages/thirdweb/package.json | 86 +- packages/thirdweb/scripts/typedoc.mjs | 1 + packages/thirdweb/src/engine/index.ts | 1 + .../thirdweb/src/engine/server-wallet.test.ts | 161 ++++ packages/thirdweb/src/engine/server-wallet.ts | 311 +++++++ packages/thirdweb/src/exports/engine.ts | 1 + packages/thirdweb/src/exports/thirdweb.ts | 5 + packages/thirdweb/src/utils/domains.ts | 9 + packages/thirdweb/src/utils/fetch.ts | 15 +- packages/thirdweb/src/wallets/engine/index.ts | 1 + .../src/wallets/smart/lib/paymaster.ts | 4 +- packages/thirdweb/tsdoc.json | 7 +- pnpm-lock.yaml | 83 +- 33 files changed, 2076 insertions(+), 104 deletions(-) create mode 100644 packages/engine/CHANGELOG.md create mode 100644 packages/engine/README.md create mode 100644 packages/engine/biome.json create mode 100644 packages/engine/openapi-ts.config.ts create mode 100644 packages/engine/package.json create mode 100644 packages/engine/src/client/client.gen.ts create mode 100644 packages/engine/src/client/index.ts create mode 100644 packages/engine/src/client/sdk.gen.ts create mode 100644 packages/engine/src/client/types.gen.ts create mode 100644 packages/engine/src/configure.ts create mode 100644 packages/engine/src/exports/thirdweb.ts create mode 100644 packages/engine/tsconfig.base.json create mode 100644 packages/engine/tsconfig.build.json create mode 100644 packages/engine/tsconfig.json create mode 100644 packages/thirdweb/src/engine/index.ts create mode 100644 packages/thirdweb/src/engine/server-wallet.test.ts create mode 100644 packages/thirdweb/src/engine/server-wallet.ts create mode 100644 packages/thirdweb/src/exports/engine.ts diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/tx-table/tx-table-ui.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/tx-table/tx-table-ui.tsx index 9dd432f4e34..c8adcd0882e 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/tx-table/tx-table-ui.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/tx-table/tx-table-ui.tsx @@ -220,6 +220,10 @@ const statusDetails = { name: "Reverted", type: "destructive", }, + FAILED: { + name: "Failed", + type: "destructive", + }, } as const; function StatusSelector(props: { diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/tx-table/types.ts b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/tx-table/types.ts index 8c2daacd52d..66552766fc2 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/tx-table/types.ts +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/tx-table/types.ts @@ -78,7 +78,8 @@ export type TransactionStatus = | "QUEUED" | "SUBMITTED" | "CONFIRMED" - | "REVERTED"; + | "REVERTED" + | "FAILED"; type Pagination = { totalCount: number; diff --git a/apps/portal/src/app/references/components/TDoc/utils/getSidebarLinkgroups.ts b/apps/portal/src/app/references/components/TDoc/utils/getSidebarLinkgroups.ts index 8c2cc36c62e..3a8efb43d57 100644 --- a/apps/portal/src/app/references/components/TDoc/utils/getSidebarLinkgroups.ts +++ b/apps/portal/src/app/references/components/TDoc/utils/getSidebarLinkgroups.ts @@ -45,6 +45,7 @@ const tagsToGroup = { "@account": "Account", "@nebula": "Nebula", "@insight": "Insight", + "@engine": "Engine", } as const; type TagKey = keyof typeof tagsToGroup; @@ -63,6 +64,7 @@ const sidebarGroupOrder: TagKey[] = [ "@contract", "@transaction", "@insight", + "@engine", "@bridge", "@nebula", "@social", diff --git a/package.json b/package.json index 2bfb307df2a..521258a96a7 100644 --- a/package.json +++ b/package.json @@ -16,13 +16,13 @@ "storybook:react": "turbo run storybook --filter=./packages/*", "prefix": "pnpm dlx sherif@latest -i remark-gfm -i eslint --fix", "fix": "turbo run fix", - "wallet-ui": "turbo run dev --filter=./apps/wallet-ui --filter=./packages/thirdweb --filter=./packages/insight", - "wallet-ui:build": "turbo run build --filter=./apps/wallet-ui --filter=./packages/thirdweb --filter=./packages/insight", - "playground": "turbo run dev --filter=./apps/playground-web --filter=./packages/thirdweb --filter=./packages/insight", + "wallet-ui": "turbo run dev --filter=./apps/wallet-ui --filter=./packages/thirdweb --filter=./packages/insight --filter=./packages/engine", + "wallet-ui:build": "turbo run build --filter=./apps/wallet-ui --filter=./packages/thirdweb --filter=./packages/insight --filter=./packages/engine", + "playground": "turbo run dev --filter=./apps/playground-web --filter=./packages/thirdweb --filter=./packages/insight --filter=./packages/engine", "playground:build": "turbo run build --filter=./apps/playground-web", - "portal": "turbo run dev --filter=./apps/portal --filter=./packages/thirdweb --filter=./packages/insight", + "portal": "turbo run dev --filter=./apps/portal --filter=./packages/thirdweb --filter=./packages/insight --filter=./packages/engine", "portal:build": "turbo run build --filter=./apps/portal", - "dashboard": "turbo run dev --filter=./apps/dashboard --filter=./packages/thirdweb --filter=./packages/insight --filter=./packages/vault-sdk", + "dashboard": "turbo run dev --filter=./apps/dashboard --filter=./packages/thirdweb --filter=./packages/insight --filter=./packages/vault-sdk --filter=./packages/engine", "dashboard:build": "turbo run build --filter=./apps/dashboard", "build": "turbo run build --filter=./packages/*", "build:release": "turbo run build --filter=./packages/* --force", diff --git a/packages/engine/CHANGELOG.md b/packages/engine/CHANGELOG.md new file mode 100644 index 00000000000..2654a69bd10 --- /dev/null +++ b/packages/engine/CHANGELOG.md @@ -0,0 +1,33 @@ +# @thirdweb-dev/insight + +## 1.0.0 + +### Major Changes + +- [#6706](https://github.com/thirdweb-dev/js/pull/6706) [`185d2f3`](https://github.com/thirdweb-dev/js/commit/185d2f309c349e37ac84bd3a2ce5a1c9c7011083) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Initial release of dedicated insight TS sdk + + This package is a thin openAPI wrapper for insight, our in-house indexer. + + ## Configuration + + ```ts + import { configure } from "@thirdweb-dev/insight"; + + // call this once at the startup of your application + configure({ + clientId: "", + }); + ``` + + ## Example Usage + + ```ts + import { getV1Events } from "@thirdweb-dev/insight"; + + const events = await getV1Events({ + query: { + chain: [1, 137], + filter_address: "0x1234567890123456789012345678901234567890", + }, + }); + ``` diff --git a/packages/engine/README.md b/packages/engine/README.md new file mode 100644 index 00000000000..0e55b8cdbe5 --- /dev/null +++ b/packages/engine/README.md @@ -0,0 +1,29 @@ +# Insight TypeScript SDK + +This package is a thin openAPI wrapper for insight, our in-house indexer. + +## Configuration + +```ts +import { configure } from "@thirdweb-dev/insight"; + +// call this once at the startup of your application +configure({ + clientId: "", +}); +``` + +## Example Usage + +```ts +import { getV1Events } from "@thirdweb-dev/insight"; + +const events = await getV1Events({ + query: { + chain: [1, 137], + filter_address: "0x1234567890123456789012345678901234567890", + }, +}); +``` + +This package was autogenerated from the [Insight openAPI spec](https://insight-api.thirdweb.com/reference) using [@hey-api/openapi-ts](https://github.com/hey-api/openapi-ts) diff --git a/packages/engine/biome.json b/packages/engine/biome.json new file mode 100644 index 00000000000..7520ee9c4d2 --- /dev/null +++ b/packages/engine/biome.json @@ -0,0 +1,7 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.9.2/schema.json", + "extends": ["../../biome.json"], + "files": { + "ignore": ["src/client/**"] + } +} diff --git a/packages/engine/openapi-ts.config.ts b/packages/engine/openapi-ts.config.ts new file mode 100644 index 00000000000..61ead8e7c88 --- /dev/null +++ b/packages/engine/openapi-ts.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from "@hey-api/openapi-ts"; + +export default defineConfig({ + input: "https://engine-cloud-dev-l8wt.chainsaw-dev.zeet.app/openapi", // TODO: update to prod + output: { path: "src/client" }, + plugins: ["@hey-api/client-fetch"], +}); diff --git a/packages/engine/package.json b/packages/engine/package.json new file mode 100644 index 00000000000..d14d8897cec --- /dev/null +++ b/packages/engine/package.json @@ -0,0 +1,57 @@ +{ + "name": "@thirdweb-dev/engine", + "version": "3.0.0", + "repository": { + "type": "git", + "url": "git+https://github.com/thirdweb-dev/js.git#main" + }, + "license": "Apache-2.0", + "bugs": { + "url": "https://github.com/thirdweb-dev/js/issues" + }, + "author": "thirdweb eng ", + "type": "module", + "main": "./dist/cjs/exports/thirdweb.js", + "module": "./dist/esm/exports/thirdweb.js", + "types": "./dist/types/exports/thirdweb.d.ts", + "typings": "./dist/types/exports/thirdweb.d.ts", + "exports": { + ".": { + "types": "./dist/types/exports/thirdweb.d.ts", + "import": "./dist/esm/exports/thirdweb.js", + "default": "./dist/cjs/exports/thirdweb.js" + }, + "./package.json": "./package.json" + }, + "files": ["dist/*", "src/*"], + "dependencies": { + "@hey-api/client-fetch": "0.10.0" + }, + "devDependencies": { + "@hey-api/openapi-ts": "0.67.0", + "rimraf": "6.0.1", + "tslib": "^2.8.1" + }, + "peerDependencies": { + "typescript": ">=5.0.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + }, + "scripts": { + "format": "biome format ./src --write", + "lint": "biome check ./src", + "fix": "biome check ./src --fix", + "build": "pnpm clean && pnpm build:cjs && pnpm build:esm && pnpm build:types", + "build:cjs": "tsc --project ./tsconfig.build.json --module commonjs --outDir ./dist/cjs --verbatimModuleSyntax false && printf '{\"type\":\"commonjs\"}' > ./dist/cjs/package.json", + "build:esm": "tsc --project ./tsconfig.build.json --module es2020 --outDir ./dist/esm && printf '{\"type\": \"module\",\"sideEffects\":false}' > ./dist/esm/package.json", + "build:types": "tsc --project ./tsconfig.build.json --module esnext --declarationDir ./dist/types --emitDeclarationOnly --declaration --declarationMap", + "clean": "rimraf dist", + "build:generate": "openapi-ts && pnpm format" + }, + "engines": { + "node": ">=18" + } +} diff --git a/packages/engine/src/client/client.gen.ts b/packages/engine/src/client/client.gen.ts new file mode 100644 index 00000000000..6549af15edf --- /dev/null +++ b/packages/engine/src/client/client.gen.ts @@ -0,0 +1,28 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import { + type Config, + type ClientOptions as DefaultClientOptions, + createClient, + createConfig, +} from "@hey-api/client-fetch"; +import type { ClientOptions } from "./types.gen.js"; + +/** + * The `createClientConfig()` function will be called on client initialization + * and the returned object will become the client's initial configuration. + * + * You may want to initialize your client this way instead of calling + * `setConfig()`. This is useful for example if you're using Next.js + * to ensure your client always has the correct values. + */ +export type CreateClientConfig = + ( + override?: Config, + ) => Config & T>; + +export const client = createClient( + createConfig({ + baseUrl: "https://engine-cloud-dev-l8wt.chainsaw-dev.zeet.app", + }), +); diff --git a/packages/engine/src/client/index.ts b/packages/engine/src/client/index.ts new file mode 100644 index 00000000000..65f317569c2 --- /dev/null +++ b/packages/engine/src/client/index.ts @@ -0,0 +1,3 @@ +// This file is auto-generated by @hey-api/openapi-ts +export * from "./types.gen.js"; +export * from "./sdk.gen.js"; diff --git a/packages/engine/src/client/sdk.gen.ts b/packages/engine/src/client/sdk.gen.ts new file mode 100644 index 00000000000..f1e1b32b940 --- /dev/null +++ b/packages/engine/src/client/sdk.gen.ts @@ -0,0 +1,348 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { + Client, + Options as ClientOptions, + TDataShape, +} from "@hey-api/client-fetch"; +import { client as _heyApiClient } from "./client.gen.js"; +import type { + GetTransactionAnalyticsData, + GetTransactionAnalyticsResponse, + GetTransactionAnalyticsSummaryData, + GetTransactionAnalyticsSummaryResponse, + PostEncodeContractData, + PostEncodeContractResponse, + PostReadBalanceData, + PostReadBalanceResponse, + PostReadContractData, + PostReadContractResponse, + PostSignMessageData, + PostSignMessageResponse, + PostSignTransactionData, + PostSignTransactionResponse, + PostSignTypedDataData, + PostSignTypedDataResponse, + PostWriteContractData, + PostWriteContractResponse, + PostWriteTransactionData, + PostWriteTransactionResponse, + SearchTransactionsData, + SearchTransactionsResponse, +} from "./types.gen.js"; + +export type Options< + TData extends TDataShape = TDataShape, + ThrowOnError extends boolean = boolean, +> = ClientOptions & { + /** + * You can provide a client instance returned by `createClient()` instead of + * individual options. This might be also useful if you want to implement a + * custom client. + */ + client?: Client; + /** + * You can pass arbitrary values through the `meta` object. This can be + * used to access values that aren't defined as part of the SDK function. + */ + meta?: Record; +}; + +/** + * Write to a Contract with a Function Call + * Call a write function on a contract. + */ +export const postWriteContract = ( + options?: Options, +) => { + return (options?.client ?? _heyApiClient).post< + PostWriteContractResponse, + unknown, + ThrowOnError + >({ + security: [ + { + name: "x-secret-key", + type: "apiKey", + }, + ], + url: "/write/contract", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); +}; + +/** + * Send an Encoded Transaction + * Send a transaction or a batch of transactions + */ +export const postWriteTransaction = ( + options?: Options, +) => { + return (options?.client ?? _heyApiClient).post< + PostWriteTransactionResponse, + unknown, + ThrowOnError + >({ + security: [ + { + name: "x-secret-key", + type: "apiKey", + }, + ], + url: "/write/transaction", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); +}; + +/** + * Sign Transaction + * Sign transactions without sending them. + */ +export const postSignTransaction = ( + options?: Options, +) => { + return (options?.client ?? _heyApiClient).post< + PostSignTransactionResponse, + unknown, + ThrowOnError + >({ + security: [ + { + name: "x-secret-key", + type: "apiKey", + }, + ], + url: "/sign/transaction", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); +}; + +/** + * Sign Message + * Sign arbitrary messages. + */ +export const postSignMessage = ( + options?: Options, +) => { + return (options?.client ?? _heyApiClient).post< + PostSignMessageResponse, + unknown, + ThrowOnError + >({ + security: [ + { + name: "x-secret-key", + type: "apiKey", + }, + ], + url: "/sign/message", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); +}; + +/** + * Sign Typed Data + * Sign EIP-712 typed data. + */ +export const postSignTypedData = ( + options?: Options, +) => { + return (options?.client ?? _heyApiClient).post< + PostSignTypedDataResponse, + unknown, + ThrowOnError + >({ + security: [ + { + name: "x-secret-key", + type: "apiKey", + }, + ], + url: "/sign/typed-data", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); +}; + +/** + * Read from a Contract + * Call read-only functions using multicall. + */ +export const postReadContract = ( + options?: Options, +) => { + return (options?.client ?? _heyApiClient).post< + PostReadContractResponse, + unknown, + ThrowOnError + >({ + security: [ + { + name: "x-secret-key", + type: "apiKey", + }, + ], + url: "/read/contract", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); +}; + +/** + * Read the Native Balance for an Address + * Fetches the native cryptocurrency balance (e.g., ETH, MATIC) for a given address on a specific chain. + */ +export const postReadBalance = ( + options?: Options, +) => { + return (options?.client ?? _heyApiClient).post< + PostReadBalanceResponse, + unknown, + ThrowOnError + >({ + security: [ + { + name: "x-secret-key", + type: "apiKey", + }, + ], + url: "/read/balance", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); +}; + +/** + * Encode A Contract Function Call + * Get transaction parameters (to, data, value) for contract calls. + */ +export const postEncodeContract = ( + options?: Options, +) => { + return (options?.client ?? _heyApiClient).post< + PostEncodeContractResponse, + unknown, + ThrowOnError + >({ + security: [ + { + name: "x-secret-key", + type: "apiKey", + }, + ], + url: "/encode/contract", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); +}; + +/** + * Search Transactions + * Advanced search for transactions with complex nested filters + */ +export const searchTransactions = ( + options?: Options, +) => { + return (options?.client ?? _heyApiClient).post< + SearchTransactionsResponse, + unknown, + ThrowOnError + >({ + security: [ + { + name: "x-secret-key", + type: "apiKey", + }, + ], + url: "/project/transactions/search", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); +}; + +/** + * Transaction Analytics + * Get transaction count analytics over time with filtering + */ +export const getTransactionAnalytics = ( + options?: Options, +) => { + return (options?.client ?? _heyApiClient).post< + GetTransactionAnalyticsResponse, + unknown, + ThrowOnError + >({ + security: [ + { + name: "x-secret-key", + type: "apiKey", + }, + ], + url: "/project/transactions/analytics", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); +}; + +/** + * Transaction Analytics Summary + * Get a summary (total count and total gas calculation) for transactions within a time range, supporting complex nested filters. + */ +export const getTransactionAnalyticsSummary = < + ThrowOnError extends boolean = false, +>( + options?: Options, +) => { + return (options?.client ?? _heyApiClient).post< + GetTransactionAnalyticsSummaryResponse, + unknown, + ThrowOnError + >({ + security: [ + { + name: "x-secret-key", + type: "apiKey", + }, + ], + url: "/project/transactions/analytics-summary", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); +}; diff --git a/packages/engine/src/client/types.gen.ts b/packages/engine/src/client/types.gen.ts new file mode 100644 index 00000000000..d9d1696b091 --- /dev/null +++ b/packages/engine/src/client/types.gen.ts @@ -0,0 +1,862 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export type TransactionsFilterValue = { + field: 'id' | 'batchIndex' | 'from' | 'signerAddress' | 'smartAccountAddress' | 'chainId'; + values: Array; + operation: 'AND' | 'OR'; +}; + +export type TransactionsFilterNested = { + operation: 'AND' | 'OR'; + filters: Array; +}; + +/** + * This is the default execution option. If you do not specify an execution type, and only specify a "from" string, engine will automatically determine the most optimal options for you. If you would like to specify granular options about execution strategy choose one of the other `executionOptions` type and provide them. + */ +export type AutoExecutionOptions = { + /** + * This is the default, a `type` does not need to be specified + */ + type?: 'auto'; + /** + * The address of the account to send the transaction from. Can be the address of a smart account or an EOA. + */ + from: string; + /** + * The idempotency key of the transaction. Transaction requests sent with the same idempotency key will be de-duplicated. If not provided, a randomUUID will be generated. This is also used as the ID of a queued/stored transaction. + */ + idempotencyKey?: string; + /** + * The chain id of the transaction + */ + chainId: string; +}; + +export type AaExecutionOptions = { + type: 'ERC4337'; + /** + * The address of the engine managed account which can send transactions from your smart account + */ + signerAddress: string; + sponsorGas?: boolean; + /** + * The address of the smart account factory. Defaults to thirdweb default v0.7 Account Factory. Only specify this if you are using a custom account factory. + */ + factoryAddress?: string; + /** + * The address of the entrypoint contract. Defaults to the v0.7 entrypoint for the chain. Only specify this if you want to specify a different version. + */ + entrypointAddress?: string; + /** + * The address of the smart account to send the transaction from. Either specify this, or the salt. If not specified, the inferred smart account will be with null salt. If both are specified, the salt will be ignored. + */ + smartAccountAddress?: string; + /** + * The salt of the smart account to send the transaction from. Only specify this if you want to specify a custom salt. If omitted, and smart account address is not provided, the inferred smart account will be with null salt. If a smart account address is provided, the salt will be ignored. + */ + accountSalt?: string; + /** + * The idempotency key of the transaction. Transaction requests sent with the same idempotency key will be de-duplicated. If not provided, a randomUUID will be generated. This is also used as the ID of a queued/stored transaction. + */ + idempotencyKey?: string; + /** + * The chain id of the transaction + */ + chainId: string; +}; + +/** + * Uses zkSync native AA for execution. This type of execution is only available on zkSync chains. + */ +export type AaZksyncExecutionOptions = { + type: 'zksync'; + /** + * The EOA address of the account to send the zksync native AA transaction from. + */ + accountAddress: string; + sponsorGas?: boolean; + /** + * The idempotency key of the transaction. Transaction requests sent with the same idempotency key will be de-duplicated. If not provided, a randomUUID will be generated. This is also used as the ID of a queued/stored transaction. + */ + idempotencyKey?: string; + /** + * The chain id of the transaction + */ + chainId: string; +}; + +export type PostWriteContractData = { + body?: { + /** + * Use a specific execution type and provide options to configure engine's execution strategy. The default execution option is `auto`, (doesn't need to be specified) which will automatically determine the most optimal options for you. If you would like to specify granular options about execution strategy choose one of the other `executionOptions` type and provide them. + */ + executionOptions: AutoExecutionOptions | AaExecutionOptions | AaZksyncExecutionOptions; + params: Array<{ + /** + * The function to call on the contract + */ + method: string; + /** + * The parameters to pass to the function + */ + params: Array; + /** + * The contract address to call + */ + contractAddress: string; + /** + * The ABI of the contract + */ + abi?: Array; + /** + * The value to send with the transaction + */ + value?: string; + }>; + }; + headers?: { + /** + * Vault Access Token used to access your EOA + */ + 'x-vault-access-token'?: string; + }; + path?: never; + query?: never; + url: '/write/contract'; +}; + +export type PostWriteContractResponses = { + /** + * Transaction sent successfully + */ + 200: { + result: Array<{ + id: string; + batchIndex: number; + chainId: string; + from: string | null; + transactionParams: (string | number | boolean | null) | {} | Array; + transactionHash: string | null; + confirmedAt: string | null; + confirmedAtBlockNumber: string | null; + enrichedData: (string | number | boolean | null) | {} | Array; + executionParams: (string | number | boolean | null) | {} | Array; + executionResult: (string | number | boolean | null) | {} | Array | null; + createdAt: string; + errorMessage: string | null; + cancelledAt: string | null; + }>; + }; + /** + * Transaction queued successfully + */ + 202: { + result: { + transactions: Array<{ + id: string; + batchIndex: number; + chainId: string; + from: string | null; + transactionParams: (string | number | boolean | null) | {} | Array; + transactionHash: string | null; + confirmedAt: string | null; + confirmedAtBlockNumber: string | null; + enrichedData: (string | number | boolean | null) | {} | Array; + executionParams: (string | number | boolean | null) | {} | Array; + executionResult: (string | number | boolean | null) | {} | Array | null; + createdAt: string; + errorMessage: string | null; + cancelledAt: string | null; + }>; + }; + }; +}; + +export type PostWriteContractResponse = PostWriteContractResponses[keyof PostWriteContractResponses]; + +export type PostWriteTransactionData = { + body?: { + /** + * Use a specific execution type and provide options to configure engine's execution strategy. The default execution option is `auto`, (doesn't need to be specified) which will automatically determine the most optimal options for you. If you would like to specify granular options about execution strategy choose one of the other `executionOptions` type and provide them. + */ + executionOptions: AutoExecutionOptions | AaExecutionOptions | AaZksyncExecutionOptions; + params: Array<{ + /** + * The address of the contract to send the transaction to + */ + to?: string; + /** + * The data of the transaction + */ + data?: string; + /** + * The value of the transaction + */ + value?: string; + }>; + }; + headers?: { + /** + * Vault Access Token used to access your EOA + */ + 'x-vault-access-token'?: string; + }; + path?: never; + query?: never; + url: '/write/transaction'; +}; + +export type PostWriteTransactionResponses = { + /** + * Transaction sent successfully + */ + 200: { + result: Array<{ + id: string; + batchIndex: number; + chainId: string; + from: string | null; + transactionParams: (string | number | boolean | null) | {} | Array; + transactionHash: string | null; + confirmedAt: string | null; + confirmedAtBlockNumber: string | null; + enrichedData: (string | number | boolean | null) | {} | Array; + executionParams: (string | number | boolean | null) | {} | Array; + executionResult: (string | number | boolean | null) | {} | Array | null; + createdAt: string; + errorMessage: string | null; + cancelledAt: string | null; + }>; + }; + /** + * Transaction queued successfully + */ + 202: { + result: { + transactions: Array<{ + id: string; + batchIndex: number; + chainId: string; + from: string | null; + transactionParams: (string | number | boolean | null) | {} | Array; + transactionHash: string | null; + confirmedAt: string | null; + confirmedAtBlockNumber: string | null; + enrichedData: (string | number | boolean | null) | {} | Array; + executionParams: (string | number | boolean | null) | {} | Array; + executionResult: (string | number | boolean | null) | {} | Array | null; + createdAt: string; + errorMessage: string | null; + cancelledAt: string | null; + }>; + }; + }; +}; + +export type PostWriteTransactionResponse = PostWriteTransactionResponses[keyof PostWriteTransactionResponses]; + +export type PostSignTransactionData = { + body?: { + /** + * Use a specific execution type and provide options to configure engine's execution strategy. The default execution option is `auto`, (doesn't need to be specified) which will automatically determine the most optimal options for you. If you would like to specify granular options about execution strategy choose one of the other `executionOptions` type and provide them. + */ + executionOptions: AutoExecutionOptions | AaExecutionOptions | AaZksyncExecutionOptions; + params: Array<{ + /** + * The recipient address + */ + to: string; + /** + * The transaction data as hex + */ + data?: string; + /** + * The value to send with the transaction + */ + value?: string; + /** + * Transaction nonce + */ + nonce?: number; + /** + * Gas limit + */ + gasLimit?: string; + /** + * Gas price (for legacy transactions) + */ + gasPrice?: string; + /** + * Max fee per gas (for EIP-1559) + */ + maxFeePerGas?: string; + /** + * Max priority fee per gas (for EIP-1559) + */ + maxPriorityFeePerGas?: string; + /** + * Access list for EIP-2930 and later transactions + */ + accessList?: Array<{ + /** + * EVM address in hex format + */ + address: string; + storageKeys: Array; + }>; + /** + * Max fee per blob gas (for EIP-4844) + */ + maxFeePerBlobGas?: string; + /** + * Blob versioned hashes (for EIP-4844) + */ + blobVersionedHashes?: Array; + /** + * Authorization list (for EIP-7702) + */ + authorizationList?: Array<{ + /** + * Authorization address + */ + address: string; + /** + * r value of the signature + */ + r: string; + /** + * s value of the signature + */ + s: string; + /** + * v value of the signature + */ + v?: number | string; + /** + * yParity value (0 or 1) + */ + yParity: number; + /** + * Authorization nonce + */ + nonce: string; + /** + * Authorization chainId + */ + chainId: number; + }>; + }>; + }; + headers?: { + /** + * Vault Access Token used to access your EOA + */ + 'x-vault-access-token'?: string; + }; + path?: never; + query?: never; + url: '/sign/transaction'; +}; + +export type PostSignTransactionResponses = { + /** + * OK + */ + 200: { + result: { + results: Array<{ + success: true; + result: { + /** + * The resulting signature + */ + signature: string; + /** + * Optional signed data + */ + signedData?: string; + }; + } | { + success: false; + /** + * Standardized error object + */ + error: { + kind: string; + code: string; + status: number; + message?: string; + /** + * EVM address in hex format + */ + address?: string; + chainId?: string; + }; + }>; + }; + }; +}; + +export type PostSignTransactionResponse = PostSignTransactionResponses[keyof PostSignTransactionResponses]; + +export type PostSignMessageData = { + body?: { + /** + * Use a specific execution type and provide options to configure engine's execution strategy. The default execution option is `auto`, (doesn't need to be specified) which will automatically determine the most optimal options for you. If you would like to specify granular options about execution strategy choose one of the other `executionOptions` type and provide them. + */ + executionOptions: AutoExecutionOptions | AaExecutionOptions | AaZksyncExecutionOptions; + params: Array<{ + /** + * The message to sign + */ + message: string; + /** + * Format of the message (text or hex) + */ + messageFormat?: 'text' | 'hex'; + }>; + }; + headers?: { + /** + * Vault Access Token used to access your EOA + */ + 'x-vault-access-token'?: string; + }; + path?: never; + query?: never; + url: '/sign/message'; +}; + +export type PostSignMessageResponses = { + /** + * OK + */ + 200: { + result: { + results: Array<{ + success: true; + result: { + /** + * The resulting signature + */ + signature: string; + /** + * Optional signed data + */ + signedData?: string; + }; + } | { + success: false; + /** + * Standardized error object + */ + error: { + kind: string; + code: string; + status: number; + message?: string; + /** + * EVM address in hex format + */ + address?: string; + chainId?: string; + }; + }>; + }; + }; +}; + +export type PostSignMessageResponse = PostSignMessageResponses[keyof PostSignMessageResponses]; + +export type PostSignTypedDataData = { + body?: { + /** + * Use a specific execution type and provide options to configure engine's execution strategy. The default execution option is `auto`, (doesn't need to be specified) which will automatically determine the most optimal options for you. If you would like to specify granular options about execution strategy choose one of the other `executionOptions` type and provide them. + */ + executionOptions: AutoExecutionOptions | AaExecutionOptions | AaZksyncExecutionOptions; + params: Array<{ + domain: { + chainId?: number | number; + name?: string; + salt?: string; + verifyingContract?: string; + version?: string; + }; + types: { + [key: string]: Array<{ + name: string; + type: string; + }>; + }; + primaryType: string; + message: {}; + }>; + }; + headers?: { + /** + * Vault Access Token used to access your EOA + */ + 'x-vault-access-token'?: string; + }; + path?: never; + query?: never; + url: '/sign/typed-data'; +}; + +export type PostSignTypedDataResponses = { + /** + * OK + */ + 200: { + result: { + results: Array<{ + success: true; + result: { + /** + * The resulting signature + */ + signature: string; + /** + * Optional signed data + */ + signedData?: string; + }; + } | { + success: false; + /** + * Standardized error object + */ + error: { + kind: string; + code: string; + status: number; + message?: string; + /** + * EVM address in hex format + */ + address?: string; + chainId?: string; + }; + }>; + }; + }; +}; + +export type PostSignTypedDataResponse = PostSignTypedDataResponses[keyof PostSignTypedDataResponses]; + +export type PostReadContractData = { + body?: { + readOptions: { + /** + * Optional multicall address, defaults to the default multicall3 address for the chain + */ + multicallAddress?: string; + /** + * The chain id of the transaction + */ + chainId: string; + /** + * EVM address in hex format + */ + from?: string; + }; + params: Array<{ + /** + * The function to call on the contract + */ + method: string; + /** + * The parameters to pass to the function + */ + params: Array; + /** + * The contract address to call + */ + contractAddress: string; + /** + * The ABI of the contract + */ + abi?: Array; + }>; + }; + path?: never; + query?: never; + url: '/read/contract'; +}; + +export type PostReadContractResponses = { + /** + * OK + */ + 200: { + result: { + results: Array<{ + success: boolean; + result?: null; + }>; + }; + }; +}; + +export type PostReadContractResponse = PostReadContractResponses[keyof PostReadContractResponses]; + +export type PostReadBalanceData = { + body?: { + /** + * The chain ID to query the balance on. + */ + chainId: string; + /** + * The EVM address to get the native balance for. + */ + address: string; + }; + path?: never; + query?: never; + url: '/read/balance'; +}; + +export type PostReadBalanceErrors = { + /** + * Bad Request - Invalid input + */ + 400: unknown; + /** + * Internal Server Error + */ + 500: unknown; +}; + +export type PostReadBalanceResponses = { + /** + * OK - Balance fetched successfully. + */ + 200: { + result: { + /** + * The native balance of the address as a stringified integer. + */ + balance: string; + }; + }; +}; + +export type PostReadBalanceResponse = PostReadBalanceResponses[keyof PostReadBalanceResponses]; + +export type PostEncodeContractData = { + body?: { + encodeOptions: { + /** + * The chain id of the transaction + */ + chainId: string; + }; + params: Array<{ + /** + * The function to call on the contract + */ + method: string; + /** + * The parameters to pass to the function + */ + params: Array; + /** + * The contract address to call + */ + contractAddress: string; + /** + * The ABI of the contract + */ + abi?: Array; + /** + * The value to send with the transaction + */ + value?: string; + }>; + }; + path?: never; + query?: never; + url: '/encode/contract'; +}; + +export type PostEncodeContractResponses = { + /** + * OK + */ + 200: { + result: { + results: Array<{ + success: true; + result: { + /** + * EVM address in hex format + */ + to: string; + data: string; + /** + * A string representing an bigint response, safe to parse with BigInt + */ + value: string; + }; + } | { + success: false; + /** + * Standardized error object + */ + error: { + kind: string; + code: string; + status: number; + message?: string; + /** + * EVM address in hex format + */ + address?: string; + chainId?: string; + }; + }>; + }; + }; +}; + +export type PostEncodeContractResponse = PostEncodeContractResponses[keyof PostEncodeContractResponses]; + +export type SearchTransactionsData = { + body?: { + page?: number; + limit?: number; + filters?: Array; + filtersOperation?: 'AND' | 'OR'; + sortBy?: 'createdAt' | 'confirmedAt'; + sortDirection?: 'asc' | 'desc'; + }; + path?: never; + query?: never; + url: '/project/transactions/search'; +}; + +export type SearchTransactionsResponses = { + /** + * Transactions + */ + 200: { + result: { + transactions: Array<{ + id: string; + batchIndex: number; + chainId: string; + from: string | null; + transactionParams: (string | number | boolean | null) | {} | Array; + transactionHash: string | null; + confirmedAt: string | null; + confirmedAtBlockNumber: string | null; + enrichedData: (string | number | boolean | null) | {} | Array; + executionParams: (string | number | boolean | null) | {} | Array; + executionResult: (string | number | boolean | null) | {} | Array | null; + createdAt: string; + errorMessage: string | null; + cancelledAt: string | null; + }>; + pagination: { + totalCount: number; + page: number; + limit: number; + }; + }; + }; +}; + +export type SearchTransactionsResponse = SearchTransactionsResponses[keyof SearchTransactionsResponses]; + +export type GetTransactionAnalyticsData = { + body?: { + startDate: string; + endDate: string; + resolution: 'hour' | 'day' | 'week' | 'month'; + filters?: Array; + filtersOperation?: 'AND' | 'OR'; + }; + path?: never; + query?: never; + url: '/project/transactions/analytics'; +}; + +export type GetTransactionAnalyticsResponses = { + /** + * Transaction Analytics + */ + 200: { + result: { + analytics: Array<{ + timeBucket: string; + chainId: string; + count: number; + }>; + metadata: { + resolution: 'hour' | 'day' | 'week' | 'month'; + startDate: string; + endDate: string; + }; + }; + }; +}; + +export type GetTransactionAnalyticsResponse = GetTransactionAnalyticsResponses[keyof GetTransactionAnalyticsResponses]; + +export type GetTransactionAnalyticsSummaryData = { + body?: { + startDate?: string; + endDate?: string; + filters?: Array; + filtersOperation?: 'AND' | 'OR'; + }; + path?: never; + query?: never; + url: '/project/transactions/analytics-summary'; +}; + +export type GetTransactionAnalyticsSummaryErrors = { + /** + * Bad Request (e.g., invalid date format, filter depth exceeded) + */ + 400: unknown; + /** + * Internal Server Error (e.g., database error) + */ + 500: unknown; +}; + +export type GetTransactionAnalyticsSummaryResponses = { + /** + * Transaction Analytics Summary + */ + 200: { + result: { + summary: { + /** + * Total number of transactions matching the criteria. + */ + totalCount: number; + /** + * Sum of actualGasCost (in wei) for all matching transactions, as a string. + */ + totalGasCostWei: string; + /** + * Sum of actualGasUsed (gas units) for all matching transactions, as a string. + */ + totalGasUnitsUsed: string; + }; + metadata: { + startDate?: string; + endDate?: string; + }; + }; + }; +}; + +export type GetTransactionAnalyticsSummaryResponse = GetTransactionAnalyticsSummaryResponses[keyof GetTransactionAnalyticsSummaryResponses]; + +export type ClientOptions = { + baseUrl: 'https://engine-cloud-dev-l8wt.chainsaw-dev.zeet.app' | (string & {}); +}; \ No newline at end of file diff --git a/packages/engine/src/configure.ts b/packages/engine/src/configure.ts new file mode 100644 index 00000000000..6bd913d8f52 --- /dev/null +++ b/packages/engine/src/configure.ts @@ -0,0 +1,19 @@ +import type { Config } from "@hey-api/client-fetch"; +import { client } from "./client/client.gen.js"; + +export type EngineClientOptions = { + readonly clientId: string; + readonly secretKey?: string; +}; + +export function configure( + options: EngineClientOptions & { override?: Config }, +) { + client.setConfig({ + headers: { + ...(options.clientId && { "x-client-id": options.clientId }), + ...(options.secretKey && { "x-secret-key": options.secretKey }), + }, + ...(options.override ?? {}), + }); +} diff --git a/packages/engine/src/exports/thirdweb.ts b/packages/engine/src/exports/thirdweb.ts new file mode 100644 index 00000000000..1165d980d84 --- /dev/null +++ b/packages/engine/src/exports/thirdweb.ts @@ -0,0 +1,3 @@ +export * from "../client/index.js"; +export type { CreateClientConfig } from "../client/client.gen.js"; +export { configure, type EngineClientOptions } from "../configure.js"; diff --git a/packages/engine/tsconfig.base.json b/packages/engine/tsconfig.base.json new file mode 100644 index 00000000000..2b519cac7ea --- /dev/null +++ b/packages/engine/tsconfig.base.json @@ -0,0 +1,47 @@ +{ + // This tsconfig file contains the shared config for the build (tsconfig.build.json) and type checking (tsconfig.json) config. + "include": [], + "compilerOptions": { + // Incremental builds + // NOTE: Enabling incremental builds speeds up `tsc`. Keep in mind though that it does not reliably bust the cache when the `tsconfig.json` file changes. + "incremental": false, + + // Type checking + "strict": true, + "useDefineForClassFields": true, // Not enabled by default in `strict` mode unless we bump `target` to ES2022. + "noFallthroughCasesInSwitch": true, // Not enabled by default in `strict` mode. + "noImplicitReturns": true, // Not enabled by default in `strict` mode. + "useUnknownInCatchVariables": true, // TODO: This would normally be enabled in `strict` mode but would require some adjustments to the codebase. + "noImplicitOverride": true, // Not enabled by default in `strict` mode. + "noUnusedLocals": true, // Not enabled by default in `strict` mode. + "noUnusedParameters": true, // Not enabled by default in `strict` mode. + "exactOptionalPropertyTypes": false, // Not enabled by default in `strict` mode. + "noUncheckedIndexedAccess": true, // Not enabled by default in `strict` mode. + + // JavaScript support + "allowJs": false, + "checkJs": false, + + // Interop constraints + "esModuleInterop": false, + "allowSyntheticDefaultImports": true, + "forceConsistentCasingInFileNames": true, + "verbatimModuleSyntax": true, + "importHelpers": true, // This is only used for build validation. Since we do not have `tslib` installed, this will fail if we accidentally make use of anything that'd require injection of helpers. + + // Language and environment + "moduleResolution": "NodeNext", + "module": "NodeNext", + "target": "ES2021", // Setting this to `ES2021` enables native support for `Node v16+`: https://github.com/microsoft/TypeScript/wiki/Node-Target-Mapping. + "lib": [ + "ES2022", // By using ES2022 we get access to the `.cause` property on `Error` instances. + "DOM" // We are adding `DOM` here to get the `fetch`, etc. types. This should be removed once these types are available via DefinitelyTyped. + ], + + // Skip type checking for node modules + "skipLibCheck": true, + + // jsx for "/react" portion + "jsx": "react-jsx" + } +} diff --git a/packages/engine/tsconfig.build.json b/packages/engine/tsconfig.build.json new file mode 100644 index 00000000000..3ae3943df87 --- /dev/null +++ b/packages/engine/tsconfig.build.json @@ -0,0 +1,16 @@ +{ + "extends": "./tsconfig.base.json", + "include": ["src"], + "exclude": [ + "src/**/*.test.ts", + "src/**/*.test.tsx", + "src/**/*.test-d.ts", + "src/**/*.bench.ts", + "src/**/*.macro.ts" + ], + "compilerOptions": { + "moduleResolution": "node", + "sourceMap": true, + "rootDir": "./src" + } +} diff --git a/packages/engine/tsconfig.json b/packages/engine/tsconfig.json new file mode 100644 index 00000000000..f8b525d0c97 --- /dev/null +++ b/packages/engine/tsconfig.json @@ -0,0 +1,13 @@ +{ + // This configuration is used for local development and type checking. + "extends": "./tsconfig.base.json", + "include": ["src", "test"], + "exclude": [], + // "references": [{ "path": "./scripts/tsconfig.json" }], + "compilerOptions": { + "baseUrl": ".", + "paths": { + "~test/*": ["./test/src/*"] + } + } +} diff --git a/packages/insight/package.json b/packages/insight/package.json index f209b9f36bc..6f20c3630db 100644 --- a/packages/insight/package.json +++ b/packages/insight/package.json @@ -28,7 +28,7 @@ "@hey-api/client-fetch": "0.10.0" }, "devDependencies": { - "@hey-api/openapi-ts": "0.66.5", + "@hey-api/openapi-ts": "0.67.0", "rimraf": "6.0.1", "tslib": "^2.8.1" }, diff --git a/packages/insight/src/configure.ts b/packages/insight/src/configure.ts index ffe2e1bce7b..0e90879dff7 100644 --- a/packages/insight/src/configure.ts +++ b/packages/insight/src/configure.ts @@ -12,7 +12,7 @@ export function configure( client.setConfig({ headers: { ...(options.clientId && { "x-client-id": options.clientId }), - ...(options.secretKey && { "x-api-key": options.secretKey }), + ...(options.secretKey && { "x-secret-key": options.secretKey }), }, ...(options.override ?? {}), }); diff --git a/packages/thirdweb/package.json b/packages/thirdweb/package.json index 4f96331c95f..67183c15dbf 100644 --- a/packages/thirdweb/package.json +++ b/packages/thirdweb/package.json @@ -138,70 +138,35 @@ "import": "./dist/esm/exports/insight.js", "default": "./dist/cjs/exports/insight.js" }, + "./engine": { + "types": "./dist/types/exports/engine.d.ts", + "import": "./dist/esm/exports/engine.js", + "default": "./dist/cjs/exports/engine.js" + }, "./package.json": "./package.json" }, "typesVersions": { "*": { - "adapters/*": [ - "./dist/types/exports/adapters/*.d.ts" - ], - "auth": [ - "./dist/types/exports/auth.d.ts" - ], - "chains": [ - "./dist/types/exports/chains.d.ts" - ], - "contract": [ - "./dist/types/exports/contract.d.ts" - ], - "deploys": [ - "./dist/types/exports/deploys.d.ts" - ], - "event": [ - "./dist/types/exports/event.d.ts" - ], - "extensions/*": [ - "./dist/types/exports/extensions/*.d.ts" - ], - "pay": [ - "./dist/types/exports/pay.d.ts" - ], - "react": [ - "./dist/types/exports/react.d.ts" - ], - "react-native": [ - "./dist/types/exports/react-native.d.ts" - ], - "rpc": [ - "./dist/types/exports/rpc.d.ts" - ], - "storage": [ - "./dist/types/exports/storage.d.ts" - ], - "transaction": [ - "./dist/types/exports/transaction.d.ts" - ], - "utils": [ - "./dist/types/exports/utils.d.ts" - ], - "wallets": [ - "./dist/types/exports/wallets.d.ts" - ], - "wallets/*": [ - "./dist/types/exports/wallets/*.d.ts" - ], - "modules": [ - "./dist/types/exports/modules.d.ts" - ], - "social": [ - "./dist/types/exports/social.d.ts" - ], - "ai": [ - "./dist/types/exports/ai.d.ts" - ], - "bridge": [ - "./dist/types/exports/bridge.d.ts" - ] + "adapters/*": ["./dist/types/exports/adapters/*.d.ts"], + "auth": ["./dist/types/exports/auth.d.ts"], + "chains": ["./dist/types/exports/chains.d.ts"], + "contract": ["./dist/types/exports/contract.d.ts"], + "deploys": ["./dist/types/exports/deploys.d.ts"], + "event": ["./dist/types/exports/event.d.ts"], + "extensions/*": ["./dist/types/exports/extensions/*.d.ts"], + "pay": ["./dist/types/exports/pay.d.ts"], + "react": ["./dist/types/exports/react.d.ts"], + "react-native": ["./dist/types/exports/react-native.d.ts"], + "rpc": ["./dist/types/exports/rpc.d.ts"], + "storage": ["./dist/types/exports/storage.d.ts"], + "transaction": ["./dist/types/exports/transaction.d.ts"], + "utils": ["./dist/types/exports/utils.d.ts"], + "wallets": ["./dist/types/exports/wallets.d.ts"], + "wallets/*": ["./dist/types/exports/wallets/*.d.ts"], + "modules": ["./dist/types/exports/modules.d.ts"], + "social": ["./dist/types/exports/social.d.ts"], + "ai": ["./dist/types/exports/ai.d.ts"], + "bridge": ["./dist/types/exports/bridge.d.ts"] } }, "browser": { @@ -231,6 +196,7 @@ "@radix-ui/react-icons": "1.3.2", "@radix-ui/react-tooltip": "1.2.3", "@tanstack/react-query": "5.74.4", + "@thirdweb-dev/engine": "workspace:*", "@thirdweb-dev/insight": "workspace:*", "@walletconnect/ethereum-provider": "2.19.1", "@walletconnect/sign-client": "2.19.1", diff --git a/packages/thirdweb/scripts/typedoc.mjs b/packages/thirdweb/scripts/typedoc.mjs index 8366ef4cf6a..e1fc363932e 100644 --- a/packages/thirdweb/scripts/typedoc.mjs +++ b/packages/thirdweb/scripts/typedoc.mjs @@ -14,6 +14,7 @@ const app = await Application.bootstrapWithPlugins({ "src/bridge/Buy.ts", "src/bridge/Sell.ts", "src/insight/index.ts", + "src/engine/index.ts", ], exclude: [ "src/exports/*.native.ts", diff --git a/packages/thirdweb/src/engine/index.ts b/packages/thirdweb/src/engine/index.ts new file mode 100644 index 00000000000..23e45b8c396 --- /dev/null +++ b/packages/thirdweb/src/engine/index.ts @@ -0,0 +1 @@ +export { serverWallet, type ServerWalletOptions } from "./server-wallet.js"; diff --git a/packages/thirdweb/src/engine/server-wallet.test.ts b/packages/thirdweb/src/engine/server-wallet.test.ts new file mode 100644 index 00000000000..783114a7f5c --- /dev/null +++ b/packages/thirdweb/src/engine/server-wallet.test.ts @@ -0,0 +1,161 @@ +import { beforeAll, describe, expect, it } from "vitest"; +import { TEST_CLIENT } from "../../test/src/test-clients.js"; +import { TEST_ACCOUNT_B } from "../../test/src/test-wallets.js"; +import { typedData } from "../../test/src/typed-data.js"; +import { arbitrumSepolia } from "../chains/chain-definitions/arbitrum-sepolia.js"; +import { sepolia } from "../chains/chain-definitions/sepolia.js"; +import { getContract } from "../contract/contract.js"; +import { setContractURI } from "../extensions/common/__generated__/IContractMetadata/write/setContractURI.js"; +import { claimTo } from "../extensions/erc1155/drops/write/claimTo.js"; +import { sendTransaction } from "../transaction/actions/send-transaction.js"; +import { setThirdwebDomains } from "../utils/domains.js"; +import type { Account } from "../wallets/interfaces/wallet.js"; +import { + DEFAULT_ACCOUNT_FACTORY_V0_6, + ENTRYPOINT_ADDRESS_v0_6, +} from "../wallets/smart/lib/constants.js"; +import { smartWallet } from "../wallets/smart/smart-wallet.js"; +import { generateAccount } from "../wallets/utils/generateAccount.js"; +import * as Engine from "./index.js"; + +describe.runIf( + process.env.TW_SECRET_KEY && + process.env.VAULT_TOKEN && + process.env.ENGINE_CLOUD_WALLET_ADDRESS && + process.env.ENGINE_CLOUD_WALLET_ADDRESS_EOA, +)( + "Engine Cloud", + { + retry: 0, + }, + () => { + let serverWallet: Account; + + beforeAll(async () => { + setThirdwebDomains({ + rpc: "rpc.thirdweb-dev.com", + storage: "storage.thirdweb-dev.com", + bundler: "bundler.thirdweb-dev.com", + engineCloud: "engine-cloud-dev-l8wt.chainsaw-dev.zeet.app", + }); + serverWallet = Engine.serverWallet({ + client: TEST_CLIENT, + vaultAccessToken: process.env.VAULT_TOKEN as string, + walletAddress: process.env.ENGINE_CLOUD_WALLET_ADDRESS as string, + chain: arbitrumSepolia, + }); + }); + + it("should sign a message", async () => { + const signature = await serverWallet.signMessage({ + message: "hello", + }); + expect(signature).toBeDefined(); + }); + + it("should sign typed data", async () => { + const signature = await serverWallet.signTypedData({ + ...typedData.basic, + }); + expect(signature).toBeDefined(); + }); + + it("should send a tx", async () => { + const tx = await sendTransaction({ + account: serverWallet, + transaction: { + client: TEST_CLIENT, + chain: arbitrumSepolia, + to: TEST_ACCOUNT_B.address, + value: 0n, + }, + }); + expect(tx).toBeDefined(); + }); + + it("should send a extension tx", async () => { + const nftContract = getContract({ + client: TEST_CLIENT, + chain: sepolia, + address: "0xe2cb0eb5147b42095c2FfA6F7ec953bb0bE347D8", + }); + const claimTx = claimTo({ + contract: nftContract, + to: TEST_ACCOUNT_B.address, + tokenId: 0n, + quantity: 1n, + }); + const tx = await sendTransaction({ + account: serverWallet, + transaction: claimTx, + }); + expect(tx).toBeDefined(); + }); + + it("should get revert reason", async () => { + const nftContract = getContract({ + client: TEST_CLIENT, + chain: sepolia, + address: "0xe2cb0eb5147b42095c2FfA6F7ec953bb0bE347D8", + }); + const transaction = setContractURI({ + contract: nftContract, + uri: "https://example.com", + overrides: { + gas: 1000000n, // skip simulation + }, + }); + await expect( + sendTransaction({ + account: serverWallet, + transaction, + }), + ).rejects.toThrow(); + }); + + it.only("should send a session key tx", async () => { + const personalAccount = await generateAccount({ + client: TEST_CLIENT, + }); + const smart = smartWallet({ + chain: sepolia, + sponsorGas: true, + sessionKey: { + address: process.env.ENGINE_CLOUD_WALLET_ADDRESS_EOA as string, + permissions: { + approvedTargets: "*", + }, + }, + }); + const smartAccount = await smart.connect({ + client: TEST_CLIENT, + personalAccount, + }); + expect(smartAccount.address).toBeDefined(); + + const serverWallet = Engine.serverWallet({ + client: TEST_CLIENT, + vaultAccessToken: process.env.VAULT_TOKEN as string, + walletAddress: process.env.ENGINE_CLOUD_WALLET_ADDRESS_EOA as string, + chain: sepolia, + executionOptions: { + type: "ERC4337", + signerAddress: process.env.ENGINE_CLOUD_WALLET_ADDRESS_EOA as string, + smartAccountAddress: smartAccount.address, + entrypointAddress: ENTRYPOINT_ADDRESS_v0_6, + factoryAddress: DEFAULT_ACCOUNT_FACTORY_V0_6, + }, + }); + const tx = await sendTransaction({ + account: serverWallet, + transaction: { + client: TEST_CLIENT, + chain: sepolia, + to: TEST_ACCOUNT_B.address, + value: 0n, + }, + }); + expect(tx).toBeDefined(); + }); + }, +); diff --git a/packages/thirdweb/src/engine/server-wallet.ts b/packages/thirdweb/src/engine/server-wallet.ts new file mode 100644 index 00000000000..05cd7affa9f --- /dev/null +++ b/packages/thirdweb/src/engine/server-wallet.ts @@ -0,0 +1,311 @@ +import { + type AaExecutionOptions, + type AaZksyncExecutionOptions, + postSignMessage, + postSignTypedData, + postWriteTransaction, + searchTransactions, +} from "@thirdweb-dev/engine"; +import type { Chain } from "../chains/types.js"; +import type { ThirdwebClient } from "../client/client.js"; +import { getThirdwebBaseUrl } from "../utils/domains.js"; +import { type Hex, toHex } from "../utils/encoding/hex.js"; +import { getClientFetch } from "../utils/fetch.js"; +import { stringify } from "../utils/json.js"; +import type { + Account, + SendTransactionOption, +} from "../wallets/interfaces/wallet.js"; + +/** + * Options for creating an server wallet. + */ +export type ServerWalletOptions = { + /** + * The thirdweb client to use for authentication to thirdweb services. + */ + client: ThirdwebClient; + /** + * The vault access token to use your server wallet. + */ + vaultAccessToken: string; + /** + * The server wallet address to use for sending transactions inside engine. + */ + walletAddress: string; + /** + * The chain to use for signing messages and typed data (smart server wallet only). + */ + chain?: Chain; + /** + * Optional custom execution options to use for sending transactions and signing data. + */ + executionOptions?: + | Omit + | Omit; +}; + +type RevertDataSerialized = { + revertReason?: string; + decodedError?: { + name: string; + signature: string; + args: string[]; + }; +}; + +type ExecutionResult = + | { + status: "QUEUED"; + } + | { + status: "FAILED"; + error: string; + } + | { + status: "SUBMITTED"; + monitoringStatus: "WILL_MONITOR" | "CANNOT_MONITOR"; + userOpHash: string; + } + | ({ + status: "CONFIRMED"; + userOpHash: Hex; + transactionHash: Hex; + actualGasCost: string; + actualGasUsed: string; + nonce: string; + } & ( + | { + onchainStatus: "SUCCESS"; + } + | { + onchainStatus: "REVERTED"; + revertData?: RevertDataSerialized; + } + )); + +/** + * Create a server wallet for sending transactions and signing messages via engine (v3+). + * @param options - The server wallet options. + * @returns An account object that can be used to send transactions and sign messages. + * @engine + * @example + * ```ts + * import { Engine } from "thirdweb"; + * + * const myServerWallet = Engine.serverWallet({ + * client, + * vaultAccessToken, + * walletAddress, + * }); + * + * // then use the account as you would any other account + * const transaction = claimTo({ + * contract, + * to: "0x...", + * quantity: 1n, + * }); + * const result = await sendTransaction({ + * transaction, + * account: myServerWallet + * }); + * console.log("Transaction sent:", result.transactionHash); + * ``` + */ +export function serverWallet(options: ServerWalletOptions): Account { + const { client, vaultAccessToken, walletAddress, chain, executionOptions } = + options; + const headers: HeadersInit = { + "x-vault-access-token": vaultAccessToken, + }; + + const getExecutionOptions = (chainId: number) => { + return executionOptions + ? { + ...executionOptions, + chainId: chainId.toString(), + } + : { + from: walletAddress, + chainId: chainId.toString(), + }; + }; + + return { + address: walletAddress, + sendTransaction: async (transaction: SendTransactionOption) => { + const body = { + executionOptions: getExecutionOptions(transaction.chainId), + params: [ + { + to: transaction.to ?? undefined, + data: transaction.data, + value: transaction.value?.toString(), + }, + ], + }; + + const result = await postWriteTransaction({ + baseUrl: getThirdwebBaseUrl("engineCloud"), + fetch: getClientFetch(client), + headers, + body, + }); + + if (result.error) { + throw new Error(`Error sending transaction: ${result.error}`); + } + + const data = result.data?.result; + if (!data) { + throw new Error("No data returned from engine"); + } + const transactionId = + "transactions" in data ? data.transactions?.[0]?.id : data[0]?.id; // TODO (cloud) fix return type + if (!transactionId) { + throw new Error("No transactionId returned from engine"); + } + + // wait for the queueId to be processed + const startTime = Date.now(); + const TIMEOUT_IN_MS = 5 * 60 * 1000; // 5 minutes in milliseconds + + while (Date.now() - startTime < TIMEOUT_IN_MS) { + const searchResult = await searchTransactions({ + baseUrl: getThirdwebBaseUrl("engineCloud"), + fetch: getClientFetch(client), + body: { + filters: [ + { + field: "id", + values: [transactionId], + operation: "OR", + }, + ], + }, + }); + + if (searchResult.error) { + throw new Error( + `Error searching for transaction: ${stringify(searchResult.error)}`, + ); + } + + const data = searchResult.data?.result?.transactions?.[0]; + + if (data) { + const executionResult = data.executionResult as ExecutionResult; + const status = executionResult.status; + + if (status === "FAILED") { + throw new Error( + `Transaction failed: ${executionResult.error || "Unknown error"}`, + ); + } + + const onchainStatus = + executionResult && "onchainStatus" in executionResult + ? executionResult.onchainStatus + : null; + + if (status === "CONFIRMED" && onchainStatus === "REVERTED") { + const revertData = + "revertData" in executionResult + ? executionResult.revertData + : undefined; + throw new Error( + `Transaction reverted: ${revertData?.decodedError?.name || revertData?.revertReason || "Unknown revert reason"}`, + ); + } + + if (data.transactionHash) { + return { + transactionHash: data.transactionHash as Hex, + }; + } + } + // wait 1s before checking again + await new Promise((resolve) => setTimeout(resolve, 1000)); + } + throw new Error("Transaction timed out after 5 minutes"); + }, + signMessage: async (data) => { + const { message, chainId } = data; + let engineMessage: string | Hex; + let isBytes = false; + if (typeof message === "string") { + engineMessage = message; + } else { + engineMessage = toHex(message.raw); + isBytes = true; + } + + const signingChainId = chainId || chain?.id; + if (!signingChainId) { + throw new Error("Chain ID is required for signing messages"); + } + + const signResult = await postSignMessage({ + baseUrl: getThirdwebBaseUrl("engineCloud"), + fetch: getClientFetch(client), + headers, + body: { + executionOptions: getExecutionOptions(signingChainId), + params: [ + { + message: engineMessage, + messageFormat: isBytes ? "hex" : "text", + }, + ], + }, + }); + + if (signResult.error) { + throw new Error( + `Error signing message: ${stringify(signResult.error)}`, + ); + } + + const signatureResult = signResult.data?.result.results[0]; + if (signatureResult?.success) { + return signatureResult.result.signature as Hex; + } + + throw new Error( + `Failed to sign message: ${signatureResult?.error?.message || "Unknown error"}`, + ); + }, + signTypedData: async (typedData) => { + const signingChainId = chain?.id; + if (!signingChainId) { + throw new Error("Chain ID is required for signing messages"); + } + + const signResult = await postSignTypedData({ + baseUrl: getThirdwebBaseUrl("engineCloud"), + fetch: getClientFetch(client), + headers, + body: { + executionOptions: getExecutionOptions(signingChainId), + // biome-ignore lint/suspicious/noExplicitAny: TODO: fix ts / hey-api type clash + params: [typedData as any], + }, + }); + + if (signResult.error) { + throw new Error( + `Error signing message: ${stringify(signResult.error)}`, + ); + } + + const signatureResult = signResult.data?.result.results[0]; + if (signatureResult?.success) { + return signatureResult.result.signature as Hex; + } + + throw new Error( + `Failed to sign message: ${signatureResult?.error?.message || "Unknown error"}`, + ); + }, + }; +} diff --git a/packages/thirdweb/src/exports/engine.ts b/packages/thirdweb/src/exports/engine.ts new file mode 100644 index 00000000000..1d1406f823e --- /dev/null +++ b/packages/thirdweb/src/exports/engine.ts @@ -0,0 +1 @@ +export * from "../engine/index.js"; diff --git a/packages/thirdweb/src/exports/thirdweb.ts b/packages/thirdweb/src/exports/thirdweb.ts index 503b110bfa7..2e1b8f9e865 100644 --- a/packages/thirdweb/src/exports/thirdweb.ts +++ b/packages/thirdweb/src/exports/thirdweb.ts @@ -81,6 +81,11 @@ export * as Bridge from "../bridge/index.js"; */ export * as Insight from "../insight/index.js"; +/** + * ENGINE + */ +export * as Engine from "../engine/index.js"; + /** * WALLETS */ diff --git a/packages/thirdweb/src/utils/domains.ts b/packages/thirdweb/src/utils/domains.ts index 2a94c2ee7f9..f0f7a9d5031 100644 --- a/packages/thirdweb/src/utils/domains.ts +++ b/packages/thirdweb/src/utils/domains.ts @@ -39,6 +39,11 @@ type DomainOverrides = { * @default "insight.thirdweb.com" */ insight?: string; + /** + * The base URL for the engine cloud server. + * @default "engine.thirdweb.com" + */ + engineCloud?: string; }; export const DEFAULT_RPC_URL = "rpc.thirdweb.com"; @@ -49,6 +54,8 @@ const DEFAULT_STORAGE_URL = "storage.thirdweb.com"; const DEFAULT_BUNDLER_URL = "bundler.thirdweb.com"; const DEFAULT_ANALYTICS_URL = "c.thirdweb.com"; const DEFAULT_INSIGHT_URL = "insight.thirdweb.com"; +const DEFAULT_ENGINE_CLOUD_URL = "engine.thirdweb.com"; + let domains: { [k in keyof DomainOverrides]-?: string } = { rpc: DEFAULT_RPC_URL, inAppWallet: DEFAULT_IN_APP_WALLET_URL, @@ -58,6 +65,7 @@ let domains: { [k in keyof DomainOverrides]-?: string } = { bundler: DEFAULT_BUNDLER_URL, analytics: DEFAULT_ANALYTICS_URL, insight: DEFAULT_INSIGHT_URL, + engineCloud: DEFAULT_ENGINE_CLOUD_URL, }; export const setThirdwebDomains = (DomainOverrides: DomainOverrides) => { @@ -70,6 +78,7 @@ export const setThirdwebDomains = (DomainOverrides: DomainOverrides) => { bundler: DomainOverrides.bundler ?? DEFAULT_BUNDLER_URL, analytics: DomainOverrides.analytics ?? DEFAULT_ANALYTICS_URL, insight: DomainOverrides.insight ?? DEFAULT_INSIGHT_URL, + engineCloud: DomainOverrides.engineCloud ?? DEFAULT_ENGINE_CLOUD_URL, }; }; diff --git a/packages/thirdweb/src/utils/fetch.ts b/packages/thirdweb/src/utils/fetch.ts index f3f7474e939..a3697e7c1dd 100644 --- a/packages/thirdweb/src/utils/fetch.ts +++ b/packages/thirdweb/src/utils/fetch.ts @@ -27,7 +27,11 @@ export function getClientFetch(client: ThirdwebClient, ecosystem?: Ecosystem) { const { requestTimeoutMs = DEFAULT_REQUEST_TIMEOUT, ...restInit } = init || {}; - let headers = restInit.headers ? new Headers(restInit.headers) : undefined; + let headers = restInit.headers + ? new Headers(restInit.headers) + : typeof url === "object" + ? url.headers + : undefined; const urlString = typeof url === "string" ? url : url.url; // check if we are making a request to a thirdweb service (we don't want to send any headers to non-thirdweb services) @@ -60,9 +64,13 @@ export function getClientFetch(client: ThirdwebClient, ecosystem?: Ecosystem) { if (client.teamId) { headers.set("x-team-id", client.teamId); } - } else if (secretKey) { + } + + if (secretKey) { headers.set("x-secret-key", secretKey); - } else if (clientId) { + } + + if (clientId) { headers.set("x-client-id", clientId); } @@ -112,6 +120,7 @@ const THIRDWEB_DOMAINS = [ // dev domains ".thirdweb.dev", ".thirdweb-dev.com", + ".chainsaw-dev.zeet.app", // TODO (cloud): remove this once we have a proper domain ] as const; export const IS_THIRDWEB_URL_CACHE = new LruMap(4096); diff --git a/packages/thirdweb/src/wallets/engine/index.ts b/packages/thirdweb/src/wallets/engine/index.ts index 1d982d22023..53e122d0619 100644 --- a/packages/thirdweb/src/wallets/engine/index.ts +++ b/packages/thirdweb/src/wallets/engine/index.ts @@ -42,6 +42,7 @@ export type EngineAccountOptions = { /** * Creates an account that uses your engine backend wallet for sending transactions and signing messages. + * @deprecated This for v2 dedicated engine instances, for v3 and engine cloud use Engine.serverWallet() * * @param options - The options for the engine account. * @returns An account that uses your engine backend wallet. diff --git a/packages/thirdweb/src/wallets/smart/lib/paymaster.ts b/packages/thirdweb/src/wallets/smart/lib/paymaster.ts index 8c91ecbd233..00d90b753f8 100644 --- a/packages/thirdweb/src/wallets/smart/lib/paymaster.ts +++ b/packages/thirdweb/src/wallets/smart/lib/paymaster.ts @@ -82,9 +82,9 @@ export async function getPaymasterAndData(args: { }; } // check for policy errors - if (res.result.policyId && res.result.reason) { + if (res.result.reason) { console.warn( - `Paymaster policy rejected this transaction with reason: ${res.result.reason} (policyId: ${res.result.policyId})`, + `Paymaster policy rejected this transaction with reason: ${res.result.reason} ${res.result.policyId ? `(policyId: ${res.result.policyId})` : ""}`, ); } diff --git a/packages/thirdweb/tsdoc.json b/packages/thirdweb/tsdoc.json index 823f07d24fa..68f207404d9 100644 --- a/packages/thirdweb/tsdoc.json +++ b/packages/thirdweb/tsdoc.json @@ -104,6 +104,10 @@ { "tagName": "@insight", "syntaxKind": "block" + }, + { + "tagName": "@engine", + "syntaxKind": "block" } ], "supportForTags": { @@ -132,6 +136,7 @@ "@account": true, "@beta": true, "@nebula": true, - "@insight": true + "@insight": true, + "@engine": true } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4e3a8424207..251351efeab 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -958,18 +958,37 @@ importers: specifier: 5.8.3 version: 5.8.3 + packages/engine: + dependencies: + '@hey-api/client-fetch': + specifier: 0.10.0 + version: 0.10.0(@hey-api/openapi-ts@0.67.0(magicast@0.3.5)(typescript@5.8.3)) + typescript: + specifier: '>=5.0.4' + version: 5.8.3 + devDependencies: + '@hey-api/openapi-ts': + specifier: 0.67.0 + version: 0.67.0(magicast@0.3.5)(typescript@5.8.3) + rimraf: + specifier: 6.0.1 + version: 6.0.1 + tslib: + specifier: ^2.8.1 + version: 2.8.1 + packages/insight: dependencies: '@hey-api/client-fetch': specifier: 0.10.0 - version: 0.10.0(@hey-api/openapi-ts@0.66.5(magicast@0.3.5)(typescript@5.8.3)) + version: 0.10.0(@hey-api/openapi-ts@0.67.0(magicast@0.3.5)(typescript@5.8.3)) typescript: specifier: '>=5.0.4' version: 5.8.3 devDependencies: '@hey-api/openapi-ts': - specifier: 0.66.5 - version: 0.66.5(magicast@0.3.5)(typescript@5.8.3) + specifier: 0.67.0 + version: 0.67.0(magicast@0.3.5)(typescript@5.8.3) rimraf: specifier: 6.0.1 version: 6.0.1 @@ -1095,6 +1114,9 @@ importers: '@tanstack/react-query': specifier: 5.74.4 version: 5.74.4(react@19.1.0) + '@thirdweb-dev/engine': + specifier: workspace:* + version: link:../engine '@thirdweb-dev/insight': specifier: workspace:* version: link:../insight @@ -3311,12 +3333,12 @@ packages: peerDependencies: '@hey-api/openapi-ts': < 2 - '@hey-api/json-schema-ref-parser@1.0.4': - resolution: {integrity: sha512-IaJ4yFgU5r63KZyeySHRKSM1bavFIda8KdwCFi5BxQCIklltzEByBksNOPms+yHXpWWfR+OopIusVZV8roycYg==} + '@hey-api/json-schema-ref-parser@1.0.5': + resolution: {integrity: sha512-bWUV9ICwvU5I3YKVZqWIUXFC2SIXznUi/u+LqurJx6ILiyImfZD5+g/lj3w4EiyXxmjqyaxptzUz/1IgK3vVtw==} engines: {node: '>= 16'} - '@hey-api/openapi-ts@0.66.5': - resolution: {integrity: sha512-3bk0UMrIYdF7IC+SoJ941jni0rmc0cTk10MzcKarW5ybR2z631UrIU1sHNzadftaYL/WK+dbaDRwJ5Juhmf9/A==} + '@hey-api/openapi-ts@0.67.0': + resolution: {integrity: sha512-AWRcLkTy3vJbyxT6wxEkKw8Qou6Pl2hV0zGn2/eu3EVmmk6WEEjzbRg2v6qkDXtsaxVep6N8ON1yajHg8t33JQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=22.10.0} hasBin: true peerDependencies: @@ -19510,19 +19532,20 @@ snapshots: dependencies: react: 19.1.0 - '@hey-api/client-fetch@0.10.0(@hey-api/openapi-ts@0.66.5(magicast@0.3.5)(typescript@5.8.3))': + '@hey-api/client-fetch@0.10.0(@hey-api/openapi-ts@0.67.0(magicast@0.3.5)(typescript@5.8.3))': dependencies: - '@hey-api/openapi-ts': 0.66.5(magicast@0.3.5)(typescript@5.8.3) + '@hey-api/openapi-ts': 0.67.0(magicast@0.3.5)(typescript@5.8.3) - '@hey-api/json-schema-ref-parser@1.0.4': + '@hey-api/json-schema-ref-parser@1.0.5': dependencies: '@jsdevtools/ono': 7.1.3 '@types/json-schema': 7.0.15 js-yaml: 4.1.0 + lodash: 4.17.21 - '@hey-api/openapi-ts@0.66.5(magicast@0.3.5)(typescript@5.8.3)': + '@hey-api/openapi-ts@0.67.0(magicast@0.3.5)(typescript@5.8.3)': dependencies: - '@hey-api/json-schema-ref-parser': 1.0.4 + '@hey-api/json-schema-ref-parser': 1.0.5 c12: 2.0.1(magicast@0.3.5) commander: 13.0.0 handlebars: 4.7.8 @@ -28089,8 +28112,8 @@ snapshots: '@typescript-eslint/parser': 7.14.1(eslint@8.57.0)(typescript@5.8.3) eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0)(eslint@8.57.0) - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.0) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.8.3))(eslint@8.57.0))(eslint@8.57.0) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.8.3))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) eslint-plugin-jsx-a11y: 6.10.2(eslint@8.57.0) eslint-plugin-react: 7.37.5(eslint@8.57.0) eslint-plugin-react-hooks: 5.2.0(eslint@8.57.0) @@ -28109,8 +28132,8 @@ snapshots: '@typescript-eslint/parser': 7.14.1(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3) eslint: 9.24.0(jiti@2.4.2) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.14.1(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.24.0(jiti@2.4.2)))(eslint@9.24.0(jiti@2.4.2)) - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.14.1(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.14.1(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.24.0(jiti@2.4.2)))(eslint@9.24.0(jiti@2.4.2)))(eslint@9.24.0(jiti@2.4.2)) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0)(eslint@9.24.0(jiti@2.4.2)) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.14.1(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.24.0(jiti@2.4.2)) eslint-plugin-jsx-a11y: 6.10.2(eslint@9.24.0(jiti@2.4.2)) eslint-plugin-react: 7.37.5(eslint@9.24.0(jiti@2.4.2)) eslint-plugin-react-hooks: 5.2.0(eslint@9.24.0(jiti@2.4.2)) @@ -28129,33 +28152,33 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.14.1(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.24.0(jiti@2.4.2)))(eslint@9.24.0(jiti@2.4.2)): + eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.8.3))(eslint@8.57.0))(eslint@8.57.0): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.4.0(supports-color@8.1.1) - eslint: 9.24.0(jiti@2.4.2) + eslint: 8.57.0 get-tsconfig: 4.10.0 is-bun-module: 2.0.0 stable-hash: 0.0.5 tinyglobby: 0.2.13 unrs-resolver: 1.6.3 optionalDependencies: - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.14.1(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.14.1(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.24.0(jiti@2.4.2)))(eslint@9.24.0(jiti@2.4.2)))(eslint@9.24.0(jiti@2.4.2)) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.8.3))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0)(eslint@8.57.0): + eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0)(eslint@9.24.0(jiti@2.4.2)): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.4.0(supports-color@8.1.1) - eslint: 8.57.0 + eslint: 9.24.0(jiti@2.4.2) get-tsconfig: 4.10.0 is-bun-module: 2.0.0 stable-hash: 0.0.5 tinyglobby: 0.2.13 unrs-resolver: 1.6.3 optionalDependencies: - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.0) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.14.1(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.24.0(jiti@2.4.2)) transitivePeerDependencies: - supports-color @@ -28180,29 +28203,29 @@ snapshots: - bluebird - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0)(eslint@8.57.0))(eslint@8.57.0): + eslint-module-utils@2.12.0(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.8.3))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 7.14.1(eslint@8.57.0)(typescript@5.8.3) eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0)(eslint@8.57.0) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.8.3))(eslint@8.57.0))(eslint@8.57.0) transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@7.14.1(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.14.1(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.24.0(jiti@2.4.2)))(eslint@9.24.0(jiti@2.4.2)))(eslint@9.24.0(jiti@2.4.2)): + eslint-module-utils@2.12.0(@typescript-eslint/parser@7.14.1(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0)(eslint@9.24.0(jiti@2.4.2)))(eslint@9.24.0(jiti@2.4.2)): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 7.14.1(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3) eslint: 9.24.0(jiti@2.4.2) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.14.1(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.24.0(jiti@2.4.2)))(eslint@9.24.0(jiti@2.4.2)) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0)(eslint@9.24.0(jiti@2.4.2)) transitivePeerDependencies: - supports-color - eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.0): + eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.8.3))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.8 @@ -28213,7 +28236,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0)(eslint@8.57.0))(eslint@8.57.0) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.8.3))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -28231,7 +28254,7 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.14.1(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.14.1(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.24.0(jiti@2.4.2)))(eslint@9.24.0(jiti@2.4.2)))(eslint@9.24.0(jiti@2.4.2)): + eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.14.1(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.24.0(jiti@2.4.2)): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.8 @@ -28242,7 +28265,7 @@ snapshots: doctrine: 2.1.0 eslint: 9.24.0(jiti@2.4.2) eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@7.14.1(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.14.1(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.24.0(jiti@2.4.2)))(eslint@9.24.0(jiti@2.4.2)))(eslint@9.24.0(jiti@2.4.2)) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@7.14.1(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0)(eslint@9.24.0(jiti@2.4.2)))(eslint@9.24.0(jiti@2.4.2)) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 From 40670bb0ae495d3ea2d0b8132ce9ac01650bdd9c Mon Sep 17 00:00:00 2001 From: Joaquim Verges Date: Mon, 5 May 2025 00:44:14 +1200 Subject: [PATCH 088/101] add sdk example --- .../server-wallets/components/try-it-out.tsx | 43 ++++++++++++++++++- .../thirdweb/src/engine/server-wallet.test.ts | 4 +- 2 files changed, 43 insertions(+), 4 deletions(-) diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/server-wallets/components/try-it-out.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/server-wallets/components/try-it-out.tsx index 2fbe7e700b8..552d769843c 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/server-wallets/components/try-it-out.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/server-wallets/components/try-it-out.tsx @@ -13,7 +13,7 @@ export function TryItOut(props: { team_slug: string; project_slug: string; }) { - const [activeTab, setActiveTab] = useState("curl"); + const [activeTab, setActiveTab] = useState("sdk"); return (
@@ -33,7 +33,12 @@ export function TryItOut(props: { setActiveTab("sdk"), + isActive: activeTab === "sdk", + }, + { + name: "Curl", onClick: () => setActiveTab("curl"), isActive: activeTab === "curl", }, @@ -62,6 +67,9 @@ export function TryItOut(props: {
+ {activeTab === "sdk" && ( + + )} {activeTab === "curl" && ( `\ +import { Engine, createThirdwebClient, sendTransaction } from "thirdweb"; +import { claimTo } from "thirdweb/extensions/erc1155"; + +const client = createThirdwebClient({ + secretKey: "", +}); + +// Create a server wallet +const serverWallet = Engine.serverWallet({ + client, + walletAddress: "", + vaultAccessToken: "", +}); + +// Prepare a transaction +const transaction = claimTo({ + contract, + to: "0x...", + tokenId: 0n, + quantity: 1n, +}); + +// Send the transaction +const result = await sendTransaction({ + account: serverWallet, + transaction, +}); +console.log("Transaction hash:", result.transactionHash); +`; + const curlExample = () => `\ curl -X POST "${THIRDWEB_ENGINE_CLOUD_URL}/write/contract" \\ -H "Content-Type: application/json" \\ diff --git a/packages/thirdweb/src/engine/server-wallet.test.ts b/packages/thirdweb/src/engine/server-wallet.test.ts index 783114a7f5c..9963a2f8ab4 100644 --- a/packages/thirdweb/src/engine/server-wallet.test.ts +++ b/packages/thirdweb/src/engine/server-wallet.test.ts @@ -113,7 +113,7 @@ describe.runIf( ).rejects.toThrow(); }); - it.only("should send a session key tx", async () => { + it.skip("should send a session key tx", async () => { const personalAccount = await generateAccount({ client: TEST_CLIENT, }); @@ -142,7 +142,7 @@ describe.runIf( type: "ERC4337", signerAddress: process.env.ENGINE_CLOUD_WALLET_ADDRESS_EOA as string, smartAccountAddress: smartAccount.address, - entrypointAddress: ENTRYPOINT_ADDRESS_v0_6, + entrypointAddress: ENTRYPOINT_ADDRESS_v0_6, // TODO (cloud): not working for 0.6, needs fix factoryAddress: DEFAULT_ACCOUNT_FACTORY_V0_6, }, }); From 5846922766c17fe249ceafe47af576a489eb4df4 Mon Sep 17 00:00:00 2001 From: Joaquim Verges Date: Mon, 5 May 2025 11:18:55 +1200 Subject: [PATCH 089/101] update routes --- .../engine/cloud/analytics/tx-table/tx-table.tsx | 2 +- .../[project_slug]/engine/cloud/lib/analytics.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/tx-table/tx-table.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/tx-table/tx-table.tsx index dfec5616089..557d7016f60 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/tx-table/tx-table.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/tx-table/tx-table.tsx @@ -35,7 +35,7 @@ async function getTransactions({ }) { const transactions = await engineCloudProxy<{ result: TransactionsResponse }>( { - pathname: "/project/transactions/search", + pathname: "/transactions/search", method: "POST", headers: { "Content-Type": "application/json", diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/lib/analytics.ts b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/lib/analytics.ts index e926c2f55cb..0f48a5e425c 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/lib/analytics.ts +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/lib/analytics.ts @@ -43,7 +43,7 @@ export async function getTransactionAnalyticsSummary(props: { try { const response = await fetch( - `${THIRDWEB_ENGINE_CLOUD_URL}/project/transactions/analytics-summary`, + `${THIRDWEB_ENGINE_CLOUD_URL}/transactions/analytics-summary`, { method: "POST", headers: { @@ -102,7 +102,7 @@ export async function getTransactionsChart({ }; const response = await fetch( - `${THIRDWEB_ENGINE_CLOUD_URL}/project/transactions/analytics`, + `${THIRDWEB_ENGINE_CLOUD_URL}/transactions/analytics`, { method: "POST", headers: { @@ -172,7 +172,7 @@ export async function getSingleTransaction({ }; const response = await fetch( - `${THIRDWEB_ENGINE_CLOUD_URL}/project/transactions/search`, + `${THIRDWEB_ENGINE_CLOUD_URL}/transactions/search`, { method: "POST", headers: { From d3c5ea4f4248b5bce2308d7bbf80eb6e891ae949 Mon Sep 17 00:00:00 2001 From: Joaquim Verges Date: Mon, 5 May 2025 13:35:36 +1200 Subject: [PATCH 090/101] new autogen api --- packages/engine/biome.json | 5 +- packages/engine/openapi-ts.config.ts | 4 +- packages/engine/src/client/client.gen.ts | 2 +- packages/engine/src/client/sdk.gen.ts | 90 +- packages/engine/src/client/types.gen.ts | 1635 +++++++++-------- .../thirdweb/src/engine/server-wallet.test.ts | 29 +- packages/thirdweb/src/engine/server-wallet.ts | 69 +- 7 files changed, 998 insertions(+), 836 deletions(-) diff --git a/packages/engine/biome.json b/packages/engine/biome.json index 7520ee9c4d2..aad1b5fabcf 100644 --- a/packages/engine/biome.json +++ b/packages/engine/biome.json @@ -1,7 +1,4 @@ { "$schema": "https://biomejs.dev/schemas/1.9.2/schema.json", - "extends": ["../../biome.json"], - "files": { - "ignore": ["src/client/**"] - } + "extends": ["../../biome.json"] } diff --git a/packages/engine/openapi-ts.config.ts b/packages/engine/openapi-ts.config.ts index 61ead8e7c88..f21fc337c5c 100644 --- a/packages/engine/openapi-ts.config.ts +++ b/packages/engine/openapi-ts.config.ts @@ -1,7 +1,7 @@ import { defineConfig } from "@hey-api/openapi-ts"; export default defineConfig({ - input: "https://engine-cloud-dev-l8wt.chainsaw-dev.zeet.app/openapi", // TODO: update to prod - output: { path: "src/client" }, + input: "http://localhost:3009/openapi", // TODO: update to prod + output: { path: "src/client", lint: "biome", format: "biome" }, plugins: ["@hey-api/client-fetch"], }); diff --git a/packages/engine/src/client/client.gen.ts b/packages/engine/src/client/client.gen.ts index 6549af15edf..a0b05b5e1c0 100644 --- a/packages/engine/src/client/client.gen.ts +++ b/packages/engine/src/client/client.gen.ts @@ -23,6 +23,6 @@ export type CreateClientConfig = export const client = createClient( createConfig({ - baseUrl: "https://engine-cloud-dev-l8wt.chainsaw-dev.zeet.app", + baseUrl: "http://localhost:3009", }), ); diff --git a/packages/engine/src/client/sdk.gen.ts b/packages/engine/src/client/sdk.gen.ts index f1e1b32b940..59c2801af4d 100644 --- a/packages/engine/src/client/sdk.gen.ts +++ b/packages/engine/src/client/sdk.gen.ts @@ -7,28 +7,28 @@ import type { } from "@hey-api/client-fetch"; import { client as _heyApiClient } from "./client.gen.js"; import type { + EncodeFunctionDataData, + EncodeFunctionDataResponse, GetTransactionAnalyticsData, GetTransactionAnalyticsResponse, GetTransactionAnalyticsSummaryData, GetTransactionAnalyticsSummaryResponse, - PostEncodeContractData, - PostEncodeContractResponse, PostReadBalanceData, PostReadBalanceResponse, - PostReadContractData, - PostReadContractResponse, - PostSignMessageData, - PostSignMessageResponse, - PostSignTransactionData, - PostSignTransactionResponse, - PostSignTypedDataData, - PostSignTypedDataResponse, - PostWriteContractData, - PostWriteContractResponse, - PostWriteTransactionData, - PostWriteTransactionResponse, + ReadContractData, + ReadContractResponse, SearchTransactionsData, SearchTransactionsResponse, + SendTransactionData, + SendTransactionResponse, + SignMessageData, + SignMessageResponse, + SignTransactionData, + SignTransactionResponse, + SignTypedDataData, + SignTypedDataResponse, + WriteContractData, + WriteContractResponse, } from "./types.gen.js"; export type Options< @@ -49,14 +49,14 @@ export type Options< }; /** - * Write to a Contract with a Function Call + * Write Contract * Call a write function on a contract. */ -export const postWriteContract = ( - options?: Options, +export const writeContract = ( + options?: Options, ) => { return (options?.client ?? _heyApiClient).post< - PostWriteContractResponse, + WriteContractResponse, unknown, ThrowOnError >({ @@ -76,14 +76,14 @@ export const postWriteContract = ( }; /** - * Send an Encoded Transaction - * Send a transaction or a batch of transactions + * Send Transaction + * Send an encoded transaction or a batch of transactions */ -export const postWriteTransaction = ( - options?: Options, +export const sendTransaction = ( + options?: Options, ) => { return (options?.client ?? _heyApiClient).post< - PostWriteTransactionResponse, + SendTransactionResponse, unknown, ThrowOnError >({ @@ -106,11 +106,11 @@ export const postWriteTransaction = ( * Sign Transaction * Sign transactions without sending them. */ -export const postSignTransaction = ( - options?: Options, +export const signTransaction = ( + options?: Options, ) => { return (options?.client ?? _heyApiClient).post< - PostSignTransactionResponse, + SignTransactionResponse, unknown, ThrowOnError >({ @@ -133,11 +133,11 @@ export const postSignTransaction = ( * Sign Message * Sign arbitrary messages. */ -export const postSignMessage = ( - options?: Options, +export const signMessage = ( + options?: Options, ) => { return (options?.client ?? _heyApiClient).post< - PostSignMessageResponse, + SignMessageResponse, unknown, ThrowOnError >({ @@ -160,11 +160,11 @@ export const postSignMessage = ( * Sign Typed Data * Sign EIP-712 typed data. */ -export const postSignTypedData = ( - options?: Options, +export const signTypedData = ( + options?: Options, ) => { return (options?.client ?? _heyApiClient).post< - PostSignTypedDataResponse, + SignTypedDataResponse, unknown, ThrowOnError >({ @@ -184,14 +184,14 @@ export const postSignTypedData = ( }; /** - * Read from a Contract - * Call read-only functions using multicall. + * Read Contract + * Call read-only contract functions or batch read using multicall. */ -export const postReadContract = ( - options?: Options, +export const readContract = ( + options?: Options, ) => { return (options?.client ?? _heyApiClient).post< - PostReadContractResponse, + ReadContractResponse, unknown, ThrowOnError >({ @@ -238,14 +238,14 @@ export const postReadBalance = ( }; /** - * Encode A Contract Function Call - * Get transaction parameters (to, data, value) for contract calls. + * Encode Function Data + * Encode a contract call into transaction parameters (to, data, value). */ -export const postEncodeContract = ( - options?: Options, +export const encodeFunctionData = ( + options?: Options, ) => { return (options?.client ?? _heyApiClient).post< - PostEncodeContractResponse, + EncodeFunctionDataResponse, unknown, ThrowOnError >({ @@ -282,7 +282,7 @@ export const searchTransactions = ( type: "apiKey", }, ], - url: "/project/transactions/search", + url: "/transactions/search", ...options, headers: { "Content-Type": "application/json", @@ -309,7 +309,7 @@ export const getTransactionAnalytics = ( type: "apiKey", }, ], - url: "/project/transactions/analytics", + url: "/transactions/analytics", ...options, headers: { "Content-Type": "application/json", @@ -338,7 +338,7 @@ export const getTransactionAnalyticsSummary = < type: "apiKey", }, ], - url: "/project/transactions/analytics-summary", + url: "/transactions/analytics-summary", ...options, headers: { "Content-Type": "application/json", diff --git a/packages/engine/src/client/types.gen.ts b/packages/engine/src/client/types.gen.ts index d9d1696b091..489656fd936 100644 --- a/packages/engine/src/client/types.gen.ts +++ b/packages/engine/src/client/types.gen.ts @@ -1,862 +1,1017 @@ // This file is auto-generated by @hey-api/openapi-ts export type TransactionsFilterValue = { - field: 'id' | 'batchIndex' | 'from' | 'signerAddress' | 'smartAccountAddress' | 'chainId'; - values: Array; - operation: 'AND' | 'OR'; + field: + | "id" + | "batchIndex" + | "from" + | "signerAddress" + | "smartAccountAddress" + | "chainId"; + values: Array; + operation: "AND" | "OR"; }; export type TransactionsFilterNested = { - operation: 'AND' | 'OR'; - filters: Array; + operation: "AND" | "OR"; + filters: Array; }; /** * This is the default execution option. If you do not specify an execution type, and only specify a "from" string, engine will automatically determine the most optimal options for you. If you would like to specify granular options about execution strategy choose one of the other `executionOptions` type and provide them. */ export type AutoExecutionOptions = { - /** - * This is the default, a `type` does not need to be specified - */ - type?: 'auto'; - /** - * The address of the account to send the transaction from. Can be the address of a smart account or an EOA. - */ - from: string; - /** - * The idempotency key of the transaction. Transaction requests sent with the same idempotency key will be de-duplicated. If not provided, a randomUUID will be generated. This is also used as the ID of a queued/stored transaction. - */ - idempotencyKey?: string; - /** - * The chain id of the transaction - */ - chainId: string; + /** + * This is the default, a `type` does not need to be specified + */ + type?: "auto"; + /** + * The address of the account to send the transaction from. Can be the address of a smart account or an EOA. + */ + from: string; + /** + * The idempotency key of the transaction. Transaction requests sent with the same idempotency key will be de-duplicated. If not provided, a randomUUID will be generated. This is also used as the ID of a queued/stored transaction. + */ + idempotencyKey?: string; + /** + * The chain id of the transaction + */ + chainId: string; }; export type AaExecutionOptions = { - type: 'ERC4337'; - /** - * The address of the engine managed account which can send transactions from your smart account - */ - signerAddress: string; - sponsorGas?: boolean; - /** - * The address of the smart account factory. Defaults to thirdweb default v0.7 Account Factory. Only specify this if you are using a custom account factory. - */ - factoryAddress?: string; - /** - * The address of the entrypoint contract. Defaults to the v0.7 entrypoint for the chain. Only specify this if you want to specify a different version. - */ - entrypointAddress?: string; - /** - * The address of the smart account to send the transaction from. Either specify this, or the salt. If not specified, the inferred smart account will be with null salt. If both are specified, the salt will be ignored. - */ - smartAccountAddress?: string; - /** - * The salt of the smart account to send the transaction from. Only specify this if you want to specify a custom salt. If omitted, and smart account address is not provided, the inferred smart account will be with null salt. If a smart account address is provided, the salt will be ignored. - */ - accountSalt?: string; - /** - * The idempotency key of the transaction. Transaction requests sent with the same idempotency key will be de-duplicated. If not provided, a randomUUID will be generated. This is also used as the ID of a queued/stored transaction. - */ - idempotencyKey?: string; - /** - * The chain id of the transaction - */ - chainId: string; + type: "ERC4337"; + /** + * The address of the engine managed account which can send transactions from your smart account + */ + signerAddress: string; + sponsorGas?: boolean; + /** + * The address of the smart account factory. Defaults to thirdweb default v0.7 Account Factory. Only specify this if you are using a custom account factory. + */ + factoryAddress?: string; + /** + * The address of the entrypoint contract. Defaults to the v0.7 entrypoint for the chain. Only specify this if you want to specify a different version. + */ + entrypointAddress?: string; + /** + * The address of the smart account to send the transaction from. Either specify this, or the salt. If not specified, the inferred smart account will be with null salt. If both are specified, the salt will be ignored. + */ + smartAccountAddress?: string; + /** + * The salt of the smart account to send the transaction from. Only specify this if you want to specify a custom salt. If omitted, and smart account address is not provided, the inferred smart account will be with null salt. If a smart account address is provided, the salt will be ignored. + */ + accountSalt?: string; + /** + * The idempotency key of the transaction. Transaction requests sent with the same idempotency key will be de-duplicated. If not provided, a randomUUID will be generated. This is also used as the ID of a queued/stored transaction. + */ + idempotencyKey?: string; + /** + * The chain id of the transaction + */ + chainId: string; }; /** * Uses zkSync native AA for execution. This type of execution is only available on zkSync chains. */ export type AaZksyncExecutionOptions = { - type: 'zksync'; - /** - * The EOA address of the account to send the zksync native AA transaction from. - */ - accountAddress: string; - sponsorGas?: boolean; + type: "zksync"; + /** + * The EOA address of the account to send the zksync native AA transaction from. + */ + accountAddress: string; + sponsorGas?: boolean; + /** + * The idempotency key of the transaction. Transaction requests sent with the same idempotency key will be de-duplicated. If not provided, a randomUUID will be generated. This is also used as the ID of a queued/stored transaction. + */ + idempotencyKey?: string; + /** + * The chain id of the transaction + */ + chainId: string; +}; + +export type WriteContractData = { + body?: { /** - * The idempotency key of the transaction. Transaction requests sent with the same idempotency key will be de-duplicated. If not provided, a randomUUID will be generated. This is also used as the ID of a queued/stored transaction. + * Use a specific execution type and provide options to configure engine's execution strategy. The default execution option is `auto`, (doesn't need to be specified) which will automatically determine the most optimal options for you. If you would like to specify granular options about execution strategy choose one of the other `executionOptions` type and provide them. */ - idempotencyKey?: string; + executionOptions: + | AutoExecutionOptions + | AaExecutionOptions + | AaZksyncExecutionOptions; + params: Array<{ + /** + * The function to call on the contract + */ + method: string; + /** + * The parameters to pass to the function + */ + params: Array; + /** + * The contract address to call + */ + contractAddress: string; + /** + * The ABI of the contract + */ + abi?: Array; + /** + * The value to send with the transaction + */ + value?: string; + }>; + }; + headers?: { /** - * The chain id of the transaction + * Vault Access Token used to access your EOA */ - chainId: string; + "x-vault-access-token"?: string; + }; + path?: never; + query?: never; + url: "/write/contract"; }; -export type PostWriteContractData = { - body?: { - /** - * Use a specific execution type and provide options to configure engine's execution strategy. The default execution option is `auto`, (doesn't need to be specified) which will automatically determine the most optimal options for you. If you would like to specify granular options about execution strategy choose one of the other `executionOptions` type and provide them. - */ - executionOptions: AutoExecutionOptions | AaExecutionOptions | AaZksyncExecutionOptions; - params: Array<{ - /** - * The function to call on the contract - */ - method: string; - /** - * The parameters to pass to the function - */ - params: Array; - /** - * The contract address to call - */ - contractAddress: string; - /** - * The ABI of the contract - */ - abi?: Array; - /** - * The value to send with the transaction - */ - value?: string; - }>; +export type WriteContractResponses = { + /** + * Transaction sent successfully + */ + 200: { + result: { + transactions: Array<{ + id: string; + batchIndex: number; + chainId: string; + from: string | null; + transactionParams: + | (string | number | boolean | null) + | { + [key: string]: unknown; + } + | Array; + transactionHash: string | null; + confirmedAt: string | null; + confirmedAtBlockNumber: string | null; + enrichedData: + | (string | number | boolean | null) + | { + [key: string]: unknown; + } + | Array; + executionParams: + | (string | number | boolean | null) + | { + [key: string]: unknown; + } + | Array; + executionResult: + | (string | number | boolean | null) + | { + [key: string]: unknown; + } + | Array + | null; + createdAt: string; + errorMessage: string | null; + cancelledAt: string | null; + }>; }; - headers?: { - /** - * Vault Access Token used to access your EOA - */ - 'x-vault-access-token'?: string; + }; + /** + * Transaction queued successfully + */ + 202: { + result: { + transactions: Array<{ + id: string; + batchIndex: number; + chainId: string; + from: string | null; + transactionParams: + | (string | number | boolean | null) + | { + [key: string]: unknown; + } + | Array; + transactionHash: string | null; + confirmedAt: string | null; + confirmedAtBlockNumber: string | null; + enrichedData: + | (string | number | boolean | null) + | { + [key: string]: unknown; + } + | Array; + executionParams: + | (string | number | boolean | null) + | { + [key: string]: unknown; + } + | Array; + executionResult: + | (string | number | boolean | null) + | { + [key: string]: unknown; + } + | Array + | null; + createdAt: string; + errorMessage: string | null; + cancelledAt: string | null; + }>; }; - path?: never; - query?: never; - url: '/write/contract'; + }; }; -export type PostWriteContractResponses = { +export type WriteContractResponse = + WriteContractResponses[keyof WriteContractResponses]; + +export type SendTransactionData = { + body?: { /** - * Transaction sent successfully + * Use a specific execution type and provide options to configure engine's execution strategy. The default execution option is `auto`, (doesn't need to be specified) which will automatically determine the most optimal options for you. If you would like to specify granular options about execution strategy choose one of the other `executionOptions` type and provide them. */ - 200: { - result: Array<{ - id: string; - batchIndex: number; - chainId: string; - from: string | null; - transactionParams: (string | number | boolean | null) | {} | Array; - transactionHash: string | null; - confirmedAt: string | null; - confirmedAtBlockNumber: string | null; - enrichedData: (string | number | boolean | null) | {} | Array; - executionParams: (string | number | boolean | null) | {} | Array; - executionResult: (string | number | boolean | null) | {} | Array | null; - createdAt: string; - errorMessage: string | null; - cancelledAt: string | null; - }>; - }; + executionOptions: + | AutoExecutionOptions + | AaExecutionOptions + | AaZksyncExecutionOptions; + params: Array<{ + /** + * The address of the contract to send the transaction to + */ + to?: string; + /** + * The data of the transaction + */ + data?: string; + /** + * The value of the transaction + */ + value?: string; + }>; + }; + headers?: { /** - * Transaction queued successfully + * Vault Access Token used to access your EOA */ - 202: { - result: { - transactions: Array<{ - id: string; - batchIndex: number; - chainId: string; - from: string | null; - transactionParams: (string | number | boolean | null) | {} | Array; - transactionHash: string | null; - confirmedAt: string | null; - confirmedAtBlockNumber: string | null; - enrichedData: (string | number | boolean | null) | {} | Array; - executionParams: (string | number | boolean | null) | {} | Array; - executionResult: (string | number | boolean | null) | {} | Array | null; - createdAt: string; - errorMessage: string | null; - cancelledAt: string | null; - }>; - }; - }; + "x-vault-access-token"?: string; + }; + path?: never; + query?: never; + url: "/write/transaction"; }; -export type PostWriteContractResponse = PostWriteContractResponses[keyof PostWriteContractResponses]; - -export type PostWriteTransactionData = { - body?: { - /** - * Use a specific execution type and provide options to configure engine's execution strategy. The default execution option is `auto`, (doesn't need to be specified) which will automatically determine the most optimal options for you. If you would like to specify granular options about execution strategy choose one of the other `executionOptions` type and provide them. - */ - executionOptions: AutoExecutionOptions | AaExecutionOptions | AaZksyncExecutionOptions; - params: Array<{ - /** - * The address of the contract to send the transaction to - */ - to?: string; - /** - * The data of the transaction - */ - data?: string; - /** - * The value of the transaction - */ - value?: string; - }>; +export type SendTransactionResponses = { + /** + * Transaction sent successfully + */ + 200: { + result: { + transactions: Array<{ + id: string; + batchIndex: number; + chainId: string; + from: string | null; + transactionParams: + | (string | number | boolean | null) + | { + [key: string]: unknown; + } + | Array; + transactionHash: string | null; + confirmedAt: string | null; + confirmedAtBlockNumber: string | null; + enrichedData: + | (string | number | boolean | null) + | { + [key: string]: unknown; + } + | Array; + executionParams: + | (string | number | boolean | null) + | { + [key: string]: unknown; + } + | Array; + executionResult: + | (string | number | boolean | null) + | { + [key: string]: unknown; + } + | Array + | null; + createdAt: string; + errorMessage: string | null; + cancelledAt: string | null; + }>; }; - headers?: { - /** - * Vault Access Token used to access your EOA - */ - 'x-vault-access-token'?: string; + }; + /** + * Transaction queued successfully + */ + 202: { + result: { + transactions: Array<{ + id: string; + batchIndex: number; + chainId: string; + from: string | null; + transactionParams: + | (string | number | boolean | null) + | { + [key: string]: unknown; + } + | Array; + transactionHash: string | null; + confirmedAt: string | null; + confirmedAtBlockNumber: string | null; + enrichedData: + | (string | number | boolean | null) + | { + [key: string]: unknown; + } + | Array; + executionParams: + | (string | number | boolean | null) + | { + [key: string]: unknown; + } + | Array; + executionResult: + | (string | number | boolean | null) + | { + [key: string]: unknown; + } + | Array + | null; + createdAt: string; + errorMessage: string | null; + cancelledAt: string | null; + }>; }; - path?: never; - query?: never; - url: '/write/transaction'; + }; }; -export type PostWriteTransactionResponses = { - /** - * Transaction sent successfully - */ - 200: { - result: Array<{ - id: string; - batchIndex: number; - chainId: string; - from: string | null; - transactionParams: (string | number | boolean | null) | {} | Array; - transactionHash: string | null; - confirmedAt: string | null; - confirmedAtBlockNumber: string | null; - enrichedData: (string | number | boolean | null) | {} | Array; - executionParams: (string | number | boolean | null) | {} | Array; - executionResult: (string | number | boolean | null) | {} | Array | null; - createdAt: string; - errorMessage: string | null; - cancelledAt: string | null; - }>; - }; +export type SendTransactionResponse = + SendTransactionResponses[keyof SendTransactionResponses]; + +export type SignTransactionData = { + body?: { /** - * Transaction queued successfully + * Use a specific execution type and provide options to configure engine's execution strategy. The default execution option is `auto`, (doesn't need to be specified) which will automatically determine the most optimal options for you. If you would like to specify granular options about execution strategy choose one of the other `executionOptions` type and provide them. */ - 202: { - result: { - transactions: Array<{ - id: string; - batchIndex: number; - chainId: string; - from: string | null; - transactionParams: (string | number | boolean | null) | {} | Array; - transactionHash: string | null; - confirmedAt: string | null; - confirmedAtBlockNumber: string | null; - enrichedData: (string | number | boolean | null) | {} | Array; - executionParams: (string | number | boolean | null) | {} | Array; - executionResult: (string | number | boolean | null) | {} | Array | null; - createdAt: string; - errorMessage: string | null; - cancelledAt: string | null; - }>; - }; - }; -}; - -export type PostWriteTransactionResponse = PostWriteTransactionResponses[keyof PostWriteTransactionResponses]; - -export type PostSignTransactionData = { - body?: { + executionOptions: + | AutoExecutionOptions + | AaExecutionOptions + | AaZksyncExecutionOptions; + params: Array<{ + /** + * The recipient address + */ + to: string; + /** + * The transaction data as hex + */ + data?: string; + /** + * The value to send with the transaction + */ + value?: string; + /** + * Transaction nonce + */ + nonce?: number; + /** + * Gas limit + */ + gasLimit?: string; + /** + * Gas price (for legacy transactions) + */ + gasPrice?: string; + /** + * Max fee per gas (for EIP-1559) + */ + maxFeePerGas?: string; + /** + * Max priority fee per gas (for EIP-1559) + */ + maxPriorityFeePerGas?: string; + /** + * Access list for EIP-2930 and later transactions + */ + accessList?: Array<{ /** - * Use a specific execution type and provide options to configure engine's execution strategy. The default execution option is `auto`, (doesn't need to be specified) which will automatically determine the most optimal options for you. If you would like to specify granular options about execution strategy choose one of the other `executionOptions` type and provide them. + * EVM address in hex format */ - executionOptions: AutoExecutionOptions | AaExecutionOptions | AaZksyncExecutionOptions; - params: Array<{ - /** - * The recipient address - */ - to: string; - /** - * The transaction data as hex - */ - data?: string; - /** - * The value to send with the transaction - */ - value?: string; - /** - * Transaction nonce - */ - nonce?: number; - /** - * Gas limit - */ - gasLimit?: string; - /** - * Gas price (for legacy transactions) - */ - gasPrice?: string; - /** - * Max fee per gas (for EIP-1559) - */ - maxFeePerGas?: string; - /** - * Max priority fee per gas (for EIP-1559) - */ - maxPriorityFeePerGas?: string; - /** - * Access list for EIP-2930 and later transactions - */ - accessList?: Array<{ - /** - * EVM address in hex format - */ - address: string; - storageKeys: Array; - }>; - /** - * Max fee per blob gas (for EIP-4844) - */ - maxFeePerBlobGas?: string; - /** - * Blob versioned hashes (for EIP-4844) - */ - blobVersionedHashes?: Array; - /** - * Authorization list (for EIP-7702) - */ - authorizationList?: Array<{ - /** - * Authorization address - */ - address: string; - /** - * r value of the signature - */ - r: string; - /** - * s value of the signature - */ - s: string; - /** - * v value of the signature - */ - v?: number | string; - /** - * yParity value (0 or 1) - */ - yParity: number; - /** - * Authorization nonce - */ - nonce: string; - /** - * Authorization chainId - */ - chainId: number; - }>; - }>; - }; - headers?: { + address: string; + storageKeys: Array; + }>; + /** + * Max fee per blob gas (for EIP-4844) + */ + maxFeePerBlobGas?: string; + /** + * Blob versioned hashes (for EIP-4844) + */ + blobVersionedHashes?: Array; + /** + * Authorization list (for EIP-7702) + */ + authorizationList?: Array<{ /** - * Vault Access Token used to access your EOA + * Authorization address */ - 'x-vault-access-token'?: string; - }; - path?: never; - query?: never; - url: '/sign/transaction'; -}; - -export type PostSignTransactionResponses = { - /** - * OK - */ - 200: { - result: { - results: Array<{ - success: true; - result: { - /** - * The resulting signature - */ - signature: string; - /** - * Optional signed data - */ - signedData?: string; - }; - } | { - success: false; - /** - * Standardized error object - */ - error: { - kind: string; - code: string; - status: number; - message?: string; - /** - * EVM address in hex format - */ - address?: string; - chainId?: string; - }; - }>; - }; - }; -}; - -export type PostSignTransactionResponse = PostSignTransactionResponses[keyof PostSignTransactionResponses]; - -export type PostSignMessageData = { - body?: { + address: string; /** - * Use a specific execution type and provide options to configure engine's execution strategy. The default execution option is `auto`, (doesn't need to be specified) which will automatically determine the most optimal options for you. If you would like to specify granular options about execution strategy choose one of the other `executionOptions` type and provide them. + * r value of the signature */ - executionOptions: AutoExecutionOptions | AaExecutionOptions | AaZksyncExecutionOptions; - params: Array<{ - /** - * The message to sign - */ - message: string; - /** - * Format of the message (text or hex) - */ - messageFormat?: 'text' | 'hex'; - }>; - }; - headers?: { + r: string; /** - * Vault Access Token used to access your EOA + * s value of the signature */ - 'x-vault-access-token'?: string; - }; - path?: never; - query?: never; - url: '/sign/message'; -}; - -export type PostSignMessageResponses = { + s: string; + /** + * v value of the signature + */ + v?: number | string; + /** + * yParity value (0 or 1) + */ + yParity: number; + /** + * Authorization nonce + */ + nonce: string; + /** + * Authorization chainId + */ + chainId: number; + }>; + }>; + }; + headers?: { /** - * OK + * Vault Access Token used to access your EOA */ - 200: { - result: { - results: Array<{ - success: true; - result: { - /** - * The resulting signature - */ - signature: string; - /** - * Optional signed data - */ - signedData?: string; - }; - } | { - success: false; - /** - * Standardized error object - */ - error: { - kind: string; - code: string; - status: number; - message?: string; - /** - * EVM address in hex format - */ - address?: string; - chainId?: string; - }; - }>; - }; - }; + "x-vault-access-token"?: string; + }; + path?: never; + query?: never; + url: "/sign/transaction"; }; -export type PostSignMessageResponse = PostSignMessageResponses[keyof PostSignMessageResponses]; - -export type PostSignTypedDataData = { - body?: { - /** - * Use a specific execution type and provide options to configure engine's execution strategy. The default execution option is `auto`, (doesn't need to be specified) which will automatically determine the most optimal options for you. If you would like to specify granular options about execution strategy choose one of the other `executionOptions` type and provide them. - */ - executionOptions: AutoExecutionOptions | AaExecutionOptions | AaZksyncExecutionOptions; - params: Array<{ - domain: { - chainId?: number | number; - name?: string; - salt?: string; - verifyingContract?: string; - version?: string; +export type SignTransactionResponses = { + /** + * OK + */ + 200: { + result: { + results: Array< + | { + success: true; + result: { + /** + * The resulting signature + */ + signature: string; + /** + * Optional signed data + */ + signedData?: string; }; - types: { - [key: string]: Array<{ - name: string; - type: string; - }>; + } + | { + success: false; + /** + * Standardized error object + */ + error: { + kind: string; + code: string; + status: number; + message?: string; + /** + * EVM address in hex format + */ + address?: string; + chainId?: string; }; - primaryType: string; - message: {}; - }>; - }; - headers?: { - /** - * Vault Access Token used to access your EOA - */ - 'x-vault-access-token'?: string; + } + >; }; - path?: never; - query?: never; - url: '/sign/typed-data'; + }; }; -export type PostSignTypedDataResponses = { +export type SignTransactionResponse = + SignTransactionResponses[keyof SignTransactionResponses]; + +export type SignMessageData = { + body?: { /** - * OK + * Use a specific execution type and provide options to configure engine's execution strategy. The default execution option is `auto`, (doesn't need to be specified) which will automatically determine the most optimal options for you. If you would like to specify granular options about execution strategy choose one of the other `executionOptions` type and provide them. */ - 200: { - result: { - results: Array<{ - success: true; - result: { - /** - * The resulting signature - */ - signature: string; - /** - * Optional signed data - */ - signedData?: string; - }; - } | { - success: false; - /** - * Standardized error object - */ - error: { - kind: string; - code: string; - status: number; - message?: string; - /** - * EVM address in hex format - */ - address?: string; - chainId?: string; - }; - }>; - }; - }; + executionOptions: + | AutoExecutionOptions + | AaExecutionOptions + | AaZksyncExecutionOptions; + params: Array<{ + /** + * The message to sign + */ + message: string; + /** + * Format of the message (text or hex) + */ + messageFormat?: "text" | "hex"; + }>; + }; + headers?: { + /** + * Vault Access Token used to access your EOA + */ + "x-vault-access-token"?: string; + }; + path?: never; + query?: never; + url: "/sign/message"; }; -export type PostSignTypedDataResponse = PostSignTypedDataResponses[keyof PostSignTypedDataResponses]; - -export type PostReadContractData = { - body?: { - readOptions: { - /** - * Optional multicall address, defaults to the default multicall3 address for the chain - */ - multicallAddress?: string; - /** - * The chain id of the transaction - */ - chainId: string; - /** - * EVM address in hex format - */ - from?: string; - }; - params: Array<{ - /** - * The function to call on the contract - */ - method: string; - /** - * The parameters to pass to the function - */ - params: Array; - /** - * The contract address to call - */ - contractAddress: string; - /** - * The ABI of the contract - */ - abi?: Array; - }>; +export type SignMessageResponses = { + /** + * OK + */ + 200: { + result: { + results: Array< + | { + success: true; + result: { + /** + * The resulting signature + */ + signature: string; + /** + * Optional signed data + */ + signedData?: string; + }; + } + | { + success: false; + /** + * Standardized error object + */ + error: { + kind: string; + code: string; + status: number; + message?: string; + /** + * EVM address in hex format + */ + address?: string; + chainId?: string; + }; + } + >; }; - path?: never; - query?: never; - url: '/read/contract'; + }; }; -export type PostReadContractResponses = { +export type SignMessageResponse = + SignMessageResponses[keyof SignMessageResponses]; + +export type SignTypedDataData = { + body?: { /** - * OK + * Use a specific execution type and provide options to configure engine's execution strategy. The default execution option is `auto`, (doesn't need to be specified) which will automatically determine the most optimal options for you. If you would like to specify granular options about execution strategy choose one of the other `executionOptions` type and provide them. */ - 200: { - result: { - results: Array<{ - success: boolean; - result?: null; - }>; - }; + executionOptions: + | AutoExecutionOptions + | AaExecutionOptions + | AaZksyncExecutionOptions; + params: Array<{ + domain: { + chainId?: number | number; + name?: string; + salt?: string; + verifyingContract?: string; + version?: string; + }; + types: { + [key: string]: Array<{ + name: string; + type: string; + }>; + }; + primaryType: string; + message: { + [key: string]: unknown; + }; + }>; + }; + headers?: { + /** + * Vault Access Token used to access your EOA + */ + "x-vault-access-token"?: string; + }; + path?: never; + query?: never; + url: "/sign/typed-data"; +}; + +export type SignTypedDataResponses = { + /** + * OK + */ + 200: { + result: { + results: Array< + | { + success: true; + result: { + /** + * The resulting signature + */ + signature: string; + /** + * Optional signed data + */ + signedData?: string; + }; + } + | { + success: false; + /** + * Standardized error object + */ + error: { + kind: string; + code: string; + status: number; + message?: string; + /** + * EVM address in hex format + */ + address?: string; + chainId?: string; + }; + } + >; }; + }; }; -export type PostReadContractResponse = PostReadContractResponses[keyof PostReadContractResponses]; +export type SignTypedDataResponse = + SignTypedDataResponses[keyof SignTypedDataResponses]; + +export type ReadContractData = { + body?: { + readOptions: { + /** + * Optional multicall address, defaults to the default multicall3 address for the chain + */ + multicallAddress?: string; + /** + * The chain id of the transaction + */ + chainId: string; + /** + * EVM address in hex format + */ + from?: string; + }; + params: Array<{ + /** + * The function to call on the contract + */ + method: string; + /** + * The parameters to pass to the function + */ + params: Array; + /** + * The contract address to call + */ + contractAddress: string; + /** + * The ABI of the contract + */ + abi?: Array; + }>; + }; + path?: never; + query?: never; + url: "/read/contract"; +}; -export type PostReadBalanceData = { - body?: { - /** - * The chain ID to query the balance on. - */ - chainId: string; - /** - * The EVM address to get the native balance for. - */ - address: string; +export type ReadContractResponses = { + /** + * OK + */ + 200: { + result: { + results: Array<{ + success: boolean; + result?: null; + }>; }; - path?: never; - query?: never; - url: '/read/balance'; + }; }; -export type PostReadBalanceErrors = { +export type ReadContractResponse = + ReadContractResponses[keyof ReadContractResponses]; + +export type PostReadBalanceData = { + body?: { /** - * Bad Request - Invalid input + * The chain ID to query the balance on. */ - 400: unknown; + chainId: string; /** - * Internal Server Error + * The EVM address to get the native balance for. */ - 500: unknown; + address: string; + }; + path?: never; + query?: never; + url: "/read/balance"; +}; + +export type PostReadBalanceErrors = { + /** + * Bad Request - Invalid input + */ + 400: unknown; + /** + * Internal Server Error + */ + 500: unknown; }; export type PostReadBalanceResponses = { - /** - * OK - Balance fetched successfully. - */ - 200: { - result: { - /** - * The native balance of the address as a stringified integer. - */ - balance: string; - }; + /** + * OK - Balance fetched successfully. + */ + 200: { + result: { + /** + * The native balance of the address as a stringified integer. + */ + balance: string; }; + }; }; -export type PostReadBalanceResponse = PostReadBalanceResponses[keyof PostReadBalanceResponses]; +export type PostReadBalanceResponse = + PostReadBalanceResponses[keyof PostReadBalanceResponses]; -export type PostEncodeContractData = { - body?: { - encodeOptions: { - /** - * The chain id of the transaction - */ - chainId: string; - }; - params: Array<{ - /** - * The function to call on the contract - */ - method: string; - /** - * The parameters to pass to the function - */ - params: Array; - /** - * The contract address to call - */ - contractAddress: string; - /** - * The ABI of the contract - */ - abi?: Array; - /** - * The value to send with the transaction - */ - value?: string; - }>; +export type EncodeFunctionDataData = { + body?: { + encodeOptions: { + /** + * The chain id of the transaction + */ + chainId: string; }; - path?: never; - query?: never; - url: '/encode/contract'; + params: Array<{ + /** + * The function to call on the contract + */ + method: string; + /** + * The parameters to pass to the function + */ + params: Array; + /** + * The contract address to call + */ + contractAddress: string; + /** + * The ABI of the contract + */ + abi?: Array; + /** + * The value to send with the transaction + */ + value?: string; + }>; + }; + path?: never; + query?: never; + url: "/encode/contract"; }; -export type PostEncodeContractResponses = { - /** - * OK - */ - 200: { - result: { - results: Array<{ - success: true; - result: { - /** - * EVM address in hex format - */ - to: string; - data: string; - /** - * A string representing an bigint response, safe to parse with BigInt - */ - value: string; - }; - } | { - success: false; - /** - * Standardized error object - */ - error: { - kind: string; - code: string; - status: number; - message?: string; - /** - * EVM address in hex format - */ - address?: string; - chainId?: string; - }; - }>; - }; +export type EncodeFunctionDataResponses = { + /** + * OK + */ + 200: { + result: { + results: Array< + | { + success: true; + result: { + /** + * EVM address in hex format + */ + to: string; + data: string; + /** + * A string representing an bigint response, safe to parse with BigInt + */ + value: string; + }; + } + | { + success: false; + /** + * Standardized error object + */ + error: { + kind: string; + code: string; + status: number; + message?: string; + /** + * EVM address in hex format + */ + address?: string; + chainId?: string; + }; + } + >; }; + }; }; -export type PostEncodeContractResponse = PostEncodeContractResponses[keyof PostEncodeContractResponses]; +export type EncodeFunctionDataResponse = + EncodeFunctionDataResponses[keyof EncodeFunctionDataResponses]; export type SearchTransactionsData = { - body?: { - page?: number; - limit?: number; - filters?: Array; - filtersOperation?: 'AND' | 'OR'; - sortBy?: 'createdAt' | 'confirmedAt'; - sortDirection?: 'asc' | 'desc'; - }; - path?: never; - query?: never; - url: '/project/transactions/search'; + body?: { + page?: number; + limit?: number; + filters?: Array; + filtersOperation?: "AND" | "OR"; + sortBy?: "createdAt" | "confirmedAt"; + sortDirection?: "asc" | "desc"; + }; + path?: never; + query?: never; + url: "/transactions/search"; }; export type SearchTransactionsResponses = { - /** - * Transactions - */ - 200: { - result: { - transactions: Array<{ - id: string; - batchIndex: number; - chainId: string; - from: string | null; - transactionParams: (string | number | boolean | null) | {} | Array; - transactionHash: string | null; - confirmedAt: string | null; - confirmedAtBlockNumber: string | null; - enrichedData: (string | number | boolean | null) | {} | Array; - executionParams: (string | number | boolean | null) | {} | Array; - executionResult: (string | number | boolean | null) | {} | Array | null; - createdAt: string; - errorMessage: string | null; - cancelledAt: string | null; - }>; - pagination: { - totalCount: number; - page: number; - limit: number; - }; - }; + /** + * Transactions + */ + 200: { + result: { + transactions: Array<{ + id: string; + batchIndex: number; + chainId: string; + from: string | null; + transactionParams: + | (string | number | boolean | null) + | { + [key: string]: unknown; + } + | Array; + transactionHash: string | null; + confirmedAt: string | null; + confirmedAtBlockNumber: string | null; + enrichedData: + | (string | number | boolean | null) + | { + [key: string]: unknown; + } + | Array; + executionParams: + | (string | number | boolean | null) + | { + [key: string]: unknown; + } + | Array; + executionResult: + | (string | number | boolean | null) + | { + [key: string]: unknown; + } + | Array + | null; + createdAt: string; + errorMessage: string | null; + cancelledAt: string | null; + }>; + pagination: { + totalCount: number; + page: number; + limit: number; + }; }; + }; }; -export type SearchTransactionsResponse = SearchTransactionsResponses[keyof SearchTransactionsResponses]; +export type SearchTransactionsResponse = + SearchTransactionsResponses[keyof SearchTransactionsResponses]; export type GetTransactionAnalyticsData = { - body?: { - startDate: string; - endDate: string; - resolution: 'hour' | 'day' | 'week' | 'month'; - filters?: Array; - filtersOperation?: 'AND' | 'OR'; - }; - path?: never; - query?: never; - url: '/project/transactions/analytics'; + body?: { + startDate: string; + endDate: string; + resolution: "hour" | "day" | "week" | "month"; + filters?: Array; + filtersOperation?: "AND" | "OR"; + }; + path?: never; + query?: never; + url: "/transactions/analytics"; }; export type GetTransactionAnalyticsResponses = { - /** - * Transaction Analytics - */ - 200: { - result: { - analytics: Array<{ - timeBucket: string; - chainId: string; - count: number; - }>; - metadata: { - resolution: 'hour' | 'day' | 'week' | 'month'; - startDate: string; - endDate: string; - }; - }; + /** + * Transaction Analytics + */ + 200: { + result: { + analytics: Array<{ + timeBucket: string; + chainId: string; + count: number; + }>; + metadata: { + resolution: "hour" | "day" | "week" | "month"; + startDate: string; + endDate: string; + }; }; + }; }; -export type GetTransactionAnalyticsResponse = GetTransactionAnalyticsResponses[keyof GetTransactionAnalyticsResponses]; +export type GetTransactionAnalyticsResponse = + GetTransactionAnalyticsResponses[keyof GetTransactionAnalyticsResponses]; export type GetTransactionAnalyticsSummaryData = { - body?: { - startDate?: string; - endDate?: string; - filters?: Array; - filtersOperation?: 'AND' | 'OR'; - }; - path?: never; - query?: never; - url: '/project/transactions/analytics-summary'; + body?: { + startDate?: string; + endDate?: string; + filters?: Array; + filtersOperation?: "AND" | "OR"; + }; + path?: never; + query?: never; + url: "/transactions/analytics-summary"; }; export type GetTransactionAnalyticsSummaryErrors = { - /** - * Bad Request (e.g., invalid date format, filter depth exceeded) - */ - 400: unknown; - /** - * Internal Server Error (e.g., database error) - */ - 500: unknown; + /** + * Bad Request (e.g., invalid date format, filter depth exceeded) + */ + 400: unknown; + /** + * Internal Server Error (e.g., database error) + */ + 500: unknown; }; export type GetTransactionAnalyticsSummaryResponses = { - /** - * Transaction Analytics Summary - */ - 200: { - result: { - summary: { - /** - * Total number of transactions matching the criteria. - */ - totalCount: number; - /** - * Sum of actualGasCost (in wei) for all matching transactions, as a string. - */ - totalGasCostWei: string; - /** - * Sum of actualGasUsed (gas units) for all matching transactions, as a string. - */ - totalGasUnitsUsed: string; - }; - metadata: { - startDate?: string; - endDate?: string; - }; - }; + /** + * Transaction Analytics Summary + */ + 200: { + result: { + summary: { + /** + * Total number of transactions matching the criteria. + */ + totalCount: number; + /** + * Sum of actualGasCost (in wei) for all matching transactions, as a string. + */ + totalGasCostWei: string; + /** + * Sum of actualGasUsed (gas units) for all matching transactions, as a string. + */ + totalGasUnitsUsed: string; + }; + metadata: { + startDate?: string; + endDate?: string; + }; }; + }; }; -export type GetTransactionAnalyticsSummaryResponse = GetTransactionAnalyticsSummaryResponses[keyof GetTransactionAnalyticsSummaryResponses]; +export type GetTransactionAnalyticsSummaryResponse = + GetTransactionAnalyticsSummaryResponses[keyof GetTransactionAnalyticsSummaryResponses]; export type ClientOptions = { - baseUrl: 'https://engine-cloud-dev-l8wt.chainsaw-dev.zeet.app' | (string & {}); -}; \ No newline at end of file + baseUrl: "http://localhost:3009" | (string & {}); +}; diff --git a/packages/thirdweb/src/engine/server-wallet.test.ts b/packages/thirdweb/src/engine/server-wallet.test.ts index 9963a2f8ab4..9b23f6c1207 100644 --- a/packages/thirdweb/src/engine/server-wallet.test.ts +++ b/packages/thirdweb/src/engine/server-wallet.test.ts @@ -7,13 +7,11 @@ import { sepolia } from "../chains/chain-definitions/sepolia.js"; import { getContract } from "../contract/contract.js"; import { setContractURI } from "../extensions/common/__generated__/IContractMetadata/write/setContractURI.js"; import { claimTo } from "../extensions/erc1155/drops/write/claimTo.js"; +import { getAllActiveSigners } from "../extensions/erc4337/__generated__/IAccountPermissions/read/getAllActiveSigners.js"; import { sendTransaction } from "../transaction/actions/send-transaction.js"; import { setThirdwebDomains } from "../utils/domains.js"; import type { Account } from "../wallets/interfaces/wallet.js"; -import { - DEFAULT_ACCOUNT_FACTORY_V0_6, - ENTRYPOINT_ADDRESS_v0_6, -} from "../wallets/smart/lib/constants.js"; +import { DEFAULT_ACCOUNT_FACTORY_V0_7 } from "../wallets/smart/lib/constants.js"; import { smartWallet } from "../wallets/smart/smart-wallet.js"; import { generateAccount } from "../wallets/utils/generateAccount.js"; import * as Engine from "./index.js"; @@ -113,15 +111,18 @@ describe.runIf( ).rejects.toThrow(); }); - it.skip("should send a session key tx", async () => { + it("should send a session key tx", async () => { + const sessionKeyAccountAddress = process.env + .ENGINE_CLOUD_WALLET_ADDRESS_EOA as string; const personalAccount = await generateAccount({ client: TEST_CLIENT, }); const smart = smartWallet({ chain: sepolia, sponsorGas: true, + factoryAddress: DEFAULT_ACCOUNT_FACTORY_V0_7, // TODO (cloud): not working for 0.6, needs fix sessionKey: { - address: process.env.ENGINE_CLOUD_WALLET_ADDRESS_EOA as string, + address: sessionKeyAccountAddress, permissions: { approvedTargets: "*", }, @@ -133,19 +134,27 @@ describe.runIf( }); expect(smartAccount.address).toBeDefined(); + const signers = await getAllActiveSigners({ + contract: getContract({ + client: TEST_CLIENT, + chain: sepolia, + address: smartAccount.address, + }), + }); + expect(signers.map((s) => s.signer)).toContain(sessionKeyAccountAddress); + const serverWallet = Engine.serverWallet({ client: TEST_CLIENT, vaultAccessToken: process.env.VAULT_TOKEN as string, - walletAddress: process.env.ENGINE_CLOUD_WALLET_ADDRESS_EOA as string, + walletAddress: sessionKeyAccountAddress, chain: sepolia, executionOptions: { type: "ERC4337", - signerAddress: process.env.ENGINE_CLOUD_WALLET_ADDRESS_EOA as string, + signerAddress: sessionKeyAccountAddress, smartAccountAddress: smartAccount.address, - entrypointAddress: ENTRYPOINT_ADDRESS_v0_6, // TODO (cloud): not working for 0.6, needs fix - factoryAddress: DEFAULT_ACCOUNT_FACTORY_V0_6, }, }); + const tx = await sendTransaction({ account: serverWallet, transaction: { diff --git a/packages/thirdweb/src/engine/server-wallet.ts b/packages/thirdweb/src/engine/server-wallet.ts index 05cd7affa9f..d174d121c1f 100644 --- a/packages/thirdweb/src/engine/server-wallet.ts +++ b/packages/thirdweb/src/engine/server-wallet.ts @@ -1,10 +1,10 @@ import { type AaExecutionOptions, type AaZksyncExecutionOptions, - postSignMessage, - postSignTypedData, - postWriteTransaction, searchTransactions, + sendTransaction, + signMessage, + signTypedData, } from "@thirdweb-dev/engine"; import type { Chain } from "../chains/types.js"; import type { ThirdwebClient } from "../client/client.js"; @@ -145,7 +145,7 @@ export function serverWallet(options: ServerWalletOptions): Account { ], }; - const result = await postWriteTransaction({ + const result = await sendTransaction({ baseUrl: getThirdwebBaseUrl("engineCloud"), fetch: getClientFetch(client), headers, @@ -160,8 +160,7 @@ export function serverWallet(options: ServerWalletOptions): Account { if (!data) { throw new Error("No data returned from engine"); } - const transactionId = - "transactions" in data ? data.transactions?.[0]?.id : data[0]?.id; // TODO (cloud) fix return type + const transactionId = data.transactions?.[0]?.id; if (!transactionId) { throw new Error("No transactionId returned from engine"); } @@ -193,36 +192,38 @@ export function serverWallet(options: ServerWalletOptions): Account { const data = searchResult.data?.result?.transactions?.[0]; - if (data) { - const executionResult = data.executionResult as ExecutionResult; - const status = executionResult.status; + if (!data) { + throw new Error(`Transaction ${transactionId} not found`); + } + + const executionResult = data.executionResult as ExecutionResult; + const status = executionResult.status; - if (status === "FAILED") { - throw new Error( - `Transaction failed: ${executionResult.error || "Unknown error"}`, - ); - } + if (status === "FAILED") { + throw new Error( + `Transaction failed: ${executionResult.error || "Unknown error"}`, + ); + } - const onchainStatus = - executionResult && "onchainStatus" in executionResult - ? executionResult.onchainStatus - : null; + const onchainStatus = + executionResult && "onchainStatus" in executionResult + ? executionResult.onchainStatus + : null; - if (status === "CONFIRMED" && onchainStatus === "REVERTED") { - const revertData = - "revertData" in executionResult - ? executionResult.revertData - : undefined; - throw new Error( - `Transaction reverted: ${revertData?.decodedError?.name || revertData?.revertReason || "Unknown revert reason"}`, - ); - } + if (status === "CONFIRMED" && onchainStatus === "REVERTED") { + const revertData = + "revertData" in executionResult + ? executionResult.revertData + : undefined; + throw new Error( + `Transaction reverted: ${revertData?.decodedError?.name || revertData?.revertReason || "Unknown revert reason"}`, + ); + } - if (data.transactionHash) { - return { - transactionHash: data.transactionHash as Hex, - }; - } + if (data.transactionHash) { + return { + transactionHash: data.transactionHash as Hex, + }; } // wait 1s before checking again await new Promise((resolve) => setTimeout(resolve, 1000)); @@ -245,7 +246,7 @@ export function serverWallet(options: ServerWalletOptions): Account { throw new Error("Chain ID is required for signing messages"); } - const signResult = await postSignMessage({ + const signResult = await signMessage({ baseUrl: getThirdwebBaseUrl("engineCloud"), fetch: getClientFetch(client), headers, @@ -281,7 +282,7 @@ export function serverWallet(options: ServerWalletOptions): Account { throw new Error("Chain ID is required for signing messages"); } - const signResult = await postSignTypedData({ + const signResult = await signTypedData({ baseUrl: getThirdwebBaseUrl("engineCloud"), fetch: getClientFetch(client), headers, From 1c0923968fe798940643a4bb085fcea188108582 Mon Sep 17 00:00:00 2001 From: Joaquim Verges Date: Mon, 5 May 2025 14:03:49 +1200 Subject: [PATCH 091/101] add v1 prefix to all routes --- .../cloud/analytics/send-test-tx.client.tsx | 2 +- .../cloud/analytics/tx-table/tx-table.tsx | 2 +- .../engine/cloud/lib/analytics.ts | 6 ++-- .../create-server-wallet.client.tsx | 2 +- .../server-wallets/components/try-it-out.tsx | 10 +++--- packages/engine/src/client/sdk.gen.ts | 34 +++++++++---------- packages/engine/src/client/types.gen.ts | 32 ++++++++--------- .../thirdweb/src/engine/server-wallet.test.ts | 14 +++++--- packages/thirdweb/src/engine/server-wallet.ts | 8 ++--- 9 files changed, 57 insertions(+), 53 deletions(-) diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/send-test-tx.client.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/send-test-tx.client.tsx index c0be7ec332e..c9627defbc9 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/send-test-tx.client.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/send-test-tx.client.tsx @@ -75,7 +75,7 @@ export function SendTestTransaction(props: { chainId: number; }) => { const response = await engineCloudProxy({ - pathname: "/write/transaction", + pathname: "/v1/write/transaction", method: "POST", headers: { "Content-Type": "application/json", diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/tx-table/tx-table.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/tx-table/tx-table.tsx index 557d7016f60..c19c4addab5 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/tx-table/tx-table.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/tx-table/tx-table.tsx @@ -35,7 +35,7 @@ async function getTransactions({ }) { const transactions = await engineCloudProxy<{ result: TransactionsResponse }>( { - pathname: "/transactions/search", + pathname: "/v1/transactions/search", method: "POST", headers: { "Content-Type": "application/json", diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/lib/analytics.ts b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/lib/analytics.ts index 0f48a5e425c..0362be6fadc 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/lib/analytics.ts +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/lib/analytics.ts @@ -43,7 +43,7 @@ export async function getTransactionAnalyticsSummary(props: { try { const response = await fetch( - `${THIRDWEB_ENGINE_CLOUD_URL}/transactions/analytics-summary`, + `${THIRDWEB_ENGINE_CLOUD_URL}/v1/transactions/analytics-summary`, { method: "POST", headers: { @@ -102,7 +102,7 @@ export async function getTransactionsChart({ }; const response = await fetch( - `${THIRDWEB_ENGINE_CLOUD_URL}/transactions/analytics`, + `${THIRDWEB_ENGINE_CLOUD_URL}/v1/transactions/analytics`, { method: "POST", headers: { @@ -172,7 +172,7 @@ export async function getSingleTransaction({ }; const response = await fetch( - `${THIRDWEB_ENGINE_CLOUD_URL}/transactions/search`, + `${THIRDWEB_ENGINE_CLOUD_URL}/v1/transactions/search`, { method: "POST", headers: { diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/server-wallets/components/create-server-wallet.client.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/server-wallets/components/create-server-wallet.client.tsx index 12471a64292..583f52b6984 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/server-wallets/components/create-server-wallet.client.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/server-wallets/components/create-server-wallet.client.tsx @@ -60,7 +60,7 @@ export default function CreateServerWallet(props: { // no need to await this, it's not blocking engineCloudProxy({ - pathname: "/cache/smart-account", + pathname: "/v1/cache/smart-account", method: "POST", headers: { "Content-Type": "application/json", diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/server-wallets/components/try-it-out.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/server-wallets/components/try-it-out.tsx index 552d769843c..1eac9a1ac36 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/server-wallets/components/try-it-out.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/server-wallets/components/try-it-out.tsx @@ -146,7 +146,7 @@ console.log("Transaction hash:", result.transactionHash); `; const curlExample = () => `\ -curl -X POST "${THIRDWEB_ENGINE_CLOUD_URL}/write/contract" \\ +curl -X POST "${THIRDWEB_ENGINE_CLOUD_URL}/v1/write/contract" \\ -H "Content-Type: application/json" \\ -H "x-secret-key: " \\ -H "x-vault-access-token: " \\ @@ -166,7 +166,7 @@ curl -X POST "${THIRDWEB_ENGINE_CLOUD_URL}/write/contract" \\ const jsExample = () => `\ const response = await fetch( - "${THIRDWEB_ENGINE_CLOUD_URL}/write/contract", + "${THIRDWEB_ENGINE_CLOUD_URL}/v1/write/contract", { method: "POST", headers: { @@ -194,7 +194,7 @@ const pythonExample = () => `\ import requests import json -url = "${THIRDWEB_ENGINE_CLOUD_URL}/write/contract" +url = "${THIRDWEB_ENGINE_CLOUD_URL}/v1/write/contract" headers = { "Content-Type": "application/json", "x-secret-key": "", @@ -228,7 +228,7 @@ import ( ) func main() { - url := "${THIRDWEB_ENGINE_CLOUD_URL}/write/contract" + url := "${THIRDWEB_ENGINE_CLOUD_URL}/v1/write/contract" // Create the request payload type Param struct { @@ -291,7 +291,7 @@ class Program { static async Task Main() { - var url = "${THIRDWEB_ENGINE_CLOUD_URL}/write/contract"; + var url = "${THIRDWEB_ENGINE_CLOUD_URL}/v1/write/contract"; var requestData = new { diff --git a/packages/engine/src/client/sdk.gen.ts b/packages/engine/src/client/sdk.gen.ts index 59c2801af4d..3bc3cde0993 100644 --- a/packages/engine/src/client/sdk.gen.ts +++ b/packages/engine/src/client/sdk.gen.ts @@ -9,12 +9,12 @@ import { client as _heyApiClient } from "./client.gen.js"; import type { EncodeFunctionDataData, EncodeFunctionDataResponse, + GetNativeBalanceData, + GetNativeBalanceResponse, GetTransactionAnalyticsData, GetTransactionAnalyticsResponse, GetTransactionAnalyticsSummaryData, GetTransactionAnalyticsSummaryResponse, - PostReadBalanceData, - PostReadBalanceResponse, ReadContractData, ReadContractResponse, SearchTransactionsData, @@ -66,7 +66,7 @@ export const writeContract = ( type: "apiKey", }, ], - url: "/write/contract", + url: "/v1/write/contract", ...options, headers: { "Content-Type": "application/json", @@ -93,7 +93,7 @@ export const sendTransaction = ( type: "apiKey", }, ], - url: "/write/transaction", + url: "/v1/write/transaction", ...options, headers: { "Content-Type": "application/json", @@ -120,7 +120,7 @@ export const signTransaction = ( type: "apiKey", }, ], - url: "/sign/transaction", + url: "/v1/sign/transaction", ...options, headers: { "Content-Type": "application/json", @@ -147,7 +147,7 @@ export const signMessage = ( type: "apiKey", }, ], - url: "/sign/message", + url: "/v1/sign/message", ...options, headers: { "Content-Type": "application/json", @@ -174,7 +174,7 @@ export const signTypedData = ( type: "apiKey", }, ], - url: "/sign/typed-data", + url: "/v1/sign/typed-data", ...options, headers: { "Content-Type": "application/json", @@ -201,7 +201,7 @@ export const readContract = ( type: "apiKey", }, ], - url: "/read/contract", + url: "/v1/read/contract", ...options, headers: { "Content-Type": "application/json", @@ -211,14 +211,14 @@ export const readContract = ( }; /** - * Read the Native Balance for an Address + * Read Native Balance * Fetches the native cryptocurrency balance (e.g., ETH, MATIC) for a given address on a specific chain. */ -export const postReadBalance = ( - options?: Options, +export const getNativeBalance = ( + options?: Options, ) => { return (options?.client ?? _heyApiClient).post< - PostReadBalanceResponse, + GetNativeBalanceResponse, unknown, ThrowOnError >({ @@ -228,7 +228,7 @@ export const postReadBalance = ( type: "apiKey", }, ], - url: "/read/balance", + url: "/v1/read/balance", ...options, headers: { "Content-Type": "application/json", @@ -255,7 +255,7 @@ export const encodeFunctionData = ( type: "apiKey", }, ], - url: "/encode/contract", + url: "/v1/encode/contract", ...options, headers: { "Content-Type": "application/json", @@ -282,7 +282,7 @@ export const searchTransactions = ( type: "apiKey", }, ], - url: "/transactions/search", + url: "/v1/transactions/search", ...options, headers: { "Content-Type": "application/json", @@ -309,7 +309,7 @@ export const getTransactionAnalytics = ( type: "apiKey", }, ], - url: "/transactions/analytics", + url: "/v1/transactions/analytics", ...options, headers: { "Content-Type": "application/json", @@ -338,7 +338,7 @@ export const getTransactionAnalyticsSummary = < type: "apiKey", }, ], - url: "/transactions/analytics-summary", + url: "/v1/transactions/analytics-summary", ...options, headers: { "Content-Type": "application/json", diff --git a/packages/engine/src/client/types.gen.ts b/packages/engine/src/client/types.gen.ts index 489656fd936..0c35fc41fca 100644 --- a/packages/engine/src/client/types.gen.ts +++ b/packages/engine/src/client/types.gen.ts @@ -132,7 +132,7 @@ export type WriteContractData = { }; path?: never; query?: never; - url: "/write/contract"; + url: "/v1/write/contract"; }; export type WriteContractResponses = { @@ -261,7 +261,7 @@ export type SendTransactionData = { }; path?: never; query?: never; - url: "/write/transaction"; + url: "/v1/write/transaction"; }; export type SendTransactionResponses = { @@ -461,7 +461,7 @@ export type SignTransactionData = { }; path?: never; query?: never; - url: "/sign/transaction"; + url: "/v1/sign/transaction"; }; export type SignTransactionResponses = { @@ -537,7 +537,7 @@ export type SignMessageData = { }; path?: never; query?: never; - url: "/sign/message"; + url: "/v1/sign/message"; }; export type SignMessageResponses = { @@ -622,7 +622,7 @@ export type SignTypedDataData = { }; path?: never; query?: never; - url: "/sign/typed-data"; + url: "/v1/sign/typed-data"; }; export type SignTypedDataResponses = { @@ -707,7 +707,7 @@ export type ReadContractData = { }; path?: never; query?: never; - url: "/read/contract"; + url: "/v1/read/contract"; }; export type ReadContractResponses = { @@ -727,7 +727,7 @@ export type ReadContractResponses = { export type ReadContractResponse = ReadContractResponses[keyof ReadContractResponses]; -export type PostReadBalanceData = { +export type GetNativeBalanceData = { body?: { /** * The chain ID to query the balance on. @@ -740,10 +740,10 @@ export type PostReadBalanceData = { }; path?: never; query?: never; - url: "/read/balance"; + url: "/v1/read/balance"; }; -export type PostReadBalanceErrors = { +export type GetNativeBalanceErrors = { /** * Bad Request - Invalid input */ @@ -754,7 +754,7 @@ export type PostReadBalanceErrors = { 500: unknown; }; -export type PostReadBalanceResponses = { +export type GetNativeBalanceResponses = { /** * OK - Balance fetched successfully. */ @@ -768,8 +768,8 @@ export type PostReadBalanceResponses = { }; }; -export type PostReadBalanceResponse = - PostReadBalanceResponses[keyof PostReadBalanceResponses]; +export type GetNativeBalanceResponse = + GetNativeBalanceResponses[keyof GetNativeBalanceResponses]; export type EncodeFunctionDataData = { body?: { @@ -804,7 +804,7 @@ export type EncodeFunctionDataData = { }; path?: never; query?: never; - url: "/encode/contract"; + url: "/v1/encode/contract"; }; export type EncodeFunctionDataResponses = { @@ -864,7 +864,7 @@ export type SearchTransactionsData = { }; path?: never; query?: never; - url: "/transactions/search"; + url: "/v1/transactions/search"; }; export type SearchTransactionsResponses = { @@ -932,7 +932,7 @@ export type GetTransactionAnalyticsData = { }; path?: never; query?: never; - url: "/transactions/analytics"; + url: "/v1/transactions/analytics"; }; export type GetTransactionAnalyticsResponses = { @@ -967,7 +967,7 @@ export type GetTransactionAnalyticsSummaryData = { }; path?: never; query?: never; - url: "/transactions/analytics-summary"; + url: "/v1/transactions/analytics-summary"; }; export type GetTransactionAnalyticsSummaryErrors = { diff --git a/packages/thirdweb/src/engine/server-wallet.test.ts b/packages/thirdweb/src/engine/server-wallet.test.ts index 9b23f6c1207..8d29b644af4 100644 --- a/packages/thirdweb/src/engine/server-wallet.test.ts +++ b/packages/thirdweb/src/engine/server-wallet.test.ts @@ -11,7 +11,10 @@ import { getAllActiveSigners } from "../extensions/erc4337/__generated__/IAccoun import { sendTransaction } from "../transaction/actions/send-transaction.js"; import { setThirdwebDomains } from "../utils/domains.js"; import type { Account } from "../wallets/interfaces/wallet.js"; -import { DEFAULT_ACCOUNT_FACTORY_V0_7 } from "../wallets/smart/lib/constants.js"; +import { + DEFAULT_ACCOUNT_FACTORY_V0_6, + ENTRYPOINT_ADDRESS_v0_6, +} from "../wallets/smart/lib/constants.js"; import { smartWallet } from "../wallets/smart/smart-wallet.js"; import { generateAccount } from "../wallets/utils/generateAccount.js"; import * as Engine from "./index.js"; @@ -34,12 +37,12 @@ describe.runIf( rpc: "rpc.thirdweb-dev.com", storage: "storage.thirdweb-dev.com", bundler: "bundler.thirdweb-dev.com", - engineCloud: "engine-cloud-dev-l8wt.chainsaw-dev.zeet.app", + engineCloud: "localhost:3009", // "engine-cloud-dev-l8wt.chainsaw-dev.zeet.app", }); serverWallet = Engine.serverWallet({ client: TEST_CLIENT, vaultAccessToken: process.env.VAULT_TOKEN as string, - walletAddress: process.env.ENGINE_CLOUD_WALLET_ADDRESS as string, + address: process.env.ENGINE_CLOUD_WALLET_ADDRESS as string, chain: arbitrumSepolia, }); }); @@ -120,7 +123,6 @@ describe.runIf( const smart = smartWallet({ chain: sepolia, sponsorGas: true, - factoryAddress: DEFAULT_ACCOUNT_FACTORY_V0_7, // TODO (cloud): not working for 0.6, needs fix sessionKey: { address: sessionKeyAccountAddress, permissions: { @@ -146,12 +148,14 @@ describe.runIf( const serverWallet = Engine.serverWallet({ client: TEST_CLIENT, vaultAccessToken: process.env.VAULT_TOKEN as string, - walletAddress: sessionKeyAccountAddress, + address: sessionKeyAccountAddress, chain: sepolia, executionOptions: { type: "ERC4337", signerAddress: sessionKeyAccountAddress, smartAccountAddress: smartAccount.address, + factoryAddress: DEFAULT_ACCOUNT_FACTORY_V0_6, + entrypointAddress: ENTRYPOINT_ADDRESS_v0_6, }, }); diff --git a/packages/thirdweb/src/engine/server-wallet.ts b/packages/thirdweb/src/engine/server-wallet.ts index d174d121c1f..8a0bf9a14a7 100644 --- a/packages/thirdweb/src/engine/server-wallet.ts +++ b/packages/thirdweb/src/engine/server-wallet.ts @@ -32,7 +32,7 @@ export type ServerWalletOptions = { /** * The server wallet address to use for sending transactions inside engine. */ - walletAddress: string; + address: string; /** * The chain to use for signing messages and typed data (smart server wallet only). */ @@ -113,7 +113,7 @@ type ExecutionResult = * ``` */ export function serverWallet(options: ServerWalletOptions): Account { - const { client, vaultAccessToken, walletAddress, chain, executionOptions } = + const { client, vaultAccessToken, address, chain, executionOptions } = options; const headers: HeadersInit = { "x-vault-access-token": vaultAccessToken, @@ -126,13 +126,13 @@ export function serverWallet(options: ServerWalletOptions): Account { chainId: chainId.toString(), } : { - from: walletAddress, + from: address, chainId: chainId.toString(), }; }; return { - address: walletAddress, + address, sendTransaction: async (transaction: SendTransactionOption) => { const body = { executionOptions: getExecutionOptions(transaction.chainId), From bd4f1994063e0293b5b78d422c95c0a8eae7cec5 Mon Sep 17 00:00:00 2001 From: Joaquim Verges Date: Mon, 5 May 2025 14:06:10 +1200 Subject: [PATCH 092/101] knip --- packages/thirdweb/knip.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/thirdweb/knip.json b/packages/thirdweb/knip.json index 7c6bb456bc6..932e329fc21 100644 --- a/packages/thirdweb/knip.json +++ b/packages/thirdweb/knip.json @@ -8,7 +8,8 @@ "@testing-library/jest-dom", "tslib", "@size-limit/preset-big-lib", - "@thirdweb-dev/insight" + "@thirdweb-dev/insight", + "@thirdweb-dev/engine" ], "rules": { "optionalPeerDependencies": "off", From 7254b056df45755a50cd9d6784415cc7a4391cd7 Mon Sep 17 00:00:00 2001 From: Joaquim Verges Date: Mon, 5 May 2025 15:14:07 +1200 Subject: [PATCH 093/101] sdk example --- .../cloud/explorer/components/scalar.tsx | 24 +++ .../engine/cloud/explorer/page.tsx | 24 +-- .../[project_slug]/engine/cloud/layout.tsx | 6 + .../server-wallets/components/try-it-out.tsx | 141 ++++++++++++++---- 4 files changed, 144 insertions(+), 51 deletions(-) create mode 100644 apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/explorer/components/scalar.tsx diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/explorer/components/scalar.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/explorer/components/scalar.tsx new file mode 100644 index 00000000000..07df6be94e9 --- /dev/null +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/explorer/components/scalar.tsx @@ -0,0 +1,24 @@ +"use client"; +import { ApiReferenceReact } from "@scalar/api-reference-react"; +import "@scalar/api-reference-react/style.css"; +import { THIRDWEB_ENGINE_CLOUD_URL } from "@/constants/env"; + +export function Scalar() { + return ( +
+

Full API Reference

+ +
+ ); +} diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/explorer/page.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/explorer/page.tsx index bb32eb032a3..c9fcb32347b 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/explorer/page.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/explorer/page.tsx @@ -1,23 +1,11 @@ -"use client"; -import { ApiReferenceReact } from "@scalar/api-reference-react"; -import "@scalar/api-reference-react/style.css"; -import { THIRDWEB_ENGINE_CLOUD_URL } from "@/constants/env"; +import { TryItOut } from "../server-wallets/components/try-it-out"; +import { Scalar } from "./components/scalar"; -export default function TransactionsExplorerPage() { +export default async function TransactionsExplorerPage() { return ( -
- +
+ +
); } diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/layout.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/layout.tsx index 2d7e709bdeb..536eadfebb6 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/layout.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/layout.tsx @@ -42,6 +42,12 @@ function TransactionsLayout(props: { > Cloud + + Beta +
("sdk"); return ( @@ -24,7 +23,8 @@ export function TryItOut(props: { Usage from your backend

- Send transactions from your server wallets using a simple http API + Send transactions from your server wallets using the thirdweb SDK + or the HTTP API directly.

@@ -33,7 +33,7 @@ export function TryItOut(props: { setActiveTab("sdk"), isActive: activeTab === "sdk", }, @@ -68,7 +68,70 @@ export function TryItOut(props: {
{activeTab === "sdk" && ( - +
+ + + Using the thirdweb SDK on the backend + +

+ You can use the full TypeScript thirdweb SDK in your backend, + allowing you to use:{" "} +

    +
  • + The full catalog of{" "} + + extension functions + +
  • +
  • + The{" "} + + prepareContractCall + {" "} + function to encode your transactions +
  • +
  • + The full{" "} + + account + {" "} + interface, predefined chains, and more +
  • +
+ The SDK handles encoding your transactions, signing them to + Engine and polling for status. +

+
+
+

+ Installation +

+ +

+ Usage example: Minting a ERC1155 NFT to a user +

+ +
)} {activeTab === "curl" && ( )} {activeTab === "js" && ( - +
+

+ A lightweight, type safe wrapper package of the Engine HTTP API is + available on{" "} + + NPM + + . +

+ +
)} {activeTab === "python" && ( )} - -
-
- -
); } const sdkExample = () => `\ -import { Engine, createThirdwebClient, sendTransaction } from "thirdweb"; -import { claimTo } from "thirdweb/extensions/erc1155"; +import { createThirdwebClient, sendTransaction, getContract, Engine } from "thirdweb"; +import { baseSepolia } from "thirdweb/chains"; +import { claimTo } from "thirdweb/extensions/1155"; +// Create a thirdweb client const client = createThirdwebClient({ secretKey: "", }); @@ -125,19 +196,23 @@ const client = createThirdwebClient({ // Create a server wallet const serverWallet = Engine.serverWallet({ client, - walletAddress: "", + address: "", vaultAccessToken: "", }); -// Prepare a transaction +// Prepare the transaction const transaction = claimTo({ - contract, - to: "0x...", - tokenId: 0n, - quantity: 1n, + contract: getContract({ + client, + address: "0x...", // Address of the ERC1155 token contract + chain: baseSepolia, // Chain of the ERC1155 token contract + }), + to: "0x...", // The address of the user to mint to + tokenId: 0n, // The token ID of the NFT to mint + quantity: 1n, // The quantity of NFTs to mint }); -// Send the transaction +// Send the transaction via Engine const result = await sendTransaction({ account: serverWallet, transaction, From 1c2bc579b46a88428e933549b63ce73f08e0282e Mon Sep 17 00:00:00 2001 From: Joaquim Verges Date: Mon, 5 May 2025 20:18:19 +1200 Subject: [PATCH 094/101] remove from server wallets page --- .../[project_slug]/engine/cloud/server-wallets/page.tsx | 6 ------ 1 file changed, 6 deletions(-) diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/server-wallets/page.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/server-wallets/page.tsx index c30eb7a1071..d1ac07601a8 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/server-wallets/page.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/server-wallets/page.tsx @@ -3,7 +3,6 @@ import { createVaultClient, listEoas } from "@thirdweb-dev/vault-sdk"; import { notFound } from "next/navigation"; import { THIRDWEB_VAULT_URL } from "../../../../../../../../@/constants/env"; import { getAuthToken } from "../../../../../../api/lib/getAuthToken"; -import { TryItOut } from "./components/try-it-out"; import type { Wallet } from "./wallet-table/types"; import { ServerWalletsTable } from "./wallet-table/wallet-table"; @@ -55,11 +54,6 @@ export default async function TransactionsServerWalletsPage(props: { teamSlug={team_slug} managementAccessToken={managementAccessToken ?? undefined} /> -
)} From 9e5d9c0015e749aad81d868bd299c7bd707bc831 Mon Sep 17 00:00:00 2001 From: Joaquim Verges Date: Tue, 6 May 2025 11:20:40 +1200 Subject: [PATCH 095/101] add tracking, fix cache 404 --- .../engine/cloud/analytics/send-test-tx.client.tsx | 7 +++++++ .../components/create-server-wallet.client.tsx | 9 ++++++++- .../vault/components/create-vault-account.client.tsx | 7 +++++++ .../vault/components/list-access-tokens.client.tsx | 12 ++++++++++++ .../vault/components/rotate-admin-key.client.tsx | 7 ++++++- 5 files changed, 40 insertions(+), 2 deletions(-) diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/send-test-tx.client.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/send-test-tx.client.tsx index c9627defbc9..23187201706 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/send-test-tx.client.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/send-test-tx.client.tsx @@ -26,6 +26,7 @@ import { sepolia, } from "thirdweb/chains"; import * as z from "zod"; +import { useTrack } from "../../../../../../../../hooks/analytics/useTrack"; import type { Wallet } from "../server-wallets/wallet-table/types"; import { SmartAccountCell } from "../server-wallets/wallet-table/wallet-table-ui.client"; @@ -49,6 +50,7 @@ export function SendTestTransaction(props: { const queryClient = useQueryClient(); const [hasSentTx, setHasSentTx] = useState(false); const router = useDashboardRouter(); + const trackEvent = useTrack(); const form = useForm({ resolver: zodResolver(formSchema), @@ -74,6 +76,11 @@ export function SendTestTransaction(props: { accessToken: string; chainId: number; }) => { + trackEvent({ + category: "engine-cloud", + action: "send_test_tx", + }); + const response = await engineCloudProxy({ pathname: "/v1/write/transaction", method: "POST", diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/server-wallets/components/create-server-wallet.client.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/server-wallets/components/create-server-wallet.client.tsx index 583f52b6984..a07cdabe947 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/server-wallets/components/create-server-wallet.client.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/server-wallets/components/create-server-wallet.client.tsx @@ -16,6 +16,7 @@ import { Loader2, WalletIcon } from "lucide-react"; import { useState } from "react"; import { toast } from "sonner"; import { engineCloudProxy } from "../../../../../../../../../@/actions/proxies"; +import { useTrack } from "../../../../../../../../../hooks/analytics/useTrack"; import { initVaultClient } from "../../lib/vault.client"; export default function CreateServerWallet(props: { @@ -26,6 +27,7 @@ export default function CreateServerWallet(props: { const router = useDashboardRouter(); const [label, setLabel] = useState(""); const [modalOpen, setModalOpen] = useState(false); + const trackEvent = useTrack(); const createEoaMutation = useMutation({ mutationFn: async ({ @@ -35,6 +37,11 @@ export default function CreateServerWallet(props: { managementAccessToken: string; label: string; }) => { + trackEvent({ + category: "engine-cloud", + action: "create_server_wallet", + }); + const vaultClient = await initVaultClient(); const eoa = await createEoa({ @@ -60,7 +67,7 @@ export default function CreateServerWallet(props: { // no need to await this, it's not blocking engineCloudProxy({ - pathname: "/v1/cache/smart-account", + pathname: "/cache/smart-account", method: "POST", headers: { "Content-Type": "application/json", diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/vault/components/create-vault-account.client.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/vault/components/create-vault-account.client.tsx index a5843351dd5..e5e4a9bb4b5 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/vault/components/create-vault-account.client.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/vault/components/create-vault-account.client.tsx @@ -18,6 +18,7 @@ import { createServiceAccount } from "@thirdweb-dev/vault-sdk"; import { CheckIcon, DownloadIcon, Loader2, LockIcon } from "lucide-react"; import { useState } from "react"; import { toast } from "sonner"; +import { useTrack } from "../../../../../../../../../hooks/analytics/useTrack"; import { createManagementAccessToken, createWalletAccessToken, @@ -33,11 +34,17 @@ export default function CreateVaultAccountButton(props: { const [keysConfirmed, setKeysConfirmed] = useState(false); const [keysDownloaded, setKeysDownloaded] = useState(false); const router = useDashboardRouter(); + const trackEvent = useTrack(); const initialiseProjectWithVaultMutation = useMutation({ mutationFn: async () => { setModalOpen(true); + trackEvent({ + category: "engine-cloud", + action: "create_vault_account", + }); + const vaultClient = await initVaultClient(); const serviceAccount = await createServiceAccount({ diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/vault/components/list-access-tokens.client.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/vault/components/list-access-tokens.client.tsx index 4cfc1b2d6cd..bf4fc289ce4 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/vault/components/list-access-tokens.client.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/vault/components/list-access-tokens.client.tsx @@ -18,6 +18,7 @@ import { Loader2, LockIcon, Trash2 } from "lucide-react"; import { useState } from "react"; import { toast } from "sonner"; import { toDateTimeLocal } from "utils/date-utils"; +import { useTrack } from "../../../../../../../../../hooks/analytics/useTrack"; import { SERVER_WALLET_MANAGEMENT_ACCESS_TOKEN_PURPOSE, createWalletAccessToken, @@ -32,11 +33,17 @@ export default function ListAccessTokens(props: { const [adminKey, setAdminKey] = useState(""); const [deletingTokenId, setDeletingTokenId] = useState(null); const queryClient = useQueryClient(); + const trackEvent = useTrack(); // TODO allow passing permissions to the access token const createAccessTokenMutation = useMutation({ mutationFn: async (args: { adminKey: string }) => { const vaultClient = await initVaultClient(); + trackEvent({ + category: "engine-cloud", + action: "create_access_token", + }); + const userAccessTokenRes = await createWalletAccessToken({ project: props.project, adminKey: args.adminKey, @@ -68,6 +75,11 @@ export default function ListAccessTokens(props: { setDeletingTokenId(args.accessTokenId); const vaultClient = await initVaultClient(); + trackEvent({ + category: "engine-cloud", + action: "revoke_access_token", + }); + const revokeAccessTokenRes = await revokeAccessToken({ client: vaultClient, request: { diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/vault/components/rotate-admin-key.client.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/vault/components/rotate-admin-key.client.tsx index d1634fc02d5..82983f0c4b1 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/vault/components/rotate-admin-key.client.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/vault/components/rotate-admin-key.client.tsx @@ -26,6 +26,7 @@ import { } from "lucide-react"; import { useState } from "react"; import { toast } from "sonner"; +import { useTrack } from "../../../../../../../../../hooks/analytics/useTrack"; import { createManagementAccessToken, createWalletAccessToken, @@ -38,10 +39,14 @@ export default function RotateAdminKeyButton(props: { project: Project }) { const [keysConfirmed, setKeysConfirmed] = useState(false); const [keysDownloaded, setKeysDownloaded] = useState(false); const router = useDashboardRouter(); + const trackEvent = useTrack(); const rotateAdminKeyMutation = useMutation({ mutationFn: async () => { - await new Promise((resolve) => setTimeout(resolve, 1000)); + trackEvent({ + category: "engine-cloud", + action: "rotate_admin_key", + }); const vaultClient = await initVaultClient(); const rotationCode = props.project.services.find( From 7e5993505836b0a7b3dd4f1acfd69fac96fdbee0 Mon Sep 17 00:00:00 2001 From: Joaquim Verges Date: Tue, 6 May 2025 12:39:27 +1200 Subject: [PATCH 096/101] add vault portal links --- .../engine/cloud/analytics/tx-chart/tx-chart-ui.tsx | 4 ++-- .../team/[team_slug]/[project_slug]/engine/cloud/layout.tsx | 2 +- .../engine/cloud/vault/components/key-management.tsx | 4 ++-- packages/thirdweb/src/utils/fetch.ts | 1 - 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/tx-chart/tx-chart-ui.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/tx-chart/tx-chart-ui.tsx index cbacf041f37..1a18269b723 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/tx-chart/tx-chart-ui.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/tx-chart/tx-chart-ui.tsx @@ -169,9 +169,9 @@ function EmptyChartContent(props: { {props.wallets.length === 0 ? ( <> - Engine requires a {/* TODO (cloud): add a link to the docs */} + Engine requires a{" "} diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/layout.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/layout.tsx index 536eadfebb6..8d83cccdaa7 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/layout.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/layout.tsx @@ -60,7 +60,7 @@ function TransactionsLayout(props: {
- + diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/vault/components/key-management.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/vault/components/key-management.tsx index f547829d819..7f05b0a3da3 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/vault/components/key-management.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/vault/components/key-management.tsx @@ -18,7 +18,7 @@ export function KeyManagement({

Secure, non-custodial key management system for your server wallets.{" "} Learn more. @@ -84,7 +84,7 @@ async function CreateVaultAccountAlert(props: { Your keys are stored in a hardware enclave, and all requests are end-to-end encrypted.{" "} Learn more about Vault security model. diff --git a/packages/thirdweb/src/utils/fetch.ts b/packages/thirdweb/src/utils/fetch.ts index a3697e7c1dd..e36f828f7d5 100644 --- a/packages/thirdweb/src/utils/fetch.ts +++ b/packages/thirdweb/src/utils/fetch.ts @@ -120,7 +120,6 @@ const THIRDWEB_DOMAINS = [ // dev domains ".thirdweb.dev", ".thirdweb-dev.com", - ".chainsaw-dev.zeet.app", // TODO (cloud): remove this once we have a proper domain ] as const; export const IS_THIRDWEB_URL_CACHE = new LruMap(4096); From 844c80c19d5ecc2e0ff17b9bd0e9befe0473da2a Mon Sep 17 00:00:00 2001 From: Joaquim Verges Date: Tue, 6 May 2025 12:50:12 +1200 Subject: [PATCH 097/101] fix details page --- .../cloud/analytics/tx-table/tx-table-ui.tsx | 2 +- .../cloud/tx/[id]/transaction-details-ui.tsx | 20 +------------------ .../engine/dedicated/(general)/layout.tsx | 6 +----- 3 files changed, 3 insertions(+), 25 deletions(-) diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/tx-table/tx-table-ui.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/tx-table/tx-table-ui.tsx index c8adcd0882e..7ca6fc2a8e0 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/tx-table/tx-table-ui.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/tx-table/tx-table-ui.tsx @@ -203,7 +203,7 @@ export function TransactionsTableUI(props: { ); } -const statusDetails = { +export const statusDetails = { QUEUED: { name: "Queued", type: "warning", diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/tx/[id]/transaction-details-ui.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/tx/[id]/transaction-details-ui.tsx index 739f9ba6886..35dcd10329e 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/tx/[id]/transaction-details-ui.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/tx/[id]/transaction-details-ui.tsx @@ -15,27 +15,9 @@ import { ExternalLinkIcon, InfoIcon } from "lucide-react"; import Link from "next/link"; import { toEther } from "thirdweb"; import { ChainIconClient } from "../../../../../../../../../components/icons/ChainIcon"; +import { statusDetails } from "../../analytics/tx-table/tx-table-ui"; import type { Transaction } from "../../analytics/tx-table/types"; -const statusDetails = { - QUEUED: { - name: "Queued", - type: "warning", - }, - SUBMITTED: { - name: "Submitted", - type: "warning", - }, - CONFIRMED: { - name: "Confirmed", - type: "success", - }, - REVERTED: { - name: "Reverted", - type: "destructive", - }, -} as const; - export function TransactionDetailsUI({ transaction, }: { diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(general)/layout.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(general)/layout.tsx index 645756a5376..e7bb244cb15 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(general)/layout.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/dedicated/(general)/layout.tsx @@ -94,11 +94,7 @@ function EngineLegacyBannerUI(props: {

- {/* TODO (cloud): add link to Engine Cloud blog post */} - + Date: Tue, 6 May 2025 14:59:32 +1200 Subject: [PATCH 098/101] add enqueue and status API --- packages/thirdweb/src/engine/get-status.ts | 180 +++++++++++++ packages/thirdweb/src/engine/index.ts | 12 +- .../thirdweb/src/engine/server-wallet.test.ts | 31 ++- packages/thirdweb/src/engine/server-wallet.ts | 246 ++++++++---------- 4 files changed, 327 insertions(+), 142 deletions(-) create mode 100644 packages/thirdweb/src/engine/get-status.ts diff --git a/packages/thirdweb/src/engine/get-status.ts b/packages/thirdweb/src/engine/get-status.ts new file mode 100644 index 00000000000..a00fd41925c --- /dev/null +++ b/packages/thirdweb/src/engine/get-status.ts @@ -0,0 +1,180 @@ +import { searchTransactions } from "@thirdweb-dev/engine"; +import type { Chain } from "../chains/types.js"; +import { getCachedChain } from "../chains/utils.js"; +import type { ThirdwebClient } from "../client/client.js"; +import type { WaitForReceiptOptions } from "../transaction/actions/wait-for-tx-receipt.js"; +import { getThirdwebBaseUrl } from "../utils/domains.js"; +import type { Hex } from "../utils/encoding/hex.js"; +import { getClientFetch } from "../utils/fetch.js"; +import { stringify } from "../utils/json.js"; +import type { Prettify } from "../utils/type-utils.js"; + +export type RevertData = { + errorName: string; + errorArgs: Record; +}; + +type ExecutionResult4337Serialized = + | { + status: "QUEUED"; + } + | { + status: "FAILED"; + error: string; + } + | { + status: "SUBMITTED"; + monitoringStatus: "WILL_MONITOR" | "CANNOT_MONITOR"; + userOpHash: string; + } + | ({ + status: "CONFIRMED"; + userOpHash: Hex; + transactionHash: Hex; + actualGasCost: string; + actualGasUsed: string; + nonce: string; + } & ( + | { + onchainStatus: "SUCCESS"; + } + | { + onchainStatus: "REVERTED"; + revertData?: RevertData; + } + )); + +export type ExecutionResult = Prettify< + ExecutionResult4337Serialized & { + chain: Chain; + from: string | undefined; + id: string; + } +>; + +/** + * Get the execution status of a transaction. + * @param args - The arguments for the transaction. + * @param args.client - The thirdweb client to use. + * @param args.transactionId - The id of the transaction to get the status of. + * @engine + * @example + * ```ts + * import { Engine } from "thirdweb"; + * + * const executionResult = await Engine.getTransactionStatus({ + * client, + * transactionId, + * }); + * console.log(executionResult.status); + * ``` + */ +export async function getTransactionStatus(args: { + client: ThirdwebClient; + transactionId: string; +}): Promise { + const { client, transactionId } = args; + const searchResult = await searchTransactions({ + baseUrl: getThirdwebBaseUrl("engineCloud"), + fetch: getClientFetch(client), + body: { + filters: [ + { + field: "id", + values: [transactionId], + operation: "OR", + }, + ], + }, + }); + + if (searchResult.error) { + throw new Error( + `Error searching for transaction ${transactionId}: ${stringify( + searchResult.error, + )}`, + ); + } + + const data = searchResult.data?.result?.transactions?.[0]; + + if (!data) { + throw new Error(`Transaction ${transactionId} not found`); + } + + const executionResult = data.executionResult as ExecutionResult4337Serialized; + return { + ...executionResult, + chain: getCachedChain(Number(data.chainId)), + from: data.from ?? undefined, + id: data.id, + }; +} + +/** + * Wait for a transaction to be submitted onchain and return the transaction hash. + * @param args - The arguments for the transaction. + * @param args.client - The thirdweb client to use. + * @param args.transactionId - The id of the transaction to wait for. + * @param args.timeoutInSeconds - The timeout in seconds. + * @engine + * @example + * ```ts + * import { Engine } from "thirdweb"; + * + * const { transactionHash } = await Engine.waitForTransactionHash({ + * client, + * transactionId, // the transaction id returned from enqueueTransaction + * }); + * ``` + */ +export async function waitForTransactionHash(args: { + client: ThirdwebClient; + transactionId: string; + timeoutInSeconds?: number; +}): Promise { + const startTime = Date.now(); + const TIMEOUT_IN_MS = args.timeoutInSeconds + ? args.timeoutInSeconds * 1000 + : 5 * 60 * 1000; // 5 minutes in milliseconds + + while (Date.now() - startTime < TIMEOUT_IN_MS) { + const executionResult = await getTransactionStatus(args); + const status = executionResult.status; + + switch (status) { + case "FAILED": { + throw new Error( + `Transaction failed: ${executionResult.error || "Unknown error"}`, + ); + } + case "CONFIRMED": { + const onchainStatus = + executionResult && "onchainStatus" in executionResult + ? executionResult.onchainStatus + : null; + if (onchainStatus === "REVERTED") { + const revertData = + "revertData" in executionResult + ? executionResult.revertData + : undefined; + throw new Error( + `Transaction reverted: ${revertData?.errorName || ""} ${revertData?.errorArgs ? stringify(revertData.errorArgs) : ""}`, + ); + } + return { + transactionHash: executionResult.transactionHash as Hex, + client: args.client, + chain: executionResult.chain, + }; + } + default: { + // wait for the transaction to be confirmed + await new Promise((resolve) => setTimeout(resolve, 1000)); + } + } + } + throw new Error( + `Transaction timed out after ${TIMEOUT_IN_MS / 1000} seconds`, + ); +} diff --git a/packages/thirdweb/src/engine/index.ts b/packages/thirdweb/src/engine/index.ts index 23e45b8c396..ae1ef577b65 100644 --- a/packages/thirdweb/src/engine/index.ts +++ b/packages/thirdweb/src/engine/index.ts @@ -1 +1,11 @@ -export { serverWallet, type ServerWalletOptions } from "./server-wallet.js"; +export { + serverWallet, + type ServerWalletOptions, + type ServerWallet, +} from "./server-wallet.js"; +export { + getTransactionStatus, + waitForTransactionHash, + type ExecutionResult, + type RevertData, +} from "./get-status.js"; diff --git a/packages/thirdweb/src/engine/server-wallet.test.ts b/packages/thirdweb/src/engine/server-wallet.test.ts index 8d29b644af4..6961d3d88c6 100644 --- a/packages/thirdweb/src/engine/server-wallet.test.ts +++ b/packages/thirdweb/src/engine/server-wallet.test.ts @@ -10,7 +10,6 @@ import { claimTo } from "../extensions/erc1155/drops/write/claimTo.js"; import { getAllActiveSigners } from "../extensions/erc4337/__generated__/IAccountPermissions/read/getAllActiveSigners.js"; import { sendTransaction } from "../transaction/actions/send-transaction.js"; import { setThirdwebDomains } from "../utils/domains.js"; -import type { Account } from "../wallets/interfaces/wallet.js"; import { DEFAULT_ACCOUNT_FACTORY_V0_6, ENTRYPOINT_ADDRESS_v0_6, @@ -30,14 +29,14 @@ describe.runIf( retry: 0, }, () => { - let serverWallet: Account; + let serverWallet: Engine.ServerWallet; beforeAll(async () => { setThirdwebDomains({ rpc: "rpc.thirdweb-dev.com", storage: "storage.thirdweb-dev.com", bundler: "bundler.thirdweb-dev.com", - engineCloud: "localhost:3009", // "engine-cloud-dev-l8wt.chainsaw-dev.zeet.app", + engineCloud: "engine.thirdweb-dev.com", }); serverWallet = Engine.serverWallet({ client: TEST_CLIENT, @@ -61,7 +60,7 @@ describe.runIf( expect(signature).toBeDefined(); }); - it("should send a tx", async () => { + it("should send a tx with regular API", async () => { const tx = await sendTransaction({ account: serverWallet, transaction: { @@ -74,6 +73,30 @@ describe.runIf( expect(tx).toBeDefined(); }); + it("should enqueue a tx", async () => { + const nftContract = getContract({ + client: TEST_CLIENT, + chain: sepolia, + address: "0xe2cb0eb5147b42095c2FfA6F7ec953bb0bE347D8", + }); + const claimTx = claimTo({ + contract: nftContract, + to: TEST_ACCOUNT_B.address, + tokenId: 0n, + quantity: 1n, + }); + console.time("enqueue"); + const result = await serverWallet.enqueueTransaction({ + transaction: claimTx, + }); + expect(result.transactionId).toBeDefined(); + const txHash = await Engine.waitForTransactionHash({ + client: TEST_CLIENT, + transactionId: result.transactionId, + }); + expect(txHash.transactionHash).toBeDefined(); + }); + it("should send a extension tx", async () => { const nftContract = getContract({ client: TEST_CLIENT, diff --git a/packages/thirdweb/src/engine/server-wallet.ts b/packages/thirdweb/src/engine/server-wallet.ts index 8a0bf9a14a7..f036ca55e84 100644 --- a/packages/thirdweb/src/engine/server-wallet.ts +++ b/packages/thirdweb/src/engine/server-wallet.ts @@ -1,21 +1,25 @@ import { type AaExecutionOptions, type AaZksyncExecutionOptions, - searchTransactions, sendTransaction, signMessage, signTypedData, } from "@thirdweb-dev/engine"; import type { Chain } from "../chains/types.js"; import type { ThirdwebClient } from "../client/client.js"; +import { encode } from "../transaction/actions/encode.js"; +import { toSerializableTransaction } from "../transaction/actions/to-serializable-transaction.js"; +import type { PreparedTransaction } from "../transaction/prepare-transaction.js"; import { getThirdwebBaseUrl } from "../utils/domains.js"; import { type Hex, toHex } from "../utils/encoding/hex.js"; import { getClientFetch } from "../utils/fetch.js"; import { stringify } from "../utils/json.js"; +import { resolvePromisedValue } from "../utils/promise/resolve-promised-value.js"; import type { Account, SendTransactionOption, } from "../wallets/interfaces/wallet.js"; +import { waitForTransactionHash } from "./get-status.js"; /** * Options for creating an server wallet. @@ -45,74 +49,69 @@ export type ServerWalletOptions = { | Omit; }; -type RevertDataSerialized = { - revertReason?: string; - decodedError?: { - name: string; - signature: string; - args: string[]; - }; +export type ServerWallet = Account & { + enqueueTransaction: (args: { + transaction: PreparedTransaction; + simulate?: boolean; + }) => Promise<{ transactionId: string }>; }; -type ExecutionResult = - | { - status: "QUEUED"; - } - | { - status: "FAILED"; - error: string; - } - | { - status: "SUBMITTED"; - monitoringStatus: "WILL_MONITOR" | "CANNOT_MONITOR"; - userOpHash: string; - } - | ({ - status: "CONFIRMED"; - userOpHash: Hex; - transactionHash: Hex; - actualGasCost: string; - actualGasUsed: string; - nonce: string; - } & ( - | { - onchainStatus: "SUCCESS"; - } - | { - onchainStatus: "REVERTED"; - revertData?: RevertDataSerialized; - } - )); - /** * Create a server wallet for sending transactions and signing messages via engine (v3+). * @param options - The server wallet options. * @returns An account object that can be used to send transactions and sign messages. * @engine * @example + * ### Creating a server wallet * ```ts * import { Engine } from "thirdweb"; * + * const client = createThirdwebClient({ + * secretKey: "", + * }); + * * const myServerWallet = Engine.serverWallet({ * client, - * vaultAccessToken, - * walletAddress, + * address: "", + * vaultAccessToken: "", * }); + * ``` * - * // then use the account as you would any other account + * ### Sending a transaction + * ```ts + * // prepare the transaction * const transaction = claimTo({ * contract, * to: "0x...", * quantity: 1n, * }); - * const result = await sendTransaction({ + * + * // enqueue the transaction + * const { transactionId } = await myServerWallet.enqueueTransaction({ * transaction, - * account: myServerWallet * }); - * console.log("Transaction sent:", result.transactionHash); + * ``` + * + * ### Polling for the transaction to be submitted onchain + * ```ts + * // optionally poll for the transaction to be submitted onchain + * const { transactionHash } = await Engine.waitForTransactionHash({ + * client, + * transactionId, + * }); + * console.log("Transaction sent:", transactionHash); + * ``` + * + * ### Getting the execution status of a transaction + * ```ts + * const executionResult = await Engine.getTransactionStatus({ + * client, + * transactionId, + * }); + * console.log("Transaction status:", executionResult.status); * ``` */ -export function serverWallet(options: ServerWalletOptions): Account { +export function serverWallet(options: ServerWalletOptions): ServerWallet { const { client, vaultAccessToken, address, chain, executionOptions } = options; const headers: HeadersInit = { @@ -131,104 +130,77 @@ export function serverWallet(options: ServerWalletOptions): Account { }; }; - return { - address, - sendTransaction: async (transaction: SendTransactionOption) => { - const body = { - executionOptions: getExecutionOptions(transaction.chainId), - params: [ - { - to: transaction.to ?? undefined, - data: transaction.data, - value: transaction.value?.toString(), - }, - ], - }; - - const result = await sendTransaction({ - baseUrl: getThirdwebBaseUrl("engineCloud"), - fetch: getClientFetch(client), - headers, - body, - }); + const enqueueTx = async (transaction: SendTransactionOption) => { + const body = { + executionOptions: getExecutionOptions(transaction.chainId), + params: [ + { + to: transaction.to ?? undefined, + data: transaction.data, + value: transaction.value?.toString(), + }, + ], + }; - if (result.error) { - throw new Error(`Error sending transaction: ${result.error}`); - } + const result = await sendTransaction({ + baseUrl: getThirdwebBaseUrl("engineCloud"), + fetch: getClientFetch(client), + headers, + body, + }); - const data = result.data?.result; - if (!data) { - throw new Error("No data returned from engine"); - } - const transactionId = data.transactions?.[0]?.id; - if (!transactionId) { - throw new Error("No transactionId returned from engine"); - } + if (result.error) { + throw new Error(`Error sending transaction: ${result.error}`); + } - // wait for the queueId to be processed - const startTime = Date.now(); - const TIMEOUT_IN_MS = 5 * 60 * 1000; // 5 minutes in milliseconds + const data = result.data?.result; + if (!data) { + throw new Error("No data returned from engine"); + } + const transactionId = data.transactions?.[0]?.id; + if (!transactionId) { + throw new Error("No transactionId returned from engine"); + } + return transactionId; + }; - while (Date.now() - startTime < TIMEOUT_IN_MS) { - const searchResult = await searchTransactions({ - baseUrl: getThirdwebBaseUrl("engineCloud"), - fetch: getClientFetch(client), - body: { - filters: [ - { - field: "id", - values: [transactionId], - operation: "OR", - }, - ], - }, + return { + address, + enqueueTransaction: async (args: { + transaction: PreparedTransaction; + simulate?: boolean; + }) => { + let serializedTransaction: SendTransactionOption; + if (args.simulate) { + serializedTransaction = await toSerializableTransaction({ + transaction: args.transaction, }); - - if (searchResult.error) { - throw new Error( - `Error searching for transaction: ${stringify(searchResult.error)}`, - ); - } - - const data = searchResult.data?.result?.transactions?.[0]; - - if (!data) { - throw new Error(`Transaction ${transactionId} not found`); - } - - const executionResult = data.executionResult as ExecutionResult; - const status = executionResult.status; - - if (status === "FAILED") { - throw new Error( - `Transaction failed: ${executionResult.error || "Unknown error"}`, - ); - } - - const onchainStatus = - executionResult && "onchainStatus" in executionResult - ? executionResult.onchainStatus - : null; - - if (status === "CONFIRMED" && onchainStatus === "REVERTED") { - const revertData = - "revertData" in executionResult - ? executionResult.revertData - : undefined; - throw new Error( - `Transaction reverted: ${revertData?.decodedError?.name || revertData?.revertReason || "Unknown revert reason"}`, - ); - } - - if (data.transactionHash) { - return { - transactionHash: data.transactionHash as Hex, - }; - } - // wait 1s before checking again - await new Promise((resolve) => setTimeout(resolve, 1000)); + } else { + const [to, data, value] = await Promise.all([ + args.transaction.to + ? resolvePromisedValue(args.transaction.to) + : null, + encode(args.transaction), + args.transaction.value + ? resolvePromisedValue(args.transaction.value) + : null, + ]); + serializedTransaction = { + chainId: args.transaction.chain.id, + data, + to: to ?? undefined, + value: value ?? undefined, + }; } - throw new Error("Transaction timed out after 5 minutes"); + const transactionId = await enqueueTx(serializedTransaction); + return { transactionId }; + }, + sendTransaction: async (transaction: SendTransactionOption) => { + const transactionId = await enqueueTx(transaction); + return waitForTransactionHash({ + client, + transactionId, + }); }, signMessage: async (data) => { const { message, chainId } = data; From 49d315c6837d2ed37c3372b0b1a02199bba548bf Mon Sep 17 00:00:00 2001 From: Joaquim Verges Date: Tue, 6 May 2025 21:03:25 +1200 Subject: [PATCH 099/101] update code snippet --- .../server-wallets/components/try-it-out.tsx | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/server-wallets/components/try-it-out.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/server-wallets/components/try-it-out.tsx index 93cea9d7449..c64f0f6789d 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/server-wallets/components/try-it-out.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/server-wallets/components/try-it-out.tsx @@ -212,11 +212,22 @@ const transaction = claimTo({ quantity: 1n, // The quantity of NFTs to mint }); -// Send the transaction via Engine -const result = await sendTransaction({ - account: serverWallet, +// Enqueue the transaction via Engine +const { transactionId } = await serverWallet.enqueueTransaction({ transaction, }); + +// Get the execution status of the transaction at any point in time +const executionResult = await Engine.getTransactionStatus({ + client, + transactionId, +}); + +// Utility function to poll for the transaction to be submitted onchain +const txHash = await Engine.waitForTransactionHash({ + client, + transactionId, +}); console.log("Transaction hash:", result.transactionHash); `; From 3ee95db93992fd9fd22f9b660e6e2cfad4c5a426 Mon Sep 17 00:00:00 2001 From: Joaquim Verges Date: Wed, 7 May 2025 00:03:23 +1200 Subject: [PATCH 100/101] separate admin key / access token flow in FTUX --- .../cloud/analytics/send-test-tx.client.tsx | 41 ++++++++++++------- .../create-vault-account.client.tsx | 26 ++++++------ .../components/list-access-tokens.client.tsx | 11 +++-- .../thirdweb/src/engine/server-wallet.test.ts | 1 - 4 files changed, 45 insertions(+), 34 deletions(-) diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/send-test-tx.client.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/send-test-tx.client.tsx index 23187201706..f9453587794 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/send-test-tx.client.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/send-test-tx.client.tsx @@ -15,7 +15,7 @@ import { useThirdwebClient } from "@/constants/thirdweb.client"; import { useDashboardRouter } from "@/lib/DashboardRouter"; import { zodResolver } from "@hookform/resolvers/zod"; import { useMutation, useQueryClient } from "@tanstack/react-query"; -import { Loader2, LockIcon } from "lucide-react"; +import { Loader2Icon, LockIcon } from "lucide-react"; import { useState } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; @@ -26,6 +26,7 @@ import { sepolia, } from "thirdweb/chains"; import * as z from "zod"; +import { CopyTextButton } from "../../../../../../../../@/components/ui/CopyTextButton"; import { useTrack } from "../../../../../../../../hooks/analytics/useTrack"; import type { Wallet } from "../server-wallets/wallet-table/types"; import { SmartAccountCell } from "../server-wallets/wallet-table/wallet-table-ui.client"; @@ -146,7 +147,9 @@ export function SendTestTransaction(props: { )}

- Every wallet action requires your Vault access token. + {props.userAccessToken + ? "Copy your Vault access token, you'll need it for every HTTP call to Engine." + : "Every wallet action requires your Vault access token."}

{/* Responsive container */} @@ -154,22 +157,30 @@ export function SendTestTransaction(props: {

Vault Access Token

- - {props.userAccessToken && ( + {props.userAccessToken ? (
+

- This is the project-wide access token you just created. You - can create more access tokens using your admin key, with - granular scopes and permissions. + This is a project-wide access token to access your server + wallets. You can create more access tokens using your admin + key, with granular scopes and permissions.

+ ) : ( + )}
@@ -257,7 +268,7 @@ export function SendTestTransaction(props: { > {isLoading ? ( <> - + Sending... ) : ( diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/vault/components/create-vault-account.client.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/vault/components/create-vault-account.client.tsx index e5e4a9bb4b5..7fa2bb264fb 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/vault/components/create-vault-account.client.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/vault/components/create-vault-account.client.tsx @@ -15,7 +15,7 @@ import { useDashboardRouter } from "@/lib/DashboardRouter"; import { cn } from "@/lib/utils"; import { useMutation } from "@tanstack/react-query"; import { createServiceAccount } from "@thirdweb-dev/vault-sdk"; -import { CheckIcon, DownloadIcon, Loader2, LockIcon } from "lucide-react"; +import { CheckIcon, DownloadIcon, Loader2Icon, LockIcon } from "lucide-react"; import { useState } from "react"; import { toast } from "sonner"; import { useTrack } from "../../../../../../../../../hooks/analytics/useTrack"; @@ -150,7 +150,7 @@ export default function CreateVaultAccountButton(props: { className="flex flex-row items-center gap-2" > {isLoading ? ( - + ) : ( )} @@ -177,19 +177,16 @@ export default function CreateVaultAccountButton(props: { ) : initialiseProjectWithVaultMutation.data ? (
- Vault Management Keys + Save your Vault Admin Key

- These keys are used for end-to-end encryption and are required - to interact with Vault, thirdweb's key management system. + You'll need this key to create server wallets and access + tokens.

-

- Vault Admin Key -

- This key is used to create or revoke your access tokens. + Download this key to your local machine or a password + manager.

-
+ {/*

Vault Access Token

@@ -234,10 +232,10 @@ export default function CreateVaultAccountButton(props: { with your admin key.

-
+
*/}
- Secure your keys + Secure your admin key These keys will not be displayed again. Store them securely as they provide access to your server wallets. @@ -250,7 +248,7 @@ export default function CreateVaultAccountButton(props: { className="flex h-auto items-center gap-2 p-0 text-sm text-success-text" > - {keysDownloaded ? "Keys Downloaded" : "Download Keys"} + {keysDownloaded ? "Key Downloaded" : "Download Admin Key"} {keysDownloaded && ( @@ -264,7 +262,7 @@ export default function CreateVaultAccountButton(props: { checked={keysConfirmed} onCheckedChange={(v) => setKeysConfirmed(!!v)} /> - I confirm that I've securely stored these keys + I confirm that I've securely stored my admin key
diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/vault/components/list-access-tokens.client.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/vault/components/list-access-tokens.client.tsx index bf4fc289ce4..a2aa4207662 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/vault/components/list-access-tokens.client.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/vault/components/list-access-tokens.client.tsx @@ -14,7 +14,7 @@ import { Input } from "@/components/ui/input"; import { Skeleton } from "@/components/ui/skeleton"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { listAccessTokens, revokeAccessToken } from "@thirdweb-dev/vault-sdk"; -import { Loader2, LockIcon, Trash2 } from "lucide-react"; +import { Loader2Icon, LockIcon, Trash2Icon } from "lucide-react"; import { useState } from "react"; import { toast } from "sonner"; import { toDateTimeLocal } from "utils/date-utils"; @@ -227,7 +227,10 @@ export default function ListAccessTokens(props: { {token.accessToken.includes("**") ? (

- {token.accessToken} + {token.accessToken}{" "} + + (unlock vault to reveal the full token) +

) : ( @@ -258,7 +261,7 @@ export default function ListAccessTokens(props: { {deletingTokenId === token.id ? ( ) : ( - + )}
@@ -291,7 +294,7 @@ export default function ListAccessTokens(props: { > {createAccessTokenMutation.isPending ? ( <> - + Creating new access token... ) : ( diff --git a/packages/thirdweb/src/engine/server-wallet.test.ts b/packages/thirdweb/src/engine/server-wallet.test.ts index 6961d3d88c6..f5eb8fd8699 100644 --- a/packages/thirdweb/src/engine/server-wallet.test.ts +++ b/packages/thirdweb/src/engine/server-wallet.test.ts @@ -85,7 +85,6 @@ describe.runIf( tokenId: 0n, quantity: 1n, }); - console.time("enqueue"); const result = await serverWallet.enqueueTransaction({ transaction: claimTx, }); From 398e3ae484c19aab1398b0cc04c4547bad168a06 Mon Sep 17 00:00:00 2001 From: Joaquim Verges Date: Wed, 7 May 2025 21:54:17 +1200 Subject: [PATCH 101/101] fix build --- .../components/create-server-wallet.client.tsx | 6 +++--- .../[project_slug]/engine/cloud/tx/[id]/layout.tsx | 4 ++-- .../cloud/vault/components/list-access-tokens.client.tsx | 4 ++-- .../cloud/vault/components/rotate-admin-key.client.tsx | 6 +++--- packages/thirdweb/.size-limit.json | 2 +- packages/thirdweb/src/utils/domain.test.ts | 1 + 6 files changed, 12 insertions(+), 11 deletions(-) diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/server-wallets/components/create-server-wallet.client.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/server-wallets/components/create-server-wallet.client.tsx index a07cdabe947..6001935c15b 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/server-wallets/components/create-server-wallet.client.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/server-wallets/components/create-server-wallet.client.tsx @@ -12,7 +12,7 @@ import { Input } from "@/components/ui/input"; import { useDashboardRouter } from "@/lib/DashboardRouter"; import { useMutation } from "@tanstack/react-query"; import { createEoa } from "@thirdweb-dev/vault-sdk"; -import { Loader2, WalletIcon } from "lucide-react"; +import { Loader2Icon, WalletIcon } from "lucide-react"; import { useState } from "react"; import { toast } from "sonner"; import { engineCloudProxy } from "../../../../../../../../../@/actions/proxies"; @@ -120,7 +120,7 @@ export default function CreateServerWallet(props: { className="flex flex-row items-center gap-2" > {isLoading ? ( - + ) : ( )} @@ -164,7 +164,7 @@ export default function CreateServerWallet(props: { > {isLoading ? ( <> - + Creating... ) : ( diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/tx/[id]/layout.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/tx/[id]/layout.tsx index bfa7963dd88..849b1af7bb7 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/tx/[id]/layout.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/tx/[id]/layout.tsx @@ -1,4 +1,4 @@ -import { ChevronLeft } from "lucide-react"; +import { ChevronLeftIcon } from "lucide-react"; import Link from "next/link"; export default function TransactionLayout({ @@ -15,7 +15,7 @@ export default function TransactionLayout({ href={`/team/${params.team_slug}/${params.project_slug}/engine/cloud`} className="flex items-center gap-1 text-muted-foreground text-sm hover:text-foreground" > - + Back to Transactions
diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/vault/components/list-access-tokens.client.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/vault/components/list-access-tokens.client.tsx index a2aa4207662..ecd867d4e94 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/vault/components/list-access-tokens.client.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/vault/components/list-access-tokens.client.tsx @@ -259,9 +259,9 @@ export default function ListAccessTokens(props: { } > {deletingTokenId === token.id ? ( - + ) : ( - + )}
diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/vault/components/rotate-admin-key.client.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/vault/components/rotate-admin-key.client.tsx index 82983f0c4b1..243156aa2c2 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/vault/components/rotate-admin-key.client.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/vault/components/rotate-admin-key.client.tsx @@ -21,7 +21,7 @@ import { CheckIcon, CircleAlertIcon, DownloadIcon, - Loader2, + Loader2Icon, RefreshCcwIcon, } from "lucide-react"; import { useState } from "react"; @@ -150,7 +150,7 @@ export default function RotateAdminKeyButton(props: { project: Project }) { disabled={isLoading} className="h-auto gap-2 rounded-lg bg-background px-4 py-3" > - {isLoading && } + {isLoading && } {!isLoading && } Rotate Admin Key @@ -309,7 +309,7 @@ export default function RotateAdminKeyButton(props: { project: Project }) { > {rotateAdminKeyMutation.isPending ? ( <> - + Rotating... ) : ( diff --git a/packages/thirdweb/.size-limit.json b/packages/thirdweb/.size-limit.json index 5a2f4870313..904c12d801f 100644 --- a/packages/thirdweb/.size-limit.json +++ b/packages/thirdweb/.size-limit.json @@ -2,7 +2,7 @@ { "name": "thirdweb (esm)", "path": "./dist/esm/exports/thirdweb.js", - "limit": "53 kB", + "limit": "60 kB", "import": "*" }, { diff --git a/packages/thirdweb/src/utils/domain.test.ts b/packages/thirdweb/src/utils/domain.test.ts index bef19e26bca..dfe332b6f0c 100644 --- a/packages/thirdweb/src/utils/domain.test.ts +++ b/packages/thirdweb/src/utils/domain.test.ts @@ -16,6 +16,7 @@ describe("Thirdweb Domains", () => { bundler: "bundler.thirdweb.com", analytics: "c.thirdweb.com", insight: "insight.thirdweb.com", + engineCloud: "engine.thirdweb.com", }; beforeEach(() => {