Initial implementation of log decoding
This commit is contained in:
parent
fa26567d0c
commit
2f19ab3ab3
|
@ -87,7 +87,7 @@ const Transaction: React.FC = () => {
|
||||||
/>
|
/>
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="/tx/:txhash/logs/" exact>
|
<Route path="/tx/:txhash/logs/" exact>
|
||||||
<Logs txData={txData} />
|
<Logs txData={txData} metadata={metadata} />
|
||||||
</Route>
|
</Route>
|
||||||
</Switch>
|
</Switch>
|
||||||
</SelectionContext.Provider>
|
</SelectionContext.Provider>
|
||||||
|
|
|
@ -7,12 +7,14 @@ type DecodedInputProps = {
|
||||||
|
|
||||||
const DecodedInput: React.FC<DecodedInputProps> = ({ txDesc }) => (
|
const DecodedInput: React.FC<DecodedInputProps> = ({ txDesc }) => (
|
||||||
<table className="border rounded">
|
<table className="border rounded">
|
||||||
<thead className="grid grid-cols-12 text-left gap-x-1 py-2 bg-gray-100">
|
<thead>
|
||||||
|
<tr className="grid grid-cols-12 text-left gap-x-1 py-2 bg-gray-100">
|
||||||
<th className="col-span-3 pl-1">
|
<th className="col-span-3 pl-1">
|
||||||
param <span className="text-gray-400 text-xs">(index)</span>
|
param <span className="text-gray-400 text-xs">(index)</span>
|
||||||
</th>
|
</th>
|
||||||
<th className="col-span-1">type</th>
|
<th className="col-span-1">type</th>
|
||||||
<th className="col-span-8 pr-1">value</th>
|
<th className="col-span-8 pr-1">value</th>
|
||||||
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody className="divide-y">
|
<tbody className="divide-y">
|
||||||
{txDesc.args.map((r, i) => (
|
{txDesc.args.map((r, i) => (
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
import React from "react";
|
||||||
|
import { LogDescription } from "@ethersproject/abi";
|
||||||
|
|
||||||
|
type DecodedLogProps = {
|
||||||
|
logDesc: LogDescription;
|
||||||
|
};
|
||||||
|
|
||||||
|
const DecodedLog: React.FC<DecodedLogProps> = ({ logDesc }) => (
|
||||||
|
<table className="border rounded">
|
||||||
|
<thead>
|
||||||
|
<tr className="grid grid-cols-12 text-left gap-x-1 py-2 bg-gray-100">
|
||||||
|
<th className="col-span-3 pl-1">
|
||||||
|
param <span className="text-gray-400 text-xs">(index)</span>
|
||||||
|
</th>
|
||||||
|
<th className="col-span-1">type</th>
|
||||||
|
<th className="col-span-8 pr-1">value</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody className="divide-y">
|
||||||
|
{logDesc.args.map((r, i) => (
|
||||||
|
<tr key={i} className="grid grid-cols-12 gap-x-1 py-2">
|
||||||
|
<td className="col-span-3 pl-1">
|
||||||
|
{logDesc.eventFragment.inputs[i].name}{" "}
|
||||||
|
<span className="text-gray-400 text-xs">({i})</span>
|
||||||
|
</td>
|
||||||
|
<td className="col-span-1">{logDesc.eventFragment.inputs[i].type}</td>
|
||||||
|
<td className="col-span-8 pr-1 font-code break-all">{r}</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default React.memo(DecodedLog);
|
|
@ -0,0 +1,23 @@
|
||||||
|
import React from "react";
|
||||||
|
import { EventFragment } from "@ethersproject/abi";
|
||||||
|
|
||||||
|
type DecodedLogSignatureProps = {
|
||||||
|
event: EventFragment;
|
||||||
|
};
|
||||||
|
|
||||||
|
const DecodedLogSignature: React.FC<DecodedLogSignatureProps> = ({ event }) => {
|
||||||
|
return (
|
||||||
|
<span>
|
||||||
|
<span className="text-blue-900 font-bold">{event.name}</span>(
|
||||||
|
{event.inputs.map((input, i) => (
|
||||||
|
<>
|
||||||
|
{i > 0 ? ", " : ""}
|
||||||
|
<span>{input.format("full")}</span>
|
||||||
|
</>
|
||||||
|
))}
|
||||||
|
){event.anonymous ? " anonymous" : ""}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default React.memo(DecodedLogSignature);
|
|
@ -0,0 +1,75 @@
|
||||||
|
import React from "react";
|
||||||
|
import { Log } from "@ethersproject/abstract-provider";
|
||||||
|
import { LogDescription } from "@ethersproject/abi";
|
||||||
|
import DecoratedAddressLink from "../components/DecoratedAddressLink";
|
||||||
|
import DecodedLog from "./DecodedLog";
|
||||||
|
import DecodedLogSignature from "./DecodedLogSignature";
|
||||||
|
import { TransactionData } from "../types";
|
||||||
|
|
||||||
|
type LogEntryProps = {
|
||||||
|
txData: TransactionData;
|
||||||
|
log: Log;
|
||||||
|
logDesc: LogDescription | null | undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
const LogEntry: React.FC<LogEntryProps> = ({ txData, log, logDesc }) => (
|
||||||
|
<div className="flex space-x-10 py-5">
|
||||||
|
<div>
|
||||||
|
<span className="rounded-full w-12 h-12 flex items-center justify-center bg-green-50 text-green-500">
|
||||||
|
{log.logIndex}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="w-full space-y-2">
|
||||||
|
<div className="grid grid-cols-12 gap-x-3 gap-y-5 text-sm">
|
||||||
|
<div className="font-bold text-right">Address</div>
|
||||||
|
<div className="col-span-11 mr-auto">
|
||||||
|
<DecoratedAddressLink
|
||||||
|
address={log.address}
|
||||||
|
miner={log.address === txData.confirmedData?.miner}
|
||||||
|
txFrom={log.address === txData.from}
|
||||||
|
txTo={log.address === txData.to}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{logDesc && (
|
||||||
|
<div className="grid grid-cols-12 gap-x-3 gap-y-5 text-sm">
|
||||||
|
<div className="text-right">Signature</div>
|
||||||
|
<div className="flex space-x-2 items-center col-span-11 font-mono">
|
||||||
|
<DecodedLogSignature event={logDesc.eventFragment} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{logDesc && (
|
||||||
|
<div className="grid grid-cols-12 gap-x-3 gap-y-5 text-sm">
|
||||||
|
<div className="text-right">Name</div>
|
||||||
|
<div className="flex space-x-2 items-center col-span-11">
|
||||||
|
<DecodedLog logDesc={logDesc} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{log.topics.map((t, i) => (
|
||||||
|
<div className="grid grid-cols-12 gap-x-3 gap-y-5 text-sm" key={i}>
|
||||||
|
<div className="text-right">{i === 0 && "Topics"}</div>
|
||||||
|
<div className="flex space-x-2 items-center col-span-11 font-mono">
|
||||||
|
<span className="rounded bg-gray-100 text-gray-500 px-2 py-1 text-xs">
|
||||||
|
{i}
|
||||||
|
</span>
|
||||||
|
<span>{t}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
<div className="grid grid-cols-12 gap-x-3 gap-y-5 text-sm">
|
||||||
|
<div className="text-right pt-2">Data</div>
|
||||||
|
<div className="col-span-11">
|
||||||
|
<textarea
|
||||||
|
className="w-full h-40 bg-gray-50 font-mono focus:outline-none border rounded p-2"
|
||||||
|
value={log.data}
|
||||||
|
readOnly
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default React.memo(LogEntry);
|
|
@ -1,63 +1,45 @@
|
||||||
import React from "react";
|
import React, { useMemo } from "react";
|
||||||
|
import { Interface } from "@ethersproject/abi";
|
||||||
import ContentFrame from "../ContentFrame";
|
import ContentFrame from "../ContentFrame";
|
||||||
import DecoratedAddressLink from "../components/DecoratedAddressLink";
|
import LogEntry from "./LogEntry";
|
||||||
import { TransactionData } from "../types";
|
import { TransactionData } from "../types";
|
||||||
|
import { Metadata } from "../useSourcify";
|
||||||
|
|
||||||
type LogsProps = {
|
type LogsProps = {
|
||||||
txData: TransactionData;
|
txData: TransactionData;
|
||||||
|
metadata: Metadata | null | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
const Logs: React.FC<LogsProps> = ({ txData }) => (
|
const Logs: React.FC<LogsProps> = ({ txData, metadata }) => {
|
||||||
|
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) =>
|
||||||
|
intf.parseLog({
|
||||||
|
topics: l.topics,
|
||||||
|
data: l.data,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}, [metadata, txData]);
|
||||||
|
|
||||||
|
return (
|
||||||
<ContentFrame tabs>
|
<ContentFrame tabs>
|
||||||
<div className="text-sm py-4">Transaction Receipt Event Logs</div>
|
<div className="text-sm py-4">Transaction Receipt Event Logs</div>
|
||||||
{txData.confirmedData &&
|
{txData.confirmedData &&
|
||||||
txData.confirmedData.logs.map((l, i) => (
|
txData.confirmedData.logs.map((l, i) => (
|
||||||
<div className="flex space-x-10 py-5" key={i}>
|
<LogEntry
|
||||||
<div>
|
|
||||||
<span className="rounded-full w-12 h-12 flex items-center justify-center bg-green-50 text-green-500">
|
|
||||||
{l.logIndex}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div className="w-full space-y-2">
|
|
||||||
<div className="grid grid-cols-12 gap-x-3 gap-y-5 text-sm">
|
|
||||||
<div className="font-bold text-right">Address</div>
|
|
||||||
<div className="col-span-11 mr-auto">
|
|
||||||
<DecoratedAddressLink
|
|
||||||
address={l.address}
|
|
||||||
miner={l.address === txData.confirmedData?.miner}
|
|
||||||
txFrom={l.address === txData.from}
|
|
||||||
txTo={l.address === txData.to}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{l.topics.map((t, i) => (
|
|
||||||
<div
|
|
||||||
className="grid grid-cols-12 gap-x-3 gap-y-5 text-sm"
|
|
||||||
key={i}
|
key={i}
|
||||||
>
|
txData={txData}
|
||||||
<div className="text-right">{i === 0 && "Topics"}</div>
|
log={l}
|
||||||
<div className="flex space-x-2 items-center col-span-11 font-mono">
|
logDesc={logDesc && logDesc[i]}
|
||||||
<span className="rounded bg-gray-100 text-gray-500 px-2 py-1 text-xs">
|
|
||||||
{i}
|
|
||||||
</span>
|
|
||||||
<span>{t}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
<div className="grid grid-cols-12 gap-x-3 gap-y-5 text-sm">
|
|
||||||
<div className="text-right pt-2">Data</div>
|
|
||||||
<div className="col-span-11">
|
|
||||||
<textarea
|
|
||||||
className="w-full h-20 bg-gray-50 font-mono focus:outline-none border rounded p-2"
|
|
||||||
value={l.data}
|
|
||||||
readOnly
|
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
))}
|
||||||
</ContentFrame>
|
</ContentFrame>
|
||||||
);
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default React.memo(Logs);
|
export default React.memo(Logs);
|
||||||
|
|
|
@ -133,7 +133,6 @@ export const useTransactionDescription = (
|
||||||
|
|
||||||
const abi = metadata.output.abi;
|
const abi = metadata.output.abi;
|
||||||
const intf = new Interface(abi as any);
|
const intf = new Interface(abi as any);
|
||||||
console.log(intf);
|
|
||||||
return intf.parseTransaction({
|
return intf.parseTransaction({
|
||||||
data: txData.data,
|
data: txData.data,
|
||||||
value: txData.value,
|
value: txData.value,
|
||||||
|
|
Loading…
Reference in New Issue