Skip to content

Commit d3a950e

Browse files
committed
update
1 parent 785270b commit d3a950e

3 files changed

Lines changed: 111 additions & 81 deletions

File tree

app/components/DecodedTransactionInput.jsx

Lines changed: 27 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,17 @@
1-
import { FileCode, ChevronDown, ChevronUp } from "lucide-react"
21
import { extractFunctionSelector, findMatchingFunction, decodeInputData } from "../lib/AbiDecoder"
2+
import { LogHeader, DecodedParams } from "./TransactionsLogs"
33
import { useState } from "react"
44

5+
const COPY_FEEDBACK_DURATION = 500
6+
57
export default function DecodedTransactionInput({ inputData, abi, inline = false }) {
6-
const [expanded, setExpanded] = useState(false)
8+
const [expanded, setExpanded] = useState(true)
9+
10+
const copyToClipboard = (text, field) => {
11+
navigator.clipboard.writeText(text)
12+
setCopiedField(field)
13+
setTimeout(() => setCopiedField(null), 500)
14+
}
715

816
if (!inputData || inputData === "0x") {
917
return null
@@ -45,53 +53,24 @@ export default function DecodedTransactionInput({ inputData, abi, inline = false
4553
}
4654

4755
return (
48-
<div className="card">
49-
<div className="card-content space-y-3 mt-6">
50-
<div className="flex items-center gap-2 flex-wrap">
51-
<code className="text-sm font-semibold text-[oklch(0.65_0.25_151)]">{matchingFunction.name}</code>
52-
<span className="badge badge-secondary text-xs">{matchingFunction.stateMutability || "nonpayable"}</span>
53-
</div>
54-
55-
{decodedParams.length > 0 && (
56-
<div className="bg-muted/30 rounded-lg p-3 font-mono text-sm">
57-
<div className="flex items-start justify-between gap-2">
58-
<code className="break-all text-foreground">
59-
{matchingFunction.name}(
60-
{!expanded && decodedParams.length > 2 ? (
61-
<span className="text-muted-foreground">{decodedParams.length} params</span>
62-
) : (
63-
decodedParams.map((param, index) => (
64-
<span key={index}>
65-
{index > 0 && ", "}
66-
<span className="text-muted-foreground">{param.name}:</span>{" "}
67-
<span className="text-[oklch(0.65_0.25_151)]">
68-
{expanded ? param.value : formatValue(param.value, param.type)}
69-
</span>
70-
</span>
71-
))
72-
)}
73-
)
74-
</code>
75-
{(decodedParams.length > 2 || decodedParams.some((p) => p.value.length > 16)) && (
76-
<button
77-
onClick={() => setExpanded(!expanded)}
78-
className="text-muted-foreground hover:text-foreground flex-shrink-0"
79-
>
80-
{expanded ? <ChevronUp className="w-4 h-4" /> : <ChevronDown className="w-4 h-4" />}
81-
</button>
82-
)}
83-
</div>
56+
<div className="">
57+
<div key={0} className="border border-border rounded-lg overflow-hidden">
58+
<LogHeader
59+
index={0}
60+
decodedEvent={{name: matchingFunction?.name}}
61+
isExpanded={expanded}
62+
onToggle={() => setExpanded(!expanded)}
63+
pillText={matchingFunction.stateMutability || "nonpayable"}
64+
/>
8465

85-
{expanded && (
86-
<div className="mt-3 pt-3 border-t border-border space-y-1">
87-
{decodedParams.map((param, index) => (
88-
<div key={index} className="flex gap-2 text-xs">
89-
<span className="text-muted-foreground w-24 flex-shrink-0">{param.type}</span>
90-
<span className="text-muted-foreground">{param.name}</span>
91-
</div>
92-
))}
93-
</div>
94-
)}
66+
{expanded && (
67+
<div className="p-4 space-y-4 bg-background">
68+
<DecodedParams
69+
params={decodedParams}
70+
onCopy={copyToClipboard}
71+
copiedField={""}
72+
logIndex={0}
73+
/>
9574
</div>
9675
)}
9776
</div>

app/components/TransactionsLogs.jsx

Lines changed: 72 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { useState, useCallback } from "react"
2-
import { Copy, ChevronDown, ChevronUp } from "lucide-react"
2+
import { Copy, ChevronDown, ChevronUp, AlertCircle } from "lucide-react"
33
import { Interface } from "ethers"
4+
import { truncateAddress } from "../lib/Formatters"
45

56
const COPY_FEEDBACK_DURATION = 500
67

@@ -22,7 +23,6 @@ function decodeEventLog(log, abi) {
2223
const decodedParams = parsedLog.fragment.inputs.map((input, idx) => {
2324
let value = parsedLog.args[idx]
2425

25-
// Convert BigInt and objects to string
2626
if (typeof value === "bigint") {
2727
value = value.toString()
2828
} else if (value?.toString) {
@@ -47,18 +47,22 @@ function decodeEventLog(log, abi) {
4747
}
4848
}
4949

50-
function LogHeader({ index, decodedEvent, address, isExpanded, onToggle }) {
50+
export function LogHeader({ index, decodedEvent, isExpanded, onToggle, pillText = "Log %index%" }) {
51+
52+
const pillTextUpdated = pillText.replace("%index%", index);
53+
5154
return (
5255
<div
5356
className="flex items-center justify-between p-4 bg-muted/30 cursor-pointer hover:bg-muted/50 transition-colors"
5457
onClick={onToggle}
5558
>
56-
<div className="flex items-center gap-3">
59+
<div className="flex items-center gap-3 flex-wrap">
5760
<span className="text-xs font-mono bg-[oklch(0.65_0.25_151)]/10 text-[oklch(0.65_0.25_151)] px-2 py-1 rounded">
58-
Log {index}
61+
{pillTextUpdated}
5962
</span>
63+
6064
{decodedEvent && <span className="text-sm font-semibold text-foreground">{decodedEvent.name}</span>}
61-
<span className="text-xs text-muted-foreground font-mono">{address}</span>
65+
6266
</div>
6367
<button className="btn btn-ghost btn-icon">
6468
{isExpanded ? <ChevronUp className="w-4 h-4" /> : <ChevronDown className="w-4 h-4" />}
@@ -67,7 +71,7 @@ function LogHeader({ index, decodedEvent, address, isExpanded, onToggle }) {
6771
)
6872
}
6973

70-
function DecodedParams({ params, onCopy, copiedField, logIndex }) {
74+
export function DecodedParams({ params, onCopy, copiedField, logIndex }) {
7175
return (
7276
<div className="space-y-2">
7377
<h4 className="text-sm font-medium text-muted-foreground">Decoded Parameters</h4>
@@ -111,19 +115,33 @@ function CopyButton({ value, field, copiedField, onCopy }) {
111115
)
112116
}
113117

114-
function RawLogData({ log, logIndex, copiedField, onCopy }) {
118+
function RawLogData({ log, logIndex, copiedField, isDifferentContract, onCopy }) {
115119
return (
116120
<div className="space-y-3">
117-
{/* Address */}
121+
122+
{/*}
123+
{log.data && log.data !== "0x" && (
124+
<div className="space-y-2">
125+
<span className="text-sm font-medium text-muted-foreground">Data</span>
126+
<div className="bg-muted/20 rounded p-3 border border-border">
127+
<code className="text-xs font-mono text-foreground break-all whitespace-pre-wrap">{log.data}</code>
128+
</div>
129+
</div>
130+
)}
131+
*/}
132+
118133
<div className="flex items-start justify-between">
119134
<span className="text-sm font-medium text-muted-foreground">Address</span>
120135
<div className="flex items-center gap-2">
136+
{isDifferentContract && (
137+
<span className="text-xs bg-amber-500/10 text-amber-600 px-2 py-0.5 rounded">External Contract</span>
138+
)}
121139
<code className="text-sm font-mono">{log.address}</code>
122140
<CopyButton value={log.address} field={`log-${logIndex}-address`} copiedField={copiedField} onCopy={onCopy} />
123141
</div>
124142
</div>
125143

126-
{/* Topics */}
144+
{/*
127145
{log.topics?.length > 0 && (
128146
<div className="space-y-2">
129147
<span className="text-sm font-medium text-muted-foreground">Topics</span>
@@ -143,21 +161,33 @@ function RawLogData({ log, logIndex, copiedField, onCopy }) {
143161
))}
144162
</div>
145163
)}
164+
*/}
146165

147-
{/* Data */}
148-
{log.data && log.data !== "0x" && (
149-
<div className="space-y-2">
150-
<span className="text-sm font-medium text-muted-foreground">Data</span>
151-
<div className="bg-muted/20 rounded p-3 border border-border">
152-
<code className="text-xs font-mono text-foreground break-all whitespace-pre-wrap">{log.data}</code>
153-
</div>
154-
</div>
155-
)}
166+
156167
</div>
157168
)
158169
}
159170

160-
export default function TransactionLogs({ logs, abi }) {
171+
function MissingABINotice({ address, onNavigate }) {
172+
return (
173+
<div className="flex items-start gap-3 p-3 bg-amber-500/10 border border-amber-500/20 rounded">
174+
<AlertCircle className="w-4 h-4 text-amber-600 mt-0.5 flex-shrink-0" />
175+
<div className="text-sm">
176+
<p className="text-amber-700">
177+
This event was emitted by an external contract. To decode its parameters, add the ABI for this contract.
178+
</p>
179+
<button
180+
className="mt-2 text-[oklch(0.65_0.25_151)] hover:underline font-medium"
181+
onClick={() => onNavigate?.("address", { address })}
182+
>
183+
Go to contract {truncateAddress(address)} to add ABI
184+
</button>
185+
</div>
186+
</div>
187+
)
188+
}
189+
190+
export default function TransactionLogs({ logs, abi, getContractABI, transactionTo, onNavigate }) {
161191
const [expandedLogs, setExpandedLogs] = useState({})
162192
const [copiedField, setCopiedField] = useState(null)
163193

@@ -173,6 +203,8 @@ export default function TransactionLogs({ logs, abi }) {
173203

174204
if (!logs?.length) return null
175205

206+
const normalizedTxTo = transactionTo?.toLowerCase()
207+
176208
return (
177209
<div className="card p-6">
178210
<div className="mb-4">
@@ -185,20 +217,39 @@ export default function TransactionLogs({ logs, abi }) {
185217
<div className="space-y-3">
186218
{logs.map((log, index) => {
187219
const isExpanded = expandedLogs[index]
188-
const decodedEvent = decodeEventLog(log, abi)
220+
const logAddress = log.address?.toLowerCase()
221+
222+
const isDifferentContract = normalizedTxTo && logAddress !== normalizedTxTo
223+
224+
let abiForLog = abi
225+
if (isDifferentContract && getContractABI) {
226+
const externalABI = getContractABI(log.address)
227+
if (externalABI) {
228+
abiForLog = externalABI
229+
}
230+
}
231+
232+
const decodedEvent = decodeEventLog(log, abiForLog)
233+
const needsExternalABI = isDifferentContract && !decodedEvent && getContractABI
189234

190235
return (
191236
<div key={index} className="border border-border rounded-lg overflow-hidden">
237+
192238
<LogHeader
193239
index={index}
194240
decodedEvent={decodedEvent}
195241
address={log.address}
196242
isExpanded={isExpanded}
197243
onToggle={() => toggleLog(index)}
244+
isDifferentContract={isDifferentContract}
245+
onNavigate={onNavigate}
198246
/>
199247

200248
{isExpanded && (
201249
<div className="p-4 space-y-4 bg-background">
250+
<RawLogData isDifferentContract={isDifferentContract} log={log} logIndex={index} copiedField={copiedField} onCopy={copyToClipboard} />
251+
{needsExternalABI && <MissingABINotice address={log.address} onNavigate={onNavigate} />}
252+
202253
{decodedEvent && (
203254
<DecodedParams
204255
params={decodedEvent.params}
@@ -207,7 +258,6 @@ export default function TransactionLogs({ logs, abi }) {
207258
logIndex={index}
208259
/>
209260
)}
210-
<RawLogData log={log} logIndex={index} copiedField={copiedField} onCopy={copyToClipboard} />
211261
</div>
212262
)}
213263
</div>

app/pages/TransactionDetailPage.jsx

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,10 @@ export default function TransactionDetailPage({ hash }) {
204204

205205
{/* Decoded Input */}
206206
{contractABI && transaction.input && transaction.input !== "0x" && (
207-
<DecodedTransactionInput inputData={transaction.input} abi={contractABI} />
207+
<div>
208+
<h3 className="text-lg font-semibold mb-4">Decoded Input Data</h3>
209+
<DecodedTransactionInput inputData={transaction.input} abi={contractABI} />
210+
</div>
208211
)}
209212
</div>
210213

@@ -254,21 +257,19 @@ export default function TransactionDetailPage({ hash }) {
254257
)
255258

256259
const EventsContent = () => (
257-
<div className="space-y-6">
258-
{transaction.logs && transaction.logs.length > 0 ? (
259-
<TransactionLogs logs={transaction.logs} abi={contractABI} />
260-
) : (
261-
<div className="card p-12 text-center">
262-
<p className="text-muted-foreground">No events emitted in this transaction</p>
263-
</div>
264-
)}
265-
</div>
260+
<TransactionLogs
261+
logs={transaction.logs}
262+
abi={contractABI}
263+
getContractABI={getContractABI}
264+
transactionTo={transaction.to}
265+
onNavigate={navigate}
266+
/>
266267
)
267268

268269
const tabs = [
269270
{ id: "overview", label: "Overview", content: <OverviewContent /> },
270271
{ id: "input-data", label: "Input Data", content: <InputDataContent /> },
271-
{ id: "events", label: "Events", content: <EventsContent /> },
272+
{ id: "events", label: `Events (${transaction?.logs?.length || 0})`, content: <EventsContent /> },
272273
]
273274

274275
return (

0 commit comments

Comments
 (0)