diff --git a/explorer/src/components/instruction/InstructionCard.tsx b/explorer/src/components/instruction/InstructionCard.tsx index 8399f753c1..543837f5bb 100644 --- a/explorer/src/components/instruction/InstructionCard.tsx +++ b/explorer/src/components/instruction/InstructionCard.tsx @@ -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 (
@@ -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} > Raw @@ -53,7 +74,7 @@ export function InstructionCard({ {showRaw ? ( "parsed" in ix ? ( - + ) : ( ) diff --git a/explorer/src/components/instruction/RawParsedDetails.tsx b/explorer/src/components/instruction/RawParsedDetails.tsx index 8e930f7620..78682386be 100644 --- a/explorer/src/components/instruction/RawParsedDetails.tsx +++ b/explorer/src/components/instruction/RawParsedDetails.tsx @@ -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 ( <> @@ -12,6 +25,24 @@ export function RawParsedDetails({ ix }: { ix: ParsedInstruction }) { + {hex ? ( + + Instruction Data (Hex) + +
{hex}
+ + + ) : null} + + {b64 ? ( + + Instruction Data (Base64) + +
{b64}
+ + + ) : null} + Instruction Data (JSON) diff --git a/explorer/src/pages/TransactionDetailsPage.tsx b/explorer/src/pages/TransactionDetailsPage.tsx index 23077cbc7f..d236df9448 100644 --- a/explorer/src/pages/TransactionDetailsPage.tsx +++ b/explorer/src/pages/TransactionDetailsPage.tsx @@ -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) { <> - + + + )} @@ -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 ; @@ -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 ; } } diff --git a/explorer/src/providers/transactions/details.tsx b/explorer/src/providers/transactions/details.tsx index 33b214cd90..75dc5ba84b 100644 --- a/explorer/src/providers/transactions/details.tsx +++ b/explorer/src/providers/transactions/details.tsx @@ -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
; @@ -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] + ); +}