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

# Account Monitoring

> Watch Solana accounts in real time with Yellowstone gRPC — track balances, token holders, program accounts, and account data changes instantly.

## Overview

Account monitoring lets you watch Solana accounts as they change. Track balances, data updates, ownership changes, and when accounts are created or deleted - all in real-time.

<Info>
  **You'll need:** Run `npm install @triton-one/yellowstone-grpc` first
</Info>

## Installation

```bash theme={null}
npm install @triton-one/yellowstone-grpc
```

## Complete Working Example

Here's a ready-to-use monitor that tracks account changes:

```javascript theme={null}
const Client = require("@triton-one/yellowstone-grpc").default;
const { CommitmentLevel } = require("@triton-one/yellowstone-grpc");

// Example: Monitor Token Program accounts
const TOKEN_PROGRAM = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";

// Connect to Yellowstone gRPC
const getClient = async () => {
  let client = false;
  try {
    client = new Client(
      process.env.GRPC_ENDPOINT || "https://grpc.solanatracker.io",
      process.env.GRPC_API_KEY,
      {
        "grpc.max_receive_message_length": 100 * 1024 * 1024,
      }
    );
    const version = await client.getVersion();
    if (version) {
      console.log("Connected to Yellowstone gRPC! Version:", version);
      return client;
    }
  } catch (e) {
    console.error("Failed to connect:", e);
  }
  if (!client) {
    throw new Error("Failed to connect!");
  }
};

// Track account updates
const accountStats = {
  totalUpdates: 0,
  uniqueAccounts: new Set(),
  totalLamports: 0,
  lastReportTime: Date.now()
};

(async () => {
  const client = await getClient();
  const stream = await client.subscribe();

  // Handle stream lifecycle
  const streamClosed = new Promise((resolve, reject) => {
    stream.on("error", (error) => {
      console.error("Stream error:", error);
    });
    stream.on("end", () => {
      console.log("Stream ended");
      resolve();
    });
    stream.on("close", () => {
      console.log("Stream closed");
      resolve();
    });
  });

  // Handle incoming account updates
  stream.on("data", (data) => {
    if (data?.account) {
      const account = data.account.account;
      accountStats.totalUpdates++;
      accountStats.uniqueAccounts.add(account.pubkey);
      
      console.log(`\n[Account Update #${accountStats.totalUpdates}]`);
      console.log(`  Account: ${account.pubkey.slice(0, 12)}...`);
      console.log(`  Owner: ${account.owner.slice(0, 12)}...`);
      console.log(`  Lamports: ${account.lamports.toLocaleString()}`);
      console.log(`  Data Size: ${account.data?.length || 0} bytes`);
      console.log(`  Slot: ${data.account.slot}`);
      console.log(`  Executable: ${account.executable ? 'Yes' : 'No'}`);
      console.log(`  Rent Epoch: ${account.rentEpoch}`);
      
      accountStats.totalLamports += account.lamports;
      
      // Report summary every 100 updates
      if (accountStats.totalUpdates % 100 === 0) {
        const now = Date.now();
        const elapsed = (now - accountStats.lastReportTime) / 1000;
        const updatesPerSec = 100 / elapsed;
        const avgLamports = accountStats.totalLamports / accountStats.totalUpdates;
        
        console.log(`\n=== Account Summary ===`);
        console.log(`  Total Updates: ${accountStats.totalUpdates}`);
        console.log(`  Unique Accounts: ${accountStats.uniqueAccounts.size}`);
        console.log(`  Updates/sec: ${updatesPerSec.toFixed(1)}`);
        console.log(`  Avg Lamports: ${avgLamports.toLocaleString()}`);
        console.log(`  Updates/Account: ${(accountStats.totalUpdates / accountStats.uniqueAccounts.size).toFixed(2)}`);
        
        accountStats.lastReportTime = now;
      }
    }
  });

  // Subscribe to accounts
  // Customize this request to watch the accounts you want
  const request = {
    accounts: {
      tokenAccounts: {
        account: [],                    // Specific accounts (empty = all)
        owner: [TOKEN_PROGRAM],        // Filter by owner program
        filters: [
          { dataSize: 165 }            // Optional: filter by data size
        ]
      }
    },
    slots: {},
    transactions: {},
    transactionsStatus: {},
    entry: {},
    blocks: {},
    blocksMeta: {},
    accountsDataSlice: [],             // Optional: slice data to reduce bandwidth
    ping: undefined,
    commitment: CommitmentLevel.CONFIRMED,
  };

  // Send subscribe request
  await new Promise((resolve, reject) => {
    stream.write(request, (err) => {
      if (err === null || err === undefined) {
        console.log("✅ Monitoring accounts\n");
        resolve();
      } else {
        reject(err);
      }
    });
  }).catch((reason) => {
    console.error("Subscribe failed:", reason);
    throw reason;
  });

  await streamClosed;
})();

