11import { useState , useCallback } from "react"
2- import { Copy , ChevronDown , ChevronUp } from "lucide-react"
2+ import { Copy , ChevronDown , ChevronUp , AlertCircle } from "lucide-react"
33import { Interface } from "ethers"
4+ import { truncateAddress } from "../lib/Formatters"
45
56const 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 >
0 commit comments