First working prototype with call tree
This commit is contained in:
parent
75612fc3cc
commit
1b9f26a3f0
|
@ -25,6 +25,12 @@ const Logs = React.lazy(
|
|||
/* webpackChunkName: "txlogs", webpackPrefetch: true */ "./transaction/Logs"
|
||||
)
|
||||
);
|
||||
const Trace = React.lazy(
|
||||
() =>
|
||||
import(
|
||||
/* webpackChunkName: "txtrace", webpackPrefetch: true */ "./transaction/Trace"
|
||||
)
|
||||
);
|
||||
|
||||
type TransactionParams = {
|
||||
txhash: string;
|
||||
|
@ -87,6 +93,7 @@ const Transaction: React.FC = () => {
|
|||
{txData && ` (${txData.confirmedData?.logs?.length ?? 0})`}
|
||||
</NavTab>
|
||||
)}
|
||||
<NavTab href={`/tx/${txhash}/trace`}>Trace</NavTab>
|
||||
</Tab.List>
|
||||
</Tab.Group>
|
||||
<React.Suspense fallback={null}>
|
||||
|
@ -105,6 +112,9 @@ const Transaction: React.FC = () => {
|
|||
<Route path="/tx/:txhash/logs/" exact>
|
||||
<Logs txData={txData} metadata={metadata} />
|
||||
</Route>
|
||||
<Route path="/tx/:txhash/trace" exact>
|
||||
<Trace txData={txData} />
|
||||
</Route>
|
||||
</Switch>
|
||||
</React.Suspense>
|
||||
</SelectionContext.Provider>
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
import React, { useContext } from "react";
|
||||
import AddressHighlighter from "../components/AddressHighlighter";
|
||||
import DecoratedAddressLink from "../components/DecoratedAddressLink";
|
||||
import ContentFrame from "../ContentFrame";
|
||||
import { TransactionData } from "../types";
|
||||
import { useTraceTransaction } from "../useErigonHooks";
|
||||
import { RuntimeContext } from "../useRuntime";
|
||||
import TraceItem from "./TraceItem";
|
||||
|
||||
type TraceProps = {
|
||||
txData: TransactionData;
|
||||
};
|
||||
|
||||
const Trace: React.FC<TraceProps> = ({ txData }) => {
|
||||
const { provider } = useContext(RuntimeContext);
|
||||
const traces = useTraceTransaction(provider, txData.transactionHash);
|
||||
|
||||
return (
|
||||
<ContentFrame tabs>
|
||||
<div className="mt-4 mb-5 space-y-3 font-code text-sm flex flex-col items-start">
|
||||
<div>
|
||||
<AddressHighlighter address={txData.from}>
|
||||
<DecoratedAddressLink
|
||||
address={txData.from}
|
||||
miner={txData.from === txData.confirmedData?.miner}
|
||||
txFrom
|
||||
txTo={txData.from === txData.to}
|
||||
/>
|
||||
</AddressHighlighter>
|
||||
</div>
|
||||
{traces?.map((t, i, a) => (
|
||||
<TraceItem key={i} t={t} txData={txData} last={i === a.length - 1} />
|
||||
))}
|
||||
</div>
|
||||
</ContentFrame>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(Trace);
|
|
@ -0,0 +1,68 @@
|
|||
import React from "react";
|
||||
import AddressHighlighter from "../components/AddressHighlighter";
|
||||
import DecoratedAddressLink from "../components/DecoratedAddressLink";
|
||||
import { TransactionData } from "../types";
|
||||
import { rawInputTo4Bytes } from "../use4Bytes";
|
||||
import { TraceGroup } from "../useErigonHooks";
|
||||
|
||||
type TraceItemProps = {
|
||||
t: TraceGroup;
|
||||
txData: TransactionData;
|
||||
last: boolean;
|
||||
};
|
||||
|
||||
const TraceItem: React.FC<TraceItemProps> = ({ t, txData, last }) => {
|
||||
const raw4Bytes = rawInputTo4Bytes(t.input);
|
||||
return (
|
||||
<>
|
||||
<div className="flex">
|
||||
<div className="relative w-5">
|
||||
<div className="absolute border-l border-b w-full h-full transform -translate-y-1/2"></div>
|
||||
{!last && (
|
||||
<div className="absolute left-0 border-l w-full h-full transform translate-y-1/2"></div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-baseline border rounded px-1 py-px">
|
||||
<span className="mr-2">{t.depth}</span>
|
||||
<span className="text-xs text-gray-400 lowercase">{t.type}</span>
|
||||
<span>
|
||||
<AddressHighlighter address={t.to}>
|
||||
<DecoratedAddressLink
|
||||
address={t.to}
|
||||
miner={t.to === txData.confirmedData?.miner}
|
||||
txFrom={t.to === txData.from}
|
||||
txTo={t.to === txData.to}
|
||||
/>
|
||||
</AddressHighlighter>
|
||||
</span>
|
||||
<span>.</span>
|
||||
<span>{raw4Bytes}</span>
|
||||
<span>(</span>
|
||||
{t.input.length > 10 && (
|
||||
<span className="whitespace-nowrap">
|
||||
input=[{t.input.slice(10)}]
|
||||
</span>
|
||||
)}
|
||||
<span>)</span>
|
||||
</div>
|
||||
</div>
|
||||
{t.children && (
|
||||
<div className="flex">
|
||||
<div className={`w-10 ${last ? "" : "border-l"}`}></div>
|
||||
<div className="space-y-3">
|
||||
{t.children.map((tc, i, a) => (
|
||||
<TraceItem
|
||||
key={i}
|
||||
t={tc}
|
||||
txData={txData}
|
||||
last={i === a.length - 1}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(TraceItem);
|
|
@ -308,3 +308,89 @@ export const useInternalOperations = (
|
|||
|
||||
return intTransfers;
|
||||
};
|
||||
|
||||
export type TraceEntry = {
|
||||
type: string;
|
||||
depth: number;
|
||||
from: string;
|
||||
to: string;
|
||||
value: BigNumber;
|
||||
input: string;
|
||||
};
|
||||
|
||||
export type TraceGroup = TraceEntry & {
|
||||
children: TraceGroup[] | null;
|
||||
};
|
||||
|
||||
export const useTraceTransaction = (
|
||||
provider: JsonRpcProvider | undefined,
|
||||
txHash: string
|
||||
): TraceGroup[] | undefined => {
|
||||
const [traceGroups, setTraceGroups] = useState<TraceGroup[] | undefined>();
|
||||
|
||||
useEffect(() => {
|
||||
if (!provider) {
|
||||
setTraceGroups(undefined);
|
||||
return;
|
||||
}
|
||||
|
||||
const traceTx = async () => {
|
||||
const results = await provider.send("ots_traceTransaction", [txHash]);
|
||||
|
||||
// Implement better formatter
|
||||
for (let i = 0; i < results.length; i++) {
|
||||
results[i].from = provider.formatter.address(results[i].from);
|
||||
results[i].to = provider.formatter.address(results[i].to);
|
||||
}
|
||||
|
||||
// Build trace tree
|
||||
const buildTraceTree = (
|
||||
flatList: TraceEntry[],
|
||||
depth: number = 0
|
||||
): TraceGroup[] => {
|
||||
const entries: TraceGroup[] = [];
|
||||
|
||||
let children: TraceEntry[] | null = null;
|
||||
for (let i = 0; i < flatList.length; i++) {
|
||||
if (flatList[i].depth === depth) {
|
||||
if (children !== null) {
|
||||
const childrenTree = buildTraceTree(children, depth + 1);
|
||||
const prev = entries.pop();
|
||||
if (prev) {
|
||||
prev.children = childrenTree;
|
||||
entries.push(prev);
|
||||
}
|
||||
}
|
||||
|
||||
entries.push({
|
||||
...flatList[i],
|
||||
children: null,
|
||||
});
|
||||
children = null;
|
||||
} else {
|
||||
if (children === null) {
|
||||
children = [];
|
||||
}
|
||||
children.push(flatList[i]);
|
||||
}
|
||||
}
|
||||
if (children !== null) {
|
||||
const childrenTree = buildTraceTree(children, depth + 1);
|
||||
const prev = entries.pop();
|
||||
if (prev) {
|
||||
prev.children = childrenTree;
|
||||
entries.push(prev);
|
||||
}
|
||||
}
|
||||
|
||||
return entries;
|
||||
};
|
||||
|
||||
const traceTree = buildTraceTree(results);
|
||||
setTraceGroups(traceTree);
|
||||
};
|
||||
traceTx();
|
||||
}, [provider, txHash]);
|
||||
|
||||
return traceGroups;
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue