> ## Documentation Index
> Fetch the complete documentation index at: https://www.helius.dev/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Decodificación y análisis de datos de transacciones

> Aprende a decodificar y analizar datos de transacciones de Laserstream para comprender mejor las transacciones de Solana.

**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.

```ts [expandable] theme={"system"}
import { subscribe, CommitmentLevel, SubscribeUpdate, LaserstreamConfig } from 'helius-laserstream';

async function runTransactionSubscription() {
  const config: LaserstreamConfig = {
    apiKey: 'YOUR_API_KEY', // from https://dashboard.helius.dev/
    endpoint: 'https://laserstream-mainnet-ewr.helius-rpc.com', // pick the closest region
  };

  const request = {
    transactions: {
      "Jupiter-transactions": {
        vote: false,
        failed: false,
        accountInclude: ['JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4']
      }
    },
    commitment: CommitmentLevel.PROCESSED,
    accounts: {}, slots: {}, transactionsStatus: {}, blocks: {}, blocksMeta: {}, entry: {}, accountsDataSlice: []
  };

  const stream = await subscribe(
    config,
    request,
    (u: SubscribeUpdate) => console.log('💸 Transaction update', u),
    console.error
  );

  console.log(`✅ stream id → ${stream.id}`);
  process.on('SIGINT', () => { stream.cancel(); process.exit(0); });
}
runTransactionSubscription().catch(console.error);
```

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

```json theme={"system"}
{
 filters: [ 'Jupiter-transactions' ],
  account: undefined,
  transaction: {
    transaction: {
      signature: <Buffer 00 bf a0 e8 9f cc 84 0c a4 83 e3 97 cd b7 57 e2 2b bc 1d ca 8c a6 1b ce b5 57 d7 47 5e ec 1f 46 ae b2 2d 6a 12 cb 88 48 1d 07 bf f6 b2 d3 a8 0b c9 04 ... 14 more bytes>,
      transaction: [Object],
      meta: [Object],
      index: '1177'
    },
    slot: '351704819'
  },
  transactionStatus: undefined,
  block: undefined,
  blockMeta: undefined,
  entry: undefined,
  ping: undefined,
  pong: undefined,
  createdAt: 2025-07-07T10:58:44.403Z
}
```

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:

```json [expandable] theme={"system"}
{
  "filters": ["Jupiter-transactions"],
  "account": undefined,
  "transaction": {
    "transaction": {
      "signature": "5u62i53R1Hdc4thm6DQTNWNkyypuJJSaXSMwwQDxNqKMaAw62H1Xa3Md7QDhYjoPk5dCPg18fwz83kUR6TrMviTx",
      "transaction": {
        "message": {
          "header": {
            "numRequiredSignatures": 1,
            "numReadonlySignedAccounts": 0,
            "numReadonlyUnsignedAccounts": 8
          },
          "accountKeys": [
            "AF9KFSWQeKVxd3kVvFvysWXmATHyYzrN8zN8GtXn4qTF",
            "G9VzXwhDPQ8KRbQAJN6TyGf2gWukYDAvmnXJhPZFev4f",
            "ES9qPxWQVMRZkobJ9yr3U6XSrXzGNLJdSe6p6fS7b82T",
            "JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4",
            "ComputeBudget111111111111111111111111111111",
            "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL",
            "So11111111111111111111111111111111111111112",
            "11111111111111111111111111111111",
            "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
          ],
          "recentBlockhash": "8sGjRxJHLJVWqpHt5UdN8qxtLLdgcnLKBpFj9Qrn5PNF",
          "instructions": [
            {
              "programIdIndex": 4,
              "accounts": [],
              "data": "3bjaAzoXPjbY"
            },
            {
              "programIdIndex": 3,
              "accounts": [0, 1, 2, 5, 6, 7, 8],
              "data": "2L1xoA2KEqBgWfGt3fwFJK8k4FPJRJzYHRgH4R3xC8A7"
            }
          ]
        },
        "signatures": [
          "5u62i53R1Hdc4thm6DQTNWNkyypuJJSaXSMwwQDxNqKMaAw62H1Xa3Md7QDhYjoPk5dCPg18fwz83kUR6TrMviTx"
        ]
      },
      "meta": {
        "err": null,
        "fee": 12500,
        "preBalances": [1075517572, 0, 207594496815, 0, 0, 0, 0, 0, 0],
        "postBalances": [1075502572, 1461600, 207594496815, 2001231920, 2039280, 0, 0, 0, 0],
        "innerInstructions": [
          {
            "index": 1,
            "instructions": [
              {
                "programIdIndex": 5,
                "accounts": [1, 2, 0],
                "data": "3Bxs4h24hBtQy9rw"
              }
            ]
          }
        ],
        "logMessages": [
          "Program ComputeBudget111111111111111111111111111111 invoke [1]",
          "Program ComputeBudget111111111111111111111111111111 success",
          "Program JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4 invoke [1]",
          "Program log: Instruction: Swap",
          "Program ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL invoke [2]",
          "Program log: Create",
          "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [3]",
          "Program log: Instruction: GetAccountDataSize",
          "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 1569 of 242833 compute units",
          "Program return: TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA pQAAAAAAAAA=",
          "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success",
          "Program JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4 success"
        ],
        "preTokenBalances": [],
        "postTokenBalances": [],
        "computeUnitsConsumed": 182564
      },
      "index": "1177"
    },
    "slot": "351709933"
  },
  "transactionStatus": undefined,
  "block": undefined,
  "blockMeta": undefined,
  "entry": undefined,
  "ping": undefined,
  "pong": undefined,
  "createdAt": "2025-01-14T10:58:44.403Z"
}
```

