Learn how to decode and parse transaction data from Laserstream for better understanding of Solana transactions.
<Buffer 00 bf a0 e8...>
instead of readable addresses and signatures.
This guide shows you how to: Decode that binary data into human-readable format, extract meaningful information, and understand the complete transaction story from proposal to execution.
filters
, createdAt
plus a transaction
branch that hides two children:
transaction.transaction.transaction
โ the signed messagetransaction.transaction.meta
โ the execution metaUint8Array
remains opaque for the moment.
When you run the script with the decoding function, youโll see the actual nested structure with readable addresses:
Uint8Array
objects that are unreadable. You need to convert these to base58 strings to make sense of the transaction.
The solution: Laserstream uses Yellowstone gRPC, which provides built-in decoding utilities. Instead of writing separate decoders for each field type, we use one recursive function that converts all binary data to human-readable format.
transaction.transaction.transaction
โ the signed message (userโs proposal)transaction.transaction.meta
โ the execution metadata (validatorโs response)numRequiredSignatures
tells the validator how many signatures to verify, while the two numReadonly*
values label accounts the runtime can treat as read-only, enabling parallel execution.
accountKeys
is a plain list of public keys that acts as a lookup table. Every later integer in the transaction - programIdIndex
, each element in an instructionโs accounts
array - points back into this list by index, saving more than a kilobyte per message.
recentBlockhash
expires once it scrolls out of the last 150 block-hashes, roughly ninety seconds on mainnet.
programIdIndex
): Points to an address in the accountKeys
array (e.g., index 10 = ComputeBudget111111111111111111111111111111
)accounts
): A base58-encoded string representing which account indexes this instruction touchesdata
): The actual instruction data encoded as base58convertBuffers
function, accounts appears as base58 but actually contains account indices (e.g., "3vtmrQMafzDoG2CBz1iqgXPTnC"
decodes to indices [21, 19, 12, 17, 2, 6, 1, 22])
This design means instead of repeating full 32-byte addresses, each instruction just references positions in the lookup table.
signatures
contains the cryptographic signatures proving the required accounts authorized this transaction. The number of signatures must match header.numRequiredSignatures
.
versioned
is true
, addressTableLookups
appears with an on-chain table and two index lists. Lookup tables lift the hard cap on address count to dozens while keeping the packet under the 1,232-byte MTU.
accountKeys
lists all addresses this transaction will touchheader
specifies how many signatures are required and which accounts are read-onlyinstruction
points to:
programIdIndex
โ accountKeys[index]
)accounts
โ multiple accountKeys[index]
positions)data
)signatures
proves the required accounts approved this transactionrecentBlockhash
ensures this transaction canโt be replayed latererr: null
= successerr: {...}
= failure with error detailsfee
= lamports charged for this transactionaccountKeys
array by index:
bs58.encode()
to convert binary fields to readable base58 stringsaccountKeys
arraypreBalances
and postBalances
to see what changed