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

# Transaction Monitoring with LaserStream

> Stream Solana transactions in real-time with LaserStream — program filtering, execution details, token balance changes, and reconnect-safe replay.

Transaction monitoring lets you track transaction execution, success/failure status, program interactions, and token balance changes across Solana in real-time. This guide covers filtering strategies and practical implementations using the [`helius-laserstream`](https://www.helius.dev/docs/laserstream/clients) SDK.

> [!NOTE]
>
> **Prerequisites:** This guide assumes you've completed the [LaserStream gRPC Quickstart](https://www.helius.dev/docs/laserstream/grpc.md) and have an API key.

***

## Transaction Filtering Options

LaserStream uses the same filter shape as Yellowstone gRPC, including the `tokenAccounts` (ATA expansion) filter. The fields you'll set inside `transactions.<label>`:

* **`accountInclude`** — match if any of these accounts appears (logical OR)
* **`accountRequired`** — match only if all of these accounts appear (logical AND)
* **`accountExclude`** — drop if any of these accounts appears
* **`vote` / `failed`** — boolean flags for vote and failed transactions
* **`tokenAccounts`** — opt-in associated token account (ATA) expansion (`"balanceChanged"`, `"all"`, or `"none"`), so an `accountInclude` wallet also matches transactions where it owns an SPL token balance. See [Token Account (ATA) Filtering](https://www.helius.dev/docs/laserstream/token-account-filtering.md) and the **Watching a Wallet** tab below.
* **`matchMints`** — opt-in flag that also matches the account lists against the mints in a transaction's pre/post token balances, so a mint in `accountInclude` catches every transfer, swap, mint, and burn for that token. See [Token Mint Filtering](https://www.helius.dev/docs/laserstream/mint-filtering.md) and the **Watching a Token** tab below.

> [tab] Program Filtering
>
> **Monitor transactions involving specific programs**
>
> Track all transactions that touch programs you care about:
>
> ```typescript theme={"system"}
> import { subscribe, CommitmentLevel, LaserstreamConfig, SubscribeRequest } from 'helius-laserstream';
>
> const subscriptionRequest: SubscribeRequest = {
>   transactions: {
>     "program-filter": {
>       accountInclude: [
>         "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", // Token Program
>         "11111111111111111111111111111111",              // System Program
>         "675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8"  // Your program
>       ],
>       accountExclude: [],
>       accountRequired: [],
>       vote: false,
>       failed: false
>     }
>   },
>   commitment: CommitmentLevel.CONFIRMED,
>   accounts: {},
>   slots: {},
>   transactionsStatus: {},
>   blocks: {},
>   blocksMeta: {},
>   entry: {},
>   accountsDataSlice: [],
> };
> ```
>
> **Best for:** Program-specific monitoring, DeFi protocol tracking, smart contract interactions.

> [tab] Account-Specific
>
> **Monitor transactions affecting specific accounts**
>
> ```typescript theme={"system"}
> const subscriptionRequest: SubscribeRequest = {
>   transactions: {
>     "wallet-filter": {
>       accountInclude: [
>         "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", // USDC mint
>         "YourWalletAddress"                                // Your wallet
>       ],
>       accountExclude: [],
>       accountRequired: [],
>       vote: false,
>       failed: true // Include failures to track errors
>     }
>   },
>   commitment: CommitmentLevel.CONFIRMED,
>   accounts: {}, slots: {}, transactionsStatus: {},
>   blocks: {}, blocksMeta: {}, entry: {}, accountsDataSlice: [],
> };
> ```
>
> **Use case:** Wallet monitoring, token mint tracking, account activity dashboards.

> [tab] Advanced Filtering
>
> **Combine multiple filter criteria**
>
> ```typescript theme={"system"}
> const subscriptionRequest: SubscribeRequest = {
>   transactions: {
>     "advanced-filter": {
>       accountInclude: ["TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"],
>       accountRequired: ["YourProgramId"], // Must include this program
>       accountExclude: ["VoteProgram"],     // Exclude vote-related txs
>       vote: false,
>       failed: false
>     }
>   },
>   commitment: CommitmentLevel.CONFIRMED,
>   accounts: {}, slots: {}, transactionsStatus: {},
>   blocks: {}, blocksMeta: {}, entry: {}, accountsDataSlice: [],
> };
> ```
>
> **Filter logic:** `accountInclude` (OR) **AND** `accountRequired` (AND) **AND NOT** `accountExclude`.

> [tab] Watching a Wallet
>
> **Catch incoming token transfers, not just direct activity**
>
> `accountInclude` only matches transactions where the wallet appears directly in the account keys. When someone sends the wallet an SPL token, the transfer touches the wallet's **associated token account (ATA)**, not the wallet pubkey — so a plain `accountInclude: [wallet]` never sees it.
>
> Set `tokenAccounts` to expand matching to transactions where the wallet **owns** a token balance. It takes a string:
>
> * **`"balanceChanged"`** — match when an owned token balance changed (or its token account was closed). Best for "tell me when money actually moved" — narrower, lower-volume, the recommended default.
> * **`"all"`** — match any transaction referencing an owned token balance, even if unchanged. Substantially higher volume.
> * **`"none"`** — no expansion (same as omitting the field).
>
> ```typescript theme={"system"}
> const subscriptionRequest: SubscribeRequest = {
>   transactions: {
>     "wallet-with-tokens": {
>       accountInclude: ["YourWalletAddress"],
>       accountExclude: [],
>       accountRequired: [],
>       vote: false,
>       failed: false,
>       tokenAccounts: "balanceChanged" // also match the wallet's ATAs
>     }
>   },
>   commitment: CommitmentLevel.CONFIRMED,
>   accounts: {}, slots: {}, transactionsStatus: {},
>   blocks: {}, blocksMeta: {}, entry: {}, accountsDataSlice: [],
> };
> ```
>
> > [!NOTE]
> >
> > Matching is owner-based — it catches any token account the wallet owns (including non-canonical ones), not just the derived ATA address. The SDK converts the string to the wire-level `TokenAccountExpansionControlFlag` enum for you.

> [tab] Watching a Token
>
> **Catch every transaction for a mint, not just the ones that name it**
>
> A plain `accountInclude: [mint]` only matches transactions whose account keys contain the mint, such as `MintTo`, `Burn`, or `TransferChecked`. A classic SPL `Transfer` never references the mint, so most transfers of the token are missed.
>
> Set `matchMints: true` and the account lists are also matched against the mints in the transaction's `preTokenBalances` and `postTokenBalances`:
>
> ```typescript theme={"system"}
> const USDC = 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v';
>
> const subscriptionRequest: SubscribeRequest = {
>   transactions: {
>     "usdc-activity": {
>       accountInclude: [USDC],
>       accountExclude: [],
>       accountRequired: [],
>       vote: false,
>       failed: false,
>       matchMints: true // also match via pre/post token-balance mints
>     }
>   },
>   commitment: CommitmentLevel.CONFIRMED,
>   accounts: {}, slots: {}, transactionsStatus: {},
>   blocks: {}, blocksMeta: {}, entry: {}, accountsDataSlice: [],
> };
> ```
>
> Combine it with `tokenAccounts` to watch one token for one wallet: put the wallet in `accountInclude` with `tokenAccounts: "balanceChanged"`, and the mint in `accountRequired` with `matchMints: true`. Requires `helius-laserstream` 0.8.5+ (JS), 0.6.4+ (Rust), or `go/v0.3.0`+ (Go). See [Token Mint Filtering](https://www.helius.dev/docs/laserstream/mint-filtering.md) for full semantics.

***

## Practical Examples

### Example 1: Monitor DEX Transactions

Track transactions touching popular DEX programs:

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

async function monitorDEXTransactions() {
  const subscriptionRequest: SubscribeRequest = {
    transactions: {
      "dex-filter": {
        accountInclude: [
          "675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8", // Raydium
          "CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK", // Raydium CLMM
          "JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4"   // Jupiter
        ],
        accountExclude: [],
        accountRequired: [],
        vote: false,
        failed: false
      }
    },
    commitment: CommitmentLevel.CONFIRMED,
    accounts: {}, slots: {}, transactionsStatus: {},
    blocks: {}, blocksMeta: {}, entry: {}, accountsDataSlice: [],
  };

  const config: LaserstreamConfig = {
    apiKey: 'YOUR_API_KEY',
    endpoint: 'https://laserstream-mainnet-ewr.helius-rpc.com', // Choose your closest region
  };

  await subscribe(config, subscriptionRequest, async (data) => {
    if (!data.transaction?.transaction) return;
    const tx = data.transaction.transaction;
    console.log(`\n🔄 DEX Transaction:`);
    console.log(`  Signature: ${bs58.encode(tx.signature)}`);
    console.log(`  Slot: ${data.transaction.slot}`);
    console.log(`  Status: ${tx.meta?.err ? 'Failed' : 'Success'}`);
    console.log(`  Fee: ${tx.meta?.fee || 0} lamports`);
    console.log(`  Compute Units: ${tx.meta?.computeUnitsConsumed || 0}`);

    // Token balance changes
    if (tx.meta?.preTokenBalances?.length > 0) {
      console.log(`  Token Balance Changes:`);
      tx.meta.preTokenBalances.forEach((preBalance: any, index: number) => {
        const postBalance = tx.meta.postTokenBalances[index];
        if (preBalance && postBalance) {
          const change = postBalance.uiTokenAmount.uiAmount - preBalance.uiTokenAmount.uiAmount;
          if (change !== 0) {
            console.log(`    ${preBalance.mint}: ${change > 0 ? '+' : ''}${change}`);
          }
        }
      });
    }
  }, async (error) => {
    console.error('Stream error:', error);
  });
}

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

### Example 2: Monitor Failed Transactions

Track failed transactions to surface application issues:

```typescript [expandable] theme={"system"}
async function monitorFailedTransactions() {
  const subscriptionRequest: SubscribeRequest = {
    transactions: {
      "failures": {
        accountInclude: ["YourProgramId"],
        accountExclude: [],
        accountRequired: [],
        vote: false,
        failed: true // Only failed transactions
      }
    },
    commitment: CommitmentLevel.CONFIRMED,
    accounts: {}, slots: {}, transactionsStatus: {},
    blocks: {}, blocksMeta: {}, entry: {}, accountsDataSlice: [],
  };

  const config: LaserstreamConfig = {
    apiKey: 'YOUR_API_KEY',
    endpoint: 'https://laserstream-mainnet-ewr.helius-rpc.com',
  };

  await subscribe(config, subscriptionRequest, async (data) => {
    if (!data.transaction?.transaction?.meta?.err) return;
    const tx = data.transaction.transaction;
    console.log(`\n❌ Failed Transaction:`);
    console.log(`  Signature: ${bs58.encode(tx.signature)}`);
    console.log(`  Slot: ${data.transaction.slot}`);
    console.log(`  Error: ${JSON.stringify(tx.meta.err)}`);
    console.log(`  Fee: ${tx.meta.fee} lamports`);
    console.log(`  Compute Units: ${tx.meta.computeUnitsConsumed || 0}`);
  }, async (error) => {
    console.error('Stream error:', error);
  });
}
```

### Example 3: Monitor High-Value Transactions

Track transactions with significant SOL transfers:

```typescript [expandable] theme={"system"}
async function monitorHighValueTransactions() {
  const subscriptionRequest: SubscribeRequest = {
    transactions: {
      "system-program": {
        accountInclude: ["11111111111111111111111111111111"],
        accountExclude: [],
        accountRequired: [],
        vote: false,
        failed: false
      }
    },
    commitment: CommitmentLevel.CONFIRMED,
    accounts: {}, slots: {}, transactionsStatus: {},
    blocks: {}, blocksMeta: {}, entry: {}, accountsDataSlice: [],
  };

  const config: LaserstreamConfig = {
    apiKey: 'YOUR_API_KEY',
    endpoint: 'https://laserstream-mainnet-ewr.helius-rpc.com',
  };

  await subscribe(config, subscriptionRequest, async (data) => {
    if (!data.transaction?.transaction?.meta) return;
    const tx = data.transaction.transaction;
    const preBalances = tx.meta.preBalances || [];
    const postBalances = tx.meta.postBalances || [];

    let maxChange = 0;
    preBalances.forEach((preBalance: number, index: number) => {
      const postBalance = postBalances[index] || 0;
      maxChange = Math.max(maxChange, Math.abs(postBalance - preBalance));
    });

    const changeInSOL = maxChange / 1e9;
    if (changeInSOL > 10) {
      console.log(`\n💰 High-Value Transaction:`);
      console.log(`  Signature: ${bs58.encode(tx.signature)}`);
      console.log(`  Slot: ${data.transaction.slot}`);
      console.log(`  Max SOL Transfer: ${changeInSOL.toFixed(2)} SOL`);
      console.log(`  Fee: ${tx.meta.fee / 1e9} SOL`);
    }
  }, async (error) => {
    console.error('Stream error:', error);
  });
}
```

### Example 4: Watch a Wallet (incl. token transfers)

Monitor everything that moves money for a wallet — including incoming SPL token transfers that touch its ATAs — by adding `tokenAccounts` to a plain `accountInclude` filter:

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

async function watchWallet(wallet: string) {
  const subscriptionRequest: SubscribeRequest = {
    transactions: {
      "wallet-activity": {
        accountInclude: [wallet],
        accountExclude: [],
        accountRequired: [],
        vote: false,
        failed: false,
        // Also match txs touching token accounts this wallet owns.
        // "balanceChanged" = only when an owned token balance actually moved.
        tokenAccounts: "balanceChanged"
      }
    },
    commitment: CommitmentLevel.CONFIRMED,
    accounts: {}, slots: {}, transactionsStatus: {},
    blocks: {}, blocksMeta: {}, entry: {}, accountsDataSlice: [],
  };

  const config: LaserstreamConfig = {
    apiKey: 'YOUR_API_KEY',
    endpoint: 'https://laserstream-mainnet-ewr.helius-rpc.com',
  };

  await subscribe(config, subscriptionRequest, async (data) => {
    if (!data.transaction?.transaction) return;
    const tx = data.transaction.transaction;
    console.log(`\n👛 Wallet activity:`);
    console.log(`  Signature: ${bs58.encode(tx.signature)}`);
    console.log(`  Slot: ${data.transaction.slot}`);

    // Surface token balances this wallet owns that changed in the tx
    const owned = (tx.meta?.postTokenBalances || []).filter((b: any) => b.owner === wallet);
    owned.forEach((post: any) => {
      const pre = (tx.meta.preTokenBalances || []).find(
        (b: any) => b.accountIndex === post.accountIndex
      );
      const before = pre?.uiTokenAmount?.uiAmount || 0;
      const after = post.uiTokenAmount?.uiAmount || 0;
      if (after !== before) {
        console.log(`  ${post.mint}: ${after - before > 0 ? '+' : ''}${after - before}`);
      }
    });
  }, async (error) => {
    console.error('Stream error:', error);
  });
}
```

***

## Transaction Data Structure

> [accordion] Transaction Message Structure
>
> ```typescript theme={"system"}
> {
>   signature: string;
>   isVote: boolean;
>   transaction: {
>     message: {
>       accountKeys: string[];        // All accounts involved
>       instructions: Instruction[];  // Program instructions
>       recentBlockhash: string;
>     };
>     signatures: string[];
>   };
>   meta: {
>     err: any;                      // Error details if failed
>     fee: number;                   // Transaction fee in lamports
>     computeUnitsConsumed: number;
>     preBalances: number[];
>     postBalances: number[];
>     preTokenBalances: TokenBalance[];
>     postTokenBalances: TokenBalance[];
>     logMessages: string[];
>   };
> }
> ```

> [accordion] Token Balance Changes
>
> ```typescript theme={"system"}
> {
>   accountIndex: number;
>   mint: string;
>   owner: string;
>   uiTokenAmount: {
>     amount: string;
>     decimals: number;
>     uiAmount: number;
>     uiAmountString: string;
>   };
> }
> ```

> [accordion] Instruction Details
>
> ```typescript theme={"system"}
> {
>   programIdIndex: number; // Index in accountKeys array
>   accounts: number[];
>   data: string;           // Instruction data (base58)
> }
> ```

***

## Filter Logic Reference

> [card] Include Logic (OR)
>
> **`accountInclude`:** Transaction must involve ANY of these accounts.
>
> `["A", "B"]` matches transactions involving account A OR account B.

> [card] Required Logic (AND)
>
> **`accountRequired`:** Transaction must involve ALL of these accounts.
>
> `["A", "B"]` matches transactions involving account A AND account B.

> [card] Exclude Logic (NOT)
>
> **`accountExclude`:** Transaction must NOT involve any of these accounts.

> [card] Combined Logic
>
> Final filter: `(accountInclude OR empty) AND (accountRequired AND all) AND NOT (accountExclude OR any)`.

***

## Performance Considerations

> [tab] Volume Management
>
> Transaction streams can be high-volume. To keep up:
>
> * Start with specific program filters (don't subscribe to "all transactions")
> * Use `confirmed` over `processed` when you can tolerate \~1.5s extra latency
> * Monitor your processing capacity with a counter
> * Consider running parallel consumers behind a queue
>
> ```typescript theme={"system"}
> let count = 0;
> const startTime = Date.now();
> // inside your subscribe handler:
> count++;
> if (count % 100 === 0) {
>   const elapsed = (Date.now() - startTime) / 1000;
>   console.log(`Processing ${(count / elapsed).toFixed(1)} tx/sec`);
> }
> ```

> [tab] Data Processing
>
> Extract only what you need to keep memory low:
>
> ```typescript theme={"system"}
> import bs58 from 'bs58';
>
> function extractTransactionData(tx: any) {
>   return {
>     signature: bs58.encode(tx.signature),
>     slot: tx.slot,
>     success: !tx.meta?.err,
>     fee: tx.meta?.fee || 0,
>     computeUnits: tx.meta?.computeUnitsConsumed || 0,
>   };
> }
> ```

***

## Error Handling

> [accordion] Too Many Transactions
>
> **Symptom:** Overwhelming transaction volume.
>
> **Solutions:** Add stricter filters (`accountRequired`, `accountExclude`); use higher commitment; implement sampling or rate limiting; process asynchronously.

> [accordion] Missing Transactions
>
> **Symptom:** Expected transactions not appearing.
>
> **Solutions:** Verify program addresses are correct; check that the transactions actually exist; try `processed` for faster updates; loosen restrictive `accountRequired`/`accountExclude` filters.

> [accordion] Parse Errors
>
> **Symptom:** Cannot parse transaction data.
>
> **Solutions:** Handle missing fields gracefully; validate structure before processing; wrap parsing in try/catch; see [Decoding Transaction Data](https://www.helius.dev/docs/laserstream/guides/decoding-transaction-data.md).

***

## Next Steps

> [card] [Slot & Block Monitoring](https://www.helius.dev/docs/laserstream/guides/slot-and-block-monitoring.md)
>
> Track network consensus and block production.

> [card] [Stream Pump AMM Data](https://www.helius.dev/docs/laserstream/guides/stream-pump-amm-data.md)
>
> Real-world example: monitor Pump.fun AMM transactions.

> [card] [Decoding Transaction Data](https://www.helius.dev/docs/laserstream/guides/decoding-transaction-data.md)
>
> Parse the binary transaction payloads into readable Solana transactions.

> [card] [Yellowstone protocol reference](https://www.helius.dev/docs/grpc/transaction-monitoring)
>
> The same workflow against the raw Yellowstone gRPC protocol.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.