Files
solana/explorer/src/components/transaction/InstructionsSection.tsx
microwavedcola1 1b7b261460 feat(explorer): render program name, ix name, and account names from on chain idl for specific anchor programs (#23499)
* show titles of ix, from idl

Signed-off-by: microwavedcola1 <microwavedcola@gmail.com>

* remove unused

Signed-off-by: microwavedcola1 <microwavedcola@gmail.com>

* remaining accounts

Signed-off-by: microwavedcola1 <microwavedcola@gmail.com>

* fallback

Signed-off-by: microwavedcola1 <microwavedcola@gmail.com>

* fix from code review: remove default for the non fallback case

Signed-off-by: microwavedcola1 <microwavedcola@gmail.com>

* keep camelcase

Signed-off-by: microwavedcola1 <microwavedcola@gmail.com>

* formatting

Signed-off-by: microwavedcola1 <microwavedcola@gmail.com>
2022-03-23 12:14:26 -07:00

237 lines
7.6 KiB
TypeScript

import React from "react";
import { ErrorCard } from "components/common/ErrorCard";
import {
ParsedInnerInstruction,
ParsedInstruction,
ParsedTransaction,
PartiallyDecodedInstruction,
PublicKey,
SignatureResult,
TransactionSignature,
} from "@solana/web3.js";
import { BpfLoaderDetailsCard } from "components/instruction/bpf-loader/BpfLoaderDetailsCard";
import { MemoDetailsCard } from "components/instruction/MemoDetailsCard";
import { SerumDetailsCard } from "components/instruction/SerumDetailsCard";
import { StakeDetailsCard } from "components/instruction/stake/StakeDetailsCard";
import { SystemDetailsCard } from "components/instruction/system/SystemDetailsCard";
import { TokenDetailsCard } from "components/instruction/token/TokenDetailsCard";
import { TokenLendingDetailsCard } from "components/instruction/TokenLendingDetailsCard";
import { TokenSwapDetailsCard } from "components/instruction/TokenSwapDetailsCard";
import { WormholeDetailsCard } from "components/instruction/WormholeDetailsCard";
import { UnknownDetailsCard } from "components/instruction/UnknownDetailsCard";
import { BonfidaBotDetailsCard } from "components/instruction/BonfidaBotDetails";
import {
INNER_INSTRUCTIONS_START_SLOT,
SignatureProps,
} from "pages/TransactionDetailsPage";
import { intoTransactionInstruction } from "utils/tx";
import { isSerumInstruction } from "components/instruction/serum/types";
import { isTokenLendingInstruction } from "components/instruction/token-lending/types";
import { isTokenSwapInstruction } from "components/instruction/token-swap/types";
import { isBonfidaBotInstruction } from "components/instruction/bonfida-bot/types";
import { useFetchTransactionDetails } from "providers/transactions/parsed";
import {
useTransactionDetails,
useTransactionStatus,
} from "providers/transactions";
import { Cluster, useCluster } from "providers/cluster";
import { BpfUpgradeableLoaderDetailsCard } from "components/instruction/bpf-upgradeable-loader/BpfUpgradeableLoaderDetailsCard";
import { VoteDetailsCard } from "components/instruction/vote/VoteDetailsCard";
import { isWormholeInstruction } from "components/instruction/wormhole/types";
import { AssociatedTokenDetailsCard } from "components/instruction/AssociatedTokenDetailsCard";
import { MangoDetailsCard } from "components/instruction/MangoDetails";
import { isPythInstruction } from "components/instruction/pyth/types";
import { PythDetailsCard } from "components/instruction/pyth/PythDetailsCard";
import { isInstructionFromAnAnchorProgram } from "../instruction/anchor/types";
import { GenericAnchorDetailsCard } from "../instruction/GenericAnchorDetails";
import { isMangoInstruction } from "../instruction/mango/types";
export type InstructionDetailsProps = {
tx: ParsedTransaction;
ix: ParsedInstruction;
index: number;
result: SignatureResult;
innerCards?: JSX.Element[];
childIndex?: number;
};
export function InstructionsSection({ signature }: SignatureProps) {
const status = useTransactionStatus(signature);
const details = useTransactionDetails(signature);
const { cluster } = useCluster();
const fetchDetails = useFetchTransactionDetails();
const refreshDetails = () => fetchDetails(signature);
if (!status?.data?.info || !details?.data?.transaction) return null;
const { transaction } = details.data.transaction;
const { meta } = details.data.transaction;
if (transaction.message.instructions.length === 0) {
return <ErrorCard retry={refreshDetails} text="No instructions found" />;
}
const innerInstructions: {
[index: number]: (ParsedInstruction | PartiallyDecodedInstruction)[];
} = {};
if (
meta?.innerInstructions &&
(cluster !== Cluster.MainnetBeta ||
details.data.transaction.slot >= INNER_INSTRUCTIONS_START_SLOT)
) {
meta.innerInstructions.forEach((parsed: ParsedInnerInstruction) => {
if (!innerInstructions[parsed.index]) {
innerInstructions[parsed.index] = [];
}
parsed.instructions.forEach((ix) => {
innerInstructions[parsed.index].push(ix);
});
});
}
const result = status.data.info.result;
const instructionDetails = transaction.message.instructions.map(
(instruction, index) => {
let innerCards: JSX.Element[] = [];
if (index in innerInstructions) {
innerInstructions[index].forEach((ix, childIndex) => {
if (typeof ix.programId === "string") {
ix.programId = new PublicKey(ix.programId);
}
let res = renderInstructionCard({
index,
ix,
result,
signature,
tx: transaction,
childIndex,
});
innerCards.push(res);
});
}
return renderInstructionCard({
index,
ix: instruction,
result,
signature,
tx: transaction,
innerCards,
});
}
);
return (
<>
<div className="container">
<div className="header">
<div className="header-body">
<h3 className="mb-0">
{instructionDetails.length > 1 ? "Instructions" : "Instruction"}
</h3>
</div>
</div>
</div>
{instructionDetails}
</>
);
}
function renderInstructionCard({
ix,
tx,
result,
index,
signature,
innerCards,
childIndex,
}: {
ix: ParsedInstruction | PartiallyDecodedInstruction;
tx: ParsedTransaction;
result: SignatureResult;
index: number;
signature: TransactionSignature;
innerCards?: JSX.Element[];
childIndex?: number;
}) {
const key = `${index}-${childIndex}`;
if ("parsed" in ix) {
const props = {
tx,
ix,
result,
index,
innerCards,
childIndex,
key,
};
switch (ix.program) {
case "spl-token":
return <TokenDetailsCard {...props} />;
case "bpf-loader":
return <BpfLoaderDetailsCard {...props} />;
case "bpf-upgradeable-loader":
return <BpfUpgradeableLoaderDetailsCard {...props} />;
case "system":
return <SystemDetailsCard {...props} />;
case "stake":
return <StakeDetailsCard {...props} />;
case "spl-memo":
return <MemoDetailsCard {...props} />;
case "spl-associated-token-account":
return <AssociatedTokenDetailsCard {...props} />;
case "vote":
return <VoteDetailsCard {...props} />;
default:
return <UnknownDetailsCard {...props} />;
}
}
const transactionIx = intoTransactionInstruction(tx, ix);
if (!transactionIx) {
return (
<ErrorCard
key={key}
text="Could not display this instruction, please report"
/>
);
}
const props = {
ix: transactionIx,
result,
index,
signature,
innerCards,
childIndex,
};
if (isBonfidaBotInstruction(transactionIx)) {
return <BonfidaBotDetailsCard key={key} {...props} />;
} else if (isInstructionFromAnAnchorProgram(transactionIx)) {
return <GenericAnchorDetailsCard key={key} {...props} />;
} else if (isMangoInstruction(transactionIx)) {
return <MangoDetailsCard key={key} {...props} />;
} else if (isSerumInstruction(transactionIx)) {
return <SerumDetailsCard key={key} {...props} />;
} else if (isTokenSwapInstruction(transactionIx)) {
return <TokenSwapDetailsCard key={key} {...props} />;
} else if (isTokenLendingInstruction(transactionIx)) {
return <TokenLendingDetailsCard key={key} {...props} />;
} else if (isWormholeInstruction(transactionIx)) {
return <WormholeDetailsCard key={key} {...props} />;
} else if (isPythInstruction(transactionIx)) {
return <PythDetailsCard key={key} {...props} />;
} else {
return <UnknownDetailsCard key={key} {...props} />;
}
}