diff --git a/explorer/src/components/instruction/RawDetails.tsx b/explorer/src/components/instruction/RawDetails.tsx index a40ee847d6..7cff419cdb 100644 --- a/explorer/src/components/instruction/RawDetails.tsx +++ b/explorer/src/components/instruction/RawDetails.tsx @@ -24,7 +24,9 @@ export function RawDetails({ ix }: { ix: TransactionInstruction }) { ))} - Instruction Data (Hex) + + Instruction Data (Hex) +
{data}
diff --git a/explorer/src/components/instruction/RawParsedDetails.tsx b/explorer/src/components/instruction/RawParsedDetails.tsx index 76b514b2f2..59f5352952 100644 --- a/explorer/src/components/instruction/RawParsedDetails.tsx +++ b/explorer/src/components/instruction/RawParsedDetails.tsx @@ -13,9 +13,11 @@ export function RawParsedDetails({ {children} - Instruction Data (JSON) + + Instruction Data (JSON) + -
+          
             {JSON.stringify(ix.parsed, null, 2)}
           
diff --git a/explorer/src/components/instruction/bpf-loader/BpfLoaderDetailsCard.tsx b/explorer/src/components/instruction/bpf-loader/BpfLoaderDetailsCard.tsx index 49a57bcd34..2303eed6fa 100644 --- a/explorer/src/components/instruction/bpf-loader/BpfLoaderDetailsCard.tsx +++ b/explorer/src/components/instruction/bpf-loader/BpfLoaderDetailsCard.tsx @@ -84,7 +84,7 @@ export function BpfLoaderWriteDetailsCard(props: Props) { - Bytes (base 64) + Bytes (Base 64)
{bytes}
diff --git a/explorer/src/components/instruction/upgradeable-bpf-loader/UpgradeableBpfLoaderDetailsCard.tsx b/explorer/src/components/instruction/upgradeable-bpf-loader/UpgradeableBpfLoaderDetailsCard.tsx new file mode 100644 index 0000000000..8d418c1dbd --- /dev/null +++ b/explorer/src/components/instruction/upgradeable-bpf-loader/UpgradeableBpfLoaderDetailsCard.tsx @@ -0,0 +1,112 @@ +import React from "react"; +import { + ParsedTransaction, + ParsedInstruction, + SignatureResult, + PublicKey, +} from "@solana/web3.js"; +import { Address } from "components/common/Address"; +import { coerce, Struct } from "superstruct"; +import { camelToTitleCase } from "utils"; +import { reportError } from "utils/sentry"; +import { ParsedInfo } from "validators"; +import { InstructionCard } from "../InstructionCard"; +import { UnknownDetailsCard } from "../UnknownDetailsCard"; +import { + DeployWithMaxDataLenInfo, + InitializeBufferInfo, + SetAuthorityInfo, + UpgradeInfo, + WriteInfo, +} from "./types"; + +type DetailsProps = { + tx: ParsedTransaction; + ix: ParsedInstruction; + index: number; + result: SignatureResult; + innerCards?: JSX.Element[]; + childIndex?: number; +}; + +export function UpgradeableBpfLoaderDetailsCard(props: DetailsProps) { + try { + const parsed = coerce(props.ix.parsed, ParsedInfo); + switch (parsed.type) { + case "write": { + return renderDetails(props, parsed, WriteInfo); + } + case "upgrade": { + return renderDetails(props, parsed, UpgradeInfo); + } + case "setAuthority": { + return renderDetails(props, parsed, SetAuthorityInfo); + } + case "deployWithMaxDataLen": { + return renderDetails( + props, + parsed, + DeployWithMaxDataLenInfo + ); + } + case "initializeBuffer": { + return renderDetails( + props, + parsed, + InitializeBufferInfo + ); + } + default: + return ; + } + } catch (error) { + reportError(error, { + signature: props.tx.signatures[0], + }); + return ; + } +} + +function renderDetails( + props: DetailsProps, + parsed: ParsedInfo, + struct: Struct +) { + const info = coerce(parsed.info, struct); + + const attributes: JSX.Element[] = []; + for (let [key, value] of Object.entries(info)) { + if (value instanceof PublicKey) { + value =
; + } else if (key === "bytes") { + value = ( +
{value}
+ ); + } + + attributes.push( + + + {camelToTitleCase(key)}{" "} + {key === "bytes" && (Base 64)} + + {value} + + ); + } + + return ( + + + Program + +
+ + + {attributes} + + ); +} diff --git a/explorer/src/components/instruction/upgradeable-bpf-loader/types.ts b/explorer/src/components/instruction/upgradeable-bpf-loader/types.ts new file mode 100644 index 0000000000..7f206dbc6f --- /dev/null +++ b/explorer/src/components/instruction/upgradeable-bpf-loader/types.ts @@ -0,0 +1,62 @@ +/* eslint-disable @typescript-eslint/no-redeclare */ + +import { enums, nullable, number, pick, string, StructType } from "superstruct"; +import { Pubkey } from "validators/pubkey"; + +export type WriteInfo = StructType; +export const WriteInfo = pick({ + account: Pubkey, + authority: Pubkey, + bytes: string(), + offset: number(), +}); + +export type InitializeBufferInfo = StructType; +export const InitializeBufferInfo = pick({ + account: Pubkey, + authority: Pubkey, +}); + +export type UpgradeInfo = StructType; +export const UpgradeInfo = pick({ + programDataAccount: Pubkey, + programAccount: Pubkey, + bufferAccount: Pubkey, + spillAccount: Pubkey, + authority: Pubkey, + rentSysvar: Pubkey, + clockSysvar: Pubkey, +}); + +export type SetAuthorityInfo = StructType; +export const SetAuthorityInfo = pick({ + account: Pubkey, + authority: Pubkey, + newAuthority: nullable(Pubkey), +}); + +export type DeployWithMaxDataLenInfo = StructType< + typeof DeployWithMaxDataLenInfo +>; +export const DeployWithMaxDataLenInfo = pick({ + programDataAccount: Pubkey, + programAccount: Pubkey, + payerAccount: Pubkey, + bufferAccount: Pubkey, + authority: Pubkey, + rentSysvar: Pubkey, + clockSysvar: Pubkey, + systemProgram: Pubkey, + maxDataLen: number(), +}); + +export type UpgradeableBpfLoaderInstructionType = StructType< + typeof UpgradeableBpfLoaderInstructionType +>; +export const UpgradeableBpfLoaderInstructionType = enums([ + "initializeBuffer", + "deployWithMaxDataLen", + "setAuthority", + "write", + "finalize", +]); diff --git a/explorer/src/components/transaction/InstructionsSection.tsx b/explorer/src/components/transaction/InstructionsSection.tsx index ed2331e52d..7957257d31 100644 --- a/explorer/src/components/transaction/InstructionsSection.tsx +++ b/explorer/src/components/transaction/InstructionsSection.tsx @@ -35,6 +35,7 @@ import { } from "providers/transactions"; import { Cluster, useCluster } from "providers/cluster"; import { VoteDetailsCard } from "components/instruction/vote/VoteDetailsCard"; +import { UpgradeableBpfLoaderDetailsCard } from "components/instruction/upgradeable-bpf-loader/UpgradeableBpfLoaderDetailsCard"; export function InstructionsSection({ signature }: SignatureProps) { const status = useTransactionStatus(signature); @@ -162,6 +163,8 @@ function renderInstructionCard({ return ; case "bpf-loader": return ; + case "bpf-upgradeable-loader": + return ; case "system": return ; case "stake": diff --git a/explorer/src/scss/_solana.scss b/explorer/src/scss/_solana.scss index dabfe2f7f7..7d6e06a31c 100644 --- a/explorer/src/scss/_solana.scss +++ b/explorer/src/scss/_solana.scss @@ -341,7 +341,7 @@ div.inner-cards { border: 1px solid red; } -pre.data-wrap { +pre.data-wrap, pre.json-wrap { max-width: 23rem; white-space: pre-wrap; white-space: -moz-pre-wrap; @@ -349,3 +349,7 @@ pre.data-wrap { white-space: -o-pre-wrap; word-wrap: break-word; } + +pre.json-wrap { + max-width: 36rem; +} diff --git a/explorer/src/utils/index.tsx b/explorer/src/utils/index.tsx index 5649d49586..29d45f525f 100644 --- a/explorer/src/utils/index.tsx +++ b/explorer/src/utils/index.tsx @@ -110,3 +110,8 @@ export function localStorageIsAvailable() { return false; } } + +export function camelToTitleCase(str: string): string { + const result = str.replace(/([A-Z])/g, " $1"); + return result.charAt(0).toUpperCase() + result.slice(1); +}