***

## 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.

```ts [expandable] theme={"system"}
import bs58 from 'bs58';
import { subscribe, CommitmentLevel, SubscribeUpdate, LaserstreamConfig } from 'helius-laserstream';

// Recursive function to convert all Buffer/Uint8Array fields to base58
function convertBuffers(obj: any): any {
  if (!obj) return obj;
  if (Buffer.isBuffer(obj) || obj instanceof Uint8Array) {
    return bs58.encode(obj);
  }
  if (Array.isArray(obj)) {
    return obj.map(item => convertBuffers(item));
  }
  if (typeof obj === 'object') {
    return Object.fromEntries(
      Object.entries(obj).map(([key, value]) => [key, convertBuffers(value)])
    );
  }
  return obj;
}

async function runTransactionSubscription() {
  const config: LaserstreamConfig = {
    apiKey: 'YOUR_API_KEY', // from https://dashboard.helius.dev/
    endpoint: 'https://laserstream-mainnet-ewr.helius-rpc.com', // pick the closest region
  };

  const request = {
    transactions: {
      "Jupiter-transactions": {
        vote: false,
        failed: false,
        accountInclude: ['JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4']
      }
    },
    commitment: CommitmentLevel.PROCESSED,
    accounts: {}, slots: {}, transactionsStatus: {}, blocks: {}, blocksMeta: {}, entry: {}, accountsDataSlice: []
  };

  const stream = await subscribe(
    config,
    request,
    (update: SubscribeUpdate) => {
      if (update.transaction) {
        // Convert all binary fields to human-readable format
        const decodedTransaction = convertBuffers(update.transaction);
        console.log('💸 Decoded transaction:', JSON.stringify(decodedTransaction, null, 2));
        
        // Or process specific fields
        processTransaction(update.transaction);
      }
    },
    console.error
  );

  console.log(`✅ stream id → ${stream.id}`);
  process.on('SIGINT', () => { stream.cancel(); process.exit(0); });
}

function processTransaction(txUpdate: any) {
  const tx = txUpdate.transaction;
  const meta = tx.meta;
  
  console.log('Transaction Details:');
  console.log('- Signature:', bs58.encode(tx.signature));
  console.log('- Slot:', txUpdate.slot);
  console.log('- Success:', meta.err === null);
  console.log('- Fee:', meta.fee, 'lamports');
  console.log('- Compute Units:', meta.computeUnitsConsumed);
  
  // Account keys are already available in the message
  const message = tx.transaction.message;
  if (message.accountKeys) {
    console.log('- Account Keys:');
    message.accountKeys.forEach((key: Uint8Array, index: number) => {
      console.log(`  ${index}: ${bs58.encode(key)}`);
    });
  }
  
  // Log messages are already UTF-8 strings
  if (meta.logMessages && meta.logMessages.length > 0) {
    console.log('- Log Messages:');
    meta.logMessages.forEach((log: string) => {
      console.log(`  ${log}`);
    });
  }
}

runTransactionSubscription();
```

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

