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

# Suscripción y actualizaciones de cuentas

> Aprende a suscribirte a las actualizaciones de cuentas y a monitorear eficientemente los cambios de estado en cadena mediante Laserstream.

Al crear aplicaciones que deben responder a cambios en cadena, consultar periódicamente los endpoints RPC para obtener actualizaciones de cuentas es ineficiente y lento. Las suscripciones de cuentas solucionan este problema al enviar actualizaciones en tiempo real sobre los cambios de estado de las cuentas directamente a tu aplicación.

Esta guía abarca todo lo que necesitas saber sobre las suscripciones de cuentas: qué son, cómo funcionan y cómo optimizarlas para tu caso de uso específico.

***

## Contexto del modelo de cuentas

<Info>
  Omite esta sección si ya conoces las cuentas de Solana y su estructura.
</Info>

Solana utiliza un modelo basado en cuentas en el que cada dato reside en una cuenta: un contenedor que almacena tanto datos como metadatos. Cada cuenta tiene:

* **Datos**: Los bytes reales que almacenan el estado del programa, los saldos de tokens u otra información
* **Propietario**: El programa que controla esta cuenta y puede modificar sus datos
* **Lamports**: El saldo de SOL de la cuenta para la exención de renta
* **Ejecutable**: Indica si esta cuenta contiene código de programa

Los programas no tienen estado: no almacenan datos internamente. En su lugar, crean y administran cuentas separadas para almacenar su estado. Cuando interactúas con un programa, proporcionas las cuentas de las que debe leer o en las que debe escribir.

Este diseño hace que las suscripciones de cuentas sean potentes: puedes monitorear los cambios en cuentas específicas, en todas las cuentas que pertenecen a un programa o en las cuentas que cumplen ciertos criterios.

***

## Suscripción básica de cuentas

Comencemos con un ejemplo sencillo que se suscribe a los cambios en las cuentas de tokens. Este script te notificará cada vez que cambien los saldos de tokens:

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

// Utility function to recursively convert Buffer objects to base58 strings
function convertBuffersToBase58(obj: any): any {
  if (obj === null || obj === undefined) {
    return obj;
  }
  
  if (Buffer.isBuffer(obj)) {
    return bs58.encode(obj);
  }
  
  if (Array.isArray(obj)) {
    return obj.map(convertBuffersToBase58);
  }
  
  if (typeof obj === 'object') {
    const result: any = {};
    for (const key in obj) {
      if (obj.hasOwnProperty(key)) {
        result[key] = convertBuffersToBase58(obj[key]);
      }
    }
    return result;
  }
  
  return obj;
}

async function main() {
  console.log('🏦 Basic Account Subscription Example');

  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 = {
    accounts: {
      "token-accounts": {
        account: [], // Specific account pubkeys (empty = all)
        owner: ["TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"], // Token program
        filters: [
          {
            // Only token accounts (165 bytes)
            datasize: 165
          }
        ]
      }
    },
    commitment: CommitmentLevel.CONFIRMED,
    transactions: {}, slots: {}, transactionsStatus: {}, blocks: {}, blocksMeta: {}, entry: {}, accountsDataSlice: []
  };

  const stream = await subscribe(
    config,
    request,
    async (update: SubscribeUpdate) => {
      const readableUpdate = convertBuffersToBase58(update);
      console.log('🏦 Account Update:', JSON.stringify(readableUpdate, null, 2));
    },
    async (err) => console.error('❌ Stream error:', err)
  );

  console.log(`✅ Account subscription started (id: ${stream.id})`);

  process.on('SIGINT', () => {
    console.log('\n🛑 Cancelling stream...');
    stream.cancel();
    process.exit(0);
  });
}

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

Cuando ejecutes esta suscripción básica, verás las actualizaciones de cuentas en tiempo real transmitidas a tu consola:

