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:
kev zettler 2020-11-30 13:35:33 -08:00 committed by GitHub
parent 40dd46680e
commit 75e3f5cd48
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 125 additions and 7 deletions

View File

@ -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} />
)

View File

@ -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">

View File

@ -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} />;
}
}

View File

@ -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]
);
}