```json theme={"system"}
{
  "header": {
    "numRequiredSignatures": 1,
    "numReadonlySignedAccounts": 0,
    "numReadonlyUnsignedAccounts": 5
  }
}
```

`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

```json theme={"system"}
{
  "accountKeys": [
    "7YttLkHDoNj9wyDur5pM1ejNaAvT9X4eqaYcHQqtj2G5",
    "JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4",
    "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
    "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
    "So11111111111111111111111111111111111111112",
    "11111111111111111111111111111111"
  ]
}
```

`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

```json theme={"system"}
{
  "recentBlockhash": "8sGjRxJHLJVWqpHt5UdN8qxtLLdgcnLKBpFj9Qrn5PNF"
}
```

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

### Instrucciones: los comandos reales

```json theme={"system"}
{
  "instructions": [
    {
      "programIdIndex": 10,
      "data": "HnkkG7"
    },
    {
      "programIdIndex": 15,
      "accounts": "3vtmrQMafzDoG2CBz1iqgXPTnC",
      "data": "5jRcjdixRUDKQKUEt6oHJ747HCB3vWb5y"
    }
  ]
}
```

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

```json theme={"system"}
{
  "signatures": [
    "5u62i53R1Hdc4thm6DQTNWNkyypuJJSaXSMwwQDxNqKMaAw62H1Xa3Md7QDhYjoPk5dCPg18fwz83kUR6TrMviTx"
  ]
}
```

`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

```json theme={"system"}
{
  "addressTableLookups": [
    {
      "accountKey": "5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1",
      "writableIndexes": [0, 1],
      "readonlyIndexes": [2, 3, 4]
    }
  ],
  "versioned": true
}
```

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](https://github.com/solana-foundation/solana-improvement-documents/blob/main/proposals/0385-transaction-v1.md), Agave 4.2) agrega un campo más al mensaje: `transactionConfig`.

```json theme={"system"}
{
  "transactionConfig": {
    "computeUnitLimit": 200000,
    "heapSize": null,
    "loadedAccountsDataSizeLimit": 200000,
    "priorityFee": 50000
  },
  "versioned": true
}
```

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](/docs/es/rpc/transaction-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**

```json theme={"system"}
{
  "err": null,
  "fee": 12500
}
```

* `err: null` = éxito
* `err: {...}` = error con detalles
* `fee` = lamports cobrados por esta transacción

**Cambios de saldo**

```json theme={"system"}
{
  "preBalances": [1075517572, 0, 207594496815, 0, 0, 0, 0, 0, 0],
  "postBalances": [1075502572, 1461600, 207594496815, 2001231920, 2039280, 0, 0, 0, 0]
}
```

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**

```json theme={"system"}
{
  "computeUnitsConsumed": 182564
}
```

Muestra cuánto presupuesto de cómputo se utilizó (del total solicitado).

### Detalles avanzados de la ejecución

**Instrucciones internas**

```json theme={"system"}
{
  "innerInstructions": [
    {
      "index": 1,
      "instructions": [
        {
          "programIdIndex": 5,
          "accounts": [1, 2, 0],
          "data": "3Bxs4h24hBtQy9rw"
        }
      ]
    }
  ]
}
```

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**

```json theme={"system"}
{
  "logMessages": [
    "Program ComputeBudget111111111111111111111111111111 invoke [1]",
    "Program ComputeBudget111111111111111111111111111111 success",
    "Program JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4 invoke [1]",
    "Program log: Instruction: Swap",
    "Program ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL invoke [2]",
    "Program log: Create",
    "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [3]",
    "Program log: Instruction: GetAccountDataSize",
    "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 1569 of 242833 compute units",
    "Program return: TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA pQAAAAAAAAA=",
    "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success",
    "Program JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4 success"
  ]
}
```

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**

```json theme={"system"}
{
  "preTokenBalances": [],
  "postTokenBalances": [
    {
      "accountIndex": 1,
      "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
      "owner": "7YttLkHDoNj9wyDur5pM1ejNaAvT9X4eqaYcHQqtj2G5",
      "uiTokenAmount": {
        "amount": "1000000",
        "decimals": 6,
        "uiAmount": 1.0,
        "uiAmountString": "1"
      }
    }
  ]
}
```

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:

```typescript theme={"system"}
// Transaction Success
function isTransactionSuccessful(meta: any): boolean {
  return meta.err === null;
}

