Learn how to subscribe to account updates and efficiently track on-chain state changes using Laserstream.
When building applications that need to respond to on-chain changes, polling RPC endpoints for account updates is both inefficient and slow. Account subscriptions solve this by delivering real-time updates about account state changes directly to your application.This guide covers everything you need to know about account subscriptions: what they are, how they work, and how to optimize them for your specific use case.
Skip this section if you’re familiar with Solana accounts and their structure.
Solana uses an account-based model where every piece of data lives in an account - a container that holds both data and metadata. Each account has:
Data: The actual bytes storing program state, token balances, or other information
Owner: The program that controls this account and can modify its data
Lamports: The account’s SOL balance for rent exemption
Executable: Whether this account contains program code
Programs are stateless - they don’t store data internally. Instead, they create and manage separate accounts to store their state. When you interact with a program, you pass in the accounts it should read from or write to.This design makes account subscriptions powerful: you can watch for changes to specific accounts, all accounts owned by a program, or accounts matching certain criteria.
What just happened? Our subscription worked perfectly! We asked Laserstream to notify us about token account changes, and it delivered an update about account BKMHWYLAX4un3HUbR7a3u9jPmzCiLNa4mSj1RiX11eWF.This account has:
2,039,280 lamports (~0.002 SOL balance - this is the rent-exempt amount for this token account)
Owner programTokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA (this is the SPL Token program)
Transaction signature5C9Hr5nG2j8eQz6inxPmfyjbYdmXddzUDyR1iQgEnjYQ3RNvuP4Zzc8t1enLNy7Rk8KNCtQPEQztENYWxkt9GaVD showing which specific transaction caused this account to change
Slot 352366983 indicating when this update occurred on the blockchain
Data field containing 165 bytes of account data encoded as base58
To understand why we need filtering, let’s first understand what token accounts actually are. For every token a wallet holds, there’s a separate account on-chain. If your wallet holds 3 different tokens (USDC, BONK, and SOL), you actually have 1 wallet account (your main SOL account) plus 3 token accounts (one for each token type). Each token account is exactly 165 bytes and stores: which token it holds (mint address), who owns it (your wallet address), and how much of that token it contains (amount).The Token Program owns millions of accounts on Solana, but not all of them are what we think of as “token accounts” holding user balances. Here’s what happens with and without filtering:Without filtering - The flood:
This subscribes to ALL accounts owned by the Token Program, which includes:
Token accounts (165 bytes) - User balances: millions of accounts
Mint accounts (82 bytes) - Token definitions: hundreds of thousands of accounts
Multisig accounts (355 bytes) - Shared wallet controls: tens of thousands of accounts
Associated Token Program accounts (various sizes) - millions of accounts
Result: Your application receives millions of account updates constantly, most of which you don’t care about.
With smart filtering - Surgical precision:
Report incorrect code
Copy
Ask AI
accounts: { "token-accounts-only": { owner: ["TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"], filters: [{ datasize: 165 }] // ✅ Only standard token accounts }}
This filters down to only the 165-byte accounts, which are specifically the user token balance accounts - exactly what you want for tracking token transfers, balance changes, and portfolio updates.The difference:
Without filtering: Millions of account updates (mint creations, multisig changes, etc.)
With datasize filtering: Only token balance changes
That’s a significant reduction in noise, focusing only on the accounts that actually represent user token holdings.
This isn’t magic - it comes from the SPL Token program’s account structure. Looking at the source code, we can see the Account struct defines exactly 165 bytes:
This fixed size allows us to filter precisely for standard token accounts and exclude:
Mint accounts (82 bytes)
Multisig accounts (355 bytes)
Associated token account program accounts
Other token-related accounts with different sizes
For calculating account sizes in other programs, check out the Anchor Space Reference - it shows you how much space different data types take (Pubkey = 32 bytes, u64 = 8 bytes, etc.).
Combining filters: datasize + memcmp for laser precision
Now that we know the mint address lives at bytes 0-31, we can get even more specific. Let’s say we only want to monitor USDC token accounts. We can combine our datasize filter with a memcmp filter to target the exact mint address:
Report incorrect code
Copy
Ask AI
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};
Progressive filtering strategy:
Owner filter: “Give me accounts owned by Token Program” (millions of accounts)
Datasize filter: “But only 165-byte standard token accounts” (hundreds of thousands)
Memcmp filter: “And only those holding USDC” (thousands)
This progression from broad to specific is the key to efficient account monitoring. Each filter narrows down the result set, so you only receive the exact updates you care about.Important: All filters use AND logic - every condition must be met for an account update to trigger.
Reading USDC account updates: Who, How Much, Where?
Now let’s see what these filtered updates actually contain. Let’s create a USDC-specific monitor that answers the key questions when a token account changes:
Who owns this token account?
How much USDC does it now contain?
Where (which specific account) changed?
When did this change happen?
What transaction caused the change?
The raw account updates contain binary data that we need to decode. Since Solana uses base58 encoding for addresses and signatures, we use the bs58.encode() function to convert binary Buffer objects to readable strings.
Each block represents a USDC account that changed state. The first account now holds 1,500 USDC, while the second account has been emptied to 0 USDC. You get the current balance immediately after each transaction, along with which specific account changed and when.Account subscriptions show you the end result of what happened to each account, not the transaction details. If you need to understand the full transaction context (who sent to whom, fees, etc.), you’d need to fetch the full transaction using the signature shown.
Beyond the basic owner, datasize, and memcmp filters we’ve used, account subscriptions support additional filtering options to further narrow your results:
This approach works well when you know exactly which accounts matter to your application - like monitoring your application’s treasury accounts or specific user accounts.
The power comes from combining multiple filter types. Here’s the mental model:
Cast a wide net with owner - “Give me all accounts managed by this program”
Filter by structure with datasize - “But only accounts of this specific type”
Target specific data with memcmp - “And only those containing this specific information”
Monitor known accounts with account - “Or just watch these exact accounts I care about”
For example, monitoring high-value USDC accounts:
Report incorrect code
Copy
Ask AI
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 ] }}
The key insight is that each filter reduces the volume of updates you receive. Without filtering, you might get overwhelming amounts of account updates. With smart filtering, you get only the updates that matter to your specific use case.
Think of account subscriptions as watching a live feed of database changes. Solana’s state is essentially a massive key-value store where each account is an entry. When programs execute, they modify these accounts. Your subscription lets you watch specific entries change in real-time.The filtering system works like database indexes - you’re not just watching “all changes” but rather “changes to accounts that match these criteria.” This makes it possible to build responsive applications that react immediately to relevant on-chain events without overwhelming your system with irrelevant data.