Explorer: add Raw instruction data to parsed instructions (#13855)
This commit allows users to click the "raw" button on transaction instructions and fetch the raw hex or base64 representations of the instruction. Adds a fetch action to the click event of the "raw" button on the instruction UI. adds a fetchRawTransaction hook that is passed down to the instruction UI components. Adds addition `rawFetchTrigger` and `raw` props passed to the instruction card components.
This commit is contained in:
parent
40dd46680e
commit
75e3f5cd48
|
@ -1,4 +1,4 @@
|
|||
import React from "react";
|
||||
import React, { useContext } from "react";
|
||||
import {
|
||||
TransactionInstruction,
|
||||
SignatureResult,
|
||||
|
@ -6,6 +6,11 @@ import {
|
|||
} from "@solana/web3.js";
|
||||
import { RawDetails } from "./RawDetails";
|
||||
import { RawParsedDetails } from "./RawParsedDetails";
|
||||
import { SignatureContext } from "../../pages/TransactionDetailsPage";
|
||||
import {
|
||||
useTransactionDetails,
|
||||
useFetchRawTransaction,
|
||||
} from "providers/transactions/details";
|
||||
|
||||
type InstructionProps = {
|
||||
title: string;
|
||||
|
@ -26,6 +31,22 @@ export function InstructionCard({
|
|||
}: InstructionProps) {
|
||||
const [resultClass] = ixResult(result, index);
|
||||
const [showRaw, setShowRaw] = React.useState(defaultRaw || false);
|
||||
const signature = useContext(SignatureContext);
|
||||
const details = useTransactionDetails(signature);
|
||||
let raw: TransactionInstruction | undefined = undefined;
|
||||
if (details) {
|
||||
raw = details?.data?.raw?.transaction.instructions[index];
|
||||
}
|
||||
const fetchRaw = useFetchRawTransaction();
|
||||
const fetchRawTrigger = () => fetchRaw(signature);
|
||||
|
||||
const rawClickHandler = () => {
|
||||
if (!defaultRaw && !showRaw && !raw) {
|
||||
fetchRawTrigger();
|
||||
}
|
||||
|
||||
return setShowRaw((r) => !r);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="card">
|
||||
|
@ -42,7 +63,7 @@ export function InstructionCard({
|
|||
className={`btn btn-sm d-flex ${
|
||||
showRaw ? "btn-black active" : "btn-white"
|
||||
}`}
|
||||
onClick={() => setShowRaw((r) => !r)}
|
||||
onClick={rawClickHandler}
|
||||
>
|
||||
<span className="fe fe-code mr-1"></span>
|
||||
Raw
|
||||
|
@ -53,7 +74,7 @@ export function InstructionCard({
|
|||
<tbody className="list">
|
||||
{showRaw ? (
|
||||
"parsed" in ix ? (
|
||||
<RawParsedDetails ix={ix} />
|
||||
<RawParsedDetails ix={ix} raw={raw} />
|
||||
) : (
|
||||
<RawDetails ix={ix} />
|
||||
)
|
||||
|
|
|
@ -1,8 +1,21 @@
|
|||
import React from "react";
|
||||
import { ParsedInstruction } from "@solana/web3.js";
|
||||
import { ParsedInstruction, TransactionInstruction } from "@solana/web3.js";
|
||||
import { Address } from "components/common/Address";
|
||||
import { wrap } from "utils";
|
||||
|
||||
type RawParsedDetailsProps = {
|
||||
ix: ParsedInstruction;
|
||||
raw?: TransactionInstruction;
|
||||
};
|
||||
|
||||
export function RawParsedDetails({ ix, raw }: RawParsedDetailsProps) {
|
||||
let hex = null;
|
||||
let b64 = null;
|
||||
if (raw) {
|
||||
hex = wrap(raw.data.toString("hex"), 50);
|
||||
b64 = wrap(raw.data.toString("base64"), 50);
|
||||
}
|
||||
|
||||
export function RawParsedDetails({ ix }: { ix: ParsedInstruction }) {
|
||||
return (
|
||||
<>
|
||||
<tr>
|
||||
|
@ -12,6 +25,24 @@ export function RawParsedDetails({ ix }: { ix: ParsedInstruction }) {
|
|||
</td>
|
||||
</tr>
|
||||
|
||||
{hex ? (
|
||||
<tr>
|
||||
<td>Instruction Data (Hex)</td>
|
||||
<td className="text-lg-right">
|
||||
<pre className="d-inline-block text-left mb-0">{hex}</pre>
|
||||
</td>
|
||||
</tr>
|
||||
) : null}
|
||||
|
||||
{b64 ? (
|
||||
<tr>
|
||||
<td>Instruction Data (Base64)</td>
|
||||
<td className="text-lg-right">
|
||||
<pre className="d-inline-block text-left mb-0">{b64}</pre>
|
||||
</td>
|
||||
</tr>
|
||||
) : null}
|
||||
|
||||
<tr>
|
||||
<td>Instruction Data (JSON)</td>
|
||||
<td className="text-lg-right">
|
||||
|
|
|
@ -41,6 +41,8 @@ type SignatureProps = {
|
|||
signature: TransactionSignature;
|
||||
};
|
||||
|
||||
export const SignatureContext = React.createContext("");
|
||||
|
||||
enum AutoRefresh {
|
||||
Active,
|
||||
Inactive,
|
||||
|
@ -107,7 +109,9 @@ export function TransactionDetailsPage({ signature: raw }: SignatureProps) {
|
|||
<>
|
||||
<StatusCard signature={signature} autoRefresh={autoRefresh} />
|
||||
<AccountsCard signature={signature} autoRefresh={autoRefresh} />
|
||||
<InstructionsSection signature={signature} />
|
||||
<SignatureContext.Provider value={signature}>
|
||||
<InstructionsSection signature={signature} />
|
||||
</SignatureContext.Provider>
|
||||
<ProgramLogSection signature={signature} />
|
||||
</>
|
||||
)}
|
||||
|
@ -400,6 +404,8 @@ function InstructionsSection({ signature }: SignatureProps) {
|
|||
|
||||
if (!status?.data?.info || !details?.data?.transaction) return null;
|
||||
|
||||
const raw = details.data.raw?.transaction;
|
||||
|
||||
const { transaction } = details.data.transaction;
|
||||
if (transaction.message.instructions.length === 0) {
|
||||
return <ErrorCard retry={refreshDetails} text="No instructions found" />;
|
||||
|
@ -460,7 +466,12 @@ function InstructionsSection({ signature }: SignatureProps) {
|
|||
/>
|
||||
);
|
||||
default:
|
||||
const props = { ix: next, result, index };
|
||||
const props = {
|
||||
ix: next,
|
||||
result,
|
||||
index,
|
||||
raw: raw?.instructions[index],
|
||||
};
|
||||
return <UnknownDetailsCard key={index} {...props} />;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ import {
|
|||
Connection,
|
||||
TransactionSignature,
|
||||
ParsedConfirmedTransaction,
|
||||
ConfirmedTransaction,
|
||||
} from "@solana/web3.js";
|
||||
import { useCluster, Cluster } from "../cluster";
|
||||
import * as Cache from "providers/cache";
|
||||
|
@ -11,6 +12,7 @@ import { reportError } from "utils/sentry";
|
|||
|
||||
export interface Details {
|
||||
transaction?: ParsedConfirmedTransaction | null;
|
||||
raw?: ConfirmedTransaction | null;
|
||||
}
|
||||
|
||||
type State = Cache.State<Details>;
|
||||
|
@ -119,3 +121,56 @@ export function useTransactionDetailsCache(): TransactionDetailsCache {
|
|||
|
||||
return context.entries;
|
||||
}
|
||||
|
||||
async function fetchRawTransaction(
|
||||
dispatch: Dispatch,
|
||||
signature: TransactionSignature,
|
||||
cluster: Cluster,
|
||||
url: string
|
||||
) {
|
||||
dispatch({
|
||||
type: ActionType.Update,
|
||||
status: FetchStatus.Fetching,
|
||||
key: signature,
|
||||
url,
|
||||
});
|
||||
|
||||
let fetchStatus;
|
||||
let transaction;
|
||||
try {
|
||||
transaction = await new Connection(url).getConfirmedTransaction(signature);
|
||||
fetchStatus = FetchStatus.Fetched;
|
||||
} catch (error) {
|
||||
if (cluster !== Cluster.Custom) {
|
||||
reportError(error, { url });
|
||||
}
|
||||
fetchStatus = FetchStatus.FetchFailed;
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: ActionType.Update,
|
||||
status: fetchStatus,
|
||||
key: signature,
|
||||
data: {
|
||||
raw: transaction,
|
||||
},
|
||||
url,
|
||||
});
|
||||
}
|
||||
|
||||
export function useFetchRawTransaction() {
|
||||
const dispatch = React.useContext(DispatchContext);
|
||||
if (!dispatch) {
|
||||
throw new Error(
|
||||
`useFetchRawTransaaction must be used within a TransactionsProvider`
|
||||
);
|
||||
}
|
||||
|
||||
const { cluster, url } = useCluster();
|
||||
return React.useCallback(
|
||||
(signature: TransactionSignature) => {
|
||||
url && fetchRawTransaction(dispatch, signature, cluster, url);
|
||||
},
|
||||
[dispatch, cluster, url]
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue