diff --git a/src/transaction/Logs.tsx b/src/transaction/Logs.tsx index 62196fa..875f268 100644 --- a/src/transaction/Logs.tsx +++ b/src/transaction/Logs.tsx @@ -3,7 +3,8 @@ import { Interface } from "@ethersproject/abi"; import ContentFrame from "../ContentFrame"; import LogEntry from "./LogEntry"; import { TransactionData } from "../types"; -import { Metadata } from "../useSourcify"; +import { Metadata, useMultipleMetadata } from "../useSourcify"; +import { SourcifySource } from "../url"; type LogsProps = { txData: TransactionData; @@ -11,22 +12,45 @@ type LogsProps = { }; const Logs: React.FC = ({ txData, metadata }) => { + const baseMetadatas = useMemo((): Record => { + if (!txData.to || metadata === undefined) { + return {}; + } + + const md: Record = {}; + md[txData.to] = metadata; + return md; + }, [txData.to, metadata]); + + const logAddresses = useMemo( + () => txData.confirmedData?.logs.map((l) => l.address) ?? [], + [txData] + ); + const metadatas = useMultipleMetadata( + baseMetadatas, + logAddresses, + 1, + SourcifySource.CUSTOM_SNAPSHOT_SERVER + ); const logDesc = useMemo(() => { if (!metadata || !txData) { return undefined; } - const abi = metadata.output.abi; - const intf = new Interface(abi as any); - return txData.confirmedData?.logs.map((l) => - l.address === txData.to - ? intf.parseLog({ - topics: l.topics, - data: l.data, - }) - : undefined - ); - }, [metadata, txData]); + return txData.confirmedData?.logs.map((l) => { + const mt = metadatas[l.address]; + if (!mt) { + return undefined; + } + + const abi = mt.output.abi; + const intf = new Interface(abi as any); + return intf.parseLog({ + topics: l.topics, + data: l.data, + }); + }); + }, [metadatas, metadata, txData]); return ( diff --git a/src/useSourcify.ts b/src/useSourcify.ts index cfac981..e1c8607 100644 --- a/src/useSourcify.ts +++ b/src/useSourcify.ts @@ -38,11 +38,38 @@ export type Metadata = { }; }; +export const fetchSourcifyMetadata = async ( + checksummedAddress: string, + chainId: number, + source: SourcifySource, + abortController: AbortController +): Promise => { + try { + const contractMetadataURL = sourcifyMetadata( + checksummedAddress, + chainId, + source + ); + const result = await fetch(contractMetadataURL, { + signal: abortController.signal, + }); + if (result.ok) { + const _metadata = await result.json(); + return _metadata; + } + + return null; + } catch (err) { + console.error(err); + return null; + } +}; + export const useSourcify = ( checksummedAddress: string | undefined, chainId: number | undefined, source: SourcifySource -) => { +): Metadata | null | undefined => { const [rawMetadata, setRawMetadata] = useState(); useEffect(() => { @@ -53,25 +80,13 @@ export const useSourcify = ( const abortController = new AbortController(); const fetchMetadata = async () => { - try { - const contractMetadataURL = sourcifyMetadata( - checksummedAddress, - chainId, - source - ); - const result = await fetch(contractMetadataURL, { - signal: abortController.signal, - }); - if (result.ok) { - const _metadata = await result.json(); - setRawMetadata(_metadata); - } else { - setRawMetadata(null); - } - } catch (err) { - console.error(err); - setRawMetadata(null); - } + const _metadata = await fetchSourcifyMetadata( + checksummedAddress, + chainId, + source, + abortController + ); + setRawMetadata(_metadata); }; fetchMetadata(); @@ -83,6 +98,54 @@ export const useSourcify = ( return rawMetadata; }; +export const useMultipleMetadata = ( + baseMetadatas: Record, + checksummedAddress: (string | undefined)[], + chainId: number | undefined, + source: SourcifySource +): Record => { + const [rawMetadata, setRawMetadata] = useState< + Record + >({}); + + useEffect(() => { + if (!checksummedAddress || chainId === undefined) { + return; + } + setRawMetadata({}); + + const abortController = new AbortController(); + const fetchMetadata = async (addresses: string[]) => { + const promises: Promise[] = []; + for (const addr of addresses) { + promises.push( + fetchSourcifyMetadata(addr, chainId, source, abortController) + ); + } + + const results = await Promise.all(promises); + const metadatas: Record = { ...baseMetadatas }; + for (let i = 0; i < results.length; i++) { + metadatas[addresses[i]] = results[i]; + } + setRawMetadata(metadatas); + }; + + const deduped = new Set( + checksummedAddress.filter( + (a): a is string => a !== undefined && baseMetadatas[a] === undefined + ) + ); + fetchMetadata(Array.from(deduped)); + + return () => { + abortController.abort(); + }; + }, [baseMetadatas, checksummedAddress, chainId, source]); + + return rawMetadata; +}; + export const useContract = ( checksummedAddress: string, networkId: number,