diff --git a/apps/dashboard/src/components/smart-wallets/AccountAbstractionAnalytics/SponsoredTransactionsChartCard.tsx b/apps/dashboard/src/components/smart-wallets/AccountAbstractionAnalytics/SponsoredTransactionsChartCard.tsx index 9c6da89f55c..f9c3547d9d8 100644 --- a/apps/dashboard/src/components/smart-wallets/AccountAbstractionAnalytics/SponsoredTransactionsChartCard.tsx +++ b/apps/dashboard/src/components/smart-wallets/AccountAbstractionAnalytics/SponsoredTransactionsChartCard.tsx @@ -2,13 +2,14 @@ import { ExportToCSVButton } from "@/components/blocks/ExportToCSVButton"; import { + type ChartConfig, ChartContainer, ChartLegend, ChartLegendContent, ChartTooltip, ChartTooltipContent, } from "@/components/ui/chart"; -import type { UserOpStats } from "@3rdweb-sdk/react/hooks/useApi"; +import type { UserOpStatsByChain } from "@3rdweb-sdk/react/hooks/useApi"; import { EmptyChartState, LoadingChartState, @@ -20,53 +21,94 @@ import { UnityIcon } from "components/icons/brand-icons/UnityIcon"; import { UnrealIcon } from "components/icons/brand-icons/UnrealIcon"; import { DocLink } from "components/shared/DocLink"; import { format } from "date-fns"; +import { useAllChainsData } from "hooks/chains/allChains"; import { useMemo } from "react"; import { Bar, BarChart, CartesianGrid, XAxis, YAxis } from "recharts"; import { formatTickerNumber } from "../../../lib/format-utils"; -type ChartData = { +type ChartData = Record & { time: string; // human readable date - failed: number; - successful: number; }; -const chartConfig = { - successful: { - label: "Successful", - color: "hsl(var(--chart-1))", - }, - failed: { - label: "Failed", - color: "red", - }, -}; export function SponsoredTransactionsChartCard(props: { - userOpStats: UserOpStats[]; + userOpStats: UserOpStatsByChain[]; isPending: boolean; }) { const { userOpStats } = props; + const topChainsToShow = 10; + const chainsStore = useAllChainsData(); - const barChartData: ChartData[] = useMemo(() => { - const chartDataMap: Map = new Map(); + 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)); - for (const data of userOpStats) { - const chartData = chartDataMap.get(data.date); + // if no data for current day - create new entry if (!chartData) { - chartDataMap.set(data.date, { - time: format(new Date(data.date), "MMM dd"), - successful: data.successful, - failed: data.failed, - }); + _chartDataMap.set(stat.date, { + time: format(new Date(stat.date), "MMM dd"), + [chain?.name || chainId || "Unknown"]: + Math.round(stat.sponsoredUsd * 100) / 100, + } as ChartData); } else { - chartData.successful += data.successful; - chartData.failed += data.failed; + chartData[chain?.name || chainId || "Unknown"] = + (chartData[chain?.name || chainId || "Unknown"] || 0) + + Math.round(stat.sponsoredUsd * 100) / 100; } + + chainIdToVolumeMap.set( + chain?.name || chainId || "Unknown", + stat.sponsoredUsd + (chainIdToVolumeMap.get(chainId || "Unknown") || 0), + ); } - return Array.from(chartDataMap.values()); - }, [userOpStats]); + 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((walletType, i) => { + _chartConfig[walletType] = { + label: chainsToShow[i], + 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 disableActions = props.isPending || barChartData.length === 0; + const uniqueChainIds = Object.keys(chartConfig); + const disableActions = + props.isPending || + chartData.length === 0 || + chartData.every((data) => data.transactions === 0); return (
@@ -83,10 +125,13 @@ export function SponsoredTransactionsChartCard(props: { fileName="Sponsored Transactions" disabled={disableActions} getData={async () => { - const header = ["Date", "Successful", "Failed"]; - const rows = barChartData.map((data) => { - const { time, successful, failed } = data; - return [time, successful.toString(), failed.toString()]; + 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 }; }} @@ -97,10 +142,8 @@ export function SponsoredTransactionsChartCard(props: { {props.isPending ? ( - ) : barChartData.length === 0 || - barChartData.every( - (data) => data.failed === 0 && data.successful === 0, - ) ? ( + ) : chartData.length === 0 || + chartData.every((data) => data.transactions === 0) ? (
@@ -143,7 +186,7 @@ export function SponsoredTransactionsChartCard(props: { ) : ( data.successful + data.failed} + dataKey={(data) => { + return Object.entries(data) + .filter(([key]) => key !== "time") + .map(([, value]) => value) + .reduce((acc, current) => Number(acc) + Number(current), 0); + }} tickLine={false} axisLine={false} tickFormatter={(value) => formatTickerNumber(value)} @@ -173,12 +221,12 @@ export function SponsoredTransactionsChartCard(props: { } /> } /> - {(["failed", "successful"] as const).map((result) => { + {uniqueChainIds.map((chainId) => { return ( { - console.log(data); return Object.entries(data) .filter(([key]) => key !== "time") .map(([, value]) => value) diff --git a/apps/dashboard/src/components/smart-wallets/AccountAbstractionAnalytics/storyUtils.ts b/apps/dashboard/src/components/smart-wallets/AccountAbstractionAnalytics/storyUtils.ts index a7b2824d04d..a4117d03d6f 100644 --- a/apps/dashboard/src/components/smart-wallets/AccountAbstractionAnalytics/storyUtils.ts +++ b/apps/dashboard/src/components/smart-wallets/AccountAbstractionAnalytics/storyUtils.ts @@ -1,7 +1,7 @@ -import type { UserOpStats } from "@3rdweb-sdk/react/hooks/useApi"; +import type { UserOpStatsByChain } from "@3rdweb-sdk/react/hooks/useApi"; -export function createUserOpStatsStub(days: number): UserOpStats[] { - const stubbedData: UserOpStats[] = []; +export function createUserOpStatsStub(days: number): UserOpStatsByChain[] { + const stubbedData: UserOpStatsByChain[] = []; let d = days; while (d !== 0) { @@ -13,6 +13,7 @@ export function createUserOpStatsStub(days: number): UserOpStats[] { successful, failed, sponsoredUsd, + chainId: Math.floor(Math.random() * 100).toString(), }); if (Math.random() > 0.7) {