```
🏦 Basic Account Subscription Example
✅ Account subscription started (id: xyz789)

🏦 Account Update: {
  "filters": ["token-accounts"],
  "account": {
    "account": {
      "pubkey": "BKMHWYLAX4un3HUbR7a3u9jPmzCiLNa4mSj1RiX11eWF",
      "lamports": "2039280",
      "owner": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
      "rentEpoch": "18446744073709551615",
      "data": "2NUx6Xw9QkmgJCyYUP3d8TPsjJhUpSM7hcy9Fi1juGc6g9DrpPFyGyvBZzu9qiAjFtyEDbNiLHYFJsq1dD5Wxr4LPcF9Dqs4AJa15L1N92pfinnoKVfCsVCcybhV1iwkCCTMeMyxTRA4tqJm6MrLwgKG3HmmwVdhsEuXjSsGJFXGzgfgPHucVzBEgAqcpH9JPpoaQyis2MFwRJLjenxzkE8xJzWHv1Zk2T",
      "writeVersion": "2697618495",
      "txnSignature": "5C9Hr5nG2j8eQz6inxPmfyjbYdmXddzUDyR1iQgEnjYQ3RNvuP4Zzc8t1enLNy7Rk8KNCtQPEQztENYWxkt9GaVD"
    },
    "slot": "352366983"
  },
  "createdAt": "2025-07-10T11:56:22.027Z"
}
```

**¿Qué acaba de suceder?** ¡Nuestra suscripción funcionó perfectamente! Le pedimos a Laserstream que nos notificara sobre los cambios en las cuentas de tokens y nos envió una actualización sobre la cuenta `BKMHWYLAX4un3HUbR7a3u9jPmzCiLNa4mSj1RiX11eWF`.

Esta cuenta tiene:

* **2,039,280 lamports** (saldo de \~0.002 SOL; esta es la cantidad exenta de renta para esta cuenta de token)
* **Programa propietario** `TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA` (este es el programa SPL Token)
* **Firma de la transacción** `5C9Hr5nG2j8eQz6inxPmfyjbYdmXddzUDyR1iQgEnjYQ3RNvuP4Zzc8t1enLNy7Rk8KNCtQPEQztENYWxkt9GaVD` que muestra qué transacción específica provocó el cambio en esta cuenta
* **Slot 352366983** que indica cuándo ocurrió esta actualización en la blockchain
* **Campo de datos** que contiene 165 bytes de datos de la cuenta codificados en base58

### Cómo filtrar cuentas con datasize

El campo de datos es fundamental: contiene la estructura real de la cuenta de token. Usemos este conocimiento para aplicar un **filtrado inteligente de cuentas**.

#### ¿Por qué usar el filtrado por datasize?

Para entender por qué necesitamos filtrar, primero debemos comprender qué son realmente las cuentas de tokens. **Por cada token que contiene una billetera, existe una cuenta separada en cadena.** Si tu billetera contiene 3 tokens diferentes (USDC, BONK y SOL), en realidad tienes 1 cuenta de billetera (tu cuenta principal de SOL) más 3 cuentas de tokens (una por cada tipo de token). Cada cuenta de token tiene exactamente 165 bytes y almacena qué token contiene (dirección de mint), quién es su propietario (la dirección de tu billetera) y qué cantidad de ese token contiene.

El programa Token posee **millones de cuentas** en Solana, pero no todas son lo que consideramos «cuentas de tokens» que contienen saldos de usuarios. Esto es lo que ocurre con y sin filtrado:

**Sin filtrado: la avalancha:**

```ts theme={"system"}
accounts: {
  "all-token-program-accounts": {
    owner: ["TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"] // ❌ Overwhelming!
  }
}
```

Esto se suscribe a TODAS las cuentas que pertenecen al programa Token, entre ellas:

* **Cuentas de tokens** (165 bytes): saldos de usuarios; millones de cuentas
* **Cuentas de mint** (82 bytes): definiciones de tokens; cientos de miles de cuentas
* **Cuentas multifirma** (355 bytes): controles de billeteras compartidas; decenas de miles de cuentas
* **Cuentas del programa Associated Token** (varios tamaños): millones de cuentas

<Warning>
  **Resultado:** Tu aplicación recibe constantemente millones de actualizaciones de cuentas, la mayoría de las cuales no te interesan.
</Warning>

**Con filtrado inteligente: precisión quirúrgica:**

