Skip to main content
Cuando recibes datos de transacciones de Laserstream, debes buscar dos elementos importantes:
  • Mensaje → Lo que el usuario quería hacer (su propuesta firmada)
  • Metadatos → Lo que realmente ocurrió (el resultado de la ejecución)
El desafío: Los datos sin procesar de la transacción llegan como arreglos de bytes binarios, como <Buffer 00 bf a0 e8...>, en lugar de direcciones y firmas legibles. Esta guía te muestra cómo: Decodificar esos datos binarios a un formato legible, extraer información relevante y comprender la historia completa de la transacción, desde la propuesta hasta la ejecución.

Un stream en vivo, sin decodificación

Ejecuta el cliente mínimo que aparece a continuación. Los indicadores del filtro descartan las transacciones de voto y las fallidas, mientras que el arreglo accountInclude limita los resultados a la actividad que interactúa con el identificador del programa Jupiter.
Ahora tu consola muestra un contenedor —filters, createdAt— junto con una rama transaction que oculta dos elementos secundarios:
  • transaction.transaction.transaction → el mensaje firmado
  • transaction.transaction.meta → los metadatos de ejecución
Por el momento, todo lo que se parece a Uint8Array permanece opaco. Cuando ejecutes el script con la función de decodificación, verás la estructura anidada real con direcciones legibles:

Decodificación de los datos binarios

¿Por qué decodificar? Los datos sin procesar de Laserstream contienen firmas, claves de cuentas y hashes como objetos binarios Uint8Array ilegibles. Debes convertirlos en cadenas base58 para comprender la transacción. La solución: Laserstream usa Yellowstone gRPC, que proporciona utilidades de decodificación integradas. En lugar de escribir decodificadores independientes para cada tipo de campo, usamos una sola función recursiva que convierte todos los datos binarios a un formato legible.
Este enfoque aprovecha la decodificación integrada y, al mismo tiempo, gestiona los campos binarios que requieren conversión manual. La estructura de la transacción ya está analizada; solo debes convertir los campos binarios a un formato legible.

Descripción de la estructura de la transacción

Ahora que podemos ver los datos decodificados, exploremos las dos partes principales de cada actualización de transacción de Laserstream. Recuerda que, en nuestro ejemplo inicial, cada transacción contiene dos objetos clave:
  • Mensaje (propuesta) → transaction.transaction.transaction → el mensaje firmado (la propuesta del usuario)
  • Metadatos (ejecución) → transaction.transaction.meta → los metadatos de ejecución (la respuesta del validador)
Esta estructura de dos partes cuenta una historia completa: lo que el usuario solicitó frente a lo que realmente ocurrió. Examinemos cada parte en detalle.

La propuesta: todo lo que contiene message

El usuario crea un mensaje que especifica qué, quién y hasta cuándo. Así puedes decodificar cada parte:

Encabezado de la transacción

numRequiredSignatures indica al validador cuántas firmas debe verificar, mientras que los dos valores numReadonly* marcan las cuentas que el entorno de ejecución puede tratar como de solo lectura, lo que permite la ejecución en paralelo.

Diccionario de claves de cuentas

accountKeys es una lista simple de claves públicas que funciona como tabla de consulta. Cada entero posterior de la transacción —programIdIndex y cada elemento del arreglo accounts de una instrucción— apunta a esta lista mediante un índice, lo que ahorra más de un kilobyte por mensaje.

Protección contra repetición

recentBlockhash vence cuando sale de los últimos 150 hashes de bloque, aproximadamente noventa segundos en mainnet.

Instrucciones: los comandos reales

Cada instrucción contiene tres partes clave:
  • Identificador del programa (programIdIndex): Apunta a una dirección del arreglo accountKeys (p. ej., índice 10 = ComputeBudget111111111111111111111111111111)
  • Cuentas (accounts): Una cadena codificada en base58 que representa los índices de las cuentas con las que interactúa esta instrucción
  • Datos (data): Los datos reales de la instrucción codificados en base58
Debido a la función convertBuffers, las cuentas aparecen en base58, pero en realidad contienen índices de cuentas (p. ej., "3vtmrQMafzDoG2CBz1iqgXPTnC" se decodifica como los índices [21, 19, 12, 17, 2, 6, 1, 22]) Gracias a este diseño, en lugar de repetir direcciones completas de 32 bytes, cada instrucción solo hace referencia a posiciones de la tabla de consulta.

Firmas: prueba de autorización

signatures contiene las firmas criptográficas que demuestran que las cuentas requeridas autorizaron esta transacción. La cantidad de firmas debe coincidir con header.numRequiredSignatures.

Consultas de tablas de direcciones

Si versioned es true, addressTableLookups aparece con una tabla en cadena y dos listas de índices. Las tablas de consulta elevan a decenas el límite estricto de direcciones y mantienen el paquete por debajo de la MTU de 1232 bytes.

Transacción v1: presupuesto de cómputo en el encabezado