// Graceful shutdown
process.on('SIGINT', () => {
  console.log('\n\nShutting down gracefully...');
  console.log(`Final Stats - Updates: ${accountStats.totalUpdates}, Accounts: ${accountStats.uniqueAccounts.size}`);
  process.exit(0);
});
```

## How to Filter Accounts

<Tabs>
  <Tab title="Specific Accounts">
    **Watch specific accounts by their address:**

    ```javascript theme={null}
        const request = {
          accounts: {
            specificAccounts: {
              account: [
                "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", // USDC mint
                "So11111111111111111111111111111111111111112"   // Wrapped SOL
              ],
              owner: [],
              filters: []
            }
          },
          commitment: CommitmentLevel.CONFIRMED,
          // ... other fields
        };
    ```
  </Tab>

  <Tab title="By Program Owner">
    **Watch all accounts owned by a program:**

    ```javascript theme={null}
        const request = {
          accounts: {
            tokenAccounts: {
              account: [],
              owner: ["TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"],
              filters: []
            }
          },
          commitment: CommitmentLevel.CONFIRMED,
          // ... other fields
        };
    ```
  </Tab>

  <Tab title="Advanced Filters">
    **Combine filters for precise targeting:**

    ```javascript theme={null}
        const request = {
          accounts: {
            filteredAccounts: {
              account: [],
              owner: ["TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"],
              filters: [
                { dataSize: 165 }, // Only token accounts
                { 
                  memcmp: { 
                    offset: 0, 
                    bytes: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" // Specific mint
                  } 
                }
              ]
            }
          },
          commitment: CommitmentLevel.CONFIRMED,
          // ... other fields
        };
    ```
  </Tab>
</Tabs>

## More Examples

### Example 2: Watch a Specific Wallet

Track a single wallet and see all its changes:

```javascript theme={null}
const WALLET_ADDRESS = "YourWalletAddressHere";

const walletStats = {
  updateCount: 0,
  lamportHistory: [],
  lastBalance: 0
};

(async () => {
  const client = await getClient();
  const stream = await client.subscribe();

  const streamClosed = new Promise((resolve, reject) => {
    stream.on("error", (error) => console.error("Stream error:", error));
    stream.on("end", () => resolve());
    stream.on("close", () => resolve());
  });

  stream.on("data", (data) => {
    if (data?.account) {
      const account = data.account.account;
      walletStats.updateCount++;
      
      const balanceChange = account.lamports - walletStats.lastBalance;
      walletStats.lamportHistory.push(account.lamports);
      
      if (walletStats.lamportHistory.length > 100) {
        walletStats.lamportHistory.shift();
      }
      
      walletStats.lastBalance = account.lamports;
    }
  });

  const request = {
    accounts: {
      wallet: {
        account: [WALLET_ADDRESS],
        owner: [],
        filters: []
      }
    },
    slots: {},
    transactions: {},
    transactionsStatus: {},
    entry: {},
    blocks: {},
    blocksMeta: {},
    accountsDataSlice: [],
    ping: undefined,
    commitment: CommitmentLevel.CONFIRMED,
  };

  await new Promise((resolve, reject) => {
    stream.write(request, (err) => {
      if (err === null || err === undefined) {
        console.log(`Monitoring wallet: ${WALLET_ADDRESS}\n`);
        resolve();
      } else {
        reject(err);
      }
    });
  });

  await streamClosed;
})();
```

### Example 3: Watch Program Accounts

Track all accounts owned by a specific program:

```javascript theme={null}
const PROGRAM_ID = "YourProgramIdHere";

const programStats = {
  totalUpdates: 0,
  accounts: new Map(),
  createdAccounts: 0,
  lastReportTime: Date.now()
};

