요약
@solana/transaction-introspection는 getTransaction RPC 응답을 실제 주소가
포함된 명령어 목록으로 변환합니다. 계정 인덱스(조회 테이블 주소 포함)를
확인하고, 내부 Cross Program Invocation 명령어를 디코딩하며, 탐색기 순서에
따라 전체 명령어 트리를 순회합니다.
런타임에 _온체인_에서 프로그램이 형제 명령어를 검사하는 방법을 찾고 계신가요? 그것은 Instructions sysvar입니다 - 명령어 분석을 참조하세요. 이 페이지는 RPC 응답으로부터 _오프체인_에서 트랜잭션을 디코딩하는 것에 관한 내용입니다.
getTransaction 응답은 바로 사용할 수 있는
명령어 목록을 제공하지 않습니다. 계정은 숫자 인덱스로 저장되며,
버전 트랜잭션은 해당 계정을
정적 키와 주소 조회 테이블에서
로드된 주소로 분리하고, cross-program invocations(CPIs)에서 발생한 내부 명령어는
트랜잭션 메타데이터 내에 인코딩된 블롭 형태로 전달됩니다.
@solana/transaction-introspection가 이 확인 작업을 대신 수행합니다. 와이어
트랜잭션을 디코딩하고, 모든 계정 인덱스를 해당 Address에 매핑하며, 내부
명령어를 정규화하고, 블록 탐색기와 동일한 외부-내부 순서로 명령어를 반환합니다.
반환된 명령어는 codama 생성 클라이언트(예: @solana-program/*)의
parseXInstruction 헬퍼에 직접 연결됩니다.
npm install @solana/transaction-introspection @solana/kit @solana/instructions @solana-program/token
응답 디코딩
와이어 포맷 인코딩으로 트랜잭션을 가져와 응답을
decodeTransactionFromRpcResponse에 전달합니다.
import { createSolanaRpc, signature } from "@solana/kit";import { decodeTransactionFromRpcResponse } from "@solana/transaction-introspection";const rpc = createSolanaRpc("https://api.mainnet.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를
재사용합니다.
v1 트랜잭션 준비하기
v1
형식
이 활성화되면 maxSupportedTransactionVersion: 1을 전달해야 하며, 그렇지 않으면 v1 트랜잭션에서 페치가 실패합니다. decodeTransactionFromRpcResponse는 legacy, 0, 1을 모두 처리합니다(@solana/kit 7.0 기준). v1 메시지는 ComputeBudget 명령어가 아닌 transactionConfig 객체에 리소스 한도를 포함하므로, 디코딩된 명령어를 스캔하여 컴퓨팅 한도나 우선순위 수수료를 가져오는 코드는 v1에서 아무것도 찾지 못합니다.
base64, base58, 또는 json 인코딩으로 가져오세요.
decodeTransactionFromRpcResponse는 jsonParsed 응답을 거부합니다 - 해당
인코딩은 RPC 노드에서 서버 측으로 명령어를 파싱하도록 요청하므로,
클라이언트에서 디코딩할 내용이 남아 있지 않습니다.
명령어 순회
walkInstructions는 블록 탐색기에 표시되는 순서대로 모든 명령어(외부 및 내부)를
배열로 반환합니다. 즉, 각 외부 명령어 바로 뒤에 해당 CPI가 생성한 내부
명령어들이 따라옵니다. 각 명령어에는 호출 계층 내 위치를 나타내는 trace 필드가
포함됩니다.
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 내부에서 실행되었는지에
관계없이 처리됩니다.
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?)는 트랜잭션 순서대로 전체 계정 목록을 반환합니다. 이는 이전 헬퍼가 내부 명령어 계정 인덱스를 확인하는 데 필요한 입력값입니다.
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 노드가 계정과 내부 명령어를
대신 확인하도록 하세요.
참고 항목
- 트랜잭션 구조 - 이 패키지가 디코딩하는 응답의 구조
- 버전된 트랜잭션 - 주소 조회 테이블이 계정을 정적 그룹과 로드된 그룹으로 분리하는 방법
getTransaction- 트랜잭션을 반환하는 RPC 메서드
Is this page helpful?