Skip to content

Commit 2570baa

Browse files
Refactor transaction decoding to server-side with improved error handling
Co-authored-by: joaquim.verges <[email protected]>
1 parent d4f3654 commit 2570baa

File tree

3 files changed

+170
-201
lines changed

3 files changed

+170
-201
lines changed

apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/transactions/tx/[id]/decoded-transaction-params.tsx

Lines changed: 0 additions & 188 deletions
This file was deleted.

apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/transactions/tx/[id]/page.tsx

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,130 @@
11
import { loginRedirect } from "@app/login/loginRedirect";
22
import { 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";
38
import { getAuthToken } from "@/api/auth-token";
49
import { getProject } from "@/api/projects";
510
import { getClientThirdwebClient } from "@/constants/thirdweb-client.client";
11+
import { serverThirdwebClient } from "@/constants/thirdweb-client.server";
612
import {
713
getSingleTransaction,
814
getTransactionActivityLogs,
915
} from "../../lib/analytics";
16+
import type { Transaction } from "../../analytics/tx-table/types";
1017
import { 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+
12128
export 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

Comments
 (0)