(async () => {
  const client = await getClient();
  const stream = await client.subscribe();

  const streamClosed = new Promise((resolve, reject) => {
    stream.on("error", (error) => console.error("Stream error:", error));
    stream.on("end", () => resolve());
    stream.on("close", () => resolve());
  });

  stream.on("data", (data) => {
    if (data?.account) {
      const account = data.account.account;
      programStats.totalUpdates++;
      
      // Track if this is a new account
      const isNew = !programStats.accounts.has(account.pubkey);
      if (isNew) {
        programStats.createdAccounts++;
      }
      
      programStats.accounts.set(account.pubkey, {
        lamports: account.lamports,
        dataSize: account.data?.length || 0,
        lastUpdated: Date.now()
      });
      
      console.log(`\n[${isNew ? 'NEW' : 'UPDATE'} Account #${programStats.totalUpdates}]`);
      console.log(`  Account: ${account.pubkey.slice(0, 12)}...`);
      console.log(`  Owner: ${account.owner.slice(0, 12)}...`);
      console.log(`  Lamports: ${account.lamports.toLocaleString()}`);
      console.log(`  Data Size: ${account.data?.length || 0} bytes`);
      console.log(`  Slot: ${data.account.slot}`);
      
      if (isNew) {
        console.log(`  🎉 New account created!`);
      }
      
      // Report every 50 updates
      if (programStats.totalUpdates % 50 === 0) {
        const now = Date.now();
        const elapsed = (now - programStats.lastReportTime) / 1000;
        const updatesPerSec = 50 / elapsed;
        
        // Calculate total lamports across all accounts
        let totalLamports = 0;
        let totalDataSize = 0;
        programStats.accounts.forEach(acc => {
          totalLamports += acc.lamports;
          totalDataSize += acc.dataSize;
        });
        
        console.log(`\n=== Program Statistics ===`);
        console.log(`  Total Updates: ${programStats.totalUpdates}`);
        console.log(`  Active Accounts: ${programStats.accounts.size}`);
        console.log(`  New Accounts: ${programStats.createdAccounts}`);
        console.log(`  Updates/sec: ${updatesPerSec.toFixed(1)}`);
        console.log(`  Total Lamports: ${totalLamports.toLocaleString()}`);
        console.log(`  Avg Data Size: ${(totalDataSize / programStats.accounts.size).toFixed(0)} bytes`);
        
        programStats.lastReportTime = now;
      }
    }
  });

  const request = {
    accounts: {
      programAccounts: {
        account: [],
        owner: [PROGRAM_ID],
        filters: []
      }
    },
    slots: {},
    transactions: {},
    transactionsStatus: {},
    entry: {},
    blocks: {},
    blocksMeta: {},
    accountsDataSlice: [],
    ping: undefined,
    commitment: CommitmentLevel.CONFIRMED,
  };

  await new Promise((resolve, reject) => {
    stream.write(request, (err) => {
      if (err === null || err === undefined) {
        console.log(`✅ Monitoring program accounts: ${PROGRAM_ID}\n`);
        resolve();
      } else {
        reject(err);
      }
    });
  });

  await streamClosed;
})();
```

### Example 4: Monitor with Data Slicing

Only download the data you need to save bandwidth:

```javascript theme={null}
// Example: Only get balance data from token accounts
const TOKEN_PROGRAM = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";

const sliceStats = {
  totalUpdates: 0,
  balances: new Map()
};

(async () => {
  const client = await getClient();
  const stream = await client.subscribe();

  const streamClosed = new Promise((resolve, reject) => {
    stream.on("error", (error) => console.error("Stream error:", error));
    stream.on("end", () => resolve());
    stream.on("close", () => resolve());
  });

  stream.on("data", (data) => {
    if (data?.account) {
      const account = data.account.account;
      sliceStats.totalUpdates++;
      
      // Extract balance from the sliced data (if available)
      if (account.data && account.data.length >= 8) {
        try {
          const balanceBytes = Buffer.from(account.data.slice(0, 8));
          const balance = balanceBytes.readBigUInt64LE();
          
          const previousBalance = sliceStats.balances.get(account.pubkey) || 0n;
          sliceStats.balances.set(account.pubkey, balance);
          
          const change = balance - previousBalance;
          
          console.log(`\n[Balance Update #${sliceStats.totalUpdates}]`);
          console.log(`  Account: ${account.pubkey.slice(0, 12)}...`);
          console.log(`  Balance: ${balance.toString()}`);
          
          console.log(`  Slot: ${data.account.slot}`);
        } catch (e) {
          console.error("Error parsing balance:", e);
        }
      }
      
    }
  });

  const request = {
    accounts: {
      tokenAccounts: {
        account: [],
        owner: [TOKEN_PROGRAM],
        filters: [
          { dataSize: 165 } // Token account size
        ]
      }
    },
    slots: {},
    transactions: {},
    transactionsStatus: {},
    entry: {},
    blocks: {},
    blocksMeta: {},
    accountsDataSlice: [
      { offset: 64, length: 8 } // Only get balance (bytes 64-71)
    ],
    ping: undefined,
    commitment: CommitmentLevel.CONFIRMED,
  };

  await new Promise((resolve, reject) => {
    stream.write(request, (err) => {
      if (err === null || err === undefined) {
        console.log(`Monitoring token balances\n`);
        resolve();
      } else {
        reject(err);
      }
    });
  });

  await streamClosed;
})();
```

## Understanding Token Account Data

SPL Token accounts are 165 bytes with this structure:

```
Bytes   What It Is
-----   ----------
0-31    Mint address (which token)
32-63   Owner address (who owns it)
64-71   Balance (how many tokens)
72-75   Delegate option
76-107  Delegate address
108     Account state
109-112 Is native option
113-120 Is native amount
121-128 Delegated amount
129-132 Close authority option
133-164 Close authority address
```

## Common Filters

<Accordion title="Filter by Data Size">
  **Use this to filter account types:**

  ```javascript theme={null}
    filters: [
      { dataSize: 165 }  // Token accounts
    ]
  ```

  Common sizes:

  * Token accounts: 165 bytes
  * Mint accounts: 82 bytes
  * System accounts: 0 bytes (just SOL)
</Accordion>

<Accordion title="Filter by Memory Compare (memcmp)">
  **Use this to filter by specific data:**

  ```javascript theme={null}
    filters: [
      { 
        memcmp: { 
          offset: 0,  // Start at byte 0
          bytes: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"  // USDC mint
        } 
      }
    ]
  ```

  This checks if bytes at a specific offset match a value.
</Accordion>

<Accordion title="Combine Multiple Filters">
  **Use both together:**

  ```javascript theme={null}
    filters: [
      { dataSize: 165 },
      { 
        memcmp: { 
          offset: 0, 
          bytes: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"
        } 
      }
    ]
  ```

  All filters must match (AND logic).
</Accordion>

## Save Bandwidth with Data Slicing

Only download the parts of account data you need:

```javascript theme={null}
// Just get the balance (bytes 64-71) from token accounts
accountsDataSlice: [
  { offset: 64, length: 8 } // Token balance (u64)
]

