11import { loginRedirect } from "@app/login/loginRedirect" ;
22import { notFound , redirect } from "next/navigation" ;
3+ import { getContract } from "thirdweb" ;
4+ import { defineChain } from "thirdweb/chains" ;
5+ import { getCompilerMetadata } from "thirdweb/contract" ;
6+ import { decodeFunctionData , toFunctionSelector } from "thirdweb/utils" ;
7+ import type { AbiFunction } from "abitype" ;
38import { getAuthToken } from "@/api/auth-token" ;
49import { getProject } from "@/api/projects" ;
510import { getClientThirdwebClient } from "@/constants/thirdweb-client.client" ;
11+ import { serverThirdwebClient } from "@/constants/thirdweb-client.server" ;
612import {
713 getSingleTransaction ,
814 getTransactionActivityLogs ,
915} from "../../lib/analytics" ;
16+ import type { Transaction } from "../../analytics/tx-table/types" ;
1017import { TransactionDetailsUI } from "./transaction-details-ui" ;
1118
19+ type AbiItem =
20+ | AbiFunction
21+ | {
22+ type : string ;
23+ name ?: string ;
24+ } ;
25+
26+ export type DecodedTransactionData = {
27+ contractName : string ;
28+ functionName : string ;
29+ functionArgs : Record < string , unknown > ;
30+ } | null ;
31+
32+ async function decodeTransactionData (
33+ transaction : Transaction ,
34+ ) : Promise < DecodedTransactionData > {
35+ try {
36+ // Check if we have transaction parameters
37+ if (
38+ ! transaction . transactionParams ||
39+ transaction . transactionParams . length === 0
40+ ) {
41+ return null ;
42+ }
43+
44+ // Get the first transaction parameter (assuming single transaction)
45+ const txParam = transaction . transactionParams [ 0 ] ;
46+ if ( ! txParam || ! txParam . to || ! txParam . data ) {
47+ return null ;
48+ }
49+
50+ // Ensure we have a chainId
51+ if ( ! transaction . chainId ) {
52+ return null ;
53+ }
54+
55+ const chainId = parseInt ( transaction . chainId ) ;
56+
57+ // Create contract instance
58+ const contract = getContract ( {
59+ client : serverThirdwebClient ,
60+ address : txParam . to ,
61+ chain : defineChain ( chainId ) ,
62+ } ) ;
63+
64+ // Fetch compiler metadata
65+ const compilerMetadata = await getCompilerMetadata ( contract ) ;
66+
67+ if ( ! compilerMetadata || ! compilerMetadata . abi ) {
68+ return null ;
69+ }
70+
71+ const contractName = compilerMetadata . name || "Unknown Contract" ;
72+ const abi = compilerMetadata . abi ;
73+
74+ // Extract function selector from transaction data (first 4 bytes)
75+ const functionSelector = txParam . data . slice ( 0 , 10 ) as `0x${string } `;
76+
77+ // Find matching function in ABI
78+ const functions = ( abi as readonly AbiItem [ ] ) . filter (
79+ ( item ) : item is AbiFunction => item . type === "function" ,
80+ ) ;
81+ let matchingFunction : AbiFunction | null = null ;
82+
83+ for ( const func of functions ) {
84+ const selector = toFunctionSelector ( func ) ;
85+ if ( selector === functionSelector ) {
86+ matchingFunction = func ;
87+ break ;
88+ }
89+ }
90+
91+ if ( ! matchingFunction ) {
92+ return null ;
93+ }
94+
95+ const functionName = matchingFunction . name ;
96+
97+ // Decode function data
98+ const decodedData = ( await decodeFunctionData ( {
99+ contract : getContract ( {
100+ ...contract ,
101+ abi : [ matchingFunction ] ,
102+ } ) ,
103+ data : txParam . data ,
104+ } ) ) as { args : readonly unknown [ ] } ;
105+
106+ // Create a clean object for display
107+ const functionArgs : Record < string , unknown > = { } ;
108+ if ( matchingFunction . inputs && decodedData . args ) {
109+ for ( let index = 0 ; index < matchingFunction . inputs . length ; index ++ ) {
110+ const input = matchingFunction . inputs [ index ] ;
111+ if ( input ) {
112+ functionArgs [ input . name || `arg${ index } ` ] = decodedData . args [ index ] ;
113+ }
114+ }
115+ }
116+
117+ return {
118+ contractName,
119+ functionName,
120+ functionArgs,
121+ } ;
122+ } catch ( error ) {
123+ console . error ( "Error decoding transaction:" , error ) ;
124+ return null ;
125+ }
126+ }
127+
12128export default async function TransactionPage ( {
13129 params,
14130} : {
@@ -51,6 +167,9 @@ export default async function TransactionPage({
51167 notFound ( ) ;
52168 }
53169
170+ // Decode transaction data on the server
171+ const decodedTransactionData = await decodeTransactionData ( transactionData ) ;
172+
54173 return (
55174 < div className = "space-y-6 p-2" >
56175 < TransactionDetailsUI
@@ -59,6 +178,7 @@ export default async function TransactionPage({
59178 project = { project }
60179 teamSlug = { team_slug }
61180 transaction = { transactionData }
181+ decodedTransactionData = { decodedTransactionData }
62182 />
63183 </ div >
64184 ) ;
0 commit comments