function getTransactionFee(meta: any): number {
  return meta.fee;
}

function getComputeUnitsUsed(meta: any): number {
  return meta.computeUnitsConsumed;
}

// Balance Changes
function getBalanceChanges(meta: any, accountKeys: string[]): Array<{account: string, change: number}> {
  const changes = [];
  
  for (let i = 0; i < meta.preBalances.length; i++) {
    const change = meta.postBalances[i] - meta.preBalances[i];
    if (change !== 0) {
      changes.push({
        account: accountKeys[i],
        change: change
      });
    }
  }
  
  return changes;
}

// Program Calls
function getInvokedPrograms(meta: any, accountKeys: string[]): string[] {
  const programs = new Set<string>();
  
  meta.logMessages.forEach((log: string) => {
    const match = log.match(/Program ([1-9A-HJ-NP-Za-km-z]{32,}) invoke/);
    if (match) {
      programs.add(match[1]);
    }
  });
  
  return Array.from(programs);
}

// Token Transfers
function getTokenTransfers(meta: any): Array<{mint: string, from: string, to: string, amount: number}> {
  const transfers = [];
  
  // Compare pre and post token balances
  const preBalances = new Map();
  const postBalances = new Map();
  
  meta.preTokenBalances.forEach((balance: any) => {
    preBalances.set(balance.accountIndex, balance);
  });
  
  meta.postTokenBalances.forEach((balance: any) => {
    postBalances.set(balance.accountIndex, balance);
  });
  
  // Find changes
  for (const [accountIndex, postBalance] of postBalances) {
    const preBalance = preBalances.get(accountIndex);
    const preAmount = preBalance ? parseInt(preBalance.uiTokenAmount.amount) : 0;
    const postAmount = parseInt(postBalance.uiTokenAmount.amount);
    
    if (preAmount !== postAmount) {
      transfers.push({
        mint: postBalance.mint,
        account: postBalance.owner,
        change: postAmount - preAmount,
        decimals: postBalance.uiTokenAmount.decimals
      });
    }
  }
  
  return transfers;
}
```

***

## Ejemplo completo: decodificador de swaps de Jupiter

Este es un ejemplo completo que decodifica transacciones de swaps de Jupiter y extrae información relevante:

```typescript [expandable] theme={"system"}
import bs58 from 'bs58';
import { subscribe, CommitmentLevel, SubscribeUpdate, LaserstreamConfig } from 'helius-laserstream';

interface SwapInfo {
  signature: string;
  slot: number;
  user: string;
  inputMint: string;
  outputMint: string;
  inputAmount: number;
  outputAmount: number;
  fee: number;
  success: boolean;
}

function convertBuffers(obj: any): any {
  if (!obj) return obj;
  if (Buffer.isBuffer(obj) || obj instanceof Uint8Array) {
    return bs58.encode(obj);
  }
  if (Array.isArray(obj)) {
    return obj.map(item => convertBuffers(item));
  }
  if (typeof obj === 'object') {
    return Object.fromEntries(
      Object.entries(obj).map(([key, value]) => [key, convertBuffers(value)])
    );
  }
  return obj;
}

