Skip to main content
Laserstream에서 트랜잭션 데이터를 받으면, 주의해야 할 두 가지 중요한 사항이 있습니다:
  • 메시지 → 사용자가 수행하고자 했던 내용 (서명된 제안서)
  • 메타 → 실제로 발생한 내용 (실행 결과)
문제점: 원시 트랜잭션 데이터는 읽을 수 있는 주소 및 서명이 아닌 <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와 일치해야 합니다.

주소 테이블 조회

versionedtrue인 경우, addressTableLookups는 체인 상의 테이블과 두 개의 인덱스 리스트를 표시합니다. 조회 테이블은 주소 수에 대한 엄격한 제한을 수십 개로 올리면서 패킷을 1,232바이트 MTU 이하로 유지합니다.

거래 v1: 헤더의 컴퓨팅 예산

거래 v1 (SIMD-0385, Agave 4.2)는 메시지에 필드를 하나 더 추가합니다: transactionConfig.
v1 거래는 ComputeBudget 프로그램 명령어 대신 컴퓨팅 예산을 여기에 담고 있으므로 v1 거래의 instructions 배열에는 절대 ComputeBudget111111111111111111111111111111 항목이 포함되지 않습니다. priorityFee는 전체 거래에 대한 lamports로 계산된 총 수수료이며, 컴퓨팅 단위당 마이크로-lamports가 아닙니다. null 필드는 발신자가 설정하지 않았음을 의미합니다. 레거시 및 v0 메시지에는 transactionConfig가 없으므로 그것의 존재는 v1 거래를 식별합니다. 디코더에서 확인해야 할 두 가지 사항:
  • 우선 순위 수수료 추출. transactionConfig.priorityFee가 존재할 때 이를 읽고, 레거시 및 v0 거래에 대해서만 ComputeBudget 명령어를 스캔하도록 설정합니다. 명령어만 스캔하는 코드는 모든 v1 거래를 우선 순위 수수료가 없는 것으로 읽습니다.
  • Proto 버전. yellowstone-grpc-proto 12.6.0은 v1 필드를 포함하는 첫 번째 릴리스이며, helius-laserstream 0.8.4 (JavaScript), 0.6.3 (Rust), 0.2.0 (Go)은 이를 기반으로 구축된 첫 번째 SDK 릴리스입니다. 이전 버전은 transactionConfig를 조용히 무시합니다.
변경 사항 전체 목록은 거래 v1 지원을 참조하세요.

전체 연결: 흐름

다음은 기본 원칙에서부터 일어나는 일입니다:
  1. 조회 테이블 빌드: accountKeys는 이 거래가 접근할 모든 주소를 나열합니다
  2. 규칙 설정: header는 몇 개의 서명이 필요한지, 어떤 계정이 읽기 전용인지 지정합니다
  3. 명령어 생성: 각 instruction는 다음을 가리킵니다:
    • 프로그램 (via programIdIndexaccountKeys[index])
    • 필요한 계정 (via accounts → 여러 accountKeys[index] 위치)
    • 명령어 데이터 (data에 인코딩됨)
  4. 인증 추가: signatures는 필요한 계정이 이 거래를 승인했음을 증명합니다
  5. 만료 설정: recentBlockhash는 이 거래가 나중에 재생되지 않도록 보장합니다

실행: 메타 내의 모든 것

메시지는 사용자가 하고 싶었던 일을 보여주는 반면, 메타는 검증자들이 거래를 실행할 때 실제로 일어난 일을 보여줍니다.

기본 실행 정보

성공/실패
  • err: null = 성공
  • err: {...} = 오류 세부 사항과 함께 실패
  • fee = 이 거래에 대해 청구된 lamports
잔액 변경
잔액 배열은 인덱스별로 accountKeys 배열과 일치합니다:
  • 계정 0: 15000 lamports 손실 (수수료 결제)
  • 계정 1: 1461600 lamports 증가 (새 계정 생성)
  • 계정 3: 2001231920 lamports 증가 (프로그램 계정)
컴퓨트 사용량
요청된 양 중에서 얼마나 많은 컴퓨팅 예산이 사용되었는지를 보여줍니다.

고급 실행 세부 정보

내부 명령어
내부 명령어는 실행 중 프로그램이 호출한 추가 명령어입니다. 원래 거래의 일부가 아니지만 메인 명령어에 의해 실행되었습니다. 로그 메시지
로그 메시지는 프로그램 실행의 시간순 추적을 제공하며, 호출된 프로그램과 출력한 사용자 정의 로그 메시지를 보여줍니다. 토큰 잔액 변경
토큰 잔액 변경은 SPL 토큰 계정의 전/후 상태를 보여주며, 올바른 소수점 처리가 포함된 사람이 읽을 수 있는 금액을 포함합니다.

실용적인 디코딩 패턴

디코딩된 거래에서 유용한 정보를 추출하기 위한 일반적인 패턴은 다음과 같습니다:

완전한 예제: 주피터 스왑 디코더

주피터 스왑 거래를 디코딩하고 유의미한 정보를 추출하는 완전한 예제입니다:
이 예제는 메시지 디코딩과 메타 분석을 결합하여 복잡한 DeFi 거래에서 비즈니스 관련 정보를 추출하는 방법을 보여줍니다.

핵심 요약

  • 이중 구조: 모든 거래는 메시지 (요청된 것)와 메타 (실제로 일어난 것)로 구성됩니다
  • 이진 디코딩: bs58.encode()를 사용하여 이진 필드를 읽을 수 있는 base58 문자열로 변환합니다
  • 계정 키 조회: 명령어는 accountKeys 배열의 인덱스를 통해 계정을 참조합니다
  • 잔액 추적: preBalancespostBalances를 비교하여 변경된 내용을 확인합니다
  • 거래 v1: 컴퓨팅 예산과 우선 순위 수수료를 transactionConfig에서 읽으십시오. v1 거래에는 ComputeBudget 명령어가 없습니다.
Solana 거래를 이해하는 핵심은 효율성을 위해 설계되었음을 인식하는 것입니다: 주소를 반복하는 대신 조회 테이블과 인덱스를 사용하여 거래 크기를 최소화하면서 정보 밀도를 극대화합니다.