La transacción v1 (SIMD-0385, Agave 4.2) agrega un campo más al mensaje: transactionConfig.
Una transacción v1 incluye aquí su presupuesto de cómputo en lugar de incluirlo en las instrucciones del programa ComputeBudget, por lo que el arreglo instructions de una transacción v1 nunca contiene una entrada ComputeBudget111111111111111111111111111111. priorityFee es la comisión total en lamports de toda la transacción, no microlamports por unidad de cómputo. Un campo null significa que el remitente no lo configuró. Los mensajes heredados y v0 no tienen transactionConfig, por lo que su presencia identifica una transacción v1. Debes comprobar dos aspectos en tu decodificador:
  • Extracción de la comisión de prioridad. Lee transactionConfig.priorityFee cuando exista y recurre al análisis de las instrucciones de ComputeBudget solo para transacciones heredadas y v0. El código que únicamente analiza instrucciones interpreta que todas las transacciones v1 pagan una comisión de prioridad de cero.
  • Versión de Proto. yellowstone-grpc-proto 12.6.0 es la primera versión que incluye los campos de v1, e helius-laserstream 0.8.4 (JavaScript), 0.6.3 (Rust) y 0.2.0 (Go) son las primeras versiones del SDK creadas sobre ella. Las versiones anteriores descartan transactionConfig de forma silenciosa.
Consulta Compatibilidad con transacciones v1 para ver la lista completa de cambios.

Cómo se conecta todo: el flujo

Esto es lo que ocurre desde los principios básicos:
  1. Crear la tabla de consulta: accountKeys enumera todas las direcciones con las que interactuará esta transacción
  2. Definir las reglas: header especifica cuántas firmas se requieren y qué cuentas son de solo lectura
  3. Crear los comandos: Cada instruction apunta a:
    • Un programa (mediante programIdIndex → accountKeys[index])
    • Las cuentas que necesita (mediante accounts → varias posiciones de accountKeys[index])
    • Los datos de la instrucción (codificados en data)
  4. Agregar la autorización: signatures demuestra que las cuentas requeridas aprobaron esta transacción
  5. Definir el vencimiento: recentBlockhash garantiza que esta transacción no pueda repetirse más adelante

La ejecución: todo lo que contiene meta

Mientras que el mensaje muestra lo que el usuario quería hacer, los metadatos muestran lo que realmente ocurrió cuando los validadores ejecutaron la transacción.

Información básica de la ejecución

Éxito/Error
  • err: null = éxito
  • err: {...} = error con detalles
  • fee = lamports cobrados por esta transacción
Cambios de saldo
Los arreglos de saldos corresponden al arreglo accountKeys por índice:
  • Cuenta 0: Perdió 15000 lamports (pago de la comisión)
  • Cuenta 1: Ganó 1461600 lamports (se creó una cuenta nueva)
  • Cuenta 3: Ganó 2001231920 lamports (cuenta del programa)
Uso de cómputo
Muestra cuánto presupuesto de cómputo se utilizó (del total solicitado).

Detalles avanzados de la ejecución

Instrucciones internas
Las instrucciones internas son instrucciones adicionales que los programas invocaron durante la ejecución. No forman parte de la transacción original, sino que las activaron las instrucciones principales. Mensajes de registro
Los mensajes de registro proporcionan un seguimiento cronológico de la ejecución del programa. Muestran qué programas se invocaron y cualquier mensaje de registro personalizado que generaron. Cambios en los saldos de tokens
Los cambios en los saldos de tokens muestran los estados anteriores y posteriores de las cuentas de tokens SPL, incluidos los importes legibles con el manejo correcto de decimales.

Patrones prácticos de decodificación

Estos son algunos patrones comunes para extraer información útil de las transacciones decodificadas:

Ejemplo completo: decodificador de swaps de Jupiter

Este es un ejemplo completo que decodifica transacciones de swaps de Jupiter y extrae información relevante:
Este ejemplo muestra cómo combinar la decodificación de mensajes con el análisis de metadatos para extraer información relevante para el negocio de transacciones DeFi complejas.

Puntos clave

  • Estructura de dos partes: Cada transacción tiene un mensaje (lo que se solicitó) y metadatos (lo que realmente ocurrió)
  • Decodificación binaria: Usa bs58.encode() para convertir campos binarios en cadenas base58 legibles
  • Consultas de claves de cuentas: Las instrucciones hacen referencia a las cuentas mediante su índice en el arreglo accountKeys
  • Seguimiento de saldos: Compara preBalances y postBalances para ver qué cambió
  • Transacción v1: Lee el presupuesto de cómputo y la comisión de prioridad de transactionConfig cuando esté presente; las transacciones v1 no tienen instrucciones de ComputeBudget
La clave para comprender las transacciones de Solana es reconocer que están diseñadas para ser eficientes: en lugar de repetir direcciones, usan tablas de consulta e índices para minimizar el tamaño de la transacción y maximizar la densidad de información.