function decodeJupiterSwap(txUpdate: any): SwapInfo | null {
  const tx = txUpdate.transaction;
  const meta = tx.meta;
  const message = tx.transaction.message;
  
  // Convert binary fields to readable format
  const signature = bs58.encode(tx.signature);
  const accountKeys = message.accountKeys.map((key: any) => bs58.encode(key));
  
  // Check if this is a Jupiter transaction
  const jupiterProgram = "JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4";
  if (!accountKeys.includes(jupiterProgram)) {
    return null;
  }
  
  // Extract user (first account is typically the fee payer/user)
  const user = accountKeys[0];
  
  // Get token balance changes
  const tokenChanges = getTokenTransfers(meta);
  
  // Find input (negative change) and output (positive change)
  const inputChange = tokenChanges.find(change => change.change < 0);
  const outputChange = tokenChanges.find(change => change.change > 0);
  
  if (!inputChange || !outputChange) {
    return null;
  }
  
  return {
    signature,
    slot: parseInt(txUpdate.slot),
    user,
    inputMint: inputChange.mint,
    outputMint: outputChange.mint,
    inputAmount: Math.abs(inputChange.change),
    outputAmount: outputChange.change,
    fee: meta.fee,
    success: meta.err === null
  };
}

function getTokenTransfers(meta: any): Array<{mint: string, change: number}> {
  const transfers = [];
  
  const preBalances = new Map();
  const postBalances = new Map();
  
  meta.preTokenBalances.forEach((balance: any) => {
    preBalances.set(balance.accountIndex, balance);
  });
  
  meta.postTokenBalances.forEach((balance: any) => {
    postBalances.set(balance.accountIndex, balance);
  });
  
  for (const [accountIndex, postBalance] of postBalances) {
    const preBalance = preBalances.get(accountIndex);
    const preAmount = preBalance ? parseInt(preBalance.uiTokenAmount.amount) : 0;
    const postAmount = parseInt(postBalance.uiTokenAmount.amount);
    
    if (preAmount !== postAmount) {
      transfers.push({
        mint: postBalance.mint,
        change: postAmount - preAmount
      });
    }
  }
  
  return transfers;
}

async function runJupiterSwapMonitor() {
  const config: LaserstreamConfig = {
    apiKey: 'YOUR_API_KEY', // from https://dashboard.helius.dev/
    endpoint: 'https://laserstream-mainnet-ewr.helius-rpc.com', // pick the closest region
  };

  const request = {
    transactions: {
      "Jupiter-swaps": {
        vote: false,
        failed: false,
        accountInclude: ['JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4']
      }
    },
    commitment: CommitmentLevel.PROCESSED,
    accounts: {}, slots: {}, transactionsStatus: {}, blocks: {}, blocksMeta: {}, entry: {}, accountsDataSlice: []
  };

  const stream = await subscribe(
    config,
    request,
    (update: SubscribeUpdate) => {
      if (update.transaction) {
        const swapInfo = decodeJupiterSwap(update.transaction);
        if (swapInfo) {
          console.log('🔄 Jupiter Swap:');
          console.log(`  User: ${swapInfo.user}`);
          console.log(`  Input: ${swapInfo.inputAmount} of ${swapInfo.inputMint}`);
          console.log(`  Output: ${swapInfo.outputAmount} of ${swapInfo.outputMint}`);
          console.log(`  Fee: ${swapInfo.fee} lamports`);
          console.log(`  Success: ${swapInfo.success}`);
          console.log(`  Signature: ${swapInfo.signature}`);
          console.log('---');
        }
      }
    },
    console.error
  );

  console.log(`✅ Jupiter swap monitor started (id: ${stream.id})`);
  process.on('SIGINT', () => { stream.cancel(); process.exit(0); });
}

runJupiterSwapMonitor().catch(console.error);
```

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.