// Get multiple slices
accountsDataSlice: [
  { offset: 0, length: 32 },   // Mint address
  { offset: 32, length: 32 },  // Owner address
  { offset: 64, length: 8 }    // Balance
]
```

## Tips for Better Performance

<CardGroup cols={2}>
  <Card title="Use Data Slicing" icon="scissors">
    Only request the bytes you need to save bandwidth
  </Card>

  <Card title="Filter Smart" icon="filter">
    Use dataSize and memcmp to reduce updates
  </Card>

  <Card title="Pick Right Commitment" icon="check">
    CONFIRMED is usually best for most use cases
  </Card>

  <Card title="Watch Your Volume" icon="chart-line">
    Monitor how many updates you're receiving
  </Card>
</CardGroup>

## What You Can Build

<Accordion title="Balance Trackers">
  Monitor wallet or token balances in real-time. Get alerts when balances change significantly.
</Accordion>

<Accordion title="Wallet Watchers">
  Track specific wallets and see all their account activity. Great for analyzing behavior patterns.
</Accordion>

<Accordion title="Program Analytics">
  Monitor all accounts in your program to understand usage, track growth, and detect issues.
</Accordion>

<Accordion title="New Account Alerts">
  Get instant notifications when new accounts are created for your program or specific token mints.
</Accordion>

<Accordion title="Portfolio Tracking">
  Watch multiple token accounts to track portfolio changes and calculate total values.
</Accordion>

## Common Questions

<Accordion title="How much data will I receive?">
  It depends on your filters. Watching a single wallet = very little data. Watching all token accounts = a lot of data. Always use filters to limit what you receive.
</Accordion>

<Accordion title="Can I watch multiple accounts?">
  Yes! Just add them to the `account` array. You can watch hundreds of specific accounts at once.
</Accordion>

<Accordion title="What's the difference between CONFIRMED and FINALIZED?">
  * **CONFIRMED** (\~400ms): Faster, good for most uses. Supermajority of validators agree.
  * **FINALIZED** (\~13 seconds): Slower but absolute certainty. Cannot be rolled back.
</Accordion>

<Accordion title="How do I reduce bandwidth usage?">
  Use data slicing (`accountsDataSlice`) to only get the bytes you need. For example, if you only care about balances, just request bytes 64-71 instead of the full 165-byte account data.
</Accordion>

<Accordion title="Can I filter by token mint?">
  Yes! Use a memcmp filter at offset 0 with the mint address. This will only show accounts for that specific token.
</Accordion>

<Accordion title="What's the difference between account and owner filters?">
  * **account**: Specific account addresses you want to watch
  * **owner**: All accounts owned by a program (like Token Program or your custom program)
</Accordion>

## Next Steps

<CardGroup cols={2}>
  <Card title="Transaction Monitoring" icon="receipt" href="/yellowstone-grpc/transaction-monitoring">
    Watch transactions too
  </Card>

  <Card title="Real Example: Pump.fun" icon="chart-line" href="/yellowstone-grpc/examples/pumpfun-accounts">
    See it in action
  </Card>

  <Card title="Best Practices" icon="star" href="/yellowstone-grpc/best-practices">
    Optimize your code
  </Card>

  <Card title="Quickstart" icon="rocket" href="/yellowstone-grpc/quickstart">
    Get started fast
  </Card>
</CardGroup>
