Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions app/components/inspector/InspectorPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,14 @@ import { useFetchAccountInfo } from '@providers/accounts';
import { FetchStatus } from '@providers/cache';
import { useFetchRawTransaction, useRawTransactionDetails } from '@providers/transactions/raw';
import usePrevious from '@react-hook/previous';
import { Connection, MessageV0, PACKET_DATA_SIZE, PublicKey, VersionedMessage } from '@solana/web3.js';
import {
type CompiledInnerInstruction,
Connection,
MessageV0,
PACKET_DATA_SIZE,
PublicKey,
VersionedMessage,
} from '@solana/web3.js';
import { generated, PROGRAM_ADDRESS as SQUADS_V4_PROGRAM_ADDRESS } from '@sqds/multisig';
import { useClusterPath } from '@utils/url';
import bs58 from 'bs58';
Expand Down Expand Up @@ -38,6 +45,7 @@ export type TransactionData = {
preBalances: number[];
postBalances: number[];
};
compiledInnerInstructions?: CompiledInnerInstruction[];
};

export type SquadsProposalAccountData = {
Expand Down Expand Up @@ -397,6 +405,7 @@ function PermalinkView({
const { message, signatures, meta } = transaction;
const tx = {
accountBalances: meta,
compiledInnerInstructions: meta?.innerInstructions,
message,
rawMessage: message.serialize(),
signatures,
Expand All @@ -413,7 +422,7 @@ function LoadedView({
onClear: () => void;
showTokenBalanceChanges: boolean;
}) {
const { message, rawMessage, signatures, accountBalances } = transaction;
const { message, rawMessage, signatures, accountBalances, compiledInnerInstructions } = transaction;

const fetchAccountInfo = useFetchAccountInfo();
React.useEffect(() => {
Expand All @@ -433,7 +442,7 @@ function LoadedView({
{signatures && <TransactionSignatures message={message} signatures={signatures} rawMessage={rawMessage} />}
<AccountsCard message={message} />
<AddressTableLookupsCard message={message} />
<InstructionsSection message={message} />
<InstructionsSection message={message} compiledInnerInstructions={compiledInnerInstructions} />
</>
);
}
Expand Down
60 changes: 47 additions & 13 deletions app/components/inspector/InstructionsSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { useCluster } from '@providers/cluster';
import { ASSOCIATED_TOKEN_PROGRAM_ID, TOKEN_PROGRAM_ID } from '@solana/spl-token';
import {
AddressLookupTableAccount,
type CompiledInnerInstruction,
ComputeBudgetProgram,
SystemProgram,
TransactionInstruction,
Expand All @@ -15,6 +16,7 @@ import { getProgramName } from '@utils/tx';
import React from 'react';
import { ErrorBoundary } from 'react-error-boundary';

import { isTokenBatchInstruction, resolveInnerBatchInstructions, TokenBatchCard } from '@/app/features/token-batch';
import { useAddressLookupTables } from '@/app/providers/accounts';
import { FetchStatus } from '@/app/providers/cache';

Expand All @@ -29,7 +31,15 @@ import { AssociatedTokenDetailsCard } from './associated-token/AssociatedTokenDe
import { intoParsedInstruction, intoParsedTransaction } from './into-parsed-data';
import { UnknownDetailsCard } from './UnknownDetailsCard';

export function InstructionsSection({ message }: { message: VersionedMessage }) {
const INSPECTOR_RESULT = { err: null };

export function InstructionsSection({
message,
compiledInnerInstructions,
}: {
message: VersionedMessage;
compiledInnerInstructions?: CompiledInnerInstruction[];
}) {
// Fetch all address lookup tables
const hydratedTables = useAddressLookupTables(
message.addressTableLookups.map(lookup => lookup.accountKey.toString()),
Expand Down Expand Up @@ -59,10 +69,32 @@ export function InstructionsSection({ message }: { message: VersionedMessage })
);
const transactionMessage = TransactionMessage.decompile(message, { addressLookupTableAccounts });

const batchByIndex = compiledInnerInstructions
? resolveInnerBatchInstructions(
compiledInnerInstructions,
message.getAccountKeys({ addressLookupTableAccounts }),
message,
)
: {};

return (
<>
{transactionMessage.instructions.map((ix, index) => {
return <InspectorInstructionCard key={index} {...{ index, ix, message }} />;
const batchInnerCards = batchByIndex[index]?.map((innerIx, childIndex) => (
<ErrorBoundary key={childIndex} fallback={null}>
<TokenBatchCard index={index} childIndex={childIndex} ix={innerIx} result={INSPECTOR_RESULT} />
</ErrorBoundary>
));

return (
<InspectorInstructionCard
key={index}
index={index}
ix={ix}
message={message}
innerCards={batchInnerCards}
/>
);
Comment thread
askov marked this conversation as resolved.
})}
</>
);
Expand All @@ -72,10 +104,12 @@ function InspectorInstructionCard({
message,
ix,
index,
innerCards,
}: {
message: VersionedMessage;
ix: TransactionInstruction;
index: number;
innerCards?: React.ReactNode[];
}) {
const { cluster, url } = useCluster();

Expand All @@ -91,23 +125,25 @@ function InspectorInstructionCard({
<AnchorDetailsCard
anchorProgram={anchorProgram.program}
index={index}
// Inner cards and child are not used since we do not know what CPIs
// will be called until simulation happens, and even then, all we
// get is logs, not the TransactionInstructions
innerCards={undefined}
ix={ix}
// Always display success since it is too complicated to determine
// based on the simulation and pass that result here. Could be added
// later if desired, possibly similar to innerCards from parsing tx
// sim logs.
result={{ err: null }}
// Signature is not needed.
signature=""
/>
</ErrorBoundary>
);
}

if (isTokenBatchInstruction(ix)) {
return (
<ErrorBoundary
fallback={<UnknownDetailsCard key={index} index={index} ix={ix} programName={programName} />}
>
<TokenBatchCard index={index} ix={ix} result={INSPECTOR_RESULT} />
</ErrorBoundary>
);
}

/// Handle program-specific cards here
// - keep signature (empty string as we do not submit anything) for backward compatibility with the data from Transaction
// - result is `err: null` as at this point there should not be errors
Expand All @@ -116,8 +152,6 @@ function InspectorInstructionCard({

switch (ix.programId.toString()) {
case ASSOCIATED_TOKEN_PROGRAM_ID.toString(): {
// NOTE: current limitation is that innerInstructions won't be present at the AssociatedTokenDetailsCard. For that purpose we might need to simulateTransactions to get them.

const asParsedInstruction = intoParsedInstruction(ix);
return (
<AssociatedTokenDetailsCard
Expand Down Expand Up @@ -208,5 +242,5 @@ function InspectorInstructionCard({
}
}

return <UnknownDetailsCard key={index} index={index} ix={ix} programName={programName} />;
return <UnknownDetailsCard key={index} index={index} ix={ix} programName={programName} innerCards={innerCards} />;
}
17 changes: 16 additions & 1 deletion app/components/inspector/UnknownDetailsCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,15 @@ export function UnknownDetailsCard({
index,
ix,
programName,
innerCards,
}: {
index: number;
ix: TransactionInstruction;
programName: string;
innerCards?: React.ReactNode[];
}) {
const [expanded, setExpanded] = React.useState(false);
const hasInnerCards = innerCards && innerCards.length > 0;
const [expanded, setExpanded] = React.useState(hasInnerCards ?? false);

const scrollAnchorRef = useScrollAnchor(getInstructionCardScrollAnchorId([index + 1]));

Expand All @@ -41,6 +44,18 @@ export function UnknownDetailsCard({
<TableCardBody>
<ProgramField programId={ix.programId} showExtendedInfo />
<BaseRawDetails ix={ix} />
{hasInnerCards && (
<>
<tr className="table-sep">
<td colSpan={3}>Inner Instructions</td>
</tr>
<tr>
<td colSpan={3}>
<div className="inner-cards !e-m-0">{innerCards}</div>
</td>
</tr>
</>
)}
</TableCardBody>
)}
</div>
Expand Down
1 change: 1 addition & 0 deletions app/features/token-batch/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export { isTokenBatchInstruction } from './lib/batch-parser';
export { resolveInnerBatchInstructions } from './lib/resolve-inner-batch-instructions';
export { TokenBatchCard } from './ui/TokenBatchCard';
Loading
Loading