```ts theme={"system"}
accounts: {
  "token-accounts-only": {
    owner: ["TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"],
    filters: [{ datasize: 165 }] // ✅ Only standard token accounts
  }
}
```

Esto limita los resultados únicamente a las cuentas de 165 bytes, que son específicamente las cuentas de saldos de tokens de usuarios: justo lo que necesitas para monitorear transferencias de tokens, cambios de saldo y actualizaciones de portafolios.

**La diferencia:**

* **Sin filtrado:** Millones de actualizaciones de cuentas (creaciones de mint, cambios en multifirmas, etc.)
* **Con filtrado por datasize:** Solo cambios en los saldos de tokens

Esto reduce considerablemente el ruido y se centra solo en las cuentas que realmente representan las tenencias de tokens de los usuarios.

#### ¿De dónde provienen los 165 bytes?

No es magia: provienen de la [estructura de cuentas del programa SPL Token](https://github.com/solana-program/token/blob/d05d10807fe8cf157f6e1f024c708274c30c953a/program/src/state.rs#L87). Al revisar el código fuente, podemos ver que la estructura `Account` define exactamente 165 bytes:

```rust theme={"system"}
pub struct Account {
    pub mint: Pubkey,                    // 32 bytes
    pub owner: Pubkey,                   // 32 bytes  
    pub amount: u64,                     // 8 bytes
    pub delegate: COption<Pubkey>,       // 4 + 32 bytes
    pub state: AccountState,             // 1 byte
    pub is_native: COption<u64>,         // 4 + 8 bytes
    pub delegated_amount: u64,           // 8 bytes
    pub close_authority: COption<Pubkey> // 4 + 32 bytes
}
// Total: 32+32+8+36+1+12+8+36 = 165 bytes
```

Este tamaño fijo nos permite filtrar con precisión las cuentas de tokens estándar y excluir:

* Cuentas de mint (82 bytes)
* Cuentas multifirma (355 bytes)
* Cuentas del programa de cuentas de tokens asociadas
* Otras cuentas relacionadas con tokens que tienen tamaños diferentes

Para calcular los tamaños de las cuentas en otros programas, consulta la [Referencia de espacio de Anchor](https://www.anchor-lang.com/docs/references/space), que muestra cuánto espacio ocupan los distintos tipos de datos (Pubkey = 32 bytes, u64 = 8 bytes, etc.).

#### Decodificación de la estructura de la cuenta

Ahora que entendemos por qué filtramos por 165 bytes, decodifiquemos el contenido de nuestra cuenta de ejemplo:

```
Base58 data: 2NUx6Xw9QkmgJCyYUP3d8TPsjJhUpSM7hcy9Fi1juGc6g9...
```

Los 165 bytes se distribuyen de la siguiente manera:

* **Bytes 0-31:** Dirección de mint (qué token contiene esta cuenta)
* **Bytes 32-63:** Dirección del propietario (quién posee esta cuenta de token)
* **Bytes 64-71:** Cantidad de tokens (cuántos tokens hay en la cuenta)
* **Bytes 72-164:** Metadatos adicionales (delegado, estado, autoridad de cierre, etc.)

Este enfoque estructurado nos brinda precisión quirúrgica: solo recibimos actualizaciones de cuentas de tokens estándar, sin el ruido de otros tipos de cuentas.

### Combinación de filtros: datasize + memcmp para una precisión extrema

Ahora que sabemos que la dirección de mint se encuentra en los bytes 0-31, podemos ser aún más específicos. Supongamos que solo queremos monitorear cuentas de tokens USDC. Podemos combinar nuestro filtro `datasize` con un filtro `memcmp` para apuntar a la dirección de mint exacta:

```ts theme={"system"}
const USDC_MINT = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";

const request = {
  accounts: {
    "usdc-only": {
      owner: ["TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"],
      filters: [
        { datasize: 165 },                    // Standard token accounts only
        { 
          memcmp: {
            offset: 0,                         // Mint address starts at byte 0
            base58: USDC_MINT                  // Match this specific mint
          }
        }
      ]
    }
  },
  // ... other config
};
```

**Estrategia de filtrado progresivo:**

1. **Filtro por propietario:** «Dame las cuentas que pertenecen al programa Token» (millones de cuentas)
2. **Filtro por datasize:** «Pero solo las cuentas de tokens estándar de 165 bytes» (cientos de miles)
3. **Filtro memcmp:** «Y solo las que contienen USDC» (miles)

Esta progresión de lo general a lo específico es la clave para monitorear cuentas de forma eficiente. Cada filtro reduce el conjunto de resultados para que solo recibas las actualizaciones exactas que te interesan.

**Importante:** Todos los filtros usan lógica AND: deben cumplirse todas las condiciones para que se active una actualización de cuenta.

### Lectura de actualizaciones de cuentas de USDC: ¿quién, cuánto y dónde?

Ahora veamos qué contienen realmente estas actualizaciones filtradas. Creemos un monitor específico para USDC que responda las preguntas clave cuando cambia una cuenta de token:

* **¿Quién** posee esta cuenta de token?
* **¿Cuánto** USDC contiene ahora?
* **¿Dónde** ocurrió el cambio (en qué cuenta específica)?
* **¿Cuándo** ocurrió este cambio?
* **¿Qué transacción** provocó el cambio?

Las actualizaciones sin procesar de las cuentas contienen datos binarios que debemos decodificar. Como Solana utiliza la codificación base58 para las direcciones y las firmas, usamos la función `bs58.encode()` para convertir los objetos Buffer binarios en cadenas legibles.

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

async function main() {
  console.log('USDC Account Monitor');

  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 USDC_MINT = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";

  const request = {
    accounts: {
      "usdc-accounts": {
        account: [],
        owner: ["TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"],
        filters: [
          { datasize: 165 },                           // Standard token accounts
          { memcmp: { offset: 0, base58: USDC_MINT } } // Only USDC
        ]
      }
    },
    commitment: CommitmentLevel.CONFIRMED,
    transactions: {}, slots: {}, transactionsStatus: {}, blocks: {}, blocksMeta: {}, entry: {}, accountsDataSlice: []
  };

  const stream = await subscribe(
    config,
    request,
    async (update: SubscribeUpdate) => {
      explainAccountUpdate(update);
    },
    async (err) => console.error('Stream error:', err)
  );

  console.log(`Account monitor started (id: ${stream.id})`);

  process.on('SIGINT', () => {
    console.log('\nCancelling stream...');
    stream.cancel();
    process.exit(0);
  });
}

function explainAccountUpdate(update: SubscribeUpdate) {
  if (!update.account) return;
  
  const account = update.account.account;
  
  // Decode the key addresses
  const tokenAccountAddress = bs58.encode(account.pubkey);
  const transactionSignature = account.txnSignature ? bs58.encode(account.txnSignature) : 'Unknown';
  
  // Extract and decode the token account data (165 bytes)
  const walletOwner = bs58.encode(account.data.slice(32, 64));       // Bytes 32-63: Owner
  const tokenAmount = account.data.readBigUInt64LE(64);              // Bytes 64-71: Amount
  const usdcAmount = Number(tokenAmount) / 1_000_000;                // Convert to USDC (6 decimals)
  
  console.log(`Account: ${tokenAccountAddress}`);
  console.log(`Owner: ${walletOwner}`);
  console.log(`Balance: ${usdcAmount.toLocaleString()} USDC`);
  console.log(`Slot: ${update.account.slot}`);
  console.log(`Transaction: ${transactionSignature.slice(0, 8)}...`);
  console.log('---');
}

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

Cuando ejecutes este monitor de USDC, verás un resultado claro y estructurado como este:

```
USDC Account Monitor
Account monitor started (id: abc123)

Account: 7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU
Owner: 9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM
Balance: 1,500 USDC
Slot: 352154103
Transaction: 5v8fy0eJ...
---
Account: BQy5rNRxLfcaK6554PMzsg4VJsFXzwGnAnayb8TZKgZX
Owner: HN7cABqLq46Es1jh92dQQisAq662SmxELLLsHHe4YWrH
Balance: 0 USDC
Slot: 352154103
Transaction: 5v8fy0eJ...
---
```

Cada bloque representa una cuenta de USDC cuyo estado cambió. La primera cuenta ahora contiene 1,500 USDC, mientras que la segunda se vació y ahora contiene 0 USDC. Obtienes el saldo actual inmediatamente después de cada transacción, junto con la cuenta específica que cambió y el momento en que ocurrió.

Las suscripciones de cuentas te muestran el resultado final de lo que ocurrió en cada cuenta, no los detalles de la transacción. Si necesitas comprender todo el contexto de la transacción (quién envió a quién, las comisiones, etc.), debes obtener la transacción completa mediante la firma mostrada.

## Referencia completa de filtrado

Además de los filtros básicos `owner`, `datasize` e `memcmp` que usamos, las suscripciones de cuentas admiten opciones de filtrado adicionales para limitar aún más los resultados:

### Filtrado de cuentas específicas

Monitorea cuentas exactas mediante sus claves públicas:

```ts theme={"system"}
accounts: {
  "specific-accounts": {
    account: [
      "7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU",
      "BQy5rNRxLfcaK6554PMzsg4VJsFXzwGnAnayb8TZKgZX"
    ]
  }
}
```

Este enfoque funciona bien cuando sabes exactamente qué cuentas son importantes para tu aplicación, como al monitorear las cuentas de tesorería de tu aplicación o cuentas de usuarios específicas.

Para conjuntos de cuentas muy grandes, las listas explícitas de pubkeys resultan costosas: 32 bytes por cuenta en la solicitud de suscripción. Para más de \~10,000 cuentas, usa un [filtro de cuco](/docs/es/laserstream/cuckoo-filters) comprimido (\~3–4 bytes por cuenta) para monitorear cientos de miles de cuentas en un único stream. Está disponible en los SDK de Rust y JavaScript.

### Estrategias de filtrado combinado

La potencia proviene de combinar varios tipos de filtros. Este es el modelo mental:

1. **Abarca un conjunto amplio** con `owner`: «Dame todas las cuentas administradas por este programa»
2. **Filtra por estructura** con `datasize`: «Pero solo las cuentas de este tipo específico»
3. **Apunta a datos específicos** con `memcmp`: «Y solo las que contienen esta información específica»
4. **Monitorea cuentas conocidas** con `account`: «O simplemente observa estas cuentas exactas que me interesan»

Por ejemplo, para monitorear cuentas de USDC de alto valor:

```ts theme={"system"}
accounts: {
  "high-value-usdc": {
    owner: ["TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"],
    filters: [
      { datasize: 165 },                           // Token accounts only
      { memcmp: { offset: 0, base58: USDC_MINT } } // USDC only
      // Note: You'd implement balance filtering in your callback logic
    ]
  }
}
```

La idea clave es que cada filtro reduce el volumen de actualizaciones que recibes. Sin filtrado, podrías recibir una cantidad abrumadora de actualizaciones de cuentas. Con un filtrado inteligente, solo recibes las actualizaciones relevantes para tu caso de uso específico.

### Comprensión del panorama general

Piensa en las suscripciones de cuentas como si observaras un feed en vivo de cambios en una base de datos. El estado de Solana es, en esencia, un enorme almacén de clave-valor donde cada cuenta es una entrada. Cuando los programas se ejecutan, modifican estas cuentas. Tu suscripción te permite observar en tiempo real cómo cambian entradas específicas.

El sistema de filtrado funciona como los índices de una base de datos: no solo observas «todos los cambios», sino «los cambios en las cuentas que cumplen estos criterios». Esto permite crear aplicaciones con gran capacidad de respuesta que reaccionan de inmediato a eventos relevantes en cadena sin saturar tu sistema con datos irrelevantes.

## Aplicación de este patrón a otros programas

El enfoque que aprendimos funciona para cualquier programa de Solana. Este es el patrón general:

1. **Investiga la estructura de las cuentas**: consulta el código fuente o la documentación del programa
2. **Comienza con el filtrado por propietario**: apunta al programa que administra las cuentas
3. **Aplica filtros estructurales**: usa el tamaño de la cuenta, patrones de datos u otras características para limitar los resultados a tipos de cuentas específicos
4. **Agrega filtros dirigidos**: céntrate en cuentas, estados o valores de datos específicos que sean importantes para tu aplicación
