트랜잭션 분석

요약

@solana/transaction-introspectiongetTransaction RPC 응답을 실제 주소가 포함된 명령어 목록으로 변환합니다. 계정 인덱스(조회 테이블 주소 포함)를 확인하고, 내부 Cross Program Invocation 명령어를 디코딩하며, 탐색기 순서에 따라 전체 명령어 트리를 순회합니다.

런타임에 _온체인_에서 프로그램이 형제 명령어를 검사하는 방법을 찾고 계신가요? 그것은 Instructions sysvar입니다 - 명령어 분석을 참조하세요. 이 페이지는 RPC 응답으로부터 _오프체인_에서 트랜잭션을 디코딩하는 것에 관한 내용입니다.

getTransaction 응답은 바로 사용할 수 있는 명령어 목록을 제공하지 않습니다. 계정은 숫자 인덱스로 저장되며, 버전 트랜잭션은 해당 계정을 정적 키와 주소 조회 테이블에서 로드된 주소로 분리하고, cross-program invocations(CPIs)에서 발생한 내부 명령어는 트랜잭션 메타데이터 내에 인코딩된 블롭 형태로 전달됩니다.

@solana/transaction-introspection가 이 확인 작업을 대신 수행합니다. 와이어 트랜잭션을 디코딩하고, 모든 계정 인덱스를 해당 Address에 매핑하며, 내부 명령어를 정규화하고, 블록 탐색기와 동일한 외부-내부 순서로 명령어를 반환합니다. 반환된 명령어는 codama 생성 클라이언트(예: @solana-program/*)의 parseXInstruction 헬퍼에 직접 연결됩니다.

Terminal
npm install @solana/transaction-introspection @solana/kit @solana/instructions @solana-program/token

응답 디코딩

와이어 포맷 인코딩으로 트랜잭션을 가져와 응답을 decodeTransactionFromRpcResponse에 전달합니다.

Decode a transaction
import { createSolanaRpc, signature } from "@solana/kit";
import { decodeTransactionFromRpcResponse } from "@solana/transaction-introspection";
const rpc = createSolanaRpc("https://api.mainnet-beta.solana.com");
const txid =
"3jUKrQp1UGq5ih6FTDUUt2kkqUfoG2o4kY5T1DoVHK2tXXDLdxJSXzuJGY4JPoRivgbi45U2bc7LZfMa6C4R3szX";
const rpcTx = await rpc
.getTransaction(signature(txid), {
commitment: "confirmed",
encoding: "base64",
maxSupportedTransactionVersion: 0
})
.send();
if (!rpcTx) throw new Error(`Transaction ${txid} not found`);
const { compiledMessage, loadedAddresses } =
decodeTransactionFromRpcResponse(rpcTx);

이후 코드 스니펫은 이 단계의 rpcTx, compiledMessage, loadedAddresses를 재사용합니다.

base64, base58, 또는 json 인코딩으로 가져오세요. decodeTransactionFromRpcResponsejsonParsed 응답을 거부합니다 - 해당 인코딩은 RPC 노드에서 서버 측으로 명령어를 파싱하도록 요청하므로, 클라이언트에서 디코딩할 내용이 남아 있지 않습니다.

명령어 순회

walkInstructions는 블록 탐색기에 표시되는 순서대로 모든 명령어(외부 및 내부)를 배열로 반환합니다. 즉, 각 외부 명령어 바로 뒤에 해당 CPI가 생성한 내부 명령어들이 따라옵니다. 각 명령어에는 호출 계층 내 위치를 나타내는 trace 필드가 포함됩니다.

Walk every instruction
import { walkInstructions } from "@solana/transaction-introspection";
for (const ix of walkInstructions({
compiledMessage,
loadedAddresses,
meta: rpcTx.meta
})) {
const location =
ix.trace.kind === "outer"
? `outer[${ix.trace.index}]`
: `inner[${ix.trace.outerIndex}/${ix.trace.innerIndex}]`;
console.log(location, ix.programAddress, ix.accounts?.length ?? 0);
}

trace는 판별 유니온입니다. 최상위 명령어의 경우 { kind: "outer", index }, 외부 명령어 아래에 중첩된 CPI 명령어의 경우 { kind: "inner", outerIndex, innerIndex, stackHeight? }입니다.

meta는 선택 사항입니다. null일 수 있는 경우에도 rpcTx.meta를 직접 전달하세요. 메타 정보가 없으면 walkInstructions는 외부 명령어만 반환합니다.

프로그램 클라이언트로 파싱하기

walkInstructions에서 반환된 각 명령어는 ResolvedInstruction이므로, @solana/instructions의 조건자(predicates)와 @solana-program/* 클라이언트가 생성한 identifyXInstruction / parseXInstruction 헬퍼에서 별도의 변환 과정 없이 직접 사용할 수 있습니다.

이 예제는 트랜잭션 내의 모든 Token Program SyncNative 명령어를 감사(audit)합니다. 최상위 레벨에서 실행되었는지 CPI 내부에서 실행되었는지에 관계없이 처리됩니다.

Find and parse SyncNative instructions
import {
isInstructionForProgram,
isInstructionWithAccounts,
isInstructionWithData
} from "@solana/instructions";
import { walkInstructions } from "@solana/transaction-introspection";
import {
identifyTokenInstruction,
parseSyncNativeInstruction,
TOKEN_PROGRAM_ADDRESS,
TokenInstruction
} from "@solana-program/token";
for (const ix of walkInstructions({
compiledMessage,
loadedAddresses,
meta: rpcTx.meta
})) {
if (!isInstructionForProgram(ix, TOKEN_PROGRAM_ADDRESS)) continue;
if (!isInstructionWithData(ix) || !isInstructionWithAccounts(ix)) continue;
if (identifyTokenInstruction(ix) !== TokenInstruction.SyncNative) continue;
const parsed = parseSyncNativeInstruction(ix);
console.log(ix.trace, parsed);
}

하위 수준 헬퍼

walkInstructions는 대부분의 요구 사항을 충족하지만, 패키지는 이를 구성하는 하위 요소들도 함께 제공합니다:

  • getInstructionsFromCompiledTransactionMessage(compiledMessage, loadedAddresses?)는 계정이 확인되고 데이터가 디코딩된 외부 명령어만 반환합니다.
  • getInnerInstructionsFromMeta(meta, accountMetas)는 트랜잭션 메타데이터에서 내부 CPI 명령어를 디코딩합니다.
  • getAccountMetasFromCompiledTransactionMessage(compiledMessage, loadedAddresses?)는 트랜잭션 순서대로 전체 계정 목록을 반환합니다. 이는 이전 헬퍼가 내부 명령어 계정 인덱스를 확인하는 데 필요한 입력값입니다.
Resolve outer and inner instructions separately
import {
getAccountMetasFromCompiledTransactionMessage,
getInnerInstructionsFromMeta,
getInstructionsFromCompiledTransactionMessage
} from "@solana/transaction-introspection";
const outer = getInstructionsFromCompiledTransactionMessage(
compiledMessage,
loadedAddresses
);
const accountMetas = getAccountMetasFromCompiledTransactionMessage(
compiledMessage,
loadedAddresses
);
if (!rpcTx.meta) throw new Error("Transaction metadata missing");
const inner = getInnerInstructionsFromMeta(rpcTx.meta, accountMetas);

Rust를 사용하시나요?

이 패키지에 해당하는 Rust 버전은 없습니다. Rust에서 파싱된 명령어를 가져오려면 getTransaction에서 UiTransactionEncoding::JsonParsed를 요청하여 RPC 노드가 계정과 내부 명령어를 대신 확인하도록 하세요.

참고 항목

Is this page helpful?

목차

페이지 편집