Skip to content

Commit 06ca009

Browse files
committed
feat: resolve token batch inner instructions in inspector
1 parent 919f853 commit 06ca009

File tree

11 files changed

+371
-20
lines changed

11 files changed

+371
-20
lines changed

app/components/inspector/InspectorPage.tsx

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,14 @@ import { useFetchAccountInfo } from '@providers/accounts';
99
import { FetchStatus } from '@providers/cache';
1010
import { useFetchRawTransaction, useRawTransactionDetails } from '@providers/transactions/raw';
1111
import usePrevious from '@react-hook/previous';
12-
import { Connection, MessageV0, PACKET_DATA_SIZE, PublicKey, VersionedMessage } from '@solana/web3.js';
12+
import {
13+
type CompiledInnerInstruction,
14+
Connection,
15+
MessageV0,
16+
PACKET_DATA_SIZE,
17+
PublicKey,
18+
VersionedMessage,
19+
} from '@solana/web3.js';
1320
import { generated, PROGRAM_ADDRESS as SQUADS_V4_PROGRAM_ADDRESS } from '@sqds/multisig';
1421
import { useClusterPath } from '@utils/url';
1522
import bs58 from 'bs58';
@@ -37,6 +44,7 @@ export type TransactionData = {
3744
preBalances: number[];
3845
postBalances: number[];
3946
};
47+
compiledInnerInstructions?: CompiledInnerInstruction[];
4048
};
4149

