22
33import { ExportToCSVButton } from "@/components/blocks/ExportToCSVButton" ;
44import {
5+ type ChartConfig ,
56 ChartContainer ,
67 ChartLegend ,
78 ChartLegendContent ,
89 ChartTooltip ,
910 ChartTooltipContent ,
1011} from "@/components/ui/chart" ;
11- import type { UserOpStats } from "@3rdweb-sdk/react/hooks/useApi" ;
12+ import type { UserOpStatsByChain } from "@3rdweb-sdk/react/hooks/useApi" ;
1213import {
1314 EmptyChartState ,
1415 LoadingChartState ,
@@ -20,53 +21,94 @@ import { UnityIcon } from "components/icons/brand-icons/UnityIcon";
2021import { UnrealIcon } from "components/icons/brand-icons/UnrealIcon" ;
2122import { DocLink } from "components/shared/DocLink" ;
2223import { format } from "date-fns" ;
24+ import { useAllChainsData } from "hooks/chains/allChains" ;
2325import { useMemo } from "react" ;
2426import { Bar , BarChart , CartesianGrid , XAxis , YAxis } from "recharts" ;
2527import { formatTickerNumber } from "../../../lib/format-utils" ;
2628
27- type ChartData = {
29+ type ChartData = Record < string , number > & {
2830 time : string ; // human readable date
29- failed : number ;
30- successful : number ;
3131} ;
3232
33- const chartConfig = {
34- successful : {
35- label : "Successful" ,
36- color : "hsl(var(--chart-1))" ,
37- } ,
38- failed : {
39- label : "Failed" ,
40- color : "red" ,
41- } ,
42- } ;
4333export function SponsoredTransactionsChartCard ( props : {
44- userOpStats : UserOpStats [ ] ;
34+ userOpStats : UserOpStatsByChain [ ] ;
4535 isPending : boolean ;
4636} ) {
4737 const { userOpStats } = props ;
38+ const topChainsToShow = 10 ;
39+ const chainsStore = useAllChainsData ( ) ;
4840
49- const barChartData : ChartData [ ] = useMemo ( ( ) => {
50- const chartDataMap : Map < string , ChartData > = new Map ( ) ;
41+ const { chartConfig, chartData } = useMemo ( ( ) => {
42+ const _chartConfig : ChartConfig = { } ;
43+ const _chartDataMap : Map < string , ChartData > = new Map ( ) ;
44+ const chainIdToVolumeMap : Map < string , number > = new Map ( ) ;
45+ // for each stat, add it in _chartDataMap
46+ for ( const stat of userOpStats ) {
47+ const chartData = _chartDataMap . get ( stat . date ) ;
48+ const { chainId } = stat ;
49+ const chain = chainsStore . idToChain . get ( Number ( chainId ) ) ;
5150
52- for ( const data of userOpStats ) {
53- const chartData = chartDataMap . get ( data . date ) ;
51+ // if no data for current day - create new entry
5452 if ( ! chartData ) {
55- chartDataMap . set ( data . date , {
56- time : format ( new Date ( data . date ) , "MMM dd" ) ,
57- successful : data . successful ,
58- failed : data . failed ,
59- } ) ;
53+ _chartDataMap . set ( stat . date , {
54+ time : format ( new Date ( stat . date ) , "MMM dd" ) ,
55+ [ chain ?. name || chainId || "Unknown" ] :
56+ Math . round ( stat . sponsoredUsd * 100 ) / 100 ,
57+ } as ChartData ) ;
6058 } else {
61- chartData . successful += data . successful ;
62- chartData . failed += data . failed ;
59+ chartData [ chain ?. name || chainId || "Unknown" ] =
60+ ( chartData [ chain ?. name || chainId || "Unknown" ] || 0 ) +
61+ Math . round ( stat . sponsoredUsd * 100 ) / 100 ;
6362 }
63+
64+ chainIdToVolumeMap . set (
65+ chain ?. name || chainId || "Unknown" ,
66+ stat . sponsoredUsd + ( chainIdToVolumeMap . get ( chainId || "Unknown" ) || 0 ) ,
67+ ) ;
6468 }
6569
66- return Array . from ( chartDataMap . values ( ) ) ;
67- } , [ userOpStats ] ) ;
70+ const chainsSorted = Array . from ( chainIdToVolumeMap . entries ( ) )
71+ . sort ( ( a , b ) => b [ 1 ] - a [ 1 ] )
72+ . map ( ( w ) => w [ 0 ] ) ;
73+
74+ const chainsToShow = chainsSorted . slice ( 0 , topChainsToShow ) ;
75+ const chainsToTagAsOthers = chainsSorted . slice ( topChainsToShow ) ;
76+
77+ // replace chainIdsToTagAsOther chainId with "other"
78+ for ( const data of _chartDataMap . values ( ) ) {
79+ for ( const chainId in data ) {
80+ if ( chainsToTagAsOthers . includes ( chainId ) ) {
81+ data . others = ( data . others || 0 ) + ( data [ chainId ] || 0 ) ;
82+ delete data [ chainId ] ;
83+ }
84+ }
85+ }
86+
87+ chainsToShow . forEach ( ( walletType , i ) => {
88+ _chartConfig [ walletType ] = {
89+ label : chainsToShow [ i ] ,
90+ color : `hsl(var(--chart-${ ( i % 10 ) + 1 } ))` ,
91+ } ;
92+ } ) ;
93+
94+ // Add Other
95+ chainsToShow . push ( "others" ) ;
96+ _chartConfig . others = {
97+ label : "Others" ,
98+ color : "hsl(var(--muted-foreground))" ,
99+ } ;
100+
101+ return {
102+ chartData : Array . from ( _chartDataMap . values ( ) ) ,
103+ chartConfig : _chartConfig ,
104+ } ;
105+ } , [ userOpStats , chainsStore ] ) ;
68106
69- const disableActions = props . isPending || barChartData . length === 0 ;
107+ const uniqueChainIds = Object . keys ( chartConfig ) ;
108+ const disableActions =
109+ props . isPending ||
110+ chartData . length === 0 ||
111+ chartData . every ( ( data ) => data . transactions === 0 ) ;
70112
71113 return (
72114 < div className = "relative w-full rounded-lg border border-border bg-muted/50 p-4 md:p-6" >
@@ -83,10 +125,13 @@ export function SponsoredTransactionsChartCard(props: {
83125 fileName = "Sponsored Transactions"
84126 disabled = { disableActions }
85127 getData = { async ( ) => {
86- const header = [ "Date" , "Successful" , "Failed" ] ;
87- const rows = barChartData . map ( ( data ) => {
88- const { time, successful, failed } = data ;
89- return [ time , successful . toString ( ) , failed . toString ( ) ] ;
128+ const header = [ "Date" , ...uniqueChainIds ] ;
129+ const rows = chartData . map ( ( data ) => {
130+ const { time, ...rest } = data ;
131+ return [
132+ time ,
133+ ...uniqueChainIds . map ( ( w ) => ( rest [ w ] || 0 ) . toString ( ) ) ,
134+ ] ;
90135 } ) ;
91136 return { header, rows } ;
92137 } }
@@ -97,10 +142,8 @@ export function SponsoredTransactionsChartCard(props: {
97142 < ChartContainer config = { chartConfig } className = "h-[400px] w-full" >
98143 { props . isPending ? (
99144 < LoadingChartState />
100- ) : barChartData . length === 0 ||
101- barChartData . every (
102- ( data ) => data . failed === 0 && data . successful === 0 ,
103- ) ? (
145+ ) : chartData . length === 0 ||
146+ chartData . every ( ( data ) => data . transactions === 0 ) ? (
104147 < EmptyChartState >
105148 < div className = "flex flex-col items-center justify-center" >
106149 < span className = "mb-6 text-lg" >
@@ -143,7 +186,7 @@ export function SponsoredTransactionsChartCard(props: {
143186 ) : (
144187 < BarChart
145188 accessibilityLayer
146- data = { barChartData }
189+ data = { chartData }
147190 margin = { {
148191 top : 20 ,
149192 } }
@@ -158,7 +201,12 @@ export function SponsoredTransactionsChartCard(props: {
158201 />
159202
160203 < YAxis
161- dataKey = { ( data ) => data . successful + data . failed }
204+ dataKey = { ( data ) => {
205+ return Object . entries ( data )
206+ . filter ( ( [ key ] ) => key !== "time" )
207+ . map ( ( [ , value ] ) => value )
208+ . reduce ( ( acc , current ) => Number ( acc ) + Number ( current ) , 0 ) ;
209+ } }
162210 tickLine = { false }
163211 axisLine = { false }
164212 tickFormatter = { ( value ) => formatTickerNumber ( value ) }
@@ -173,12 +221,12 @@ export function SponsoredTransactionsChartCard(props: {
173221 }
174222 />
175223 < ChartLegend content = { < ChartLegendContent /> } />
176- { ( [ "failed" , "successful" ] as const ) . map ( ( result ) => {
224+ { uniqueChainIds . map ( ( chainId ) => {
177225 return (
178226 < Bar
179- key = { result }
180- dataKey = { result }
181- fill = { chartConfig [ result ] . color }
227+ key = { chainId }
228+ dataKey = { chainId }
229+ fill = { chartConfig [ chainId ] ? .color }
182230 radius = { 4 }
183231 stackId = "a"
184232 strokeWidth = { 1.5 }
0 commit comments