- 메시지 → 사용자가 수행하고자 했던 내용 (서명된 제안서)
- 메타 → 실제로 발생한 내용 (실행 결과)
<Buffer 00 bf a0 e8...>와 같은 이진 바이트 배열로 제공됩니다.
이 가이드는 다음을 보여줍니다: 해당 바이너리 데이터를 사람이 읽을 수 있는 형식으로 디코딩하고, 의미 있는 정보를 추출하며, 제안에서 실행까지의 완전한 트랜잭션 스토리를 이해하는 방법을 설명합니다.
실시간 스트림, 디코딩 없음
아래 최소한의 클라이언트를 실행하세요. 필터 플래그는 투표 및 실패한 트랜잭션을 제거하고,accountInclude 배열은 Jupiter 프로그램 ID와 관련된 활동으로 결과를 제한합니다.
filters, createdAt 플러스 두 자식을 숨기는 transaction 브랜치:
transaction.transaction.transaction→ 서명된 메시지transaction.transaction.meta→ 실행 메타
Uint8Array처럼 보이는 모든 것은 현재로서는 불투명하게 남습니다.
디코딩 기능과 함께 스크립트를 실행하면, 실제 중첩 구조와 읽기 가능한 주소를 볼 수 있습니다:
바이너리 데이터 디코딩
왜 디코드해야 하나요? 원시 Laserstream 데이터는 서명, 계정 키, 해시를 이진Uint8Array 객체로 포함하는데, 이는 읽을 수 없습니다. 이러한 데이터를 base58 문자열로 변환하여 트랜잭션을 이해해야 합니다.
해결책: Laserstream은 내장된 디코딩 유틸리티를 제공하는 Yellowstone gRPC를 사용합니다. 각 필드 유형에 대해 별도의 디코더를 작성하는 대신 모든 이진 데이터를 사람이 읽을 수 있는 형식으로 변환하는 재귀 함수를 사용합니다.
트랜잭션 구조 이해
이제 디코딩된 데이터를 볼 수 있으므로, 모든 Laserstream 트랜잭션 업데이트의 두 가지 주요 부분을 살펴보겠습니다. 초기 예제에서 각 트랜잭션에는 두 개의 주요 객체가 포함된다는 것을 기억하세요.- 메시지 (제안) →
transaction.transaction.transaction→ 서명된 메시지 (사용자의 제안) - 메타 (실행) →
transaction.transaction.meta→ 실행 메타데이터 (검증자의 응답)
제안: 메시지 내부 모든 것
사용자는 무엇을, 누구에게 그리고 언제까지를 지정하는 메시지를 만듭니다. 각 부분을 디코딩하는 방법은 다음과 같습니다:트랜잭션 헤더
numRequiredSignatures는 검증자에게 검증할 서명 수를 알려주는 반면, 두 numReadonly* 값은 런타임이 읽기 전용으로 처리할 수 있는 계정을 레이블 지정하여 병렬 실행을 가능하게 합니다.
계정 키 사전
accountKeys는 조회 테이블 역할을 하는 공개 키의 단순 리스트입니다. 트랜잭션의 이후 정수 - programIdIndex, 각 명령의 accounts 배열의 각 요소 -는 이 목록에서 인덱스를 기준으로 다시 가리켜, 메시지당 1킬로바이트 이상을 절약합니다.
재생 보호
recentBlockhash는 마지막 150 블록-해시에서 스크롤이 종료되면 만료됩니다. 메인넷에서는 약 90초입니다.
명령: 실제 명령
- 프로그램 ID (
programIdIndex):accountKeys배열의 주소를 가리킵니다 (예: 인덱스 10 =ComputeBudget111111111111111111111111111111) - 계정 (
accounts): 이 명령이 터치하는 계정 인덱스를 나타내는 base58-인코딩 문자열 - 데이터 (
data): base58로 인코딩된 실제 명령 데이터
convertBuffers 함수로 인해, 계정은 base58로 보이지만 실제로는 계정 인덱스를 포함합니다 (예: "3vtmrQMafzDoG2CBz1iqgXPTnC"는 [21, 19, 12, 17, 2, 6, 1, 22]로 디코딩됨)
이 설계는 전체 32바이트 주소를 반복하는 대신, 각 명령이 조회 테이블의 위치를 참조하도록 합니다.
서명: 승인 증명
signatures는 필요한 계정이 이 트랜잭션을 승인했음을 증명하는 암호화 서명을 포함합니다. 서명 수는 header.numRequiredSignatures와 일치해야 합니다.
주소 테이블 조회
versioned가 true인 경우, addressTableLookups는 체인 상의 테이블과 두 개의 인덱스 리스트를 표시합니다. 조회 테이블은 주소 수에 대한 엄격한 제한을 수십 개로 올리면서 패킷을 1,232바이트 MTU 이하로 유지합니다.
거래 v1: 헤더의 컴퓨팅 예산
거래 v1 (SIMD-0385, Agave 4.2)는 메시지에 필드를 하나 더 추가합니다:transactionConfig.
instructions 배열에는 절대 ComputeBudget111111111111111111111111111111 항목이 포함되지 않습니다. priorityFee는 전체 거래에 대한 lamports로 계산된 총 수수료이며, 컴퓨팅 단위당 마이크로-lamports가 아닙니다. null 필드는 발신자가 설정하지 않았음을 의미합니다. 레거시 및 v0 메시지에는 transactionConfig가 없으므로 그것의 존재는 v1 거래를 식별합니다.
디코더에서 확인해야 할 두 가지 사항:
- 우선 순위 수수료 추출.
transactionConfig.priorityFee가 존재할 때 이를 읽고, 레거시 및 v0 거래에 대해서만 ComputeBudget 명령어를 스캔하도록 설정합니다. 명령어만 스캔하는 코드는 모든 v1 거래를 우선 순위 수수료가 없는 것으로 읽습니다. - Proto 버전.
yellowstone-grpc-proto12.6.0은 v1 필드를 포함하는 첫 번째 릴리스이며,helius-laserstream0.8.4 (JavaScript), 0.6.3 (Rust), 0.2.0 (Go)은 이를 기반으로 구축된 첫 번째 SDK 릴리스입니다. 이전 버전은transactionConfig를 조용히 무시합니다.
전체 연결: 흐름
다음은 기본 원칙에서부터 일어나는 일입니다:- 조회 테이블 빌드:
accountKeys는 이 거래가 접근할 모든 주소를 나열합니다 - 규칙 설정:
header는 몇 개의 서명이 필요한지, 어떤 계정이 읽기 전용인지 지정합니다 - 명령어 생성: 각
instruction는 다음을 가리킵니다:- 프로그램 (via
programIdIndex→accountKeys[index]) - 필요한 계정 (via
accounts→ 여러accountKeys[index]위치) - 명령어 데이터 (
data에 인코딩됨)
- 프로그램 (via
- 인증 추가:
signatures는 필요한 계정이 이 거래를 승인했음을 증명합니다 - 만료 설정:
recentBlockhash는 이 거래가 나중에 재생되지 않도록 보장합니다
실행: 메타 내의 모든 것
메시지는 사용자가 하고 싶었던 일을 보여주는 반면, 메타는 검증자들이 거래를 실행할 때 실제로 일어난 일을 보여줍니다.기본 실행 정보
성공/실패err: null= 성공err: {...}= 오류 세부 사항과 함께 실패fee= 이 거래에 대해 청구된 lamports
accountKeys 배열과 일치합니다:
- 계정 0: 15000 lamports 손실 (수수료 결제)
- 계정 1: 1461600 lamports 증가 (새 계정 생성)
- 계정 3: 2001231920 lamports 증가 (프로그램 계정)
고급 실행 세부 정보
내부 명령어실용적인 디코딩 패턴
디코딩된 거래에서 유용한 정보를 추출하기 위한 일반적인 패턴은 다음과 같습니다:완전한 예제: 주피터 스왑 디코더
주피터 스왑 거래를 디코딩하고 유의미한 정보를 추출하는 완전한 예제입니다:핵심 요약
- 이중 구조: 모든 거래는 메시지 (요청된 것)와 메타 (실제로 일어난 것)로 구성됩니다
- 이진 디코딩:
bs58.encode()를 사용하여 이진 필드를 읽을 수 있는 base58 문자열로 변환합니다 - 계정 키 조회: 명령어는
accountKeys배열의 인덱스를 통해 계정을 참조합니다 - 잔액 추적:
preBalances와postBalances를 비교하여 변경된 내용을 확인합니다 - 거래 v1: 컴퓨팅 예산과 우선 순위 수수료를
transactionConfig에서 읽으십시오. v1 거래에는 ComputeBudget 명령어가 없습니다.