4250
export type SquadsProposalAccountData = {
@@ -396,6 +404,7 @@ function PermalinkView({
396404
const { message, signatures, meta } = transaction;
397405
const tx = {
398406
accountBalances: meta,
407+
compiledInnerInstructions: meta?.innerInstructions,
399408
message,
400409
rawMessage: message.serialize(),
401410
signatures,
@@ -412,7 +421,7 @@ function LoadedView({
412421
onClear: () => void;
413422
showTokenBalanceChanges: boolean;
414423
}) {
415-
const { message, rawMessage, signatures, accountBalances } = transaction;
424+
const { message, rawMessage, signatures, accountBalances, compiledInnerInstructions } = transaction;
416425

417426
const fetchAccountInfo = useFetchAccountInfo();
418427
React.useEffect(() => {
@@ -432,7 +441,7 @@ function LoadedView({
432441
{signatures && <TransactionSignatures message={message} signatures={signatures} rawMessage={rawMessage} />}
433442
<AccountsCard message={message} />
434443
<AddressTableLookupsCard message={message} />
435-
<InstructionsSection message={message} />
444+
<InstructionsSection message={message} compiledInnerInstructions={compiledInnerInstructions} />
436445
</>
437446
);
438447
}

app/components/inspector/InstructionsSection.tsx

Lines changed: 47 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { useCluster } from '@providers/cluster';
44
import { ASSOCIATED_TOKEN_PROGRAM_ID, TOKEN_PROGRAM_ID } from '@solana/spl-token';
55
import {
66
AddressLookupTableAccount,
7+
type CompiledInnerInstruction,
78
ComputeBudgetProgram,
89
SystemProgram,
910
TransactionInstruction,
@@ -15,6 +16,7 @@ import { getProgramName } from '@utils/tx';
1516
import React from 'react';
1617
import { ErrorBoundary } from 'react-error-boundary';
1718

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

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

32-
export function InstructionsSection({ message }: { message: VersionedMessage }) {
34+
const INSPECTOR_RESULT = { err: null };
35+
36+
export function InstructionsSection({
37+
message,
38+
compiledInnerInstructions,
39+
}: {
40+
message: VersionedMessage;
41+
compiledInnerInstructions?: CompiledInnerInstruction[];
42+
}) {
3343
// Fetch all address lookup tables
3444
const hydratedTables = useAddressLookupTables(
3545
message.addressTableLookups.map(lookup => lookup.accountKey.toString()),
@@ -59,10 +69,32 @@ export function InstructionsSection({ message }: { message: VersionedMessage })
5969
);
6070
const transactionMessage = TransactionMessage.decompile(message, { addressLookupTableAccounts });
6171

72+
const batchByIndex = compiledInnerInstructions
73+
? resolveInnerBatchInstructions(
74+
compiledInnerInstructions,
75+
message.getAccountKeys({ addressLookupTableAccounts }),
76+
message,
77+
)
78+
: {};
79+
6280
return (
6381
<>
6482
{transactionMessage.instructions.map((ix, index) => {
65-
return <InspectorInstructionCard key={index} {...{ index, ix, message }} />;
83+
const batchInnerCards = batchByIndex[index]?.map((innerIx, childIndex) => (
84+
<ErrorBoundary key={childIndex} fallback={null}>
85+
<TokenBatchCard index={index} childIndex={childIndex} ix={innerIx} result={INSPECTOR_RESULT} />
86+
</ErrorBoundary>
87+
));
88+
89+
return (
90+
<InspectorInstructionCard
91+
key={index}
92+
index={index}
93+
ix={ix}
94+
message={message}
95+
innerCards={batchInnerCards}
96+
/>
97+
);
6698
})}
6799
</>
68100
);
@@ -72,10 +104,12 @@ function InspectorInstructionCard({
72104
message,
73105
ix,
74106
index,
107+
innerCards,
75108
}: {
76109
message: VersionedMessage;
77110
ix: TransactionInstruction;
78111
index: number;
112+
innerCards?: React.ReactNode[];
79113
}) {
80114
const { cluster, url } = useCluster();
81115

@@ -91,23 +125,25 @@ function InspectorInstructionCard({
91125
<AnchorDetailsCard
92126
anchorProgram={anchorProgram.program}
93127
index={index}
94-
// Inner cards and child are not used since we do not know what CPIs
95-
// will be called until simulation happens, and even then, all we
96-
// get is logs, not the TransactionInstructions
97128
innerCards={undefined}
98129
ix={ix}
99-
// Always display success since it is too complicated to determine
100-
// based on the simulation and pass that result here. Could be added
101-
// later if desired, possibly similar to innerCards from parsing tx
102-
// sim logs.
103130
result={{ err: null }}
104-
// Signature is not needed.
105131
signature=""
106132
/>
107133
</ErrorBoundary>
108134
);
109135
}
110136

137+
if (isTokenBatchInstruction(ix)) {
138+
return (
139+
<ErrorBoundary
140+
fallback={<UnknownDetailsCard key={index} index={index} ix={ix} programName={programName} />}
141+
>
142+
<TokenBatchCard index={index} ix={ix} result={INSPECTOR_RESULT} />
143+
</ErrorBoundary>
144+
);
145+
}
146+
111147
/// Handle program-specific cards here
112148
// - keep signature (empty string as we do not submit anything) for backward compatibility with the data from Transaction
113149
// - result is `err: null` as at this point there should not be errors
@@ -116,8 +152,6 @@ function InspectorInstructionCard({
116152

117153
switch (ix.programId.toString()) {
118154
case ASSOCIATED_TOKEN_PROGRAM_ID.toString(): {
119-
// NOTE: current limitation is that innerInstructions won't be present at the AssociatedTokenDetailsCard. For that purpose we might need to simulateTransactions to get them.
120-
121155
const asParsedInstruction = intoParsedInstruction(ix);
122156
return (
123157
<AssociatedTokenDetailsCard
@@ -208,5 +242,5 @@ function InspectorInstructionCard({
208242
}
209243
}
210244

211-
return <UnknownDetailsCard key={index} index={index} ix={ix} programName={programName} />;
245+
return <UnknownDetailsCard key={index} index={index} ix={ix} programName={programName} innerCards={innerCards} />;
212246
}

app/components/inspector/UnknownDetailsCard.tsx

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,15 @@ export function UnknownDetailsCard({
1313
index,
1414
ix,
1515
programName,
16+
innerCards,
1617
}: {
1718
index: number;
1819
ix: TransactionInstruction;
1920
programName: string;
21+
innerCards?: React.ReactNode[];
2022
}) {
21-
const [expanded, setExpanded] = React.useState(false);
23+
const hasInnerCards = innerCards && innerCards.length > 0;
24+
const [expanded, setExpanded] = React.useState(hasInnerCards ?? false);
2225

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

@@ -41,6 +44,18 @@ export function UnknownDetailsCard({
4144
<TableCardBody>
4245
<ProgramField programId={ix.programId} showExtendedInfo />
4346
<BaseRawDetails ix={ix} />
47+
{hasInnerCards && (
48+
<>
49+
<tr className="table-sep">
50+
<td colSpan={3}>Inner Instructions</td>
51+
</tr>
52+
<tr>
53+
<td colSpan={3}>
54+
<div className="inner-cards !e-m-0">{innerCards}</div>
55+
</td>
56+
</tr>
57+
</>
58+
)}
4459
</TableCardBody>
4560
)}
4661
</div>

app/components/shared/Skeletons.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { ReactElement } from 'react';
2+
23
import { Skeleton } from './ui/skeleton';
34

45
type ColDef = {

app/features/token-batch/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export { isTokenBatchInstruction } from './lib/batch-parser';
2+
export { resolveInnerBatchInstructions } from './lib/resolve-inner-batch-instructions';
23
export { TokenBatchCard } from './ui/TokenBatchCard';

0 commit comments

Comments
 (0)