# Changelog Source: https://docs.solanatracker.io/changelog Product updates and announcements across our platform

Changelog

## Data API * **Improved price change percentages** — More accurate percent change calculations. * **Improved chart wicks** — Chart API shows more accurate wicks via a new aggregation technique. * **Fixed chart volume in some cases** — Ensures volume uses all pools when not filtering by pool. * **Added Humidifi, Tessera, and Solfi V1/V2 support.** ## Datastream * **Fixed price change percentages on stats room** — Corrected percent change calculations in the stats room. * **Improved pool selection** — Better pool selection on token and price rooms. * **Transaction monitor updates** — Added Raptor to the transaction monitor * **Added Humidifi, Tessera, and Solfi V1/V2 support.** * **Stability improvements** — Fixed and improved stability of transaction and price updates. ## Data API * **Chart currency option** — `/chart/{token}` and `/chart/{token}/{pool}` now support `currency=usd|sol|eur` (default `usd`). [View documentation →](/data-api/chart/get-ohlcv-data-for-a-token#parameter-currency) ## Data API * **New bundlers endpoint** — Use `/tokens/{token}/bundlers` to fetch bundler wallet stats for a token. * **Risk now includes bundlers** — The `risk` object returned on token responses includes bundler analysis. * **Search improvements** — `/search` now returns `riskScore` and bundler-related fields. ### Risk scores * **Bundlers** — New count-based + percentage-based scoring from new bundler data. * **Suspicious volume** — Flags extreme buy/sell imbalance. ## Datastream * **New room:** `bundlers:{tokenAddress}` — Real-time bundler wallet updates (wallet, current/previous amount & %, and totalBundlerPercentage). ## Top Performers * **Top performers endpoint** — Added `/top-performers/{timeframe}` to retrieve top performing tokens launched today. * **Timeframes** — Supports `5m`, `15m`, `30m`, `1h`, `6h`, `12h`, and `24h`. ## Data API * **First buyers endpoint accuracy** — Improved accuracy for first buyers endpoints to match with other PnL endpoints * **Pricing and volume fixes** — Fixed pricing and volume calculation bug on some trades * **Exact token price at trade time** — Added exact token price at time of trade for each token when using the `showMeta` flag on `/trades` ## Datastream - Trade rooms * **Global Load Balancer** - Added a global load balancer for the Datastream that routes to US or EU based Datastream servers automatically. * **Enhanced Datastream trade rooms** — Added `price` and `marketCap` to each token in Datastream trade rooms for better accuracy (uses main pool data). The main object `priceUsd` can be considered the exchange rate. ## Image proxy * **Image proxy filtering** — Enhanced image proxy filtering for invalid SVGs with reduced false positives * **Faster transaction and price updates** — Reduced latency for real-time data delivery * **Enhanced tokens overview API** — Pulse/Memescope now features improved filters and dramatically better performance * **Upgraded Yellowstone gRPC** — New Rust client delivers faster streaming performance ## Data Accuracy & Reliability Major improvements to data quality and consistency: * **Program filtering** — Excluded unknown programs that could cause invalid data from arbitrage bots and other sources * **Pool data updates** — Liquidity changes now trigger pool updates even without swaps * **Time accuracy improvements** — Edge case handling now uses `blockTime` when gRPC is behind or transaction parsing is delayed by more than a few seconds ## New Features **Automatic Pool Rotation** * Main pool rotation now happens automatically for `price:token:{address}` and `token:{primary}` rooms — no client-side changes required **Aggregated Price Room** * New **`price:aggregated:{token}`** room provides min, average, median, and max price updates * Aggregates price data across top 10 valid pools * Each update includes individual pool prices for full transparency **Pumpfun Mayhem Mode** * Added support for Pumpfun Mayhem mode * Pool data now includes `tokenProgram` and `isMayhemMode` fields ## Infrastructure **Redis Server Upgrade** * Deployed new Redis infrastructure * Eliminates random 500/503 errors during high load periods * Improved overall platform stability ## Aggregated Price Updates We've added a new WebSocket room that provides aggregated price data across multiple liquidity pools for any token. ### What's New **`price:aggregated:{tokenAddress}`** * **Multi-pool aggregation** — Get median, average, min, and max prices calculated across top liquidity pools * **Top pools data** — Detailed breakdown of the 10 highest liquidity pools with individual prices * **Real-time updates** — Live price aggregation as pools update * **Pool count tracking** — Know exactly how many pools contributed to the aggregated data * **Automatic pool rotation** - No need to subscribe to new rooms after token migration. ### Why Use Aggregated Pricing? Single pool prices can be manipulated or show temporary volatility. Aggregated pricing gives you: * **More accurate market prices** — Median and average across multiple sources * **Manipulation resistance** — Harder to distort when aggregating across pools * **Price range visibility** — See min/max spread to identify arbitrage opportunities * **Liquidity-weighted insights** — Focus on pools that matter most ```json theme={null} { "type": "message", "data": { "token": "So11111111111111111111111111111111111111112", "timestamp": 1762964449215, "price": 153.70666202103925, "pool": "3nMFwZXwY1s1M5s8vYAHqd4wGs4iSxXE4LRoUMMYqEgF", "aggregated": { "median": 153.6850599485249, "average": 153.6735820182654, "min": 153.36863801111053, "max": 153.71662179961638, "poolCount": 10 }, "topPools": [ { "poolId": "3ucNos4NbumPLZNWztqGHNFFgkHeRMBQAVemeeomsUxv", "price": 153.68241854665007, "liquidity": 17950616.091940343, "market": "raydium-clmm" }, ... ] }, "room": "price:aggregated:So11111111111111111111111111111111111111112" } ``` Perfect for trading interfaces, price oracles, and portfolio tracking applications that need reliable, manipulation-resistant pricing. [View documentation →](/datastream/websockets/priceaggregated) ## getTokenAccountsByOwners — Batch Balance Lookups We've added a new Ridge DB-powered RPC method that lets you query token balances for multiple wallets in a single request. ### What's New **`getTokenAccountsByOwners`** * **Batch up to 250 wallets** — Query hundreds of addresses in one call instead of making individual requests * **Multiple accounts support** — Automatically returns all token accounts if a wallet has multiple for the same mint * **Zero balance handling** — Wallets without a token account return a clean zero balance response * **Slot tracking** — Each account includes the slot when the balance was last updated ### Use Cases Perfect for: * Portfolio tracking across multiple wallets * Airdrop eligibility verification * Multi-wallet dashboards * Token distribution snapshots ```json theme={null} { "jsonrpc": "2.0", "id": 1, "method": "getTokenAccountsByOwners", "params": [ ["wallet1...", "wallet2...", "wallet3..."], "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" ] } ``` This method drastically reduces API calls and latency when you need to check token holdings across many addresses at once. [View documentation →](/solana-rpc/http/gettokenaccountsbyowners) ## Ridge Stream — Enhanced Holder Data All holder endpoints and real-time streams have been migrated to **Ridge Stream**, from our new high-performance service RidgeDB ### What's New **REST Endpoints** * **`/tokens/{token}/holders`** — Get top 100 holders (use /tokens//holders/pagination for pagination and higher limits ) * **`/tokens/{token}/holders/top`** — Fetch top holders by balance (excluding known wallets like lp) * **`/holders/multi`** — Multi-token holder count lookups * **`/holders/chart`** — Historical holder charts with more datapoints (now also includes SOL, USDC, and more) **Real-Time Streams** * **Holder count updates** — Live tracking of total holders * **Wallet balance updates** — Now includes native SOL with improved reliability * **Top 100 holders** — Real-time rankings of largest holders * **Snipers and insiders** — Track early buyers and insider activity You can now also get holder data for **large tokens** like WSOL, USDC, and USD1 that weren't available before. All holder counts and top 100 rankings work for these tokens via both API and real-time streaming. ## New Search Filters Added new filters to the token search endpoint: * **`launchpad`** — Filter by launch platform (`pumpfun`, `moonshot`, `letsbonk.fun`, `believe`) * **`image`** — Search by exact image URL * **`hasImage`** — Filter tokens that have/don't have images (`true`/`false`) [View documentation →](/data-api/search/token-search) ## Paginated Token Holders Endpoint We've launched a new endpoint for fetching all token holders with cursor-based pagination. ### What's New **`GET /tokens/{tokenAddress}/holders/paginated`** * **Cursor pagination** — Navigate through all holders efficiently * **Up to 5,000 per page** — Fetch large batches in a single request * **Ridge DB powered** — Fast queries even for tokens with millions of holders * **Complete holder data** — Returns wallet, token account, amount, USD value, total holder count and ownership percentage ### The Improvement Previously limited to 100 holders, this endpoint now handles tokens with any number of holders while maintaining consistent performance. ```bash theme={null} GET /tokens/{tokenAddress}/holders/paginated?limit=1000&cursor=nextPageCursor ``` [View documentation →](/data-api/tokens/get-all-token-holders-paginated) ## Ridge DB Performance & Features Update We've shipped several improvements to our Ridge DB-powered RPC methods to give you even more control and speed. ### What's New * **Total count support** — `getProgramAccountsV2` and `getTokenAccountsByOwnerV2` now return `totalCount` in responses, making pagination and UI updates easier * **Faster queries** — New indexes dramatically improve response times for certain account lookups * **Stability improvements** — Bug fixes and optimizations These updates build on our Ridge DB launch, delivering better performance and more developer-friendly responses. ## Solana Ridge DB We've rolled out **two new RPC methods** powered by **Ridge DB** — our high-performance engine designed for lightning-fast account lookups on Solana. ### 🧭 New Methods #### `getProgramAccountsV2` Supercharged program account queries with: * **`changedSince`** — Fetch only accounts updated after a given slot (great for incremental syncs) * **`excludeZero`** — Automatically skip empty token accounts * **Cursor pagination** — Retrieve up to 10,000 accounts per request #### `getTokenAccountsByOwnerV2` Optimized token account retrieval, featuring the same Ridge DB speed and: * **`changedSince`** — Get only new or updated token accounts * **`excludeZero`** — Filter out dust or empty balances * **Cursor pagination** — Smoothly handle large token sets ### 💡 Why Ridge DB? Ridge DB isn't your standard RPC backend. It's a purpose-built system for account-heavy workloads that delivers: * Faster response times * Slot-based incremental updates * Cursor pagination for big datasets In short — faster queries, cleaner data, and a smoother developer experience. ## New Documentation Site We've launched a brand-new documentation hub that brings all our products and APIs together, complete with guides and hands-on examples. You can now explore detailed docs for: * **Yellowstone gRPC** — Real-time Solana data streaming * **Data API** — REST access to historical and live Solana data * **Datastream** — WebSocket-based live data feeds * **Solana RPC** — Enhanced endpoints powered by Ridge DB * **Swap API** — High-performance token swaps and routing The new docs include interactive code samples, and best practices to help you build faster and smarter on Solana. ## getTokenAccountsByOwners — Batch Balance Lookups We've added a new Ridge DB-powered RPC method that lets you query token balances for multiple wallets in a single request. ### What's New **`getTokenAccountsByOwners`** * **Batch up to 250 wallets** — Query hundreds of addresses in one call instead of making individual requests * **Multiple accounts support** — Automatically returns all token accounts if a wallet has multiple for the same mint * **Zero balance handling** — Wallets without a token account return a clean zero balance response * **Slot tracking** — Each account includes the slot when the balance was last updated ### Use Cases Perfect for: * Portfolio tracking across multiple wallets * Airdrop eligibility verification * Multi-wallet dashboards * Token distribution snapshots ```json theme={null} { "jsonrpc": "2.0", "id": 1, "method": "getTokenAccountsByOwners", "params": [ ["wallet1...", "wallet2...", "wallet3..."], "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" ] } ``` This method drastically reduces API calls and latency when you need to check token holdings across many addresses at once. [View documentation →](/solana-rpc/http/gettokenaccountsbyowners) ## Ridge DB Performance & Features Update We've shipped several improvements to our Ridge DB-powered RPC methods to give you even more control and speed. ### What's New * **Total count support** — `getProgramAccountsV2` and `getTokenAccountsByOwnerV2` now return `totalCount` in responses, making pagination and UI updates easier * **Faster queries** — New indexes dramatically improve response times for certain account lookups * **Stability improvements** — Bug fixes and optimizations These updates build on our Ridge DB launch, delivering better performance and more developer-friendly responses. ## Solana Ridge DB We've rolled out **two new RPC methods** powered by **Ridge DB**, our high-performance engine designed for lightning-fast account lookups on Solana. ### 🧭 New Methods #### `getProgramAccountsV2` Supercharged program account queries with: * **`changedSince`** — Fetch only accounts updated after a given slot (great for incremental syncs) * **`excludeZero`** — Automatically skip empty token accounts * **Cursor pagination** — Retrieve up to 10,000 accounts per request #### `getTokenAccountsByOwnerV2` Optimized token account retrieval, featuring the same Ridge DB speed and: * **`changedSince`** — Get only new or updated token accounts * **`excludeZero`** — Filter out dust or empty balances * **Cursor pagination** — Smoothly handle large token sets ### 💡 Why Ridge DB? Ridge DB isn't your standard RPC backend. It's a purpose-built system for account-heavy workloads that delivers: * Faster response times * Slot-based incremental updates * Cursor pagination for big datasets In short — faster queries, cleaner data, and a smoother developer experience.
No updates yet. Check back soon!
## Data API * **Improved price change percentages** — More accurate percent change calculations. * **Improved chart wicks** — Chart API shows more accurate wicks via a new aggregation technique. * **Fixed chart volume in some cases** — Ensures volume uses all pools when not filtering by pool. ## Data API * **Chart currency option** — `/chart/{token}` and `/chart/{token}/{pool}` now support `currency=usd|sol|eur` (default `usd`). [View documentation →](/data-api/chart/get-ohlcv-data-for-a-token#parameter-currency) ## Data API * **New bundlers endpoint** — Use `/tokens/{token}/bundlers` to fetch bundler wallet stats for a token. * **Risk now includes bundlers** — The `risk` object returned on token responses includes bundler analysis. * **Search improvements** — `/search` now returns `riskScore` and bundler-related fields. ### Risk scores * **Bundlers** — New count-based + percentage-based scoring from new bundler data. * **Suspicious volume** — Flags extreme buy/sell imbalance. ## Top Performers * **Top performers endpoint** — Added `/top-performers/{timeframe}` to retrieve top performing tokens launched today. * **Timeframes** — Supports `5m`, `15m`, `30m`, `1h`, `6h`, `12h`, and `24h`. ## Data API * **First buyers endpoint accuracy** — Improved accuracy for first buyers endpoints to match with other PnL endpoints * **Pricing and volume fixes** — Fixed pricing and volume calculation bug on some trades * **Exact token price at trade time** — Added exact token price at time of trade for each token when using the `showMeta` flag on `/trades` ## Datastream - Trade rooms * **Global Load Balancer** - Added a global load balancer for the Datastream that routes to US or EU based Datastream servers automatically. * **Enhanced Datastream trade rooms** — Added `price` and `marketCap` to each token in Datastream trade rooms for better accuracy (uses main pool data). The main object `priceUsd` can be considered the exchange rate. ## Image proxy * **Image proxy filtering** — Enhanced image proxy filtering for invalid SVGs with reduced false positives ## Ridge Stream — Enhanced Holder Data All holder endpoints and real-time streams have been migrated to **Ridge Stream**, from our new high-performance service RidgeDB ### What's New **REST Endpoints** * **`/tokens/{token}/holders`** — Get top 100 holders (use /tokens//holders/pagination for pagination and higher limits ) * **`/tokens/{token}/holders/top`** — Fetch top holders by balance (excluding known wallets like lp) * **`/holders/multi`** — Multi-token holder count lookups * **`/holders/chart`** — Historical holder charts with more datapoints (now also includes SOL, USDC, and more) **Real-Time Streams** * **Holder count updates** — Live tracking of total holders * **Wallet balance updates** — Now includes native SOL with improved reliability * **Top 100 holders** — Real-time rankings of largest holders * **Snipers and insiders** — Track early buyers and insider activity You can now also get holder data for **large tokens** like WSOL, USDC, and USD1 that weren't available before. All holder counts and top 100 rankings work for these tokens via both API and real-time streaming. ## New Search Filters Added new filters to the token search endpoint: * **`launchpad`** — Filter by launch platform (`pumpfun`, `moonshot`, `letsbonk.fun`, `believe`) * **`image`** — Search by exact image URL * **`hasImage`** — Filter tokens that have/don't have images (`true`/`false`) [View documentation →](/data-api/search/token-search) ## Paginated Token Holders Endpoint We've launched a new endpoint for fetching all token holders with cursor-based pagination. ### What's New **`GET /tokens/{tokenAddress}/holders/paginated`** * **Cursor pagination** — Navigate through all holders efficiently * **Up to 5,000 per page** — Fetch large batches in a single request * **Ridge DB powered** — Fast queries even for tokens with millions of holders * **Complete holder data** — Returns wallet, token account, amount, USD value, total holder count and ownership percentage ### The Improvement Previously limited to 100 holders, this endpoint now handles tokens with any number of holders while maintaining consistent performance. ```bash theme={null} GET /tokens/{tokenAddress}/holders/paginated?limit=1000&cursor=nextPageCursor ``` [View documentation →](/data-api/tokens/get-all-token-holders-paginated) ## Datastream * **Fixed price change percentages on stats room** — Corrected percent change calculations in the stats room. * **Improved pool selection** — Better pool selection on token and price rooms. * **Transaction monitor updates** — Added Raptor to the transaction monitor; added Humidifi, Tessera, and Solfi V1/V2 support. * **Stability improvements** — Fixed and improved stability of transaction and price updates. ## Datastream * **New room:** `bundlers:{tokenAddress}` — Real-time bundler wallet updates (wallet, current/previous amount & %, and totalBundlerPercentage). ## Datastream - Trade rooms * **Global Load Balancer** - Added a global load balancer for the Datastream that routes to US or EU based Datastream servers automatically. * **Enhanced Datastream trade rooms** — Added `price` and `marketCap` to each token in Datastream trade rooms for better accuracy (uses main pool data). The main object `priceUsd` can be considered the exchange rate. * **Faster transaction and price updates** — Reduced latency for real-time data delivery * **Enhanced tokens overview API** — Pulse/Memescope now features improved filters and dramatically better performance * **Upgraded Yellowstone gRPC** — New Rust client delivers faster streaming performance ## Data Accuracy & Reliability Major improvements to data quality and consistency: * **Program filtering** — Excluded unknown programs that could cause invalid data from arbitrage bots and other sources * **Pool data updates** — Liquidity changes now trigger pool updates even without swaps * **Time accuracy improvements** — Edge case handling now uses `blockTime` when gRPC is behind or transaction parsing is delayed by more than a few seconds ## New Features **Automatic Pool Rotation** * Main pool rotation now happens automatically for `price:token:{address}` and `token:{primary}` rooms — no client-side changes required **Aggregated Price Room** * New **`price:aggregated:{token}`** room provides min, average, median, and max price updates * Aggregates price data across top 10 valid pools * Each update includes individual pool prices for full transparency **Pumpfun Mayhem Mode** * Added support for Pumpfun Mayhem mode * Pool data now includes `tokenProgram` and `isMayhemMode` fields ## Infrastructure **Redis Server Upgrade** * Deployed new Redis infrastructure * Eliminates random 500/503 errors during high load periods * Improved overall platform stability ## Aggregated Price Updates We've added a new WebSocket room that provides aggregated price data across multiple liquidity pools for any token. ### What's New **`price:aggregated:{tokenAddress}`** * **Multi-pool aggregation** — Get median, average, min, and max prices calculated across top liquidity pools * **Top pools data** — Detailed breakdown of the 10 highest liquidity pools with individual prices * **Real-time updates** — Live price aggregation as pools update * **Pool count tracking** — Know exactly how many pools contributed to the aggregated data * **Automatic pool rotation** - No need to subscribe to new rooms after token migration. ### Why Use Aggregated Pricing? Single pool prices can be manipulated or show temporary volatility. Aggregated pricing gives you: * **More accurate market prices** — Median and average across multiple sources * **Manipulation resistance** — Harder to distort when aggregating across pools * **Price range visibility** — See min/max spread to identify arbitrage opportunities * **Liquidity-weighted insights** — Focus on pools that matter most ```json theme={null} { "type": "message", "data": { "token": "So11111111111111111111111111111111111111112", "timestamp": 1762964449215, "price": 153.70666202103925, "pool": "3nMFwZXwY1s1M5s8vYAHqd4wGs4iSxXE4LRoUMMYqEgF", "aggregated": { "median": 153.6850599485249, "average": 153.6735820182654, "min": 153.36863801111053, "max": 153.71662179961638, "poolCount": 10 }, "topPools": [ { "poolId": "3ucNos4NbumPLZNWztqGHNFFgkHeRMBQAVemeeomsUxv", "price": 153.68241854665007, "liquidity": 17950616.091940343, "market": "raydium-clmm" }, ... ] }, "room": "price:aggregated:So11111111111111111111111111111111111111112" } ``` Perfect for trading interfaces, price oracles, and portfolio tracking applications that need reliable, manipulation-resistant pricing. [View documentation →](/datastream/websockets/priceaggregated) ## Ridge Stream — Enhanced Holder Data All holder endpoints and real-time streams have been migrated to **Ridge Stream**, from our new high-performance service RidgeDB ### What's New **Real-Time Streams** * **Holder count updates** — Live tracking of total holders * **Wallet balance updates** — Now includes native SOL with improved reliability * **Top 100 holders** — Real-time rankings of largest holders * **Snipers and insiders** — Track early buyers and insider activity
No updates yet. Check back soon!
## New Documentation Site We've launched a brand-new documentation hub that brings all our products and APIs together — complete with guides, SDKs, and hands-on examples. You can now explore detailed docs for: * **Yellowstone gRPC** — Real-time Solana data streaming * **Data API** — REST access to historical and live Solana data * **Datastream** — WebSocket-based live data feeds * **Solana RPC** — Enhanced endpoints powered by Ridge DB * **Swap API** — High-performance token swaps and routing The new docs include interactive code samples, SDK walkthroughs, and best practices to help you build faster and smarter on Solana.
# Get Bundlers Chart Data Source: https://docs.solanatracker.io/data-api/chart/get-bundlers-chart-data data-api/openapi.json get /bundlers/chart/{token} Gets bundler holding percentage data over time. Returns up to 1000 of the most recent data points # Get Holders Chart Data Source: https://docs.solanatracker.io/data-api/chart/get-holders-chart-data data-api/openapi.json get /holders/chart/{token} Gets token holder count data over time. Returns up to 1000 of the most recent data points # Get Insiders Chart Data Source: https://docs.solanatracker.io/data-api/chart/get-insiders-chart-data data-api/openapi.json get /insiders/chart/{token} Gets insider holding percentage data over time. Returns up to 1000 of the most recent data points # Get OHLCV Data for a token Source: https://docs.solanatracker.io/data-api/chart/get-ohlcv-data-for-a-token data-api/openapi.json get /chart/{token} Gets OHLCV (Open, High, Low, Close, Volume) data for charts # Get OHLCV Data for a token/pool pair Source: https://docs.solanatracker.io/data-api/chart/get-ohlcv-data-for-a-tokenpool-pair data-api/openapi.json get /chart/{token}/{pool} Gets OHLCV (Open, High, Low, Close, Volume) data for charts for a specific pool # Get Snipers Chart Data Source: https://docs.solanatracker.io/data-api/chart/get-snipers-chart-data data-api/openapi.json get /snipers/chart/{token} Gets sniper holding percentage data over time. Returns up to 1000 of the most recent data points # Get API Credits Source: https://docs.solanatracker.io/data-api/credits/get-api-credits data-api/openapi.json get /credits Gets the remaining API credits for your API key # Get Subscription Information Source: https://docs.solanatracker.io/data-api/credits/get-subscription-information data-api/openapi.json get /subscription Gets current subscription information including credits, plan, and billing details # Get Pool Events Source: https://docs.solanatracker.io/data-api/events/get-pool-events data-api/openapi.json get /events/{tokenAddress}/{poolAddress} Gets raw event data for a specific token and pool. Returns binary data that needs to be decoded # Get Token Events Source: https://docs.solanatracker.io/data-api/events/get-token-events data-api/openapi.json get /events/{tokenAddress} Gets raw event data for live processing. Returns binary data that needs to be decoded # Get First Token Buyers Source: https://docs.solanatracker.io/data-api/pnl/get-first-token-buyers data-api/openapi.json get /first-buyers/{token} Retrieves the first 100 buyers of a token (since API started recording data) with PnL data for each wallet # Get Token-Specific PnL Source: https://docs.solanatracker.io/data-api/pnl/get-token-specific-pnl data-api/openapi.json get /pnl/{wallet}/{token} Gets Profit and Loss data for a specific token in a wallet # Get Wallet PnL Source: https://docs.solanatracker.io/data-api/pnl/get-wallet-pnl data-api/openapi.json get /pnl/{wallet} Gets Profit and Loss data for all positions of a wallet # Get Historic Price Information Source: https://docs.solanatracker.io/data-api/price/get-historic-price-information data-api/openapi.json get /price/history Gets historic price information for a single token # Get lowest and highest price in time range Source: https://docs.solanatracker.io/data-api/price/get-lowest-and-highest-price-in-time-range data-api/openapi.json get /price/history/range Gets the lowest and highest price in a time range # Get Multiple Token Prices Source: https://docs.solanatracker.io/data-api/price/get-multiple-token-prices data-api/openapi.json get /price/multi Gets price information for multiple tokens (up to 100) # Get Price at Specific Timestamp Source: https://docs.solanatracker.io/data-api/price/get-price-at-specific-timestamp data-api/openapi.json get /price/history/timestamp Gets specific historic price information for a token at a given timestamp # Get Token Price Source: https://docs.solanatracker.io/data-api/price/get-token-price data-api/openapi.json get /price Gets price information for a single token # Post Multiple Token Prices Source: https://docs.solanatracker.io/data-api/price/post-multiple-token-prices data-api/openapi.json post /price/multi Similar to GET /price/multi, but accepts an array of token addresses in the request body # Risk Score Source: https://docs.solanatracker.io/data-api/risk Token risk assessment methodology and scoring system The Risk Score API evaluates token investment risk across multiple factors, generating a normalized score from 1-10 to help users make informed trading decisions on Solana. ## Risk Categories Risk factors are organized into four severity-based categories: Potential issues requiring attention but may have legitimate explanations Significant red flags indicating high risk or potential malicious activity Token liquidity and holder concentration issues Early buyer and insider wallet concentration risks ## Scoring Methodology Final risk scores are normalized to 1-10 based on the sum of individual risk factor weights. Higher scores indicate higher risk. ## Warning Risk Factors | Risk Factor | Description | Score | | ------------------------ | --------------------------------------------------------------------- | ---------- | | No social media | Token has no associated social media links | 2000 | | No file metadata | Token has no file metadata | 100 | | Pump.fun contract risks | Pump.fun contracts can be changed by Pump.fun at any time | 10 | | Incomplete bonding curve | No Raydium liquidity pool, bonding curve not complete | 4000 | | Transitioning to Raydium | Token is currently transitioning to Raydium | 100 | | Price decrease | Price decreased by more than 50% in the last 24 hours | 1000 | | Suspicious Volume | High percentage of buy transactions indicating potential manipulation | 1000-10000 | ## Danger Risk Factors | Risk Factor | Description | Score | | ------------------------ | --------------------------------------------------------------- | ----- | | Freeze Authority Enabled | Tokens can be frozen and prevented from trading in the future | 7500 | | Mint Authority Enabled | More tokens can be minted by the owner at any time | 2500 | | Rugged | No liquidity, token is considered unsafe to purchase (Rugcheck) | 20000 | | LP Burned | Allows the owner to remove liquidity at any time | 4000 | ### Dynamic Fee Risk (Meteora Curve) | Fee Range | Level | Score | | --------- | ------ | ----- | | >5% | Danger | 1000 | | >10% | Danger | 1500 | | >20% | Danger | 3000 | | >40% | Danger | 4000 | | >50% | Danger | 5000 | | >75% | Danger | 7500 | ## Liquidity Risk Factors ### Liquidity Levels | Risk Factor | Description | Level | Score | | ------------------ | ---------------------------------------------- | ------- | ----- | | Very Low Liquidity | The total liquidity for this token is very low | Danger | 7500 | | Low Liquidity | The total liquidity for this token is low | Warning | 5000 | ### Holders | Threshold | Level | Score | | --------- | ------ | ----- | | >90% | Danger | 7000 | | >80% | Danger | 6000 | | >70% | Danger | 4600 | | >60% | Danger | 4400 | | >50% | Danger | 4300 | | >40% | Danger | 4100 | | >30% | Danger | 3500 | | >20% | Danger | 2500 | | >10% | Danger | 2000 | | Risk Factor | Description | Level | Score | | -------------- | ---------------------------------------------------- | ------ | ----- | | Top 10 Holders | Top 10 holders own more than 15% of the total supply | Danger | 5000 | ## Snipers and Insiders ### Snipers Risk Levels Wallets that bought tokens within the first few transactions or blocks: | Ownership Percentage | Level | Score | | -------------------- | ------- | ----- | | >50% | Danger | 10000 | | >30% | Danger | 6000 | | >20% | Danger | 4000 | | >10% | Warning | 3000 | ### Insiders Risk Levels Wallets potentially connected to token creators or team: | Ownership Percentage | Level | Score | | -------------------- | ------- | ----- | | >50% | Danger | 10000 | | >30% | Danger | 7000 | | >20% | Danger | 5000 | | >10% | Warning | 3000 | ### Bundlers Risk Levels Wallets that bundled tokens (bought in coordinated groups): | Count/Percentage | Level | Score | | ---------------- | ------- | ----- | | ≥1000 wallets | Danger | 15000 | | ≥500 wallets | Danger | 10000 | | ≥100 wallets | Warning | 5000 | | ≥1 wallet | Warning | 2000 | | Ownership Percentage | Level | Score | | -------------------- | ------- | ----- | | >50% | Danger | 15000 | | >30% | Danger | 10000 | | >15% | Danger | 7000 | | >5% | Warning | 3000 | ### Developer Holdings Risk Levels Wallets identified as belonging to the token developer or creator: | Ownership Percentage | Level | Score | | -------------------- | ------- | ----- | | >50% | Danger | 10000 | | >30% | Danger | 8000 | | >20% | Danger | 6000 | | >10% | Danger | 4000 | | >5% | Warning | 2500 | | >1% | Warning | 1000 | ## API Response Risk scores are returned on all `token` objects with enhanced snipers, insiders, bundlers, and developer data. ```json theme={null} { "risk": { "snipers": { "count": 5, "totalBalance": 50000000, "totalPercentage": 12.5, "wallets": [ { "address": "WalletAddress1...", "balance": 20000000, "percentage": 5.0 } ] }, "insiders": { "count": 3, "totalBalance": 30000000, "totalPercentage": 7.5, "wallets": [ { "address": "WalletAddress2...", "balance": 15000000, "percentage": 3.75 } ] }, "bundlers": { "count": 25, "totalBalance": 10000000, "totalPercentage": 2.5, "wallets": [ { "address": "BundleWallet1...", "balance": 500000, "percentage": 0.125 } ] }, "top10": 25.5, "dev": { "percentage": 15.0, "amount": 15000000 }, "rugged": false, "risks": [ { "name": "Snipers", "description": "Snipers own 12.50% of the total supply.", "value": "12.50%", "level": "warning", "score": 3000 }, { "name": "Bundler Holdings", "description": "Bundlers own 2.50% of the total supply.", "value": "2.50%", "level": "warning", "score": 3000 }, { "name": "Dev Holdings", "description": "Developer holds 15.00% of the total supply.", "value": "15.00%", "level": "danger", "score": 4000 }, { "name": "Bonding curve not complete", "description": "No raydium liquidity pool, bonding curve not complete", "level": "warning", "score": 4000 } ], "score": 5, "jupiterVerified": false } } ``` # Token Search Source: https://docs.solanatracker.io/data-api/search/token-search data-api/openapi.json get /search Searches for tokens by symbol, name, or address with various filtering and sorting options # Get Token-Pool Stats Source: https://docs.solanatracker.io/data-api/stats/get-token-pool-stats data-api/openapi.json get /stats/{token}/{pool} Gets detailed stats for a token-pool pair over various time intervals # Get Token Stats Source: https://docs.solanatracker.io/data-api/stats/get-token-stats data-api/openapi.json get /stats/{token} Gets detailed stats for a token over various time intervals # Get All-Time High Price Source: https://docs.solanatracker.io/data-api/tokens/get-all-time-high-price data-api/openapi.json get /tokens/{tokenAddress}/ath Retrieves the all-time high price of a token (since the data API started recording) # Get All Token Holders (Paginated) Source: https://docs.solanatracker.io/data-api/tokens/get-all-token-holders-paginated data-api/openapi.json get /tokens/{tokenAddress}/holders/paginated Gets token holders with cursor-based pagination support. Returns holders sorted by amount in descending order. # Get Graduated Tokens Source: https://docs.solanatracker.io/data-api/tokens/get-graduated-tokens data-api/openapi.json get /tokens/multi/graduated Overview of all graduated launchpad tokens (Pump.fun, letsbonk.fun, jupiter studio etc) # Get Graduating Tokens Source: https://docs.solanatracker.io/data-api/tokens/get-graduating-tokens data-api/openapi.json get /tokens/multi/graduating Overview of all graduating launchpad tokens (Pump.fun, letsbonk.fun, jupiter studio etc) # Get Latest Tokens Source: https://docs.solanatracker.io/data-api/tokens/get-latest-tokens data-api/openapi.json get /tokens/latest Retrieves the latest 100 tokens # Get Multiple Tokens Source: https://docs.solanatracker.io/data-api/tokens/get-multiple-tokens data-api/openapi.json post /tokens/multi Accepts an array of token addresses in the request body (up to 20 per request) # Get Token Bundlers Source: https://docs.solanatracker.io/data-api/tokens/get-token-bundlers data-api/openapi.json get /tokens/{token}/bundlers Retrieves bundler information for a specific token, including top 500 bundler wallets and statistics # Get Token by Pool Address Source: https://docs.solanatracker.io/data-api/tokens/get-token-by-pool-address data-api/openapi.json get /tokens/by-pool/{poolAddress} Retrieves token information by searching with a pool address # Get Token Holders (Top 100) Source: https://docs.solanatracker.io/data-api/tokens/get-token-holders-top-100 data-api/openapi.json get /tokens/{tokenAddress}/holders Gets the top 100 holders for a specific token and the total amount # Get Token Information Source: https://docs.solanatracker.io/data-api/tokens/get-token-information data-api/openapi.json get /tokens/{tokenAddress} Retrieves comprehensive information about a specific token # Get Token Overview Source: https://docs.solanatracker.io/data-api/tokens/get-token-overview data-api/openapi.json get /tokens/multi/all Gets an overview of latest, graduating, and graduated tokens (Axiom Pulse / Photon Memescope style) # Get Tokens by Deployer Source: https://docs.solanatracker.io/data-api/tokens/get-tokens-by-deployer data-api/openapi.json get /deployer/{wallet} Retrieves all tokens created by a specific wallet with pagination support # Get Tokens by Volume Source: https://docs.solanatracker.io/data-api/tokens/get-tokens-by-volume data-api/openapi.json get /tokens/volume Retrieves the top 100 tokens sorted by highest volume within the default timeframe # Get Tokens by Volume with Timeframe Source: https://docs.solanatracker.io/data-api/tokens/get-tokens-by-volume-with-timeframe data-api/openapi.json get /tokens/volume/{timeframe} Retrieves the top 100 tokens sorted by highest volume within the specified timeframe # Get Top 20 Token Holders Source: https://docs.solanatracker.io/data-api/tokens/get-top-20-token-holders data-api/openapi.json get /tokens/{tokenAddress}/holders/top Gets the top 20 holders for a token # Get Top Performing Tokens Source: https://docs.solanatracker.io/data-api/tokens/get-top-performing-tokens data-api/openapi.json get /top-performers/{timeframe} Retrieves the top performing tokens launched today within the specified timeframe # Get Trending Tokens Source: https://docs.solanatracker.io/data-api/tokens/get-trending-tokens data-api/openapi.json get /tokens/trending Gets the top 100 trending tokens based on transaction volume in the specified timeframe (default: past hour) # Get Trending Tokens by Timeframe Source: https://docs.solanatracker.io/data-api/tokens/get-trending-tokens-by-timeframe data-api/openapi.json get /tokens/trending/{timeframe} Gets the top 100 trending tokens based on transaction volume in the specified timeframe # Get Top Traders (All Tokens) Source: https://docs.solanatracker.io/data-api/top-traders/get-top-traders-all-tokens data-api/openapi.json get /top-traders/all Gets the most profitable traders across all tokens # Get Top Traders (All Tokens) with Pagination Source: https://docs.solanatracker.io/data-api/top-traders/get-top-traders-all-tokens-with-pagination data-api/openapi.json get /top-traders/all/{page} Gets the most profitable traders across all tokens, with optional pagination # Get Top Traders for Specific Token Source: https://docs.solanatracker.io/data-api/top-traders/get-top-traders-for-specific-token data-api/openapi.json get /top-traders/{token} Gets top 100 traders by PnL for a token # Get Pool-Specific Trades Source: https://docs.solanatracker.io/data-api/trades/get-pool-specific-trades data-api/openapi.json get /trades/{tokenAddress}/{poolAddress} Gets the latest trades for a specific token and pool pair # Get Token Trades Source: https://docs.solanatracker.io/data-api/trades/get-token-trades data-api/openapi.json get /trades/{tokenAddress} Gets the latest trades for a token across all pools # Get User-Specific Pool Trades Source: https://docs.solanatracker.io/data-api/trades/get-user-specific-pool-trades data-api/openapi.json get /trades/{tokenAddress}/{poolAddress}/{owner} Gets the latest trades for a specific token, pool, and wallet address # Get User-Specific Token Trades Source: https://docs.solanatracker.io/data-api/trades/get-user-specific-token-trades data-api/openapi.json get /trades/{tokenAddress}/by-wallet/{owner} Gets the latest trades for a specific token and wallet address # Get Basic Wallet Information Source: https://docs.solanatracker.io/data-api/wallet/get-basic-wallet-information data-api/openapi.json get /wallet/{owner}/basic Gets all tokens in a wallet with current value in USD (lightweight, non-cached option) # Get Wallet Portfolio Chart Source: https://docs.solanatracker.io/data-api/wallet/get-wallet-portfolio-chart data-api/openapi.json get /wallet/{owner}/chart Gets wallet portfolio chart data with historical values and PnL information # Get Wallet Tokens Source: https://docs.solanatracker.io/data-api/wallet/get-wallet-tokens data-api/openapi.json get /wallet/{owner} Gets all tokens in a wallet with current value in USD # Get Wallet Tokens with Pagination Source: https://docs.solanatracker.io/data-api/wallet/get-wallet-tokens-with-pagination data-api/openapi.json get /wallet/{owner}/page/{page} Retrieves wallet tokens using pagination with a limit of 250 tokens per request # Get Wallet Trades Source: https://docs.solanatracker.io/data-api/wallet/get-wallet-trades data-api/openapi.json get /wallet/{owner}/trades Gets the latest trades of a wallet # Bundlers Source: https://docs.solanatracker.io/datastream/websockets/bundlers Subscribe to bundler wallet activity changes for a token # Curve percentage Source: https://docs.solanatracker.io/datastream/websockets/curvepercentage Subscribe to curve percentage alerts (when tokens reach specific bonding curve percentages) # Developer holdings Source: https://docs.solanatracker.io/datastream/websockets/developerholdings Subscribe to developer holdings changes for a token # Fee tracking Source: https://docs.solanatracker.io/datastream/websockets/feetracking Subscribe to fee tracking for a token (Global fees) # Graduated Source: https://docs.solanatracker.io/datastream/websockets/graduated Subscribe to just graduated tokens # Graduating Source: https://docs.solanatracker.io/datastream/websockets/graduating Subscribe to tokens that are graduating (approaching bonding curve completion) # Holders Source: https://docs.solanatracker.io/datastream/websockets/holders Subscribe to holder count changes for a token # Insider tracking Source: https://docs.solanatracker.io/datastream/websockets/insidertracking Subscribe to insider balance and percentage changes for a token # Latest tokens Source: https://docs.solanatracker.io/datastream/websockets/latesttokens Subscribe to new tokens and pools created on Solana. # Metadata Source: https://docs.solanatracker.io/datastream/websockets/metadata Subscribe to token metadata updates # Pool statistics Source: https://docs.solanatracker.io/datastream/websockets/poolstatistics Subscribe to pool statistics (multi-timeframe statistics) # Pool transactions Source: https://docs.solanatracker.io/datastream/websockets/pooltransactions Subscribe to transactions for a specific token in a specific pool # Pool updates Source: https://docs.solanatracker.io/datastream/websockets/poolupdates Subscribe to pool updates for a specific pool # Pool wallet transactions Source: https://docs.solanatracker.io/datastream/websockets/poolwallettransactions Subscribe to transactions for pool and wallet # Price aggregated Source: https://docs.solanatracker.io/datastream/websockets/priceaggregated Subscribe to aggregated price updates across multiple pools for a token # Price all pools Source: https://docs.solanatracker.io/datastream/websockets/priceallpools Subscribe to price updates for a token from all pools # Price by pool Source: https://docs.solanatracker.io/datastream/websockets/pricebypool Subscribe to price updates for a specific pool # Price by token Source: https://docs.solanatracker.io/datastream/websockets/pricebytoken Subscribe to price updates for a specific token from the primary pool # Sniper tracking Source: https://docs.solanatracker.io/datastream/websockets/snipertracking Subscribe to sniper balance and percentage changes for a token # Token changes Source: https://docs.solanatracker.io/datastream/websockets/tokenchanges Subscribe to all pool changes for a token # Token primary Source: https://docs.solanatracker.io/datastream/websockets/tokenprimary Subscribe to primary pool updates for a token (based on liquidity) # Token statistics Source: https://docs.solanatracker.io/datastream/websockets/tokenstatistics Subscribe to token stats (multi-timeframe statistics) # Token transactions Source: https://docs.solanatracker.io/datastream/websockets/tokentransactions Subscribe to swap transactions for a token # Top10 holders Source: https://docs.solanatracker.io/datastream/websockets/top10holders Subscribe to top 10 holders percentage changes for a token # Wallet balance Source: https://docs.solanatracker.io/datastream/websockets/walletbalance Subscribe to wallet balance changes # Wallet token balance Source: https://docs.solanatracker.io/datastream/websockets/wallettokenbalance Subscribe to specific token balance changes for a wallet # Wallet transactions Source: https://docs.solanatracker.io/datastream/websockets/wallettransactions Subscribe to swap transactions for a wallet # Get Started Source: https://docs.solanatracker.io/index Build fast, scalable Solana apps with powerful APIs, real-time data, and enterprise-grade RPC. Everything you need to build on Solana. Trade across all major Solana DEXs. Yellowstone gRPC streams with unlimited bandwidth. Custom RPC methods like our getProgramAccountsV2 endpoint. ## RPC Infrastructure Global, low-latency Solana RPC nodes. Private, high-performance infrastructure. ## Streaming & Data APIs Fastest shreds powered by Jito, available on all plans Real-time token and wallet data. Official Solana websocket RPC methods Official Solana http methods Your first API call in seconds. Compare shared and dedicated RPC nodes. Pricing for token, market, and analytics data. ## Core APIs ## Connect Connect with 1,500+ Solana developers. Contact the SolanaTracker team. # Quick Start Source: https://docs.solanatracker.io/quickstart Get up and running with the Solana Tracker Data API in under 5 minutes This guide will help you make your first API calls to the Solana Tracker Data API. You'll learn how to authenticate, make requests, and handle responses. ## Prerequisites Before you begin, make sure you have: Sign up for free to get your API key. ## Step 1: Get Your API Key After creating your account, retrieve your API key from the dashboard. This key is used to authenticate your requests by passing it in the `x-api-key` header. Visit [your account dashboard](https://www.solanatracker.io/account). Click on the "API Keys" section in the sidebar. Click the copy button next to your API key. Keep your API key secure and never commit it to public repositories. ## Step 2: Make Your First API Call Let's start by fetching the current price of SOL using the `/price` endpoint. ```bash cURL theme={null} curl -X GET "https://data.solanatracker.io/price?token=So11111111111111111111111111111111111111112" \ -H "x-api-key: YOUR_API_KEY" ``` ```javascript JavaScript theme={null} const apiKey = 'YOUR_API_KEY'; const solMint = 'So11111111111111111111111111111111111111112'; fetch(`https://data.solanatracker.io/price?token=${solMint}`, { headers: { 'x-api-key': apiKey } }) .then(response => response.json()) .then(data => console.log('SOL Price:', data.price)); ``` ```python Python theme={null} import requests api_key = 'YOUR_API_KEY' sol_mint = 'So11111111111111111111111111111111111111112' response = requests.get( f'https://data.solanatracker.io/price?token={sol_mint}', headers={ 'x-api-key': api_key } ) data = response.json() print(f"SOL Price: ${data['price']}") ``` ```json Success Response theme={null} { "price": 175.42, "priceQuote": 175.42, "liquidity": 14187570.69, "marketCap": 99213713748.19, "lastUpdated": 1760274132867 } ``` ## Step 3: Connect to RPC Nodes In addition to the Data API, Solana Tracker provides high-performance RPC nodes for direct blockchain interactions. Note that the RPC endpoint URL and authentication method (passing the API key in the URL path) are different from the Data API. ```javascript @solana/web3.js theme={null} import { Connection, PublicKey } from '@solana/web3.js'; const connection = new Connection( 'https://rpc-mainnet.solanatracker.io?api_key=YOUR_API_KEY', { commitment: 'confirmed', wsEndpoint: 'wss://rpc-mainnet.solanatracker.io?api_key=YOUR_API_KEY' } ); // Get account balance const publicKey = new PublicKey('YOUR_WALLET_ADDRESS'); const balance = await connection.getBalance(publicKey); console.log(`Balance: ${balance / 1e9} SOL`); // Subscribe to account changes connection.onAccountChange( publicKey, (accountInfo) => { console.log('Account updated:', accountInfo); } ); ``` ```python solana.py theme={null} from solana.rpc.api import Client from solders.pubkey import Pubkey # Connect to RPC client = Client("https://rpc-mainnet.solanatracker.io?api_key=YOUR_API_KEY") # Get account balance pubkey = Pubkey.from_string("YOUR_WALLET_ADDRESS") response = client.get_balance(pubkey) balance = response.value / 1e9 print(f"Balance: {balance} SOL") # Get recent blockhash blockhash = client.get_latest_blockhash() print(f"Recent blockhash: {blockhash.value.blockhash}") ``` ## Step 4: Explore Common Use Cases Monitor real-time price changes for any SPL token using the `/price` endpoint. ```javascript theme={null} const trackPrice = async (mint) => { const response = await fetch( `https://data.solanatracker.io/price?token=${mint}`, { headers: { 'x-api-key': API_KEY } } ); const data = await response.json(); console.log(`Price: $${data.price}`); }; // Track price every 5 seconds setInterval(() => trackPrice('YOUR_TOKEN_MINT'), 5000); ``` Discover newly created tokens by using the `/search` endpoint and sorting by creation time. ```javascript theme={null} const findNewTokens = async () => { const response = await fetch( 'https://data.solanatracker.io/tokens/latest', { headers: { 'x-api-key': API_KEY } } ); const result = await response.json(); result.data.forEach(token => { console.log(`New token: ${token.token.symbol} on ${token.pools[0].market}`); console.log(`Liquidity: $${token.pools[0].liquidity.usd}`); }); }; ``` Track token holdings and the total portfolio value for any wallet. ```javascript theme={null} const getWalletTokens = async (walletAddress) => { const response = await fetch( `https://data.solanatracker.io/wallet/${walletAddress}`, { headers: { 'x-api-key': API_KEY } } ); const portfolio = await response.json(); console.log(`Total Portfolio Value: $${portfolio.total}`); portfolio.tokens.forEach(holding => { const tokenInfo = holding.token; console.log(`${tokenInfo.symbol}: ${holding.balance} ($${holding.value.toFixed(2)})`); }); }; ``` ## Error Handling Always implement proper error handling in your applications. ```javascript JavaScript theme={null} try { const response = await fetch('https://data.solanatracker.io/price?token=INVALID_TOKEN_ADDRESS', { headers: { 'x-api-key': API_KEY } }); if (!response.ok) { const error = await response.json(); console.error(`API Error: ${error.message || response.statusText}`); // Handle specific error codes switch (response.status) { case 401: console.error('Invalid API key'); break; case 429: console.error('Rate limit exceeded'); break; case 500: console.error('Server error - please retry'); break; } } else { const data = await response.json(); // Process successful response } } catch (error) { console.error('Network error:', error); } ``` ```python Python theme={null} import requests from time import sleep def api_call_with_retry(url, headers, max_retries=3): for attempt in range(max_retries): try: response = requests.get(url, headers=headers) if response.status_code == 200: return response.json() elif response.status_code == 429: # Rate limited - wait and retry print(f"Rate limit exceeded. Retrying in {2 ** attempt}s...") sleep(2 ** attempt) # Exponential backoff elif response.status_code == 401: raise Exception("Invalid API key") else: print(f"Error {response.status_code}: {response.text}") break except requests.RequestException as e: print(f"Network error: {e}") if attempt < max_retries - 1: sleep(1) else: raise return None ``` **Need help?** Join our [Discord community](https://discord.gg/JH2e9rR9fc) or email [support@solanatracker.io](mailto:support@solanatracker.io). # Build swap instructions Source: https://docs.solanatracker.io/raptor/http/build-swap-instructions raptor/http/openapi.json post /swap-instructions Build swap instructions without transaction wrapper # Build swap transaction Source: https://docs.solanatracker.io/raptor/http/build-swap-transaction raptor/http/openapi.json post /swap Build a complete swap transaction from a quote # Get swap quote Source: https://docs.solanatracker.io/raptor/http/get-swap-quote raptor/http/openapi.json get /quote Get the best swap quote across all supported DEXes with optimal routing # Get transaction status Source: https://docs.solanatracker.io/raptor/http/get-transaction-status raptor/http/openapi.json get /transaction/{signature} Get the current status and details of a tracked transaction # Quote and swap in one request Source: https://docs.solanatracker.io/raptor/http/quote-and-swap-in-one-request raptor/http/openapi.json post /quote-and-swap Get quote and build transaction in single request # Send transaction via Yellowstone Jet TPU Source: https://docs.solanatracker.io/raptor/http/send-transaction-via-yellowstone-jet-tpu raptor/http/openapi.json post /send-transaction Send a signed transaction with ultra-low latency via Yellowstone Jet TPU # Overview Source: https://docs.solanatracker.io/raptor/overview DEX aggregator and Swap API for Solana **Public Beta** — Join [Discord](https://discord.gg/Gfnwee4T6S) for support queries. **Binary Release** - Now available. [Github](https://github.com/solanatracker/raptor-binary) *** ## Endpoints ### Swap | Endpoint | Description | | ----------------------------- | ----------------------------------------------------------------------------------------------------------------- | | `GET /quote` | Get swap quotes with multi-hop routing, dynamic slippage, DEX filtering, pool filtering, and platform fee support | | `POST /swap` | Build complete swap transactions with priority fee calculation and compute budget optimization | | `POST /swap-instructions` | Build swap instructions only (without transaction wrapper) | | `POST /quote-and-swap` | Combined quote and swap in single request (optional feature) | | `POST /send-transaction` | Send transactions via Yellowstone Jet TPU with automatic resending and confirmation tracking | | `GET /transaction/:signature` | Track sent transaction status, latency, and parsed events | ### Analytics | Endpoint | Description | | ------------- | ------------------------------------------------------------------------ | | `GET /health` | Health check with detailed status (pools, cache, Yellowstone connection) | *** ## WebSocket | Feature | Details | | ------------------ | --------------------------------------------------------------------------- | | `/stream` | Real-time quote streaming with subscription management | | `/stream/swap` | Real-time streaming swap quotes with pre-built transactions (ready to sign) | | Slot-based updates | Automatic quote recalculation on pool state changes | *** Program ID - Mainnet: RaptorD5ojtsqDDtJeRsunPLg6GvLYNnwKJWxYE4m87 ## Supported DEXs ### Raydium * Raydium AMM * Raydium CLMM * Raydium CPMM * Raydium LaunchLab/Launchpad ### Meteora * Meteora DLMM * Meteora Dynamic AMM * Meteora DAMM (Dynamic AMM V2) * Meteora Curve * Meteora DBC (Dynamic Bonding Curve) ### Orca * Whirlpool (legacy) * Whirlpool V2 ### Bonding Curves * Pump.fun * Pumpswap * Heaven (Buy/Sell) * MoonIt (Buy/Sell) * Boopfun (Buy/Sell) ### PropAMM * Humidifi * Tessera * Solfi V1/V2 ### Other * FluxBeam * PancakeSwap V3 *** ## Routing ### Multi-Hop * Up to 4-hop routes for optimal pricing * Route-aware slippage calculation (accounts for multi-hop risk) * DEX-specific routing preferences * Pool filtering by address lists * Circular arbitrage option ### Slippage * Dynamic slippage based on volatility and route complexity * Route-aware multi-hop slippage adjustment * Manual slippage override (numeric or `"dynamic"`) * Minimum output amount calculation with slippage protection ### Platform Fees * Fee taken from input OR output tokens * Configurable fee basis points (up to 10%) * Fee wallet specification * Extra charge on positive slippage * Automatic fee adjustment in quotes and swaps *** ## Priority Fees ### Dynamic Calculation * Route-specific priority fee calculation * DEX-specific fee adjustments * Recent fee data tracking (slots tracked, total fees) * Maximum fee caps and overrides ### Priority Levels | Level | Use Case | | --------------------- | ------------------- | | `Min` / `Low` | Cost-saving | | `Auto` / `Medium` | Recommended default | | `High` / `VeryHigh` | Speed priority | | `Turbo` / `UnsafeMax` | Maximum speed | *** ## Transaction Management ### Yellowstone Jet TPU * Transaction sending * Automatic resending with slot alignment * Multiple identity support (4 default, configurable) ### Tracking * Real-time status monitoring (`pending` / `confirmed` / `failed` / `expired`) * Latency measurement (send to confirm) * Parsed Raptor program events * Raw transaction storage * Automatic cleanup ### Rate Limiting * Configurable RPS (uses few RPC calls generally) *** ## CLI Flags ### Help & Version ``` -h, --help Show help message -v, --version Show version information ``` ### Pool Indexer ``` --no-pool-indexer Disable pool indexer client (Yellowstone only) ``` ### DEX Filtering ``` --include-dexes Only include these DEXes (comma-separated) --exclude-dexes Exclude these DEXes (comma-separated) ``` ### Performance ``` --workers Number of worker threads (default: CPU cores) ``` ### Feature Toggles ``` --enable-arbitrage Enable circular arbitrage (same input/output mint) -s, --enable-quote-and-swap Enable the /quote-and-swap endpoint --rpc-rate-limit Limit RPC calls to N per second (default: unlimited) --enable-yellowstone-jet Enable Yellowstone Jet TPU sender for /send-transaction --enable-websocket Enable WebSocket streaming quotes at /stream ``` ### Yellowstone Jet TPU ``` --jet-identity Path to identity keypair for Jet TPU (optional) --jet-identities Number of random identities for Jet (default: 4) ``` *** ## Environment Variables ### CLI Flag Equivalents | Variable | Flag | | ------------------------ | -------------------------- | | `NO_POOL_INDEXER` | `--no-pool-indexer` | | `INCLUDE_DEXES` | `--include-dexes` | | `EXCLUDE_DEXES` | `--exclude-dexes` | | `WORKER_THREADS` | `--workers` | | `ENABLE_ARBITRAGE` | `--enable-arbitrage` | | `ENABLE_QUOTE_AND_SWAP` | `--enable-quote-and-swap` | | `RPC_RATE_LIMIT` | `--rpc-rate-limit` | | `ENABLE_YELLOWSTONE_JET` | `--enable-yellowstone-jet` | | `JET_IDENTITY` | `--jet-identity` | | `JET_NUM_IDENTITIES` | `--jet-identities` | | `ENABLE_WEBSOCKET` | `--enable-websocket` | ### Additional Variables | Variable | Description | | ---------------------- | --------------------------------------------- | | `RPC_URL` | Solana RPC endpoint | | `YELLOWSTONE_ENDPOINT` | Yellowstone gRPC endpoint | | `YELLOWSTONE_TOKEN` | Yellowstone auth token (optional) | | `BIND_ADDR` | Server bind address (default: `0.0.0.0:8080`) | # Transactions Source: https://docs.solanatracker.io/raptor/transactions Send transactions via Yellowstone Jet TPU and track confirmation status ## What This Does * Sends transactions with low latency * Tracks confirmation status in real time * Automatically retries unconfirmed transactions * Returns parsed transaction data and Raptor events * Measures send → confirm latency *** ## Send a Transaction Send a signed Solana transaction. **Endpoint** `POST /send-transaction` **Request Body** ```json theme={null} { "transaction": "base64-encoded-transaction" } ``` **Response** ```json theme={null} { "signature": "4vJ9JU1bJJE96FWSJKvHsmmFADCg4gpZQff4P3bkLKi", "signature_base64": "RXNzaWduYXR1cmU=", "success": true } ``` **Notes** * Returns immediately after accepting the transaction * Sending and retrying happens in the background * Transactions are retried for up to **30 seconds** or until confirmed *** ## Track a Transaction Check the status of a transaction sent via `/send-transaction`. **Endpoints** * `GET /transaction/{signature}` The signature can be **base58 or base64**. **Response (example)** ```json theme={null} { "signature": "4vJ9JU1bJJE96FWSJKvHsmmFADCg4gpZQff4P3bkLKi", "status": "confirmed", "slot": 250123456, "sent_at": 1703123456789, "confirmed_at": 1703123456795, "latency_ms": 6, "transaction": { ... }, "events": [ ... ] } ``` ### Status Values * `pending` – sent but not confirmed * `confirmed` – finalized on-chain * `failed` – transaction error * `expired` – not confirmed before timeout *** ## Raptor Events If the transaction interacts with the **Raptor program**, events are automatically parsed and returned. **Supported Events** * `SwapEvent` * `SwapCompleteEvent` * `PlaceOrderEvent` * `FillOrderEvent` * `CancelOrderEvent` * `UpdateOrderEvent` **Event Format** ```json theme={null} { "name": "SwapEvent", "data": "base64-encoded-data", "parsed": { "dex": 1, "amountIn": 1000000, "amountOut": 999000 } } ``` *** ## Examples ### Send a Transaction ```js theme={null} const response = await fetch('/send-transaction', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ transaction: signedTx }) }); const result = await response.json(); console.log(result.signature); ``` ### Poll for Confirmation ```js theme={null} async function waitForConfirmation(signature) { for (let i = 0; i < 30; i++) { const res = await fetch(`/transaction/${signature}`); const tx = await res.json(); if (tx.status === 'confirmed' || tx.status === 'failed') { return tx; } await new Promise(r => setTimeout(r, 1000)); } throw new Error('Timeout'); } ``` ### Read Swap Results ```js theme={null} const tx = await fetch(`/transaction/${signature}`).then(r => r.json()); for (const event of tx.events ?? []) { if (event.name === 'SwapEvent') { console.log(event.parsed.amountIn, '→', event.parsed.amountOut); } } ``` *** ## Errors | Code | Meaning | | ---- | --------------------------- | | 400 | Invalid transaction | | 404 | Transaction not tracked | | 503 | Sender or tracking disabled | ```json theme={null} { "error": "Transaction not found", "code": 404 } ``` # /stream Source: https://docs.solanatracker.io/raptor/websocket/websockets/stream WebSocket channel for subscribing to real-time quote updates # /stream/swap Source: https://docs.solanatracker.io/raptor/websocket/websockets/streamswap WebSocket channel for streaming swap quotes with pre-built transactions. Subscribe to a token pair and receive real-time updates with ready-to-sign swap transactions whenever pool states change. # Credits and Rate Limits Source: https://docs.solanatracker.io/solana-rpc/credits-and-rate-limits Credit costs and rate limits for Solana RPC API plans Our API uses a credit-based system where different operations consume different amounts of credits. Most calls cost 1 credit, while resource-intensive operations cost 10 credits. ## Credit Costs ### Standard API Calls (1 Credit) Most RPC methods cost 1 credit per call, including: * Account queries (`getAccountInfo`, `getBalance`, etc.) * Block queries (`getLatestBlockhash`, `getBlockHeight`, etc.) * Transaction submission (`sendTransaction`) * Priority Fee API calls * All other methods not listed below ### Archival Data Calls (10 Credits) Historical blockchain data retrieval costs 10 credits per call: * `getTransaction` - Transaction details * `getBlock` - Block information * `getBlocks` - Multiple blocks * `getBlockTime` - Block timestamps * `getInflationReward` - Inflation rewards * `getConfirmedBlock` - Confirmed block data * `getConfirmedBlocks` - Multiple confirmed blocks * `getConfirmedTransaction` - Confirmed transaction details * `getConfirmedSignaturesForAddress2` - Confirmed signatures (v2) * `getConfirmedSignaturesForAddress` - Confirmed signatures * `getSignaturesForAddress` - All signatures for an address ### Program Account Queries * `getProgramAccounts` - 10 credits per call * `getTokenAccountsByOwner` - 10 credits per call * `getProgramAccountsV2` - **1 credit per call** (custom optimized method) * `getTokenAccountsByOwnerV2` - **1 credit per call** (custom optimized method) * `getTokenAccountsByOwners` - **1 credit per call** (custom optimized method) Use `getProgramAccountsV2` and `getTokenAccountsByOwnerV2` instead of their standard counterparts to reduce credit consumption by 90%. ### Digital Asset Standard (DAS) API (10 Credits) All DAS API methods cost 10 credits per call: * `getAsset` - Asset details * `getAssetProof` - Merkle proof for compressed NFTs * `getAssetsByOwner` - Assets by owner address * `getAssetsByAuthority` - Assets by update authority * `getAssetsByCreator` - Assets by creator address * `getAssetsByGroup` - Assets by collection * `searchAssets` - Search assets by criteria * `getSignaturesForAsset` - Transaction history for an asset * `getTokenAccounts` - Token account information * `getNFTEditions` - NFT edition details ## Rate Limits by Plan | Plan | Monthly Credits | General RPS | Send Transaction RPS | getProgramAccounts RPS | DAS API RPS | WebSocket Connections | | ---------------- | --------------- | ----------- | -------------------- | ---------------------- | ----------- | --------------------- | | **Free** | 500,000 | 10 | 1 | 1 | 2 | 2 | | **Developer** | 15,000,000 | 60 | 5 | 15 | 10 | 25 | | **Business** | 100,000,000 | 225 | 50 | 25 | 50 | 100 | | **Professional** | 220,000,000 | 500 | 100 | 50 | 100 | 250 | Rate limits are enforced per second. Requests exceeding your plan limits return a `429 Too Many Requests` error. ## Optimize Your Credit Usage ### Use Custom Methods Replace expensive calls with optimized alternatives: * Use `getProgramAccountsV2` instead of `getProgramAccounts` * Use `getTokenAccountsByOwnerV2` instead of standard token account queries ### Cache Responses Store frequently accessed data to reduce redundant API calls. Implement caching for: * Static account data * Historical transactions * NFT metadata ### Implement WebSockets Subscribe to account or program updates instead of polling: * Real-time updates without repeated API calls * More efficient than polling for changes * Lower credit consumption ### Batch Operations Group multiple queries when the API supports batching to reduce overhead. ### Monitor Usage Track credit consumption in your dashboard to identify optimization opportunities and prevent overages. ## Frequently Asked Questions Requests exceeding your rate limit receive a `429 Too Many Requests` HTTP error. Implement exponential backoff and retry logic in your application. No. Credits reset at the start of each billing cycle and do not accumulate. Yes. Upgrades take effect immediately with the new credit allowance and rate limits. This will restart your subscription. View real-time credit consumption and rate limit usage in your account dashboard. V2 methods like `getProgramAccountsV2` are custom optimized implementations that provide the same functionality as standard methods but consume 90% fewer credits (1 credit vs 10 credits). ## Need Help? Contact our support team at [contact@solanatracker.io](mailto:contact@solanatracker.io) for questions about your specific use case or to discuss custom plans. # getAccountInfo Source: https://docs.solanatracker.io/solana-rpc/http/getaccountinfo getaccountinfo post / # getBalance Source: https://docs.solanatracker.io/solana-rpc/http/getbalance getbalance post / Returns the lamport balance of the account at the specified address. This method provides the native SOL balance for any Solana account. # getBlock Source: https://docs.solanatracker.io/solana-rpc/http/getblock getblock post / # getBlockCommitment Source: https://docs.solanatracker.io/solana-rpc/http/getblockcommitment getblockcommitment post / # getBlockHeight Source: https://docs.solanatracker.io/solana-rpc/http/getblockheight getblockheight post / # getBlockProduction Source: https://docs.solanatracker.io/solana-rpc/http/getblockproduction getblockproduction post / # getBlocks Source: https://docs.solanatracker.io/solana-rpc/http/getblocks getblocks post / # getBlocksWithLimit Source: https://docs.solanatracker.io/solana-rpc/http/getblockswithlimit getblockswithlimit post / # getBlockTime Source: https://docs.solanatracker.io/solana-rpc/http/getblocktime getblocktime post / # getClusterNodes Source: https://docs.solanatracker.io/solana-rpc/http/getclusternodes getclusternodes post / # getEpochInfo Source: https://docs.solanatracker.io/solana-rpc/http/getepochinfo getepochinfo post / # getEpochSchedule Source: https://docs.solanatracker.io/solana-rpc/http/getepochschedule getepochschedule post / # getFeeForMessage Source: https://docs.solanatracker.io/solana-rpc/http/getfeeformessage getfeeformessage post / # getFirstAvailableBlock Source: https://docs.solanatracker.io/solana-rpc/http/getfirstavailableblock getfirstavailableblock post / # getGenesisHash Source: https://docs.solanatracker.io/solana-rpc/http/getgenesishash getgenesishash post / # getHealth Source: https://docs.solanatracker.io/solana-rpc/http/gethealth gethealth post / # getHighestSnapshotSlot Source: https://docs.solanatracker.io/solana-rpc/http/gethighestsnapshotslot gethighestsnapshotslot post / # getIdentity Source: https://docs.solanatracker.io/solana-rpc/http/getidentity getidentity post / # getInflationGovernor Source: https://docs.solanatracker.io/solana-rpc/http/getinflationgovernor getinflationgovernor post / # getInflationRate Source: https://docs.solanatracker.io/solana-rpc/http/getinflationrate getinflationrate post / # getInflationReward Source: https://docs.solanatracker.io/solana-rpc/http/getinflationreward getinflationreward post / # getLargestAccounts Source: https://docs.solanatracker.io/solana-rpc/http/getlargestaccounts getlargestaccounts post / # getLatestBlockhash Source: https://docs.solanatracker.io/solana-rpc/http/getlatestblockhash getlatestblockhash post / # getLeaderSchedule Source: https://docs.solanatracker.io/solana-rpc/http/getleaderschedule getleaderschedule post / # getMaxRetransmitSlot Source: https://docs.solanatracker.io/solana-rpc/http/getmaxretransmitslot getmaxretransmitslot post / # getMaxShredInsertSlot Source: https://docs.solanatracker.io/solana-rpc/http/getmaxshredinsertslot getmaxshredinsertslot post / # getMinimumBalanceForRentExemption Source: https://docs.solanatracker.io/solana-rpc/http/getminimumbalanceforrentexemption getminimumbalanceforrentexemption post / # getMultipleAccounts Source: https://docs.solanatracker.io/solana-rpc/http/getmultipleaccounts getmultipleaccounts post / # getPriorityFeeEstimate Source: https://docs.solanatracker.io/solana-rpc/http/getpriorityfeeestimate getpriorityfeeestimate post / Provides fee recommendations based on historical data, considering both global and local fee markets. Available on all Shared RPC plans and Dedicated Nodes. # getProgramAccounts Source: https://docs.solanatracker.io/solana-rpc/http/getprogramaccounts getprogramaccounts post / **⚡ Powered by Solana Ridge DB** Get faster Solana account data with **Ridge DB**, our custom-built client designed specifically for high-performance account retrieval on Solana. Unlike standard RPC methods, Ridge DB uses specialized indexes that make querying program accounts significantly faster. **What makes it different:** Need to track only new accounts? Use `changedSince` to fetch accounts modified after a specific slot, perfect for incremental updates without re-fetching everything. Want to filter out dust? The `excludeZero` parameter automatically hides empty token accounts, reducing bandwidth and speeding up your queries. Use cursor pagination to efficiently retrieve all accounts (up to 10k per request.) **High Load Considerations** This endpoint streams all accounts directly to your client. While this works great for most queries, it can return errors during high load or when querying programs with millions of accounts (like liquidity pools or protocol-owned accounts). **Need better reliability?** Use **[getProgramAccountsV2](/solana-rpc/http/getprogramaccountsv2)** instead. The V2 method includes pagination support, making it more efficient and reliable for large programs and high-traffic scenarios. # getProgramAccountsV2 Source: https://docs.solanatracker.io/solana-rpc/http/getprogramaccountsv2 getprogramaccountsv2 post / Returns all accounts owned by the provided program Pubkey with enhanced pagination support. This V2 method provides efficient querying with cursor-based pagination, filtering capabilities, and incremental updates for large result sets. **⚡ Powered by Solana Ridge DB** Get faster Solana account data with **Ridge DB**, our custom-built client designed specifically for high-performance account retrieval on Solana. Unlike standard RPC methods, Ridge DB uses specialized indexes that make querying program accounts significantly faster. **What makes it different:** Need to track only new accounts? Use `changedSince` to fetch accounts modified after a specific slot, perfect for incremental updates without re-fetching everything. Want to filter out dust? The `excludeZero` parameter automatically hides empty token accounts, reducing bandwidth and speeding up your queries. Use cursor pagination to efficiently retrieve all accounts (up to 10k per request.) # getRecentPerformanceSamples Source: https://docs.solanatracker.io/solana-rpc/http/getrecentperformancesamples getrecentperformancesamples post / # getRecentPrioritizationFees Source: https://docs.solanatracker.io/solana-rpc/http/getrecentprioritizationfees getrecentprioritizationfees post / # getSignaturesForAddress Source: https://docs.solanatracker.io/solana-rpc/http/getsignaturesforaddress getsignaturesforaddress post / # getSignatureStatuses Source: https://docs.solanatracker.io/solana-rpc/http/getsignaturestatuses getsignaturestatuses post / # getSlot Source: https://docs.solanatracker.io/solana-rpc/http/getslot getslot post / # getSlotLeader Source: https://docs.solanatracker.io/solana-rpc/http/getslotleader getslotleader post / # getSlotLeaders Source: https://docs.solanatracker.io/solana-rpc/http/getslotleaders getslotleaders post / # getStakeMinimumDelegation Source: https://docs.solanatracker.io/solana-rpc/http/getstakeminimumdelegation getstakeminimumdelegation post / # getSupply Source: https://docs.solanatracker.io/solana-rpc/http/getsupply getsupply post / # getTokenAccountBalance Source: https://docs.solanatracker.io/solana-rpc/http/gettokenaccountbalance gettokenaccountbalance post / Returns the token balance of an SPL Token account. This method provides detailed balance information including raw amount, decimals, and human-readable formatted values for any SPL Token account address. # getTokenAccountsByDelegate Source: https://docs.solanatracker.io/solana-rpc/http/gettokenaccountsbydelegate gettokenaccountsbydelegate post / Returns all SPL Token accounts that have been delegated to a specific address. This method allows you to find token accounts where spending or management authority has been granted to a delegate, useful for tracking authorized token operations and delegated permissions. # getTokenAccountsByOwner Source: https://docs.solanatracker.io/solana-rpc/http/gettokenaccountsbyowner gettokenaccountsbyowner post / Returns all SPL Token accounts owned by the specified wallet address. This method allows you to retrieve a complete list of token holdings for any Solana wallet, useful for portfolio tracking and token balance queries. **⚡ Powered by Solana Ridge DB** Get faster Solana token account data with **Ridge DB**, our custom-built client designed for high-performance token account retrieval. Ridge DB uses specialized indexes that make fetching wallet token holdings significantly faster than standard RPC methods. **What makes it different:** Need to track only new tokens? Use `changedSince` to fetch accounts modified after a specific slot, perfect for incremental portfolio updates. Want to filter out dust? The `excludeZero` parameter automatically hides empty token accounts, keeping your results clean and reducing bandwidth. **High Load Considerations** This endpoint streams all token accounts directly to your client. While this works great for most wallets, it can return errors during high load or when querying wallets with millions of token accounts (like liquidity pools or protocol-owned accounts). **Need better reliability?** Use **[getTokenAccountsByOwnerV2](/solana-rpc/http/gettokenaccountsbyownerv2)** instead. The V2 method includes pagination support, making it more efficient and reliable for large wallets and high-traffic scenarios. # getTokenAccountsByOwners Source: https://docs.solanatracker.io/solana-rpc/http/gettokenaccountsbyowners gettokenaccountsbyowners post / Returns token account balances for multiple wallets for a specific mint. Efficiently queries up to 250 wallets in a single request. Returns all token accounts per owner (if they have multiple) or zero balance if no account exists. **⚡ Powered by Solana Ridge DB** Get token balances for multiple wallets in a single request with **getTokenAccountsByOwners**. Powered by **Ridge DB**, our custom-built client designed for high-performance token account queries. Instead of making hundreds of individual requests, query up to 250 wallets at once for any token mint. **What makes it different:** Query up to 250 wallet addresses in one request, drastically reducing API calls and latency. Automatically returns all token accounts if a wallet has multiple accounts for the same mint. Wallets without a token account return a clean zero balance response instead of errors. Each account includes the slot when the balance was last updated, perfect for tracking changes and cache invalidation. # getTokenAccountsByOwnerV2 Source: https://docs.solanatracker.io/solana-rpc/http/gettokenaccountsbyownerv2 gettokenaccountsbyownerv2 post / Returns all SPL Token accounts owned by the provided wallet address with enhanced pagination support. This V2 method provides efficient querying with cursor-based pagination, filtering by mint or program ID, and incremental updates for portfolio tracking. **⚡ Powered by Solana Ridge DB** Get faster Solana account data with **Ridge DB**, our custom-built client designed specifically for high-performance account retrieval on Solana. Unlike standard RPC methods, Ridge DB uses specialized indexes that make querying program accounts significantly faster. **What makes it different:** Need to track only new accounts? Use `changedSince` to fetch accounts modified after a specific slot, perfect for incremental updates without re-fetching everything. Want to filter out dust? The `excludeZero` parameter automatically hides empty token accounts, reducing bandwidth and speeding up your queries. Use cursor pagination to efficiently retrieve all accounts (up to 10k per request.) # getTokenLargestAccounts Source: https://docs.solanatracker.io/solana-rpc/http/gettokenlargestaccounts gettokenlargestaccounts post / Returns the 20 largest token accounts for a specific SPL Token mint. This method is useful for analyzing token distribution, identifying major holders, and understanding token concentration patterns. # getTokenSupply Source: https://docs.solanatracker.io/solana-rpc/http/gettokensupply gettokensupply post / Returns the total supply of an SPL Token type # getTransaction Source: https://docs.solanatracker.io/solana-rpc/http/gettransaction gettransaction post / # getTransactionCount Source: https://docs.solanatracker.io/solana-rpc/http/gettransactioncount gettransactioncount post / # getVersion Source: https://docs.solanatracker.io/solana-rpc/http/getversion getversion post / # getVoteAccounts Source: https://docs.solanatracker.io/solana-rpc/http/getvoteaccounts getvoteaccounts post / # isBlockhashValid Source: https://docs.solanatracker.io/solana-rpc/http/isblockhashvalid isblockhashvalid post / # minimumLedgerSlot Source: https://docs.solanatracker.io/solana-rpc/http/minimumledgerslot minimumledgerslot post / # requestAirdrop Source: https://docs.solanatracker.io/solana-rpc/http/requestairdrop requestairdrop post / # sendTransaction Source: https://docs.solanatracker.io/solana-rpc/http/sendtransaction sendtransaction post / # simulateTransaction Source: https://docs.solanatracker.io/solana-rpc/http/simulatetransaction simulatetransaction post / # Account subscribe Source: https://docs.solanatracker.io/solana-rpc/websockets/accountsubscribe # Account unsubscribe Source: https://docs.solanatracker.io/solana-rpc/websockets/accountunsubscribe # Block subscribe Source: https://docs.solanatracker.io/solana-rpc/websockets/blocksubscribe # Block unsubscribe Source: https://docs.solanatracker.io/solana-rpc/websockets/blockunsubscribe # Logs subscribe Source: https://docs.solanatracker.io/solana-rpc/websockets/logssubscribe # Logs unsubscribe Source: https://docs.solanatracker.io/solana-rpc/websockets/logsunsubscribe # Program subscribe Source: https://docs.solanatracker.io/solana-rpc/websockets/programsubscribe # Program unsubscribe Source: https://docs.solanatracker.io/solana-rpc/websockets/programunsubscribe # Root subscribe Source: https://docs.solanatracker.io/solana-rpc/websockets/rootsubscribe # Root unsubscribe Source: https://docs.solanatracker.io/solana-rpc/websockets/rootunsubscribe # Shred subscribe Source: https://docs.solanatracker.io/solana-rpc/websockets/shredsubscribe # Shred unsubscribe Source: https://docs.solanatracker.io/solana-rpc/websockets/shredunsubscribe # Signature subscribe Source: https://docs.solanatracker.io/solana-rpc/websockets/signaturesubscribe # Signature unsubscribe Source: https://docs.solanatracker.io/solana-rpc/websockets/signatureunsubscribe # Slot subscribe Source: https://docs.solanatracker.io/solana-rpc/websockets/slotsubscribe # Slots updates subscribe Source: https://docs.solanatracker.io/solana-rpc/websockets/slotsupdatessubscribe # Slots updates unsubscribe Source: https://docs.solanatracker.io/solana-rpc/websockets/slotsupdatesunsubscribe # Slot unsubscribe Source: https://docs.solanatracker.io/solana-rpc/websockets/slotunsubscribe # Vote subscribe Source: https://docs.solanatracker.io/solana-rpc/websockets/votesubscribe # Vote unsubscribe Source: https://docs.solanatracker.io/solana-rpc/websockets/voteunsubscribe # Libraries Source: https://docs.solanatracker.io/swap-api/libraries Official SDK libraries for Solana token swaps # SDK Libraries Official client libraries for integrating Solana token swaps into your application. All SDKs support Pump.fun, PumpSwap, Orca, Meteora, Moonshot, Raydium, and Jupiter. ## Available SDKs For Node.js, React, Next.js, and browsers For Python applications and scripts ## Installation ```bash npm theme={null} npm install solana-swap ``` ## Quick Start ### JavaScript ```javascript theme={null} import { SolanaTracker } from 'solana-swap'; const tracker = new SolanaTracker('YOUR_API_KEY', 'YOUR_PRIVATE_KEY'); // Get quote const quote = await tracker.getRate({ from: 'So11111111111111111111111111111111111111112', to: '4k3Dyjzvzp8eMZWUXbBCjEvwSkkk59S5iCNLY3QrkX6R', amount: 1, slippage: 10 }); // Execute swap const swap = await tracker.swap({ from: 'So11111111111111111111111111111111111111112', to: '4k3Dyjzvzp8eMZWUXbBCjEvwSkkk59S5iCNLY3QrkX6R', fromAmount: 0.1, slippage: 10, payer: 'YOUR_WALLET_ADDRESS' }); ``` ### Python ```python theme={null} from solana_swap import SolanaTracker tracker = SolanaTracker('YOUR_API_KEY', 'YOUR_PRIVATE_KEY') # Get quote quote = tracker.get_rate( from_token='So11111111111111111111111111111111111111112', to_token='4k3Dyjzvzp8eMZWUXbBCjEvwSkkk59S5iCNLY3QrkX6R', amount=1, slippage=10 ) # Execute swap swap = tracker.swap( from_token='So11111111111111111111111111111111111111112', to_token='4k3Dyjzvzp8eMZWUXbBCjEvwSkkk59S5iCNLY3QrkX6R', from_amount=0.1, slippage=10, payer='YOUR_WALLET_ADDRESS' ) ``` ## React Integration ```javascript theme={null} import { useState } from 'react'; import { SolanaTracker } from 'solana-swap'; function SwapComponent() { const [quote, setQuote] = useState(null); const tracker = new SolanaTracker(process.env.NEXT_PUBLIC_API_KEY); async function getQuote(amount) { const result = await tracker.getRate({ from: 'So11111111111111111111111111111111111111112', to: '4k3Dyjzvzp8eMZWUXbBCjEvwSkkk59S5iCNLY3QrkX6R', amount: parseFloat(amount), slippage: 10 }); setQuote(result); } async function executeSwap() { const swap = await tracker.swap({ from: 'So11111111111111111111111111111111111111112', to: '4k3Dyjzvzp8eMZWUXbBCjEvwSkkk59S5iCNLY3QrkX6R', fromAmount: quote.amountIn, slippage: 10, payer: walletAddress }); console.log('Swap completed:', swap.txn); } return (
getQuote(e.target.value)} placeholder="Amount" /> {quote && (

Expected: {quote.amountOut}

Minimum: {quote.minAmountOut}

Impact: {(quote.priceImpact * 100).toFixed(2)}%

)}
); } ``` ## Python Trading Example ```python theme={null} from solana_swap import SolanaTracker tracker = SolanaTracker( api_key='YOUR_API_KEY', private_key='YOUR_PRIVATE_KEY' ) SOL = 'So11111111111111111111111111111111111111112' TARGET = '4k3Dyjzvzp8eMZWUXbBCjEvwSkkk59S5iCNLY3QrkX6R' # Get quote quote = tracker.get_rate( from_token=SOL, to_token=TARGET, amount=0.1, slippage=10 ) # Check price impact if quote['priceImpact'] < 0.03: # < 3% # Execute swap swap = tracker.swap( from_token=SOL, to_token=TARGET, from_amount=0.1, slippage=10, payer='YOUR_WALLET', priority_fee='auto', priority_fee_level='high' ) print(f"Swap completed: {swap['txn']}") else: print("Price impact too high") ``` ## Features * **Zero-Delay Trading** - Sub-second execution * **Multi-DEX Routing** - Optimal prices across DEXs * **Priority Control** - Manual or automatic fee optimization * **Custom Fees** - Monetization with add/deduct modes * **Flexible Amounts** - Numeric, auto, or percentage ## Common Token Addresses | Token | Symbol | Address | | ----------- | ------ | ---------------------------------------------- | | Wrapped SOL | SOL | `So11111111111111111111111111111111111111112` | | USD Coin | USDC | `EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v` | | Tether | USDT | `Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB` | | Raydium | RAY | `4k3Dyjzvzp8eMZWUXbBCjEvwSkkk59S5iCNLY3QrkX6R` | ## Support Community support Direct API support Swap endpoint docs Rate endpoint docs # Rate Source: https://docs.solanatracker.io/swap-api/rate swap-api/openapi.json get /rate Get swap quotes before executing transactions ## Overview The rate endpoint provides price quotes for token swaps before executing a transaction. Use this to show users the expected output amount and price impact when they're ready to swap. This endpoint is for pre-swap quotes only. Do not use it as a free pricing or market data API. Excessive requests for non-swap purposes may result in rate limiting. ## Use Cases * **Pre-Swap Quotes**: Get accurate pricing before executing a swap * **Slippage Calculation**: Show users the minimum guaranteed output * **Price Impact Check**: Validate that trade size is acceptable ## SDK Examples ```javascript JavaScript theme={null} import { SolanaTracker } from 'solana-swap'; const tracker = new SolanaTracker('YOUR_API_KEY'); // Get rate quote before swap const quote = await tracker.getRate({ from: 'So11111111111111111111111111111111111111112', // SOL to: '4k3Dyjzvzp8eMZWUXbBCjEvwSkkk59S5iCNLY3QrkX6R', amount: 1, slippage: 10 }); console.log(`Expected: ${quote.amountOut} tokens`); console.log(`Minimum: ${quote.minAmountOut} tokens`); console.log(`Price impact: ${(quote.priceImpact * 100).toFixed(2)}%`); ``` ```python Python theme={null} from solana_swap import SolanaTracker tracker = SolanaTracker('YOUR_API_KEY') # Get rate quote before swap quote = tracker.get_rate( from_token='So11111111111111111111111111111111111111112', to_token='4k3Dyjzvzp8eMZWUXbBCjEvwSkkk59S5iCNLY3QrkX6R', amount=1, slippage=10 ) print(f"Expected: {quote['amountOut']} tokens") print(f"Minimum: {quote['minAmountOut']} tokens") print(f"Price impact: {quote['priceImpact'] * 100:.2f}%") ``` ```bash cURL theme={null} curl -X GET "https://swap-v2.solanatracker.io/rate?from=So11111111111111111111111111111111111111112&to=4k3Dyjzvzp8eMZWUXbBCjEvwSkkk59S5iCNLY3QrkX6R&amount=1&slippage=10" ``` ## Response Fields | Field | Type | Description | | ---------------- | ------ | ----------------------------------- | | `amountIn` | number | Input token amount | | `amountOut` | number | Expected output token amount | | `minAmountOut` | number | Minimum output after slippage | | `currentPrice` | number | Current market price | | `executionPrice` | number | Actual execution price | | `priceImpact` | number | Price impact as decimal (0.05 = 5%) | | `fee` | number | Trading fee | | `platformFee` | number | Platform fee in lamports | | `platformFeeUI` | number | Platform fee in SOL | ### Example Response ```json theme={null} { "amountIn": 1, "amountOut": 9181.330823048, "minAmountOut": 9089.517514818, "currentPrice": 9181.330823048, "executionPrice": 9089.517514818, "priceImpact": 0.0334641736518774, "fee": 0.01, "baseCurrency": { "decimals": 9, "mint": "So11111111111111111111111111111111111111112" }, "quoteCurrency": { "decimals": 9, "mint": "4k3Dyjzvzp8eMZWUXbBCjEvwSkkk59S5iCNLY3QrkX6R" }, "platformFee": 9000000, "platformFeeUI": 0.009 } ``` ## Understanding Price Impact Price impact shows how much your trade moves the market price: * **\< 1%** - Excellent liquidity, proceed with confidence * **1-5%** - Acceptable for most trades * **> 5%** - Consider splitting into smaller trades or increasing slippage ## Validate Before Swap ```javascript theme={null} // Check rate before executing swap async function validateAndSwap(swapParams) { // Get quote first const quote = await tracker.getRate({ from: swapParams.from, to: swapParams.to, amount: swapParams.amount, slippage: swapParams.slippage }); // Check price impact if (quote.priceImpact > 0.05) { throw new Error('Price impact too high'); } // Execute swap return await tracker.swap(swapParams); } ``` ## Common Errors | Error | Description | Solution | | ---------------------------------- | -------------------------- | --------------------------- | | `Invalid or missing token address` | Token address is invalid | Verify token addresses | | `Invalid amount` | Amount is not valid | Ensure amount is positive | | `Invalid slippage tolerance` | Slippage not between 0-100 | Set slippage 0-100 | | `Unable to fetch pools` | Cannot find liquidity | Token may lack active pools | ## Next Steps Execute the swap transaction Install the SDK # Swap Source: https://docs.solanatracker.io/swap-api/swap swap-api/openapi.json get /swap Build and execute token swap transactions ## Overview The swap endpoint builds token swap transactions on Solana. You receive a serialized transaction that you sign and broadcast using your wallet. This endpoint builds the transaction but does not execute it. You must sign and send the transaction yourself. ## Supported Platforms * **Pump.fun** - Launch platform tokens * **PumpSwap** - Pump.fun liquidity pools * **Orca** - Concentrated liquidity * **Meteora** - Dynamic pools * **Moonshot** - New launches * **Raydium** - V4 AMM, CPMM, Launchpad * **Jupiter** - Aggregated routing ## SDK Examples ```javascript JavaScript theme={null} import { SolanaTracker } from 'solana-swap'; const tracker = new SolanaTracker('YOUR_API_KEY', 'YOUR_PRIVATE_KEY'); // Basic swap const swap = await tracker.swap({ from: 'So11111111111111111111111111111111111111112', // SOL to: '4k3Dyjzvzp8eMZWUXbBCjEvwSkkk59S5iCNLY3QrkX6R', fromAmount: 0.1, slippage: 10, payer: 'YOUR_WALLET_ADDRESS', priorityFee: 0.000005, txVersion: 'v0' }); console.log('Transaction:', swap.txn); ``` ```python Python theme={null} from solana_swap import SolanaTracker tracker = SolanaTracker('YOUR_API_KEY', 'YOUR_PRIVATE_KEY') # Basic swap swap = tracker.swap( from_token='So11111111111111111111111111111111111111112', to_token='4k3Dyjzvzp8eMZWUXbBCjEvwSkkk59S5iCNLY3QrkX6R', from_amount=0.1, slippage=10, payer='YOUR_WALLET_ADDRESS', priority_fee=0.000005, tx_version='v0' ) print('Transaction:', swap['txn']) ``` ```bash cURL theme={null} curl -X GET "https://swap-v2.solanatracker.io/swap?from=So11111111111111111111111111111111111111112&to=4k3Dyjzvzp8eMZWUXbBCjEvwSkkk59S5iCNLY3QrkX6R&fromAmount=0.1&slippage=10&payer=YOUR_WALLET_ADDRESS&priorityFee=0.000005&txVersion=v0" ``` ## Key Parameters ### Amount Formats The `fromAmount` parameter accepts: * **Numeric**: `1`, `0.5` - Exact amount * **Auto**: `"auto"` - Entire wallet balance * **Percentage**: `"50%"`, `"25%"` - Percentage of balance ### Priority Fees ```javascript theme={null} // Manual fee { priorityFee: 0.000005 } // Automatic with level { priorityFee: "auto", priorityFeeLevel: "high" // min, low, medium, high, veryHigh, unsafeMax } ``` ### Custom Fees Add your own fees for monetization: ```javascript theme={null} { fee: "YOUR_WALLET:0.1", // 0.1% to your wallet feeType: "add" // or "deduct" (SOL only) } ``` ## Examples ### Swap with Auto Amount ```javascript theme={null} // Swap entire balance const swap = await tracker.swap({ from: 'So11111111111111111111111111111111111111112', to: '4k3Dyjzvzp8eMZWUXbBCjEvwSkkk59S5iCNLY3QrkX6R', fromAmount: "auto", slippage: 15, payer: 'YOUR_WALLET_ADDRESS' }); ``` ### Swap with Percentage ```javascript theme={null} // Swap 50% of balance const swap = await tracker.swap({ from: 'TOKEN_ADDRESS', to: 'So11111111111111111111111111111111111111112', fromAmount: "50%", slippage: 10, payer: 'YOUR_WALLET_ADDRESS' }); ``` ### Swap with Custom Fee ```javascript theme={null} // Add 0.1% fee const swap = await tracker.swap({ from: 'So11111111111111111111111111111111111111112', to: '4k3Dyjzvzp8eMZWUXbBCjEvwSkkk59S5iCNLY3QrkX6R', fromAmount: 1, slippage: 10, payer: 'YOUR_WALLET_ADDRESS', fee: 'YOUR_FEE_WALLET:0.1', feeType: 'add' }); ``` ## Response Structure ```json theme={null} { "txn": "BASE64_ENCODED_TRANSACTION", "rate": { "amountIn": 0.1, "amountOut": 81.631985, "minAmountOut": 73.4687865, "currentPrice": 0.012219167460548153, "executionPrice": 0.010997250714493338, "priceImpact": 0.002517, "fee": 0.000005, "platformFee": 9000000, "platformFeeUI": 0.009 }, "timeTaken": 0.016, "type": "v0" } ``` ## Platform Fees Standard fee: **0.5%** of transaction value * Deducted from output * Shown in `platformFee` (lamports) and `platformFeeUI` (SOL) * Volume discounts available Contact [swap-api@solanatracker.io](mailto:swap-api@solanatracker.io) for volume pricing ## Common Errors | Error | Solution | | ---------------------- | --------------------------------- | | Invalid token address | Verify addresses are valid | | Invalid amount | Use number, "auto", or percentage | | Invalid slippage | Set between 0-100 | | Token has no pools | Token may be delisted | | Insufficient liquidity | Try smaller amount | ## Next Steps Get quotes before swapping Install the SDK # Account Monitoring Source: https://docs.solanatracker.io/yellowstone-grpc/account-monitoring Watch Solana accounts in real-time - track balances, token holders, and account 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. **You'll need:** Run `npm install @triton-one/yellowstone-grpc` first ## 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 **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 }; ``` **Watch all accounts owned by a program:** ```javascript theme={null} const request = { accounts: { tokenAccounts: { account: [], owner: ["TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"], filters: [] } }, commitment: CommitmentLevel.CONFIRMED, // ... other fields }; ``` **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 }; ``` ## 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 **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) **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. **Use both together:** ```javascript theme={null} filters: [ { dataSize: 165 }, { memcmp: { offset: 0, bytes: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" } } ] ``` All filters must match (AND logic). ## 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 Only request the bytes you need to save bandwidth Use dataSize and memcmp to reduce updates CONFIRMED is usually best for most use cases Monitor how many updates you're receiving ## What You Can Build Monitor wallet or token balances in real-time. Get alerts when balances change significantly. Track specific wallets and see all their account activity. Great for analyzing behavior patterns. Monitor all accounts in your program to understand usage, track growth, and detect issues. Get instant notifications when new accounts are created for your program or specific token mints. Watch multiple token accounts to track portfolio changes and calculate total values. ## Common Questions 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. Yes! Just add them to the `account` array. You can watch hundreds of specific accounts at once. * **CONFIRMED** (\~400ms): Faster, good for most uses. Supermajority of validators agree. * **FINALIZED** (\~13 seconds): Slower but absolute certainty. Cannot be rolled back. 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. Yes! Use a memcmp filter at offset 0 with the mint address. This will only show accounts for that specific token. * **account**: Specific account addresses you want to watch * **owner**: All accounts owned by a program (like Token Program or your custom program) ## Next Steps Watch transactions too See it in action Optimize your code Get started fast # Authentication & Setup Source: https://docs.solanatracker.io/yellowstone-grpc/authentication Get your API credentials and configure your gRPC client ## Getting Your API Key Subscribe to the gRPC service at **\$247/month** to get access to: * EU endpoint: `https://grpc.solanatracker.io` * US endpoint: `https://grpc-us.solanatracker.io` * Accelerated by Jito Shreds * No bandwidth charges After subscription, you'll receive: * Your API token * Endpoint URLs * Configuration guidelines Verify your credentials work by creating a simple test connection ## Authentication Methods ### Header-Based Authentication Add your API token to the request headers: Your API token for authenticating gRPC requests ## Client Configuration ### TypeScript/JavaScript ```bash theme={null} npm install @triton-one/yellowstone-grpc ``` ```typescript theme={null} import Client from "@triton-one/yellowstone-grpc"; const client = new Client( "https://grpc.solanatracker.io", // or https://grpc-us.solanatracker.io "your-api-token", { // Recommended settings "grpc.max_receive_message_length": 64 * 1024 * 1024, // 64MB "grpc.max_send_message_length": 64 * 1024 * 1024, // 64MB "grpc.keepalive_time_ms": 30000, // 30 seconds "grpc.keepalive_timeout_ms": 5000, // 5 seconds } ); ``` ### Rust ```toml theme={null} [dependencies] yellowstone-grpc-client = "9.0.1" yellowstone-grpc-proto = "9.0.1" tokio = { version = "1.0", features = ["full"] } tonic = "0.10" ``` ```rust theme={null} use yellowstone_grpc_client::GeyserGrpcClient; use std::time::Duration; let endpoint = "https://grpc.solanatracker.io"; let token = Some("your-api-token".to_string()); let client = GeyserGrpcClient::build_from_shared(endpoint)? .x_token(token)? .connect_timeout(Duration::from_secs(10)) .timeout(Duration::from_secs(10)) .max_decoding_message_size(64 * 1024 * 1024) .connect() .await?; ``` ### Go ```bash theme={null} go get github.com/rpcpool/yellowstone-grpc/examples/golang@latest go get google.golang.org/grpc@v1.67.1 ``` ```go theme={null} import ( "google.golang.org/grpc" "google.golang.org/grpc/metadata" ) conn, err := grpc.Dial( "grpc.solanatracker.io:443", grpc.WithTransportCredentials(credentials.NewClientTLSFromCert(nil, "")), grpc.WithDefaultCallOptions( grpc.MaxCallRecvMsgSize(64 * 1024 * 1024), grpc.MaxCallSendMsgSize(64 * 1024 * 1024), ), ) // Add authentication ctx := metadata.AppendToOutgoingContext(context.Background(), "x-token", "your-api-token") ``` ## Endpoint Selection Choose the endpoint that provides the best latency for your infrastructure: **[https://grpc.solanatracker.io](https://grpc.solanatracker.io)** * Best for global applications * Automatic routing * High availability **[https://grpc-us.solanatracker.io](https://grpc-us.solanatracker.io)** * Optimized for North America * Lower latency for US-based apps * Direct US routing Test both endpoints and use the one with lower latency from your deployment region. ## Configuration Best Practices ### Message Size Limits Set appropriate message size limits to handle large blocks and transaction batches: ```typescript theme={null} { "grpc.max_receive_message_length": 64 * 1024 * 1024, // 64MB "grpc.max_send_message_length": 64 * 1024 * 1024 // 64MB } ``` ### Keepalive Settings Configure keepalive to maintain persistent connections: ```typescript theme={null} { "grpc.keepalive_time_ms": 30000, // Send keepalive every 30s "grpc.keepalive_timeout_ms": 5000, // Wait 5s for keepalive response "grpc.keepalive_permit_without_calls": 1 // Allow keepalive without active calls } ``` ### Connection Timeouts Set reasonable timeout values: ```typescript theme={null} { "grpc.initial_reconnect_backoff_ms": 1000, // Start with 1s backoff "grpc.max_reconnect_backoff_ms": 30000, // Max 30s backoff "grpc.min_reconnect_backoff_ms": 1000 // Min 1s backoff } ``` ## Testing Your Connection Create a simple test to verify your setup: ```typescript theme={null} import Client, { CommitmentLevel } from "@triton-one/yellowstone-grpc"; async function testConnection() { const client = new Client( "https://grpc.solanatracker.io", "your-api-token", { "grpc.max_receive_message_length": 64 * 1024 * 1024 } ); try { const stream = await client.subscribe(); stream.on("data", (data) => { console.log("Connection successful! Received data:", data); stream.end(); client.close(); process.exit(0); }); stream.on("error", (error) => { console.error("Connection error:", error); process.exit(1); }); // Subscribe to slot updates (lightweight test) const request = { slots: { slotSubscribe: {} }, commitment: CommitmentLevel.CONFIRMED }; stream.write(request); console.log("Testing connection..."); } catch (error) { console.error("Failed to connect:", error); process.exit(1); } } testConnection(); ``` ## Troubleshooting **Error:** `UNAUTHENTICATED` or `Invalid token` **Solutions:** * Verify your API token is correct * Check token is properly set in headers * Ensure subscription is active **Error:** Connection times out or fails to establish **Solutions:** * Verify endpoint URL is correct (include `https://`) * Check firewall allows outbound gRPC connections * Try alternative endpoint * Increase connection timeout settings **Error:** `RESOURCE_EXHAUSTED` or message size errors **Solutions:** * Increase `max_receive_message_length` to 64MB * Increase `max_send_message_length` to 64MB * Use data slicing to reduce message sizes * Apply more specific filters ## Security Best Practices **Protect Your API Token** * Never commit tokens to version control * Use environment variables for tokens * Rotate tokens periodically * Implement token management system ### Environment Variables Store your credentials securely: ```bash theme={null} # .env file GRPC_ENDPOINT=https://grpc.solanatracker.io GRPC_API_TOKEN=your-api-token ``` ```typescript theme={null} import * as dotenv from 'dotenv'; dotenv.config(); const client = new Client( process.env.GRPC_ENDPOINT!, process.env.GRPC_API_TOKEN!, { /* ... */ } ); ``` ## Next Steps Build your first streaming application Learn to monitor account changes # Best Practices Source: https://docs.solanatracker.io/yellowstone-grpc/best-practices Optimize your Yellowstone gRPC implementation for production ## Network Latency Optimization ### Minimize Your Ping Latency starts at the network level. No matter how optimized your code is, the speed of light remains the ultimate bottleneck. The single most impactful action you can take to minimize latency is to achieve geographical **co-location** with the gRPC endpoint. **Target: 0-1 milliseconds network latency** Aim for sub-millisecond ping between your client application and the gRPC endpoint. ### Recommended Data Centers Co-locate your infrastructure in these data centers for optimal performance: **Providers:** Cherry Servers, Latitude, velia.net, hostkey.com, Teraswitch **Endpoint:** `https://grpc.solanatracker.io` **Providers:** Latitude, velia.net, hostkey.com, Teraswitch, WebNX **Endpoint:** `https://grpc-us.solanatracker.io` ### Measuring Latency It's possible to achieve **1-2 ms** latency without co-locating if you're in the same region. Use these tools to measure your latency: ```bash theme={null} # Test ping to EU endpoint ping grpc.solanatracker.io # Test ping to US endpoint ping grpc-us.solanatracker.io # Detailed route analysis mtr grpc.solanatracker.io ``` **Latency Targets:** * **Excellent:** 0-1ms (co-located) * **Good:** 1-2ms (same region) * **Acceptable:** 2-5ms (nearby region) * **Poor:** >5ms (consider relocating) ## Connection Management ### Distribute Load Across Multiple Clients Streaming multiple high-load addresses (e.g., Meteora DLMM, Pump.fun, DEX programs) in a single subscription can quickly overwhelm a single client connection. **Problems with single client:** * Message backlog at network layer * Single consumer thread bottleneck * Client may disconnect due to unmanageable backlog **Best Practice:** Split high-load addresses across multiple gRPC clients and distribute processing across separate CPU cores/threads. ### When to Use Multiple Clients **Use separate clients for:** * DEX programs (Raydium, Jupiter, Orca) * Pump.fun program * Meteora DLMM * Popular lending protocols ```typescript theme={null} // Separate client for each high-volume program const raydiumClient = new Client(endpoint, token); const jupiterClient = new Client(endpoint, token); const pumpfunClient = new Client(endpoint, token); // Process on different threads/cores await Promise.all([ processRaydiumStream(raydiumClient), processJupiterStream(jupiterClient), processPumpfunStream(pumpfunClient) ]); ``` **Combine in single client:** * Individual tokens * Liquidity pools * User wallets * Low-activity programs ```typescript theme={null} // Single client for multiple moderate-load addresses const subscribeRequest: SubscribeRequest = { accounts: { multipleAddresses: { account: [ "token1Address", "token2Address", "poolAddress", "walletAddress" ], owner: [], filters: [] } }, commitment: CommitmentLevel.CONFIRMED }; ``` ### Connection Efficiency Over-fragmenting your connections for moderate-load addresses can quickly hit connection limits. **Best Practice:** Don't create a new connection for each token, pool, or wallet address. Combine them in a single subscribe request. Example of efficient connection management: ```typescript theme={null} class ConnectionManager { private highLoadClients: Map = new Map(); private moderateLoadClient: Client; async initialize() { // Separate clients for high-load programs this.highLoadClients.set('raydium', new Client(endpoint, token)); this.highLoadClients.set('jupiter', new Client(endpoint, token)); this.highLoadClients.set('pumpfun', new Client(endpoint, token)); // Single client for all moderate-load addresses this.moderateLoadClient = new Client(endpoint, token); } async addModerateLoadAddress(address: string) { // Dynamically add address to existing subscription await this.moderateLoadClient.modifySubscription({ accounts: { moderate: { account: [address], owner: [], filters: [] } } }); } } ``` Learn more about [modifying subscriptions on the fly](/yellowstone-grpc/advanced/modify-subscription). ## Client Configuration ### Expand Max Message Size Yellowstone gRPC clients have a default maximum size of **4 MB (4194304 bytes)** for incoming messages. When streaming account updates or block updates, you can hit this limit. **Required:** Configure your gRPC client to avoid hitting the 4 MB message limit. ```typescript theme={null} const client = new Client( "https://grpc.solanatracker.io", "your-x-token", { "grpc.max_receive_message_length": 1024 * 1024 * 1024 // 1 GB } ); ``` ```rust theme={null} async fn connect(&self) -> anyhow::Result> { GeyserGrpcClient::build_from_shared(self.grpc_url.clone())? .x_token(Some(self.grpc_token.clone()))? .connect_timeout(Duration::from_secs(10)) .timeout(Duration::from_secs(10)) .tls_config(ClientTlsConfig::new().with_native_roots())? .max_decoding_message_size(1024 * 1024 * 1024) // 1 GB .connect() .await .map_err(Into::into) } ``` ```go theme={null} conn, err := grpc.Dial( "grpc.solanatracker.io:443", grpc.WithTransportCredentials(credentials.NewClientTLSFromCert(nil, "")), grpc.WithDefaultCallOptions( grpc.MaxCallRecvMsgSize(1024 * 1024 * 1024), // 1 GB grpc.MaxCallSendMsgSize(1024 * 1024 * 1024), ), ) ``` ### Keepalive Configuration Configure keepalive to maintain persistent connections: ```typescript theme={null} const client = new Client( endpoint, token, { "grpc.keepalive_time_ms": 30000, // Send keepalive every 30s "grpc.keepalive_timeout_ms": 5000, // Wait 5s for keepalive response "grpc.keepalive_permit_without_calls": 1 // Allow keepalive without active calls } ); ``` ### Connection Timeouts Set reasonable timeout values: ```typescript theme={null} { "grpc.initial_reconnect_backoff_ms": 1000, // Start with 1s backoff "grpc.max_reconnect_backoff_ms": 30000, // Max 30s backoff "grpc.min_reconnect_backoff_ms": 1000 // Min 1s backoff } ``` ## Processing Optimization ### Asynchronous Processing Decouple I/O (receiving messages) from CPU-bound work (deserializing, filtering, business logic): ```typescript theme={null} class StreamProcessor { private messageQueue: Array = []; private processing = false; async handleMessage(data: any) { // Add to queue (fast) this.messageQueue.push(data); // Process asynchronously if (!this.processing) { this.processQueue(); } } private async processQueue() { this.processing = true; while (this.messageQueue.length > 0) { const message = this.messageQueue.shift(); // CPU-intensive processing await this.parseAndProcess(message); } this.processing = false; } private async parseAndProcess(message: any) { // Your parsing and business logic here } } ``` ### Worker Thread Distribution For extremely high-volume streams, distribute processing across worker threads: ```typescript theme={null} import { Worker } from 'worker_threads'; class MultiThreadProcessor { private workers: Worker[] = []; private roundRobin = 0; constructor(numWorkers: number = 4) { for (let i = 0; i < numWorkers; i++) { this.workers.push(new Worker('./processor-worker.js')); } } async handleMessage(data: any) { // Distribute to workers in round-robin fashion const worker = this.workers[this.roundRobin]; worker.postMessage(data); this.roundRobin = (this.roundRobin + 1) % this.workers.length; } } ``` ## Error Handling ### Implement Exponential Backoff ```typescript theme={null} class ResilientStreamManager { private reconnectAttempts = 0; private readonly maxReconnectAttempts = 10; private readonly baseReconnectDelay = 1000; private async reconnect(subscribeRequest: SubscribeRequest): Promise { if (this.reconnectAttempts >= this.maxReconnectAttempts) { console.error("Max reconnection attempts reached."); return; } this.reconnectAttempts++; const delay = this.baseReconnectDelay * Math.pow(2, Math.min(this.reconnectAttempts - 1, 5)); console.log(`Reconnect attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts} in ${delay}ms...`); setTimeout(() => { this.connect(subscribeRequest).catch(console.error); }, delay); } } ``` ### Handle Stream Errors Gracefully ```typescript theme={null} stream.on("error", (error) => { console.error("Stream error:", error); // Log error details logErrorToMonitoring(error); // Don't crash - attempt to reconnect this.handleDisconnect(subscribeRequest); }); stream.on("end", () => { console.log("Stream ended, reconnecting..."); this.handleDisconnect(subscribeRequest); }); ``` ## Monitoring & Observability ### Track Key Metrics ```typescript theme={null} class StreamMetrics { private messagesReceived = 0; private messagesProcessed = 0; private errors = 0; private lastReportTime = Date.now(); recordMessage() { this.messagesReceived++; } recordProcessed() { this.messagesProcessed++; } recordError() { this.errors++; } report() { const now = Date.now(); const elapsed = (now - this.lastReportTime) / 1000; console.log(`\n=== Stream Metrics (${elapsed.toFixed(1)}s) ===`); console.log(` Messages Received: ${this.messagesReceived}`); console.log(` Messages Processed: ${this.messagesProcessed}`); console.log(` Processing Rate: ${(this.messagesProcessed / elapsed).toFixed(2)}/sec`); console.log(` Errors: ${this.errors}`); console.log(` Backlog: ${this.messagesReceived - this.messagesProcessed}`); this.lastReportTime = now; } } ``` ### Alert on Anomalies ```typescript theme={null} class AnomalyDetector { private readonly backlogThreshold = 1000; private readonly errorRateThreshold = 0.01; // 1% checkHealth(metrics: StreamMetrics) { const backlog = metrics.messagesReceived - metrics.messagesProcessed; if (backlog > this.backlogThreshold) { this.alert(`High backlog: ${backlog} messages`); } const errorRate = metrics.errors / metrics.messagesReceived; if (errorRate > this.errorRateThreshold) { this.alert(`High error rate: ${(errorRate * 100).toFixed(2)}%`); } } private alert(message: string) { console.error(`[ALERT] ${message}`); // Send to your monitoring system } } ``` ## Security Best Practices ### Protect Your Credentials **Never commit credentials to version control!** ```bash theme={null} # .env file (add to .gitignore) GRPC_ENDPOINT=https://grpc.solanatracker.io GRPC_X_TOKEN=your-secret-token # .gitignore .env *.env .env.* ``` ### Rotate Tokens Regularly ```typescript theme={null} class TokenRotationManager { private currentToken: string; private nextToken?: string; async rotateToken(newToken: string) { this.nextToken = newToken; // Gracefully switch to new token await this.reconnectWithNewToken(); this.currentToken = this.nextToken; this.nextToken = undefined; } private async reconnectWithNewToken() { // Disconnect current stream this.disconnect(); // Connect with new token await this.connect(this.nextToken!); } } ``` ## Performance Checklist **Production Deployment Checklist:** * [ ] Co-locate in recommended data center (0-1ms latency) * [ ] Separate high-load programs into different clients * [ ] Configure max message size to 1GB * [ ] Implement keepalive (30s interval) * [ ] Use asynchronous processing * [ ] Implement exponential backoff reconnection * [ ] Monitor message rates and backlog * [ ] Set up error alerting * [ ] Use environment variables for credentials * [ ] Implement token rotation mechanism * [ ] Log metrics for analysis * [ ] Test failover scenarios ## Common Pitfalls **Problem:** Slow processing logic blocks receiving new messages **Solution:** Decouple message receipt from processing using queues and async processing **Problem:** Creating separate connection for each address **Solution:** Combine moderate-load addresses in single subscription **Problem:** Stream crashes on errors without recovery **Solution:** Implement comprehensive error handling with automatic reconnection **Problem:** Can't diagnose performance issues or failures **Solution:** Implement metrics tracking and alerting from day one ## Next Steps Implement efficient account streaming Optimize transaction stream processing See best practices in action # Entry Monitoring Source: https://docs.solanatracker.io/yellowstone-grpc/entry-monitoring Watch the smallest building blocks of Solana - entries that contain transaction batches ## Overview Entries are the smallest building blocks of the Solana blockchain. Think of them as containers that hold groups of transactions. **Start here first:** Complete the [Quickstart Guide](/yellowstone-grpc/quickstart) to set up your stream manager. ## What are Entries? **Entries are transaction containers** Here's what they do: * **Bundle transactions:** They group transactions together * **Set the order:** Transactions run in a specific order * **Link together:** Each entry connects to the next one * **Track time:** They record when things happen ```typescript theme={null} const subscribeRequest: SubscribeRequest = { entry: { entrySubscribe: {} }, commitment: CommitmentLevel.CONFIRMED }; ``` **Each entry has:** * **Slot:** Which time slot it belongs to (slots are 400ms long) * **Index:** Its position in that slot * **Hash:** A unique ID * **Transactions:** The list of transactions inside * **Num Hashes:** A proof-of-history number **Why this matters:** * See how transactions are grouped * Understand validator behavior * Get more detail than just watching transactions **Use entry monitoring for:** * **Performance research:** How fast are transactions being processed? * **Validator studies:** How do validators work? * **Network problems:** Debugging consensus issues * **Research projects:** Academic blockchain studies * **Deep analysis:** Investigating transaction order **Skip this if:** You're building a regular app or need user-facing features ## Real Examples ### Example 1: Basic Entry Monitor Watch entries as they come in: ```typescript theme={null} import { StreamManager } from './yellowstone'; import { CommitmentLevel, SubscribeRequest } from "@triton-one/yellowstone-grpc"; async function monitorEntries() { const streamManager = new StreamManager( "https://grpc.solanatracker.io", process.env.GRPC_API_TOKEN || "your-api-key", handleEntry ); const subscribeRequest: SubscribeRequest = { entry: { entrySubscribe: {} }, commitment: CommitmentLevel.CONFIRMED }; console.log('Starting entry monitoring...'); console.log('Note: This is a high-volume stream\n'); await streamManager.connect(subscribeRequest); } const entryStats = { totalEntries: 0, totalTransactions: 0, emptyEntries: 0, lastReportTime: Date.now() }; function handleEntry(data: any): void { if (data.entry) { const entry = data.entry; entryStats.totalEntries++; const txCount = entry.transactions?.length || 0; entryStats.totalTransactions += txCount; if (txCount === 0) { entryStats.emptyEntries++; } console.log(`\n[Entry #${entryStats.totalEntries}]`); console.log(` Slot: ${entry.slot}`); console.log(` Index: ${entry.index || 'N/A'}`); console.log(` Hash: ${entry.hash?.slice(0, 16) || 'N/A'}...`); console.log(` Num Hashes: ${entry.numHashes || 'N/A'}`); console.log(` Transactions: ${txCount}`); if (txCount === 0) { console.log(` [Empty Entry]`); } else { // Count transaction types let successCount = 0; let failedCount = 0; let voteCount = 0; entry.transactions.forEach((tx: any) => { if (tx.isVote) { voteCount++; } if (tx.meta) { if (tx.meta.err) { failedCount++; } else { successCount++; } } }); console.log(` Transaction Breakdown:`); console.log(` Votes: ${voteCount}`); console.log(` Successful: ${successCount}`); console.log(` Failed: ${failedCount}`); // Show first few signatures if (entry.transactions.length > 0) { console.log(` First Transactions:`); entry.transactions.slice(0, 3).forEach((tx: any, idx: number) => { const sig = tx.signature?.slice(0, 16) || 'unknown'; const type = tx.isVote ? '[VOTE]' : '[TX]'; console.log(` ${idx + 1}. ${sig}... ${type}`); }); } } // Report stats every 100 entries if (entryStats.totalEntries % 100 === 0) { printEntrySummary(); } } } function printEntrySummary(): void { const now = Date.now(); const elapsed = (now - entryStats.lastReportTime) / 1000; const entriesPerSec = 100 / elapsed; const avgTxPerEntry = entryStats.totalTransactions / entryStats.totalEntries; const emptyRate = (entryStats.emptyEntries / entryStats.totalEntries * 100).toFixed(1); console.log(`\n=== Entry Summary ===`); console.log(` Total Entries: ${entryStats.totalEntries}`); console.log(` Total Transactions: ${entryStats.totalTransactions}`); console.log(` Empty Entries: ${entryStats.emptyEntries} (${emptyRate}%)`); console.log(` Entries/sec: ${entriesPerSec.toFixed(1)}`); console.log(` Avg Tx/Entry: ${avgTxPerEntry.toFixed(2)}`); entryStats.lastReportTime = now; } monitorEntries().catch(console.error); ``` ### Example 2: Filter Entries Only process entries you care about: ```typescript theme={null} async function monitorFilteredEntries() { const streamManager = new StreamManager( "https://grpc.solanatracker.io", process.env.GRPC_API_TOKEN || "your-api-key", handleFilteredEntry ); const subscribeRequest: SubscribeRequest = { entry: { entrySubscribe: {} }, commitment: CommitmentLevel.CONFIRMED }; console.log('Monitoring filtered entries (non-vote transactions only)...\n'); await streamManager.connect(subscribeRequest); } const filteredStats = { totalEntriesProcessed: 0, totalEntriesSkipped: 0, nonVoteTransactions: 0, voteTransactions: 0 }; function handleFilteredEntry(data: any): void { if (data.entry) { const entry = data.entry; // Skip empty entries if (!entry.transactions || entry.transactions.length === 0) { filteredStats.totalEntriesSkipped++; return; } // Count transaction types let nonVoteCount = 0; let voteCount = 0; entry.transactions.forEach((tx: any) => { if (tx.isVote) { voteCount++; } else { nonVoteCount++; } }); // Skip entries with only vote transactions if (nonVoteCount === 0) { filteredStats.totalEntriesSkipped++; filteredStats.voteTransactions += voteCount; return; } // Process this entry filteredStats.totalEntriesProcessed++; filteredStats.nonVoteTransactions += nonVoteCount; filteredStats.voteTransactions += voteCount; console.log(`\n[Filtered Entry #${filteredStats.totalEntriesProcessed}]`); console.log(` Slot: ${entry.slot}`); console.log(` Total Transactions: ${entry.transactions.length}`); console.log(` Non-Vote Txs: ${nonVoteCount}`); console.log(` Vote Txs: ${voteCount}`); // Show non-vote transaction signatures const nonVoteTxs = entry.transactions.filter((tx: any) => !tx.isVote); console.log(` Non-Vote Signatures:`); nonVoteTxs.slice(0, 3).forEach((tx: any, idx: number) => { const sig = tx.signature?.slice(0, 16) || 'unknown'; const status = tx.meta?.err ? '❌' : '✅'; console.log(` ${idx + 1}. ${sig}... ${status}`); }); // Report stats every 100 processed entries if (filteredStats.totalEntriesProcessed % 100 === 0) { printFilterStats(); } } } function printFilterStats(): void { const totalEntries = filteredStats.totalEntriesProcessed + filteredStats.totalEntriesSkipped; const processRate = (filteredStats.totalEntriesProcessed / totalEntries * 100).toFixed(1); const skipRate = (filteredStats.totalEntriesSkipped / totalEntries * 100).toFixed(1); console.log(`\n=== Filtering Statistics ===`); console.log(` Total Entries Seen: ${totalEntries}`); console.log(` Processed: ${filteredStats.totalEntriesProcessed} (${processRate}%)`); console.log(` Skipped: ${filteredStats.totalEntriesSkipped} (${skipRate}%)`); console.log(` Non-Vote Txs Found: ${filteredStats.nonVoteTransactions}`); console.log(` Vote Txs Found: ${filteredStats.voteTransactions}`); } monitorFilteredEntries().catch(console.error); ``` ## Entry Data Structure ```typescript theme={null} { slot: number; // Which time slot index: number; // Position in that slot hash: string; // Unique ID numHashes: number; // Proof-of-history count transactions: Array<{ // List of transactions signature: string; isVote: boolean; meta: { err: any; fee: number; }; }>; tick: boolean; // Is this a tick entry? } ``` **Entries vs Transactions:** * Entries = groups of transactions * Shows you the order things happen * Includes timing proof data **Entries vs Blocks:** * Blocks = many entries combined * Entries = smaller pieces inside blocks * Blocks show finality **Entries vs Slots:** * Slots = 400ms time windows * One slot = multiple entries * Entries = what happened in that time ## Performance Considerations **This stream is busy** * Many messages per second * Never stops during network activity * Each entry has multiple transactions * You need fast processing **Keep your code efficient** * Don't wait for each entry (use async) * Process multiple entries at once * Only look at what you need * Sample the data if analyzing a lot ## Common Use Cases **Study how fast things work** Track how the network groups transactions. Find patterns that show problems or ways to make things faster. **Study how validators work** See how validators build blocks. Look at patterns, ordering, and grouping strategies. **Fix network problems** Use entry data to find bugs, investigate weird patterns, and understand what's happening at a low level. ## Filtering Tips Entry monitoring sends you ALL entries. There's no built-in filtering. Here's how to deal with that: **Ways to handle the data:** * **Filter in your code:** Only process what you care about * **Sample it:** Look at every 10th or 100th entry for stats * **Time windows:** Only look at specific time periods * **Slot filtering:** Only process certain slots * **Transaction types:** Focus on specific transaction types (see Example 3) ## Best Practices **Use entry monitoring for:** * Deep blockchain research * Studying validators * Debugging network issues * Academic projects * Understanding Proof of History **Don't use it for:** * Normal apps * User interfaces * Business features * Trading apps **Tips for processing:** * Build fast code * Don't wait for each entry (use async) * Sample if you're analyzing a lot * Watch your memory usage * Handle when data comes too fast **Do good analysis:** * Pick specific things to measure * Use sampling for large datasets * Calculate rolling averages * Look at patterns over time * Compare with other data sources ## Troubleshooting **Problem:** The entry stream is overwhelming **Fix it:** * Filter in your code (see Example 3) * Sample the data * Use async processing * Check your system resources * Consider transaction monitoring instead **Problem:** Entries don't have enough context **Fix it:** * Also monitor transactions * Check account updates * Use block monitoring too * Keep your own state ## Common Questions Entries are created constantly as validators process transactions. The Solana network is very active, so you'll see many entries per second. Use filtering to manage the volume. Empty entries are part of Solana's Proof of History mechanism. They help maintain timing even when there are no transactions to process. They're normal and expected. Usually yes. Vote transactions are validator consensus votes and typically aren't relevant for application-level monitoring. See Example 3 for how to filter them. If you're falling behind, you'll see increasing delays between entry creation and processing. Monitor your processing time and queue sizes. Consider sampling or better filtering if you can't keep up. ## Next Steps See a real example Monitor transactions instead Higher-level monitoring Optimize your setup **Remember:** Entry monitoring is for advanced research. Most apps work better with transaction, account, or block monitoring. # Pump.fun Account Streaming Source: https://docs.solanatracker.io/yellowstone-grpc/examples/pumpfun-accounts Stream and parse Pump.fun account updates in real-time Learn how to stream and parse Pump.fun account data using Yellowstone gRPC. This guide demonstrates efficient account monitoring for trading bots, analytics platforms, and real-time DeFi applications. ## Overview For any Solana dApp, trading bot, or real-time analytics platform, efficient account data access is crucial. Yellowstone gRPC streaming offers a high-performance, low-latency alternative to traditional RPC polling and WebSockets. **What you'll learn:** * Stream all Pump.fun account updates in real-time * Decode account data using the program's IDL * Track bonding curve and global account changes * Build production-ready account monitoring systems ## Installation ```bash theme={null} npm install @triton-one/yellowstone-grpc @coral-xyz/anchor ``` ## Complete Working Example Here's a production-ready Pump.fun account monitor with bonding curve tracking: ```javascript theme={null} const Client = require("@triton-one/yellowstone-grpc").default; const { CommitmentLevel } = require("@triton-one/yellowstone-grpc"); const { BorshAccountsCoder } = require("@coral-xyz/anchor"); const fs = require('fs'); const PUMP_PROGRAM_ID = "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P"; // Load Pump.fun IDL const pumpFunIdl = JSON.parse( fs.readFileSync('./idl/pump_0.1.0.json', 'utf8') ); const accountCoder = new BorshAccountsCoder(pumpFunIdl); // Initialize and 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 statistics const stats = { totalUpdates: 0, bondingCurves: new Set(), completedCurves: 0, lastReportTime: Date.now() }; // Handle bonding curve updates function handleBondingCurve(data, account) { stats.bondingCurves.add(account.pubkey); console.log(`\n[Bonding Curve Update]`); console.log(` Address: ${account.pubkey}`); console.log(` Virtual Token Reserves: ${data.virtualTokenReserves}`); console.log(` Virtual Sol Reserves: ${data.virtualSolReserves}`); console.log(` Real Token Reserves: ${data.realTokenReserves}`); console.log(` Real Sol Reserves: ${data.realSolReserves}`); console.log(` Token Total Supply: ${data.tokenTotalSupply}`); console.log(` Complete: ${data.complete}`); // Calculate current price const price = Number(data.virtualSolReserves) / Number(data.virtualTokenReserves); console.log(` Current Price: ${price.toFixed(9)} SOL/token`); if (data.complete && !stats.bondingCurves.has(account.pubkey + '_completed')) { stats.completedCurves++; stats.bondingCurves.add(account.pubkey + '_completed'); console.log(`\n🎉 BONDING CURVE COMPLETED!`); console.log(` Address: ${account.pubkey}`); console.log(` Final Sol Reserves: ${(Number(data.realSolReserves) / 1e9).toFixed(4)} SOL`); console.log(` Token Supply: ${data.tokenTotalSupply}`); } } // Handle global account updates function handleGlobalAccount(data, account) { console.log(`\n[Global Configuration Update]`); console.log(` Address: ${account.pubkey}`); console.log(` Fee Recipient: ${data.feeRecipient}`); console.log(` Initial Virtual Token Reserves: ${data.initialVirtualTokenReserves}`); console.log(` Initial Virtual Sol Reserves: ${data.initialVirtualSolReserves}`); } (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) { stats.totalUpdates++; const accountData = data.account.account; try { // Decode account data const decodedData = accountCoder.decodeAny(accountData.data); console.log(`\n[Account Update #${stats.totalUpdates}]`); console.log(` Address: ${accountData.pubkey}`); console.log(` Type: ${decodedData.discriminator}`); console.log(` Slot: ${data.account.slot}`); // Process based on account type if (decodedData.discriminator === 'BondingCurve') { handleBondingCurve(decodedData, accountData); } else if (decodedData.discriminator === 'Global') { handleGlobalAccount(decodedData, accountData); } } catch (error) { // Ignore decoding errors for unknown account types } // Report statistics every 100 updates if (stats.totalUpdates % 100 === 0) { const now = Date.now(); const elapsed = (now - stats.lastReportTime) / 1000; const rate = 100 / elapsed; console.log(`\n=== Statistics ===`); console.log(` Total Updates: ${stats.totalUpdates}`); console.log(` Update Rate: ${rate.toFixed(2)}/sec`); console.log(` Bonding Curves Tracked: ${stats.bondingCurves.size}`); console.log(` Completed Curves: ${stats.completedCurves}`); stats.lastReportTime = now; } } }); // Subscribe to Pump.fun accounts const request = { accounts: { pumpfun: { account: [], owner: [PUMP_PROGRAM_ID], // Stream all accounts owned by Pump.fun filters: [] } }, slots: {}, transactions: {}, transactionsStatus: {}, entry: {}, blocks: {}, blocksMeta: {}, accountsDataSlice: [], ping: undefined, commitment: CommitmentLevel.PROCESSED, // Fastest updates }; // Send subscribe request await new Promise((resolve, reject) => { stream.write(request, (err) => { if (err === null || err === undefined) { console.log("✅ Monitoring Pump.fun 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: ${stats.totalUpdates}, Curves: ${stats.bondingCurves.size}, Completed: ${stats.completedCurves}`); process.exit(0); }); ``` ## Pump.fun Account Types ### Bonding Curve Account The bonding curve account contains the token economics and liquidity information: ```javascript theme={null} { virtualTokenReserves: bigint, // Virtual token reserves for pricing virtualSolReserves: bigint, // Virtual SOL reserves for pricing realTokenReserves: bigint, // Actual tokens in the curve realSolReserves: bigint, // Actual SOL in the curve tokenTotalSupply: bigint, // Total token supply complete: boolean // Whether bonding curve is complete } ``` **Use cases:** * Calculate current token price * Monitor liquidity changes * Detect when bonding curve completes * Track token supply distribution ### Global Account The global account contains program-wide configuration: ```javascript theme={null} { feeRecipient: string, // Address receiving fees initialVirtualTokenReserves: bigint, // Initial virtual token reserves initialVirtualSolReserves: bigint, // Initial virtual SOL reserves initialRealTokenReserves: bigint, // Initial real token reserves tokenTotalSupply: bigint, // Standard token total supply feeBasisPoints: number // Fee in basis points } ``` ## Advanced Examples ### Example 2: Track Specific Bonding Curves Monitor only specific bonding curve addresses: ```javascript theme={null} const BONDING_CURVES = [ "BondingCurve1...", "BondingCurve2...", "BondingCurve3..." ]; const request = { accounts: { specificCurves: { account: BONDING_CURVES, // Monitor specific accounts owner: [], filters: [] } }, // ... other fields commitment: CommitmentLevel.CONFIRMED, }; ``` ### Example 3: Filter for Active Bonding Curves Filter for incomplete bonding curves only: ```javascript theme={null} const request = { accounts: { activeBondingCurves: { account: [], owner: [PUMP_PROGRAM_ID], filters: [ { dataSize: 120 }, // Bonding curve account size { memcmp: { offset: 108, // Offset to 'complete' field bytes: Buffer.from([0]).toString('base64') // Filter for incomplete } } ] } }, // ... other fields commitment: CommitmentLevel.CONFIRMED, }; ``` ### Example 4: Price Tracking Track price movements across all bonding curves: ```javascript theme={null} const priceHistory = new Map(); // curve address -> array of prices function trackPrice(address, virtualSol, virtualToken) { const price = Number(virtualSol) / Number(virtualToken); if (!priceHistory.has(address)) { priceHistory.set(address, []); } const history = priceHistory.get(address); history.push({ price, timestamp: Date.now() }); // Keep only last 100 prices if (history.length > 100) { history.shift(); } // Calculate price change if (history.length > 1) { const oldPrice = history[0].price; const priceChange = ((price - oldPrice) / oldPrice) * 100; if (Math.abs(priceChange) > 50) { // 50% change console.log(`\n🚨 PRICE ALERT`); console.log(` Curve: ${address}`); console.log(` Change: ${priceChange.toFixed(2)}%`); console.log(` Current Price: ${price.toFixed(9)} SOL`); } } } // Call in handleBondingCurve: trackPrice( account.pubkey, data.virtualSolReserves, data.virtualTokenReserves ); ``` ### Example 5: Liquidity Monitoring Track liquidity changes: ```javascript theme={null} const liquidityTracker = new Map(); // curve -> last liquidity function trackLiquidity(address, realSol) { const liquidity = Number(realSol) / 1e9; // Convert to SOL const previous = liquidityTracker.get(address); if (previous) { const change = liquidity - previous; const percentChange = (change / previous) * 100; if (Math.abs(percentChange) > 10) { // 10% change console.log(`\n💧 LIQUIDITY CHANGE`); console.log(` Curve: ${address}`); console.log(` Current: ${liquidity.toFixed(4)} SOL`); console.log(` Change: ${change > 0 ? '+' : ''}${change.toFixed(4)} SOL (${percentChange.toFixed(1)}%)`); } } liquidityTracker.set(address, liquidity); } ``` ## Dynamic Subscription Management Modify your subscription on-the-fly without restarting: ```javascript theme={null} async function addCurveToMonitor(stream, bondingCurveAddress) { await new Promise((resolve, reject) => { stream.write({ accounts: { newCurve: { account: [bondingCurveAddress], owner: [], filters: [] } } }, (err) => { if (err === null || err === undefined) { console.log(`Added ${bondingCurveAddress} to monitoring`); resolve(); } else { reject(err); } }); }); } ``` ## Performance Tips Get updates faster with `CommitmentLevel.PROCESSED` for time-sensitive applications Use dataSize and memcmp filters to reduce bandwidth and processing load Process account updates in batches for better throughput Maintain local cache of account states to detect changes efficiently ## Common Use Cases ```javascript theme={null} function handleBondingCurve(data, account) { // Calculate current price const price = Number(data.virtualSolReserves) / Number(data.virtualTokenReserves); // Check if price is favorable if (price < TARGET_PRICE && !data.complete) { executeBuy(account.pubkey, AMOUNT); } } ``` ```javascript theme={null} const analytics = { totalLiquidity: 0, averagePrice: 0, activeCurves: 0 }; function trackAnalytics(data) { if (!data.complete) { analytics.activeCurves++; analytics.totalLiquidity += Number(data.realSolReserves); } } ``` ```javascript theme={null} function checkAlerts(data, account) { const liquidity = Number(data.realSolReserves) / 1e9; if (liquidity > LARGE_LIQUIDITY_THRESHOLD) { sendAlert(`Large bonding curve: ${liquidity.toFixed(2)} SOL`); } if (data.complete) { sendAlert(`Bonding curve completed: ${account.pubkey}`); } } ``` ## IDL File Download the Pump.fun IDL and save it as `idl/pump_0.1.0.json`: ```json theme={null} { "version": "0.1.0", "name": "pump", "accounts": [ { "name": "BondingCurve", "type": { "kind": "struct", "fields": [ { "name": "virtualTokenReserves", "type": "u64" }, { "name": "virtualSolReserves", "type": "u64" }, { "name": "realTokenReserves", "type": "u64" }, { "name": "realSolReserves", "type": "u64" }, { "name": "tokenTotalSupply", "type": "u64" }, { "name": "complete", "type": "bool" } ] } }, { "name": "Global", "type": { "kind": "struct", "fields": [ { "name": "feeRecipient", "type": "publicKey" }, { "name": "initialVirtualTokenReserves", "type": "u64" }, { "name": "initialVirtualSolReserves", "type": "u64" } ] } } ] } ``` ## Resources Optimize your implementation Detect buy/sell events General account monitoring guide # Pump.fun Buy/Sell Detection Source: https://docs.solanatracker.io/yellowstone-grpc/examples/pumpfun-transactions Detect and parse buy/sell transactions on Pump.fun in real-time Track **buy and sell events** on Pump.fun in real-time using Yellowstone gRPC. This guide is essential for building trading bots, market analysis tools, and automated trading systems that need instant transaction detection. ## Overview Tracking buy and sell events on Pump.fun is crucial for trading bots and market analysis. This guide details how to utilize Solana's Yellowstone gRPC stream to efficiently capture and parse real-time transaction data, enabling instant detection of token swaps. **What you'll learn:** * Stream Pump.fun transactions in real-time * Parse transactions with IDL-based parsing * Identify buy vs sell transactions * Extract trade amounts, prices, and participants ## Architecture This implementation consists of three key components: 1. **Stream Transactions** - Subscribe to Pump.fun transactions via gRPC 2. **Parse Transactions** - Decode instructions and events using IDL 3. **Identify Buy/Sell** - Detect transaction type based on events ```mermaid theme={null} graph LR A[Yellowstone gRPC] --> B[Subscribe to Pump.fun Txs] B --> C[Receive Transaction] C --> D[Parse with IDL] D --> E[Identify Buy/Sell] E --> F[Extract Trade Details] F --> G[Your Trading Logic] ``` ## Installation Install the required dependencies: ```bash theme={null} npm install @triton-one/yellowstone-grpc @coral-xyz/anchor ``` ## Complete Working Example Here's a production-ready Pump.fun trading bot that detects all buy/sell events: ```javascript theme={null} const Client = require("@triton-one/yellowstone-grpc").default; const { CommitmentLevel } = require("@triton-one/yellowstone-grpc"); const { BorshInstructionCoder, BorshEventCoder } = require("@coral-xyz/anchor"); const fs = require('fs'); const PUMP_PROGRAM_ID = "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P"; // Load Pump.fun IDL for parsing const pumpFunIdl = JSON.parse( fs.readFileSync('./idl/pump_0.1.0.json', 'utf8') ); const instructionCoder = new BorshInstructionCoder(pumpFunIdl); const eventCoder = new BorshEventCoder(pumpFunIdl); // Initialize and 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!"); } }; // Parse Pump.fun transaction function parsePumpTransaction(tx) { if (!tx || tx.meta?.err) return null; try { const events = []; // Parse events from logs if (tx.meta?.logMessages) { for (const log of tx.meta.logMessages) { if (log.startsWith('Program data: ')) { const eventData = log.slice('Program data: '.length); try { const decoded = eventCoder.decode(eventData); if (decoded) { events.push(decoded); } } catch (e) { // Skip non-event logs } } } } return { events }; } catch (err) { console.error("Parse error:", err); return null; } } // Identify if transaction is buy or sell function identifyTradeType(parsedTx, rawTx) { if (!parsedTx?.events || parsedTx.events.length === 0) return null; // Look for TradeEvent in parsed events const tradeEvent = parsedTx.events.find(e => e.name === 'TradeEvent'); if (!tradeEvent) return null; const { mint, isBuy, solAmount, tokenAmount, user } = tradeEvent.data; return { type: isBuy ? 'buy' : 'sell', mint: mint.toString(), signer: user.toString(), tokenAmount: Number(tokenAmount) / 1e6, // Assuming 6 decimals solAmount: Number(solAmount) / 1e9, // SOL has 9 decimals signature: rawTx.signature, slot: rawTx.slot }; } // Trading statistics const stats = { totalTrades: 0, buys: 0, sells: 0, totalVolume: 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 transactions stream.on("data", (data) => { if (data?.transaction) { const tx = data.transaction.transaction; const slot = data.transaction.slot; // Parse the transaction const parsedTx = parsePumpTransaction(tx); if (!parsedTx) return; // Identify trade type const trade = identifyTradeType(parsedTx, { signature: tx.signature, slot }); if (!trade) return; // Log the trade stats.totalTrades++; if (trade.type === 'buy') { stats.buys++; } else { stats.sells++; } stats.totalVolume += trade.solAmount; console.log(`\nTrade #${stats.totalTrades}`); console.log(` Type: ${trade.type.toUpperCase()}`); console.log(` Mint: ${trade.mint}`); console.log(` Trader: ${trade.signer.slice(0, 8)}...`); console.log(` Token Amount: ${trade.tokenAmount.toFixed(6)}`); console.log(` SOL Amount: ${trade.solAmount.toFixed(4)} SOL`); console.log(` Price: ${(trade.solAmount / trade.tokenAmount).toFixed(9)} SOL/token`); } }); // Subscribe to Pump.fun transactions const request = { accounts: {}, slots: {}, transactions: { pump: { vote: false, failed: false, signature: undefined, accountInclude: [PUMP_PROGRAM_ID], accountExclude: [], accountRequired: [], }, }, transactionsStatus: {}, entry: {}, blocks: {}, blocksMeta: {}, accountsDataSlice: [], 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 Pump.fun buy/sell events...\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 - Trades: ${stats.totalTrades}, Buys: ${stats.buys}, Sells: ${stats.sells}`); process.exit(0); }); ``` ## Environment Setup Create a `.env` file: ```bash theme={null} GRPC_ENDPOINT=https://grpc.solanatracker.io GRPC_API_KEY=your-api-key-here ``` ## IDL File Download the Pump.fun IDL and save it as `idl/pump_0.1.0.json`: ```json theme={null} {{ "address": "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P", "metadata": { "name": "pump", "version": "0.1.0", "spec": "0.1.0", "description": "Created with Anchor" }, "instructions": [ { "name": "admin_set_creator", "docs": [ "Allows Global::admin_set_creator_authority to override the bonding curve creator" ], "discriminator": [ 69, 25, 171, 142, 57, 239, 13, 4 ], "accounts": [ { "name": "admin_set_creator_authority", "signer": true, "relations": [ "global" ] }, { "name": "global", "pda": { "seeds": [ { "kind": "const", "value": [ 103, 108, 111, 98, 97, 108 ] } ] } }, { "name": "mint" }, { "name": "bonding_curve", "writable": true, "pda": { "seeds": [ { "kind": "const", "value": [ 98, 111, 110, 100, 105, 110, 103, 45, 99, 117, 114, 118, 101 ] }, { "kind": "account", "path": "mint" } ] } }, { "name": "event_authority", "pda": { "seeds": [ { "kind": "const", "value": [ 95, 95, 101, 118, 101, 110, 116, 95, 97, 117, 116, 104, 111, 114, 105, 116, 121 ] } ] } }, { "name": "program" } ], "args": [ { "name": "creator", "type": "pubkey" } ] }, { "name": "admin_set_idl_authority", "discriminator": [ 8, 217, 96, 231, 144, 104, 192, 5 ], "accounts": [ { "name": "authority", "signer": true, "relations": [ "global" ] }, { "name": "global", "pda": { "seeds": [ { "kind": "const", "value": [ 103, 108, 111, 98, 97, 108 ] } ] } }, { "name": "idl_account", "writable": true }, { "name": "system_program", "address": "11111111111111111111111111111111" }, { "name": "program_signer", "pda": { "seeds": [] } }, { "name": "event_authority", "pda": { "seeds": [ { "kind": "const", "value": [ 95, 95, 101, 118, 101, 110, 116, 95, 97, 117, 116, 104, 111, 114, 105, 116, 121 ] } ] } }, { "name": "program" } ], "args": [ { "name": "idl_authority", "type": "pubkey" } ] }, { "name": "admin_update_token_incentives", "discriminator": [ 209, 11, 115, 87, 213, 23, 124, 204 ], "accounts": [ { "name": "authority", "writable": true, "signer": true, "relations": [ "global" ] }, { "name": "global", "pda": { "seeds": [ { "kind": "const", "value": [ 103, 108, 111, 98, 97, 108 ] } ] } }, { "name": "global_volume_accumulator", "writable": true, "pda": { "seeds": [ { "kind": "const", "value": [ 103, 108, 111, 98, 97, 108, 95, 118, 111, 108, 117, 109, 101, 95, 97, 99, 99, 117, 109, 117, 108, 97, 116, 111, 114 ] } ] } }, { "name": "mint" }, { "name": "global_incentive_token_account", "writable": true, "pda": { "seeds": [ { "kind": "account", "path": "global_volume_accumulator" }, { "kind": "account", "path": "token_program" }, { "kind": "account", "path": "mint" } ], "program": { "kind": "const", "value": [ 140, 151, 37, 143, 78, 36, 137, 241, 187, 61, 16, 41, 20, 142, 13, 131, 11, 90, 19, 153, 218, 255, 16, 132, 4, 142, 123, 216, 219, 233, 248, 89 ] } } }, { "name": "associated_token_program", "address": "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL" }, { "name": "system_program", "address": "11111111111111111111111111111111" }, { "name": "token_program" }, { "name": "event_authority", "pda": { "seeds": [ { "kind": "const", "value": [ 95, 95, 101, 118, 101, 110, 116, 95, 97, 117, 116, 104, 111, 114, 105, 116, 121 ] } ] } }, { "name": "program" } ], "args": [ { "name": "start_time", "type": "i64" }, { "name": "end_time", "type": "i64" }, { "name": "seconds_in_a_day", "type": "i64" }, { "name": "day_number", "type": "u64" }, { "name": "pump_token_supply_per_day", "type": "u64" } ] }, { "name": "buy", "docs": [ "Buys tokens from a bonding curve." ], "discriminator": [ 102, 6, 61, 18, 1, 218, 235, 234 ], "accounts": [ { "name": "global", "pda": { "seeds": [ { "kind": "const", "value": [ 103, 108, 111, 98, 97, 108 ] } ] } }, { "name": "fee_recipient", "writable": true }, { "name": "mint" }, { "name": "bonding_curve", "writable": true, "pda": { "seeds": [ { "kind": "const", "value": [ 98, 111, 110, 100, 105, 110, 103, 45, 99, 117, 114, 118, 101 ] }, { "kind": "account", "path": "mint" } ] } }, { "name": "associated_bonding_curve", "writable": true, "pda": { "seeds": [ { "kind": "account", "path": "bonding_curve" }, { "kind": "const", "value": [ 6, 221, 246, 225, 215, 101, 161, 147, 217, 203, 225, 70, 206, 235, 121, 172, 28, 180, 133, 237, 95, 91, 55, 145, 58, 140, 245, 133, 126, 255, 0, 169 ] }, { "kind": "account", "path": "mint" } ], "program": { "kind": "const", "value": [ 140, 151, 37, 143, 78, 36, 137, 241, 187, 61, 16, 41, 20, 142, 13, 131, 11, 90, 19, 153, 218, 255, 16, 132, 4, 142, 123, 216, 219, 233, 248, 89 ] } } }, { "name": "associated_user", "writable": true }, { "name": "user", "writable": true, "signer": true }, { "name": "system_program", "address": "11111111111111111111111111111111" }, { "name": "token_program", "address": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" }, { "name": "creator_vault", "writable": true, "pda": { "seeds": [ { "kind": "const", "value": [ 99, 114, 101, 97, 116, 111, 114, 45, 118, 97, 117, 108, 116 ] }, { "kind": "account", "path": "bonding_curve.creator", "account": "BondingCurve" } ] } }, { "name": "event_authority", "pda": { "seeds": [ { "kind": "const", "value": [ 95, 95, 101, 118, 101, 110, 116, 95, 97, 117, 116, 104, 111, 114, 105, 116, 121 ] } ] } }, { "name": "program", "address": "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P" }, { "name": "global_volume_accumulator", "writable": true, "pda": { "seeds": [ { "kind": "const", "value": [ 103, 108, 111, 98, 97, 108, 95, 118, 111, 108, 117, 109, 101, 95, 97, 99, 99, 117, 109, 117, 108, 97, 116, 111, 114 ] } ] } }, { "name": "user_volume_accumulator", "writable": true, "pda": { "seeds": [ { "kind": "const", "value": [ 117, 115, 101, 114, 95, 118, 111, 108, 117, 109, 101, 95, 97, 99, 99, 117, 109, 117, 108, 97, 116, 111, 114 ] }, { "kind": "account", "path": "user" } ] } }, { "name": "fee_config", "pda": { "seeds": [ { "kind": "const", "value": [ 102, 101, 101, 95, 99, 111, 110, 102, 105, 103 ] }, { "kind": "const", "value": [ 1, 86, 224, 246, 147, 102, 90, 207, 68, 219, 21, 104, 191, 23, 91, 170, 81, 137, 203, 151, 245, 210, 255, 59, 101, 93, 43, 182, 253, 109, 24, 176 ] } ], "program": { "kind": "account", "path": "fee_program" } } }, { "name": "fee_program", "address": "pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ" } ], "args": [ { "name": "amount", "type": "u64" }, { "name": "max_sol_cost", "type": "u64" }, { "name": "track_volume", "type": { "defined": { "name": "OptionBool" } } } ] }, { "name": "claim_token_incentives", "discriminator": [ 16, 4, 71, 28, 204, 1, 40, 27 ], "accounts": [ { "name": "user" }, { "name": "user_ata", "writable": true, "pda": { "seeds": [ { "kind": "account", "path": "user" }, { "kind": "account", "path": "token_program" }, { "kind": "account", "path": "mint" } ], "program": { "kind": "const", "value": [ 140, 151, 37, 143, 78, 36, 137, 241, 187, 61, 16, 41, 20, 142, 13, 131, 11, 90, 19, 153, 218, 255, 16, 132, 4, 142, 123, 216, 219, 233, 248, 89 ] } } }, { "name": "global_volume_accumulator", "pda": { "seeds": [ { "kind": "const", "value": [ 103, 108, 111, 98, 97, 108, 95, 118, 111, 108, 117, 109, 101, 95, 97, 99, 99, 117, 109, 117, 108, 97, 116, 111, 114 ] } ] } }, { "name": "global_incentive_token_account", "writable": true, "pda": { "seeds": [ { "kind": "account", "path": "global_volume_accumulator" }, { "kind": "account", "path": "token_program" }, { "kind": "account", "path": "mint" } ], "program": { "kind": "const", "value": [ 140, 151, 37, 143, 78, 36, 137, 241, 187, 61, 16, 41, 20, 142, 13, 131, 11, 90, 19, 153, 218, 255, 16, 132, 4, 142, 123, 216, 219, 233, 248, 89 ] } } }, { "name": "user_volume_accumulator", "writable": true, "pda": { "seeds": [ { "kind": "const", "value": [ 117, 115, 101, 114, 95, 118, 111, 108, 117, 109, 101, 95, 97, 99, 99, 117, 109, 117, 108, 97, 116, 111, 114 ] }, { "kind": "account", "path": "user" } ] } }, { "name": "mint", "relations": [ "global_volume_accumulator" ] }, { "name": "token_program" }, { "name": "system_program", "address": "11111111111111111111111111111111" }, { "name": "associated_token_program", "address": "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL" }, { "name": "event_authority", "pda": { "seeds": [ { "kind": "const", "value": [ 95, 95, 101, 118, 101, 110, 116, 95, 97, 117, 116, 104, 111, 114, 105, 116, 121 ] } ] } }, { "name": "program", "address": "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P" }, { "name": "payer", "writable": true, "signer": true } ], "args": [] }, { "name": "close_user_volume_accumulator", "discriminator": [ 249, 69, 164, 218, 150, 103, 84, 138 ], "accounts": [ { "name": "user", "writable": true, "signer": true }, { "name": "user_volume_accumulator", "writable": true, "pda": { "seeds": [ { "kind": "const", "value": [ 117, 115, 101, 114, 95, 118, 111, 108, 117, 109, 101, 95, 97, 99, 99, 117, 109, 117, 108, 97, 116, 111, 114 ] }, { "kind": "account", "path": "user" } ] } }, { "name": "event_authority", "pda": { "seeds": [ { "kind": "const", "value": [ 95, 95, 101, 118, 101, 110, 116, 95, 97, 117, 116, 104, 111, 114, 105, 116, 121 ] } ] } }, { "name": "program" } ], "args": [] }, { "name": "collect_creator_fee", "docs": [ "Collects creator_fee from creator_vault to the coin creator account" ], "discriminator": [ 20, 22, 86, 123, 198, 28, 219, 132 ], "accounts": [ { "name": "creator", "writable": true }, { "name": "creator_vault", "writable": true, "pda": { "seeds": [ { "kind": "const", "value": [ 99, 114, 101, 97, 116, 111, 114, 45, 118, 97, 117, 108, 116 ] }, { "kind": "account", "path": "creator" } ] } }, { "name": "system_program", "address": "11111111111111111111111111111111" }, { "name": "event_authority", "pda": { "seeds": [ { "kind": "const", "value": [ 95, 95, 101, 118, 101, 110, 116, 95, 97, 117, 116, 104, 111, 114, 105, 116, 121 ] } ] } }, { "name": "program" } ], "args": [] }, { "name": "create", "docs": [ "Creates a new coin and bonding curve." ], "discriminator": [ 24, 30, 200, 40, 5, 28, 7, 119 ], "accounts": [ { "name": "mint", "writable": true, "signer": true }, { "name": "mint_authority", "pda": { "seeds": [ { "kind": "const", "value": [ 109, 105, 110, 116, 45, 97, 117, 116, 104, 111, 114, 105, 116, 121 ] } ] } }, { "name": "bonding_curve", "writable": true, "pda": { "seeds": [ { "kind": "const", "value": [ 98, 111, 110, 100, 105, 110, 103, 45, 99, 117, 114, 118, 101 ] }, { "kind": "account", "path": "mint" } ] } }, { "name": "associated_bonding_curve", "writable": true, "pda": { "seeds": [ { "kind": "account", "path": "bonding_curve" }, { "kind": "const", "value": [ 6, 221, 246, 225, 215, 101, 161, 147, 217, 203, 225, 70, 206, 235, 121, 172, 28, 180, 133, 237, 95, 91, 55, 145, 58, 140, 245, 133, 126, 255, 0, 169 ] }, { "kind": "account", "path": "mint" } ], "program": { "kind": "const", "value": [ 140, 151, 37, 143, 78, 36, 137, 241, 187, 61, 16, 41, 20, 142, 13, 131, 11, 90, 19, 153, 218, 255, 16, 132, 4, 142, 123, 216, 219, 233, 248, 89 ] } } }, { "name": "global", "pda": { "seeds": [ { "kind": "const", "value": [ 103, 108, 111, 98, 97, 108 ] } ] } }, { "name": "mpl_token_metadata", "address": "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s" }, { "name": "metadata", "writable": true, "pda": { "seeds": [ { "kind": "const", "value": [ 109, 101, 116, 97, 100, 97, 116, 97 ] }, { "kind": "const", "value": [ 11, 112, 101, 177, 227, 209, 124, 69, 56, 157, 82, 127, 107, 4, 195, 205, 88, 184, 108, 115, 26, 160, 253, 181, 73, 182, 209, 188, 3, 248, 41, 70 ] }, { "kind": "account", "path": "mint" } ], "program": { "kind": "account", "path": "mpl_token_metadata" } } }, { "name": "user", "writable": true, "signer": true }, { "name": "system_program", "address": "11111111111111111111111111111111" }, { "name": "token_program", "address": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" }, { "name": "associated_token_program", "address": "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL" }, { "name": "rent", "address": "SysvarRent111111111111111111111111111111111" }, { "name": "event_authority", "pda": { "seeds": [ { "kind": "const", "value": [ 95, 95, 101, 118, 101, 110, 116, 95, 97, 117, 116, 104, 111, 114, 105, 116, 121 ] } ] } }, { "name": "program" } ], "args": [ { "name": "name", "type": "string" }, { "name": "symbol", "type": "string" }, { "name": "uri", "type": "string" }, { "name": "creator", "type": "pubkey" } ] }, { "name": "extend_account", "docs": [ "Extends the size of program-owned accounts" ], "discriminator": [ 234, 102, 194, 203, 150, 72, 62, 229 ], "accounts": [ { "name": "account", "writable": true }, { "name": "user", "signer": true }, { "name": "system_program", "address": "11111111111111111111111111111111" }, { "name": "event_authority", "pda": { "seeds": [ { "kind": "const", "value": [ 95, 95, 101, 118, 101, 110, 116, 95, 97, 117, 116, 104, 111, 114, 105, 116, 121 ] } ] } }, { "name": "program" } ], "args": [] }, { "name": "init_user_volume_accumulator", "discriminator": [ 94, 6, 202, 115, 255, 96, 232, 183 ], "accounts": [ { "name": "payer", "writable": true, "signer": true }, { "name": "user" }, { "name": "user_volume_accumulator", "writable": true, "pda": { "seeds": [ { "kind": "const", "value": [ 117, 115, 101, 114, 95, 118, 111, 108, 117, 109, 101, 95, 97, 99, 99, 117, 109, 117, 108, 97, 116, 111, 114 ] }, { "kind": "account", "path": "user" } ] } }, { "name": "system_program", "address": "11111111111111111111111111111111" }, { "name": "event_authority", "pda": { "seeds": [ { "kind": "const", "value": [ 95, 95, 101, 118, 101, 110, 116, 95, 97, 117, 116, 104, 111, 114, 105, 116, 121 ] } ] } }, { "name": "program" } ], "args": [] }, { "name": "initialize", "docs": [ "Creates the global state." ], "discriminator": [ 175, 175, 109, 31, 13, 152, 155, 237 ], "accounts": [ { "name": "global", "writable": true, "pda": { "seeds": [ { "kind": "const", "value": [ 103, 108, 111, 98, 97, 108 ] } ] } }, { "name": "user", "writable": true, "signer": true }, { "name": "system_program", "address": "11111111111111111111111111111111" } ], "args": [] }, { "name": "migrate", "docs": [ "Migrates liquidity to pump_amm if the bonding curve is complete" ], "discriminator": [ 155, 234, 231, 146, 236, 158, 162, 30 ], "accounts": [ { "name": "global", "pda": { "seeds": [ { "kind": "const", "value": [ 103, 108, 111, 98, 97, 108 ] } ] } }, { "name": "withdraw_authority", "writable": true, "relations": [ "global" ] }, { "name": "mint" }, { "name": "bonding_curve", "writable": true, "pda": { "seeds": [ { "kind": "const", "value": [ 98, 111, 110, 100, 105, 110, 103, 45, 99, 117, 114, 118, 101 ] }, { "kind": "account", "path": "mint" } ] } }, { "name": "associated_bonding_curve", "writable": true, "pda": { "seeds": [ { "kind": "account", "path": "bonding_curve" }, { "kind": "const", "value": [ 6, 221, 246, 225, 215, 101, 161, 147, 217, 203, 225, 70, 206, 235, 121, 172, 28, 180, 133, 237, 95, 91, 55, 145, 58, 140, 245, 133, 126, 255, 0, 169 ] }, { "kind": "account", "path": "mint" } ], "program": { "kind": "const", "value": [ 140, 151, 37, 143, 78, 36, 137, 241, 187, 61, 16, 41, 20, 142, 13, 131, 11, 90, 19, 153, 218, 255, 16, 132, 4, 142, 123, 216, 219, 233, 248, 89 ] } } }, { "name": "user", "signer": true }, { "name": "system_program", "address": "11111111111111111111111111111111" }, { "name": "token_program", "address": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" }, { "name": "pump_amm", "address": "pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA" }, { "name": "pool", "writable": true, "pda": { "seeds": [ { "kind": "const", "value": [ 112, 111, 111, 108 ] }, { "kind": "const", "value": [ 0, 0 ] }, { "kind": "account", "path": "pool_authority" }, { "kind": "account", "path": "mint" }, { "kind": "account", "path": "wsol_mint" } ], "program": { "kind": "account", "path": "pump_amm" } } }, { "name": "pool_authority", "writable": true, "pda": { "seeds": [ { "kind": "const", "value": [ 112, 111, 111, 108, 45, 97, 117, 116, 104, 111, 114, 105, 116, 121 ] }, { "kind": "account", "path": "mint" } ] } }, { "name": "pool_authority_mint_account", "writable": true, "pda": { "seeds": [ { "kind": "account", "path": "pool_authority" }, { "kind": "account", "path": "token_program" }, { "kind": "account", "path": "mint" } ], "program": { "kind": "account", "path": "associated_token_program" } } }, { "name": "pool_authority_wsol_account", "writable": true, "pda": { "seeds": [ { "kind": "account", "path": "pool_authority" }, { "kind": "account", "path": "token_program" }, { "kind": "account", "path": "wsol_mint" } ], "program": { "kind": "account", "path": "associated_token_program" } } }, { "name": "amm_global_config", "pda": { "seeds": [ { "kind": "const", "value": [ 103, 108, 111, 98, 97, 108, 95, 99, 111, 110, 102, 105, 103 ] } ], "program": { "kind": "account", "path": "pump_amm" } } }, { "name": "wsol_mint", "address": "So11111111111111111111111111111111111111112" }, { "name": "lp_mint", "writable": true, "pda": { "seeds": [ { "kind": "const", "value": [ 112, 111, 111, 108, 95, 108, 112, 95, 109, 105, 110, 116 ] }, { "kind": "account", "path": "pool" } ], "program": { "kind": "account", "path": "pump_amm" } } }, { "name": "user_pool_token_account", "writable": true, "pda": { "seeds": [ { "kind": "account", "path": "pool_authority" }, { "kind": "account", "path": "token_2022_program" }, { "kind": "account", "path": "lp_mint" } ], "program": { "kind": "account", "path": "associated_token_program" } } }, { "name": "pool_base_token_account", "writable": true, "pda": { "seeds": [ { "kind": "account", "path": "pool" }, { "kind": "account", "path": "token_program" }, { "kind": "account", "path": "mint" } ], "program": { "kind": "account", "path": "associated_token_program" } } }, { "name": "pool_quote_token_account", "writable": true, "pda": { "seeds": [ { "kind": "account", "path": "pool" }, { "kind": "account", "path": "token_program" }, { "kind": "account", "path": "wsol_mint" } ], "program": { "kind": "account", "path": "associated_token_program" } } }, { "name": "token_2022_program", "address": "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb" }, { "name": "associated_token_program", "address": "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL" }, { "name": "pump_amm_event_authority", "pda": { "seeds": [ { "kind": "const", "value": [ 95, 95, 101, 118, 101, 110, 116, 95, 97, 117, 116, 104, 111, 114, 105, 116, 121 ] } ], "program": { "kind": "account", "path": "pump_amm" } } }, { "name": "event_authority", "pda": { "seeds": [ { "kind": "const", "value": [ 95, 95, 101, 118, 101, 110, 116, 95, 97, 117, 116, 104, 111, 114, 105, 116, 121 ] } ] } }, { "name": "program" } ], "args": [] }, { "name": "sell", "docs": [ "Sells tokens into a bonding curve." ], "discriminator": [ 51, 230, 133, 164, 1, 127, 131, 173 ], "accounts": [ { "name": "global", "pda": { "seeds": [ { "kind": "const", "value": [ 103, 108, 111, 98, 97, 108 ] } ] } }, { "name": "fee_recipient", "writable": true }, { "name": "mint" }, { "name": "bonding_curve", "writable": true, "pda": { "seeds": [ { "kind": "const", "value": [ 98, 111, 110, 100, 105, 110, 103, 45, 99, 117, 114, 118, 101 ] }, { "kind": "account", "path": "mint" } ] } }, { "name": "associated_bonding_curve", "writable": true, "pda": { "seeds": [ { "kind": "account", "path": "bonding_curve" }, { "kind": "const", "value": [ 6, 221, 246, 225, 215, 101, 161, 147, 217, 203, 225, 70, 206, 235, 121, 172, 28, 180, 133, 237, 95, 91, 55, 145, 58, 140, 245, 133, 126, 255, 0, 169 ] }, { "kind": "account", "path": "mint" } ], "program": { "kind": "const", "value": [ 140, 151, 37, 143, 78, 36, 137, 241, 187, 61, 16, 41, 20, 142, 13, 131, 11, 90, 19, 153, 218, 255, 16, 132, 4, 142, 123, 216, 219, 233, 248, 89 ] } } }, { "name": "associated_user", "writable": true }, { "name": "user", "writable": true, "signer": true }, { "name": "system_program", "address": "11111111111111111111111111111111" }, { "name": "creator_vault", "writable": true, "pda": { "seeds": [ { "kind": "const", "value": [ 99, 114, 101, 97, 116, 111, 114, 45, 118, 97, 117, 108, 116 ] }, { "kind": "account", "path": "bonding_curve.creator", "account": "BondingCurve" } ] } }, { "name": "token_program", "address": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" }, { "name": "event_authority", "pda": { "seeds": [ { "kind": "const", "value": [ 95, 95, 101, 118, 101, 110, 116, 95, 97, 117, 116, 104, 111, 114, 105, 116, 121 ] } ] } }, { "name": "program", "address": "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P" }, { "name": "fee_config", "pda": { "seeds": [ { "kind": "const", "value": [ 102, 101, 101, 95, 99, 111, 110, 102, 105, 103 ] }, { "kind": "const", "value": [ 1, 86, 224, 246, 147, 102, 90, 207, 68, 219, 21, 104, 191, 23, 91, 170, 81, 137, 203, 151, 245, 210, 255, 59, 101, 93, 43, 182, 253, 109, 24, 176 ] } ], "program": { "kind": "account", "path": "fee_program" } } }, { "name": "fee_program", "address": "pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ" } ], "args": [ { "name": "amount", "type": "u64" }, { "name": "min_sol_output", "type": "u64" } ] }, { "name": "set_creator", "docs": [ "Allows Global::set_creator_authority to set the bonding curve creator from Metaplex metadata or input argument" ], "discriminator": [ 254, 148, 255, 112, 207, 142, 170, 165 ], "accounts": [ { "name": "set_creator_authority", "signer": true, "relations": [ "global" ] }, { "name": "global", "pda": { "seeds": [ { "kind": "const", "value": [ 103, 108, 111, 98, 97, 108 ] } ] } }, { "name": "mint" }, { "name": "metadata", "pda": { "seeds": [ { "kind": "const", "value": [ 109, 101, 116, 97, 100, 97, 116, 97 ] }, { "kind": "const", "value": [ 11, 112, 101, 177, 227, 209, 124, 69, 56, 157, 82, 127, 107, 4, 195, 205, 88, 184, 108, 115, 26, 160, 253, 181, 73, 182, 209, 188, 3, 248, 41, 70 ] }, { "kind": "account", "path": "mint" } ], "program": { "kind": "const", "value": [ 11, 112, 101, 177, 227, 209, 124, 69, 56, 157, 82, 127, 107, 4, 195, 205, 88, 184, 108, 115, 26, 160, 253, 181, 73, 182, 209, 188, 3, 248, 41, 70 ] } } }, { "name": "bonding_curve", "writable": true, "pda": { "seeds": [ { "kind": "const", "value": [ 98, 111, 110, 100, 105, 110, 103, 45, 99, 117, 114, 118, 101 ] }, { "kind": "account", "path": "mint" } ] } }, { "name": "event_authority", "pda": { "seeds": [ { "kind": "const", "value": [ 95, 95, 101, 118, 101, 110, 116, 95, 97, 117, 116, 104, 111, 114, 105, 116, 121 ] } ] } }, { "name": "program" } ], "args": [ { "name": "creator", "type": "pubkey" } ] }, { "name": "set_metaplex_creator", "docs": [ "Syncs the bonding curve creator with the Metaplex metadata creator if it exists" ], "discriminator": [ 138, 96, 174, 217, 48, 85, 197, 246 ], "accounts": [ { "name": "mint" }, { "name": "metadata", "pda": { "seeds": [ { "kind": "const", "value": [ 109, 101, 116, 97, 100, 97, 116, 97 ] }, { "kind": "const", "value": [ 11, 112, 101, 177, 227, 209, 124, 69, 56, 157, 82, 127, 107, 4, 195, 205, 88, 184, 108, 115, 26, 160, 253, 181, 73, 182, 209, 188, 3, 248, 41, 70 ] }, { "kind": "account", "path": "mint" } ], "program": { "kind": "const", "value": [ 11, 112, 101, 177, 227, 209, 124, 69, 56, 157, 82, 127, 107, 4, 195, 205, 88, 184, 108, 115, 26, 160, 253, 181, 73, 182, 209, 188, 3, 248, 41, 70 ] } } }, { "name": "bonding_curve", "writable": true, "pda": { "seeds": [ { "kind": "const", "value": [ 98, 111, 110, 100, 105, 110, 103, 45, 99, 117, 114, 118, 101 ] }, { "kind": "account", "path": "mint" } ] } }, { "name": "event_authority", "pda": { "seeds": [ { "kind": "const", "value": [ 95, 95, 101, 118, 101, 110, 116, 95, 97, 117, 116, 104, 111, 114, 105, 116, 121 ] } ] } }, { "name": "program" } ], "args": [] }, { "name": "set_params", "docs": [ "Sets the global state parameters." ], "discriminator": [ 27, 234, 178, 52, 147, 2, 187, 141 ], "accounts": [ { "name": "global", "writable": true, "pda": { "seeds": [ { "kind": "const", "value": [ 103, 108, 111, 98, 97, 108 ] } ] } }, { "name": "authority", "writable": true, "signer": true, "relations": [ "global" ] }, { "name": "event_authority", "pda": { "seeds": [ { "kind": "const", "value": [ 95, 95, 101, 118, 101, 110, 116, 95, 97, 117, 116, 104, 111, 114, 105, 116, 121 ] } ] } }, { "name": "program" } ], "args": [ { "name": "initial_virtual_token_reserves", "type": "u64" }, { "name": "initial_virtual_sol_reserves", "type": "u64" }, { "name": "initial_real_token_reserves", "type": "u64" }, { "name": "token_total_supply", "type": "u64" }, { "name": "fee_basis_points", "type": "u64" }, { "name": "withdraw_authority", "type": "pubkey" }, { "name": "enable_migrate", "type": "bool" }, { "name": "pool_migration_fee", "type": "u64" }, { "name": "creator_fee_basis_points", "type": "u64" }, { "name": "set_creator_authority", "type": "pubkey" }, { "name": "admin_set_creator_authority", "type": "pubkey" } ] }, { "name": "sync_user_volume_accumulator", "discriminator": [ 86, 31, 192, 87, 163, 87, 79, 238 ], "accounts": [ { "name": "user" }, { "name": "global_volume_accumulator", "pda": { "seeds": [ { "kind": "const", "value": [ 103, 108, 111, 98, 97, 108, 95, 118, 111, 108, 117, 109, 101, 95, 97, 99, 99, 117, 109, 117, 108, 97, 116, 111, 114 ] } ] } }, { "name": "user_volume_accumulator", "writable": true, "pda": { "seeds": [ { "kind": "const", "value": [ 117, 115, 101, 114, 95, 118, 111, 108, 117, 109, 101, 95, 97, 99, 99, 117, 109, 117, 108, 97, 116, 111, 114 ] }, { "kind": "account", "path": "user" } ] } }, { "name": "event_authority", "pda": { "seeds": [ { "kind": "const", "value": [ 95, 95, 101, 118, 101, 110, 116, 95, 97, 117, 116, 104, 111, 114, 105, 116, 121 ] } ] } }, { "name": "program" } ], "args": [] }, { "name": "update_global_authority", "discriminator": [ 227, 181, 74, 196, 208, 21, 97, 213 ], "accounts": [ { "name": "global", "writable": true, "pda": { "seeds": [ { "kind": "const", "value": [ 103, 108, 111, 98, 97, 108 ] } ] } }, { "name": "authority", "signer": true, "relations": [ "global" ] }, { "name": "new_authority" }, { "name": "event_authority", "pda": { "seeds": [ { "kind": "const", "value": [ 95, 95, 101, 118, 101, 110, 116, 95, 97, 117, 116, 104, 111, 114, 105, 116, 121 ] } ] } }, { "name": "program" } ], "args": [] } ], "accounts": [ { "name": "BondingCurve", "discriminator": [ 23, 183, 248, 55, 96, 216, 172, 96 ] }, { "name": "FeeConfig", "discriminator": [ 143, 52, 146, 187, 219, 123, 76, 155 ] }, { "name": "Global", "discriminator": [ 167, 232, 232, 177, 200, 108, 114, 127 ] }, { "name": "GlobalVolumeAccumulator", "discriminator": [ 202, 42, 246, 43, 142, 190, 30, 255 ] }, { "name": "UserVolumeAccumulator", "discriminator": [ 86, 255, 112, 14, 102, 53, 154, 250 ] } ], "events": [ { "name": "AdminSetCreatorEvent", "discriminator": [ 64, 69, 192, 104, 29, 30, 25, 107 ] }, { "name": "AdminSetIdlAuthorityEvent", "discriminator": [ 245, 59, 70, 34, 75, 185, 109, 92 ] }, { "name": "AdminUpdateTokenIncentivesEvent", "discriminator": [ 147, 250, 108, 120, 247, 29, 67, 222 ] }, { "name": "ClaimTokenIncentivesEvent", "discriminator": [ 79, 172, 246, 49, 205, 91, 206, 232 ] }, { "name": "CloseUserVolumeAccumulatorEvent", "discriminator": [ 146, 159, 189, 172, 146, 88, 56, 244 ] }, { "name": "CollectCreatorFeeEvent", "discriminator": [ 122, 2, 127, 1, 14, 191, 12, 175 ] }, { "name": "CompleteEvent", "discriminator": [ 95, 114, 97, 156, 212, 46, 152, 8 ] }, { "name": "CompletePumpAmmMigrationEvent", "discriminator": [ 189, 233, 93, 185, 92, 148, 234, 148 ] }, { "name": "CreateEvent", "discriminator": [ 27, 114, 169, 77, 222, 235, 99, 118 ] }, { "name": "ExtendAccountEvent", "discriminator": [ 97, 97, 215, 144, 93, 146, 22, 124 ] }, { "name": "InitUserVolumeAccumulatorEvent", "discriminator": [ 134, 36, 13, 72, 232, 101, 130, 216 ] }, { "name": "SetCreatorEvent", "discriminator": [ 237, 52, 123, 37, 245, 251, 72, 210 ] }, { "name": "SetMetaplexCreatorEvent", "discriminator": [ 142, 203, 6, 32, 127, 105, 191, 162 ] }, { "name": "SetParamsEvent", "discriminator": [ 223, 195, 159, 246, 62, 48, 143, 131 ] }, { "name": "SyncUserVolumeAccumulatorEvent", "discriminator": [ 197, 122, 167, 124, 116, 81, 91, 255 ] }, { "name": "TradeEvent", "discriminator": [ 189, 219, 127, 211, 78, 230, 97, 238 ] }, { "name": "UpdateGlobalAuthorityEvent", "discriminator": [ 182, 195, 137, 42, 35, 206, 207, 247 ] } ], "errors": [ { "code": 6000, "name": "NotAuthorized", "msg": "The given account is not authorized to execute this instruction." }, { "code": 6001, "name": "AlreadyInitialized", "msg": "The program is already initialized." }, { "code": 6002, "name": "TooMuchSolRequired", "msg": "slippage: Too much SOL required to buy the given amount of tokens." }, { "code": 6003, "name": "TooLittleSolReceived", "msg": "slippage: Too little SOL received to sell the given amount of tokens." }, { "code": 6004, "name": "MintDoesNotMatchBondingCurve", "msg": "The mint does not match the bonding curve." }, { "code": 6005, "name": "BondingCurveComplete", "msg": "The bonding curve has completed and liquidity migrated to raydium." }, { "code": 6006, "name": "BondingCurveNotComplete", "msg": "The bonding curve has not completed." }, { "code": 6007, "name": "NotInitialized", "msg": "The program is not initialized." }, { "code": 6008, "name": "WithdrawTooFrequent", "msg": "Withdraw too frequent" }, { "code": 6009, "name": "NewSizeShouldBeGreaterThanCurrentSize", "msg": "new_size should be > current_size" }, { "code": 6010, "name": "AccountTypeNotSupported", "msg": "Account type not supported" }, { "code": 6011, "name": "InitialRealTokenReservesShouldBeLessThanTokenTotalSupply", "msg": "initial_real_token_reserves should be less than token_total_supply" }, { "code": 6012, "name": "InitialVirtualTokenReservesShouldBeGreaterThanInitialRealTokenReserves", "msg": "initial_virtual_token_reserves should be greater than initial_real_token_reserves" }, { "code": 6013, "name": "FeeBasisPointsGreaterThanMaximum", "msg": "fee_basis_points greater than maximum" }, { "code": 6014, "name": "AllZerosWithdrawAuthority", "msg": "Withdraw authority cannot be set to System Program ID" }, { "code": 6015, "name": "PoolMigrationFeeShouldBeLessThanFinalRealSolReserves", "msg": "pool_migration_fee should be less than final_real_sol_reserves" }, { "code": 6016, "name": "PoolMigrationFeeShouldBeGreaterThanCreatorFeePlusMaxMigrateFees", "msg": "pool_migration_fee should be greater than creator_fee + MAX_MIGRATE_FEES" }, { "code": 6017, "name": "DisabledWithdraw", "msg": "Migrate instruction is disabled" }, { "code": 6018, "name": "DisabledMigrate", "msg": "Migrate instruction is disabled" }, { "code": 6019, "name": "InvalidCreator", "msg": "Invalid creator pubkey" }, { "code": 6020, "name": "BuyZeroAmount", "msg": "Buy zero amount" }, { "code": 6021, "name": "NotEnoughTokensToBuy", "msg": "Not enough tokens to buy" }, { "code": 6022, "name": "SellZeroAmount", "msg": "Sell zero amount" }, { "code": 6023, "name": "NotEnoughTokensToSell", "msg": "Not enough tokens to sell" }, { "code": 6024, "name": "Overflow", "msg": "Overflow" }, { "code": 6025, "name": "Truncation", "msg": "Truncation" }, { "code": 6026, "name": "DivisionByZero", "msg": "Division by zero" }, { "code": 6027, "name": "NotEnoughRemainingAccounts", "msg": "Not enough remaining accounts" }, { "code": 6028, "name": "AllFeeRecipientsShouldBeNonZero", "msg": "All fee recipients should be non-zero" }, { "code": 6029, "name": "UnsortedNotUniqueFeeRecipients", "msg": "Unsorted or not unique fee recipients" }, { "code": 6030, "name": "CreatorShouldNotBeZero", "msg": "Creator should not be zero" }, { "code": 6031, "name": "StartTimeInThePast" }, { "code": 6032, "name": "EndTimeInThePast" }, { "code": 6033, "name": "EndTimeBeforeStartTime" }, { "code": 6034, "name": "TimeRangeTooLarge" }, { "code": 6035, "name": "EndTimeBeforeCurrentDay" }, { "code": 6036, "name": "SupplyUpdateForFinishedRange" }, { "code": 6037, "name": "DayIndexAfterEndIndex" }, { "code": 6038, "name": "DayInActiveRange" }, { "code": 6039, "name": "InvalidIncentiveMint" } ], "types": [ { "name": "AdminSetCreatorEvent", "type": { "kind": "struct", "fields": [ { "name": "timestamp", "type": "i64" }, { "name": "admin_set_creator_authority", "type": "pubkey" }, { "name": "mint", "type": "pubkey" }, { "name": "bonding_curve", "type": "pubkey" }, { "name": "old_creator", "type": "pubkey" }, { "name": "new_creator", "type": "pubkey" } ] } }, { "name": "AdminSetIdlAuthorityEvent", "type": { "kind": "struct", "fields": [ { "name": "idl_authority", "type": "pubkey" } ] } }, { "name": "AdminUpdateTokenIncentivesEvent", "type": { "kind": "struct", "fields": [ { "name": "start_time", "type": "i64" }, { "name": "end_time", "type": "i64" }, { "name": "day_number", "type": "u64" }, { "name": "token_supply_per_day", "type": "u64" }, { "name": "mint", "type": "pubkey" }, { "name": "seconds_in_a_day", "type": "i64" }, { "name": "timestamp", "type": "i64" } ] } }, { "name": "BondingCurve", "type": { "kind": "struct", "fields": [ { "name": "virtual_token_reserves", "type": "u64" }, { "name": "virtual_sol_reserves", "type": "u64" }, { "name": "real_token_reserves", "type": "u64" }, { "name": "real_sol_reserves", "type": "u64" }, { "name": "token_total_supply", "type": "u64" }, { "name": "complete", "type": "bool" }, { "name": "creator", "type": "pubkey" } ] } }, { "name": "ClaimTokenIncentivesEvent", "type": { "kind": "struct", "fields": [ { "name": "user", "type": "pubkey" }, { "name": "mint", "type": "pubkey" }, { "name": "amount", "type": "u64" }, { "name": "timestamp", "type": "i64" }, { "name": "total_claimed_tokens", "type": "u64" }, { "name": "current_sol_volume", "type": "u64" } ] } }, { "name": "CloseUserVolumeAccumulatorEvent", "type": { "kind": "struct", "fields": [ { "name": "user", "type": "pubkey" }, { "name": "timestamp", "type": "i64" }, { "name": "total_unclaimed_tokens", "type": "u64" }, { "name": "total_claimed_tokens", "type": "u64" }, { "name": "current_sol_volume", "type": "u64" }, { "name": "last_update_timestamp", "type": "i64" } ] } }, { "name": "CollectCreatorFeeEvent", "type": { "kind": "struct", "fields": [ { "name": "timestamp", "type": "i64" }, { "name": "creator", "type": "pubkey" }, { "name": "creator_fee", "type": "u64" } ] } }, { "name": "CompleteEvent", "type": { "kind": "struct", "fields": [ { "name": "user", "type": "pubkey" }, { "name": "mint", "type": "pubkey" }, { "name": "bonding_curve", "type": "pubkey" }, { "name": "timestamp", "type": "i64" } ] } }, { "name": "CompletePumpAmmMigrationEvent", "type": { "kind": "struct", "fields": [ { "name": "user", "type": "pubkey" }, { "name": "mint", "type": "pubkey" }, { "name": "mint_amount", "type": "u64" }, { "name": "sol_amount", "type": "u64" }, { "name": "pool_migration_fee", "type": "u64" }, { "name": "bonding_curve", "type": "pubkey" }, { "name": "timestamp", "type": "i64" }, { "name": "pool", "type": "pubkey" } ] } }, { "name": "CreateEvent", "type": { "kind": "struct", "fields": [ { "name": "name", "type": "string" }, { "name": "symbol", "type": "string" }, { "name": "uri", "type": "string" }, { "name": "mint", "type": "pubkey" }, { "name": "bonding_curve", "type": "pubkey" }, { "name": "user", "type": "pubkey" }, { "name": "creator", "type": "pubkey" }, { "name": "timestamp", "type": "i64" }, { "name": "virtual_token_reserves", "type": "u64" }, { "name": "virtual_sol_reserves", "type": "u64" }, { "name": "real_token_reserves", "type": "u64" }, { "name": "token_total_supply", "type": "u64" } ] } }, { "name": "ExtendAccountEvent", "type": { "kind": "struct", "fields": [ { "name": "account", "type": "pubkey" }, { "name": "user", "type": "pubkey" }, { "name": "current_size", "type": "u64" }, { "name": "new_size", "type": "u64" }, { "name": "timestamp", "type": "i64" } ] } }, { "name": "FeeConfig", "type": { "kind": "struct", "fields": [ { "name": "bump", "type": "u8" }, { "name": "admin", "type": "pubkey" }, { "name": "flat_fees", "type": { "defined": { "name": "Fees" } } }, { "name": "fee_tiers", "type": { "vec": { "defined": { "name": "FeeTier" } } } } ] } }, { "name": "FeeTier", "type": { "kind": "struct", "fields": [ { "name": "market_cap_lamports_threshold", "type": "u128" }, { "name": "fees", "type": { "defined": { "name": "Fees" } } } ] } }, { "name": "Fees", "type": { "kind": "struct", "fields": [ { "name": "lp_fee_bps", "type": "u64" }, { "name": "protocol_fee_bps", "type": "u64" }, { "name": "creator_fee_bps", "type": "u64" } ] } }, { "name": "Global", "type": { "kind": "struct", "fields": [ { "name": "initialized", "docs": [ "Unused" ], "type": "bool" }, { "name": "authority", "type": "pubkey" }, { "name": "fee_recipient", "type": "pubkey" }, { "name": "initial_virtual_token_reserves", "type": "u64" }, { "name": "initial_virtual_sol_reserves", "type": "u64" }, { "name": "initial_real_token_reserves", "type": "u64" }, { "name": "token_total_supply", "type": "u64" }, { "name": "fee_basis_points", "type": "u64" }, { "name": "withdraw_authority", "type": "pubkey" }, { "name": "enable_migrate", "docs": [ "Unused" ], "type": "bool" }, { "name": "pool_migration_fee", "type": "u64" }, { "name": "creator_fee_basis_points", "type": "u64" }, { "name": "fee_recipients", "type": { "array": [ "pubkey", 7 ] } }, { "name": "set_creator_authority", "type": "pubkey" }, { "name": "admin_set_creator_authority", "type": "pubkey" } ] } }, { "name": "GlobalVolumeAccumulator", "type": { "kind": "struct", "fields": [ { "name": "start_time", "type": "i64" }, { "name": "end_time", "type": "i64" }, { "name": "seconds_in_a_day", "type": "i64" }, { "name": "mint", "type": "pubkey" }, { "name": "total_token_supply", "type": { "array": [ "u64", 30 ] } }, { "name": "sol_volumes", "type": { "array": [ "u64", 30 ] } } ] } }, { "name": "InitUserVolumeAccumulatorEvent", "type": { "kind": "struct", "fields": [ { "name": "payer", "type": "pubkey" }, { "name": "user", "type": "pubkey" }, { "name": "timestamp", "type": "i64" } ] } }, { "name": "OptionBool", "type": { "kind": "struct", "fields": [ "bool" ] } }, { "name": "SetCreatorEvent", "type": { "kind": "struct", "fields": [ { "name": "timestamp", "type": "i64" }, { "name": "mint", "type": "pubkey" }, { "name": "bonding_curve", "type": "pubkey" }, { "name": "creator", "type": "pubkey" } ] } }, { "name": "SetMetaplexCreatorEvent", "type": { "kind": "struct", "fields": [ { "name": "timestamp", "type": "i64" }, { "name": "mint", "type": "pubkey" }, { "name": "bonding_curve", "type": "pubkey" }, { "name": "metadata", "type": "pubkey" }, { "name": "creator", "type": "pubkey" } ] } }, { "name": "SetParamsEvent", "type": { "kind": "struct", "fields": [ { "name": "initial_virtual_token_reserves", "type": "u64" }, { "name": "initial_virtual_sol_reserves", "type": "u64" }, { "name": "initial_real_token_reserves", "type": "u64" }, { "name": "final_real_sol_reserves", "type": "u64" }, { "name": "token_total_supply", "type": "u64" }, { "name": "fee_basis_points", "type": "u64" }, { "name": "withdraw_authority", "type": "pubkey" }, { "name": "enable_migrate", "type": "bool" }, { "name": "pool_migration_fee", "type": "u64" }, { "name": "creator_fee_basis_points", "type": "u64" }, { "name": "fee_recipients", "type": { "array": [ "pubkey", 8 ] } }, { "name": "timestamp", "type": "i64" }, { "name": "set_creator_authority", "type": "pubkey" }, { "name": "admin_set_creator_authority", "type": "pubkey" } ] } }, { "name": "SyncUserVolumeAccumulatorEvent", "type": { "kind": "struct", "fields": [ { "name": "user", "type": "pubkey" }, { "name": "total_claimed_tokens_before", "type": "u64" }, { "name": "total_claimed_tokens_after", "type": "u64" }, { "name": "timestamp", "type": "i64" } ] } }, { "name": "TradeEvent", "type": { "kind": "struct", "fields": [ { "name": "mint", "type": "pubkey" }, { "name": "sol_amount", "type": "u64" }, { "name": "token_amount", "type": "u64" }, { "name": "is_buy", "type": "bool" }, { "name": "user", "type": "pubkey" }, { "name": "timestamp", "type": "i64" }, { "name": "virtual_sol_reserves", "type": "u64" }, { "name": "virtual_token_reserves", "type": "u64" }, { "name": "real_sol_reserves", "type": "u64" }, { "name": "real_token_reserves", "type": "u64" }, { "name": "fee_recipient", "type": "pubkey" }, { "name": "fee_basis_points", "type": "u64" }, { "name": "fee", "type": "u64" }, { "name": "creator", "type": "pubkey" }, { "name": "creator_fee_basis_points", "type": "u64" }, { "name": "creator_fee", "type": "u64" }, { "name": "track_volume", "type": "bool" }, { "name": "total_unclaimed_tokens", "type": "u64" }, { "name": "total_claimed_tokens", "type": "u64" }, { "name": "current_sol_volume", "type": "u64" }, { "name": "last_update_timestamp", "type": "i64" } ] } }, { "name": "UpdateGlobalAuthorityEvent", "type": { "kind": "struct", "fields": [ { "name": "global", "type": "pubkey" }, { "name": "authority", "type": "pubkey" }, { "name": "new_authority", "type": "pubkey" }, { "name": "timestamp", "type": "i64" } ] } }, { "name": "UserVolumeAccumulator", "type": { "kind": "struct", "fields": [ { "name": "user", "type": "pubkey" }, { "name": "needs_claim", "type": "bool" }, { "name": "total_unclaimed_tokens", "type": "u64" }, { "name": "total_claimed_tokens", "type": "u64" }, { "name": "current_sol_volume", "type": "u64" }, { "name": "last_update_timestamp", "type": "i64" }, { "name": "has_total_claimed_tokens", "type": "bool" } ] } } ] } ``` ## Advanced Features ### Price Tracking Track price movements across trades: ```javascript theme={null} const priceHistory = new Map(); // mint -> array of prices function trackPrice(trade) { const price = trade.solAmount / trade.tokenAmount; if (!priceHistory.has(trade.mint)) { priceHistory.set(trade.mint, []); } const prices = priceHistory.get(trade.mint); prices.push({ price, timestamp: Date.now(), type: trade.type }); // Keep only last 100 prices if (prices.length > 100) { prices.shift(); } // Calculate price change if (prices.length > 1) { const oldPrice = prices[0].price; const priceChange = ((price - oldPrice) / oldPrice) * 100; if (Math.abs(priceChange) > 10) { console.log(`\n⚠️ PRICE ALERT`); console.log(` Mint: ${trade.mint}`); console.log(` Change: ${priceChange.toFixed(2)}%`); console.log(` Current Price: ${price.toFixed(9)} SOL`); } } } // Call in data handler: if (trade) { trackPrice(trade); } ``` ### Large Trade Alerts Alert on significant trades: ```javascript theme={null} const LARGE_TRADE_THRESHOLD = 10.0; // 1 SOL function checkLargeTrade(trade) { if (trade.solAmount > LARGE_TRADE_THRESHOLD) { console.log(` Amount: ${trade.solAmount.toFixed(4)} SOL`); console.log(` Token: ${trade.mint}`); console.log(` Trader: ${trade.signer}`); // Send notification (implement your notification logic) // sendTelegramAlert(trade); } } ``` ### Buy/Sell Pressure Calculate market pressure: ```javascript theme={null} const pressureWindow = { buys: [], sells: [], windowSize: 50 // Last 50 trades }; function calculatePressure(trade) { if (trade.type === 'buy') { pressureWindow.buys.push(trade.solAmount); } else { pressureWindow.sells.push(trade.solAmount); } // Maintain window size if (pressureWindow.buys.length > pressureWindow.windowSize) { pressureWindow.buys.shift(); } if (pressureWindow.sells.length > pressureWindow.windowSize) { pressureWindow.sells.shift(); } // Calculate pressure const buyPressure = pressureWindow.buys.reduce((a, b) => a + b, 0); const sellPressure = pressureWindow.sells.reduce((a, b) => a + b, 0); const ratio = buyPressure / (sellPressure || 1); if (stats.totalTrades % 50 === 0) { console.log(`\n=== Market Pressure ===`); console.log(` Buy Pressure: ${buyPressure.toFixed(2)} SOL`); console.log(` Sell Pressure: ${sellPressure.toFixed(2)} SOL`); console.log(` Ratio: ${ratio.toFixed(2)}`); if (ratio > 2) { console.log(` Status: 🟢 BULLISH`); } else if (ratio < 0.5) { console.log(` Status: 🔴 BEARISH`); } else { console.log(` Status: ⚪ NEUTRAL`); } } } ``` ## Performance Optimization Balance speed and finality with `CommitmentLevel.CONFIRMED` Process transactions asynchronously to avoid blocking the stream Only parse transactions that match your criteria Implement data cleanup and garbage collection for long-running bots ## Resources Stream Pump.fun account updates Optimize your implementation General transaction monitoring guide # Yellowstone gRPC Source: https://docs.solanatracker.io/yellowstone-grpc/index High-performance, real-time Solana blockchain data streaming with ultra-low latency Yellowstone gRPC delivers **real-time Solana blockchain data** with **ultra-low latency** by connecting directly to Solana leaders as they produce shreds. Enhanced with **Jito Shreds acceleration**, it provides lightning-fast, reliable data streams for demanding applications. Binary protocol with efficient serialization for maximum throughput and minimal bandwidth usage. Bidirectional streaming with instant subscription creation and cancellation. Precisely control which data you receive using account, transaction, and program-level filters. Subscribe to accounts, transactions, slots, blocks, and entries—all in a single stream. *** ## Pricing & Access **\$247/month** ### Endpoints * **EU Region:** `https://grpc.solanatracker.io` * **US Region:** `https://grpc-us.solanatracker.io` ### Features * Ultra-low latency data streaming * Accelerated with Jito Shreds * 24/7 uptime with automatic failover * No bandwidth or request-based charges *** ## Stream Types **Monitor account state changes in real time** Track balance updates, data modifications, ownership changes, and account creation/deletion events with flexible filters. Learn how to stream account updates with filtering examples. **Stream transaction execution data** Receive transaction signatures, statuses, program invocations, and token balance updates as they occur. Monitor transactions with program-level filters and execution details. **Track consensus and block production** Stream slot updates, block creation, and network state changes across commitment levels. Stream slots and blocks with transaction details. **Monitor low-level blockchain entries** Access core execution units containing transaction batches and results. Stream entries and transaction batches. *** ## Quick Start Get up and streaming in minutes: Step-by-step setup guide with installation, authentication, and your first stream. *** ## Key Features ### Jito Shreds Acceleration Enhanced with **Jito Shreds**, **50–100 ms faster data availability** compared to standard Yellowstone gRPC nodes. ### Regional Endpoints Select the closest endpoint for optimal performance: * **EU:** `https://grpc.solanatracker.io` * **US:** `https://grpc-us.solanatracker.io` ### Flexible Filtering Fine-tune exactly what data you receive: * Filter by account, owner, or program * Include or exclude vote/failed transactions * Apply `memcmp` and data size filters * Slice account data to minimize bandwidth usage *** ## Subscription Request Structure Each gRPC subscription includes a structured request defining what and how data is streamed. ### Core Parameters **Data consistency level** * `processed` — fastest, unconfirmed data * `confirmed` — confirmed by cluster * `finalized` — fully finalized data **Keep the connection alive** Set to `true` to receive periodic `pong` messages (every 15 s) and prevent timeouts. **Optimize account data transfer** Request specific byte ranges: ```json theme={null} [ { "offset": 0, "length": 100 }, { "offset": 200, "length": 50 } ] ``` *** ### Filter Configuration List of account public keys to monitor (logical OR). List of account owners to monitor (logical OR). Data size and memory comparison filters (logical AND): ```json theme={null} [ { "dataSize": 165 }, { "memcmp": { "offset": 0, "bytes": "base58_encoded_bytes" } } ] ``` Multiple filter types use **AND** logic. Values within arrays use **OR** logic. Include or exclude vote transactions. Include or exclude failed transactions. Monitor a specific transaction signature. Include transactions involving any of these accounts (OR logic). Exclude transactions involving these accounts. Include only transactions involving **all** specified accounts (AND logic). *** ## Example: Basic Transaction Monitoring A minimal example using TypeScript: ```typescript theme={null} import Client, { CommitmentLevel, SubscribeRequest } from "@triton-one/yellowstone-grpc"; const client = new Client( "https://grpc.solanatracker.io", "your-api-token", { "grpc.max_receive_message_length": 64 * 1024 * 1024 } ); const stream = await client.subscribe(); // Handle incoming data stream.on("data", (data) => { if (data.transaction) { console.log(`Transaction: ${data.transaction.signature}`); console.log(`Success: ${!data.transaction.meta?.err}`); } }); // Subscribe to transactions const subscribeRequest: SubscribeRequest = { transactions: { client: { accountInclude: [ "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", // Token Program "11111111111111111111111111111111" // System Program ], accountExclude: [], accountRequired: [], vote: false, failed: false } }, commitment: CommitmentLevel.CONFIRMED, ping: { id: 1 } }; stream.write(subscribeRequest); ``` For production deployments, include retry logic, error handling, and structured data processing. See detailed guides for robust implementation patterns. *** ## Ready to Start? Installation, authentication, and first stream implementation. Real-world example: streaming Pump.fun transactions in real time. *** ## Resources * **[Yellowstone gRPC Repository](https://github.com/rpcpool/yellowstone-grpc)** — Full protobuf definitions and example clients * **[Support](mailto:support@solanatracker.io)** — Get help with setup or integration # Quickstart Guide Source: https://docs.solanatracker.io/yellowstone-grpc/quickstart Get started with Yellowstone gRPC streaming in minutes ## What is Yellowstone gRPC? Yellowstone gRPC provides direct access to Solana blockchain data through high-performance streaming. Unlike traditional RPC polling, gRPC delivers real-time updates with minimal latency, making it ideal for: * **Trading Bots** - Execute trades faster with real-time data * **DeFi Applications** - Monitor liquidity pools and token swaps instantly * **Analytics Platforms** - Track on-chain activity as it happens * **Arbitrage Systems** - Detect opportunities with millisecond precision ## Quick Start Subscribe to the gRPC service at **\$247/month**: [Subscribe to Yellowstone gRPC](https://www.solanatracker.io/solana-rpc) **What you get:** * **Two Regional Endpoints:** * EU Region: `https://grpc.solanatracker.io` * US Region: `https://grpc-us.solanatracker.io` * **Jito Shreds** - 50-100ms faster data delivery * **No Bandwidth Charges** - Unlimited data streaming * **24/7 Uptime** - Automatic failover and monitoring Choose the endpoint closest to your infrastructure for optimal performance. Co-locate in the same data center for sub-millisecond latency. After subscribing, retrieve your authentication credentials: [Get Your API Token](https://www.solanatracker.io/account/rpc) You'll receive: * **x-token** - Your authentication token for gRPC requests * **Endpoint URLs** - Both EU and US regions * **Configuration Guidelines** - Recommended settings **Keep your token secure!** * Never commit tokens to version control * Use environment variables * Rotate tokens periodically Install the Yellowstone gRPC client: ```bash theme={null} npm install @triton-one/yellowstone-grpc ``` ## Complete Working Example Here's a complete, production-ready example that monitors transactions: ```javascript theme={null} const Client = require("@triton-one/yellowstone-grpc").default; const { CommitmentLevel } = require("@triton-one/yellowstone-grpc"); // Initialize and connect to Yellowstone gRPC const getClient = async () => { let client = false; try { client = new Client( "https://grpc.solanatracker.io", "your-api-key-here", { "grpc.max_receive_message_length": 100 * 1024 * 1024, } ); const version = await client.getVersion(); if (version) { console.log("Connected! Version:", version); return client; } } catch (e) { console.error("Failed to connect:", e); } if (!client) { throw new Error("Failed to connect!"); } }; (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); reject(error); }); stream.on("end", () => { console.log("Stream ended"); resolve(); }); stream.on("close", () => { console.log("Stream closed"); resolve(); }); }); // Handle incoming data stream.on("data", (data) => { if (data?.transaction) { const tx = data.transaction.transaction; console.log("\n[Transaction]"); console.log(" Signature:", tx.signature); console.log(" Slot:", data.transaction.slot); console.log(" Success:", !tx.meta?.err); if (tx.meta?.fee) { console.log(" Fee:", (tx.meta.fee / 1e9).toFixed(6), "SOL"); } } }); // Subscribe to Token Program transactions const request = { accounts: {}, slots: {}, transactions: { tokenTransactions: { vote: false, failed: false, signature: undefined, accountInclude: ["TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"], accountExclude: [], accountRequired: [], }, }, transactionsStatus: {}, entry: {}, blocks: {}, blocksMeta: {}, accountsDataSlice: [], ping: undefined, commitment: CommitmentLevel.CONFIRMED, }; // Send subscribe request await new Promise((resolve, reject) => { stream.write(request, (err) => { if (err === null || err === undefined) { console.log("Subscribed to Token Program transactions"); resolve(); } else { reject(err); } }); }).catch((reason) => { console.error("Subscribe failed:", reason); throw reason; }); await streamClosed; })(); ``` ## What This Example Does 1. **Connects to Yellowstone gRPC** with proper error handling 2. **Creates a stream** with lifecycle event handlers 3. **Subscribes to transactions** involving the Token Program 4. **Processes each transaction** and logs key details 5. **Handles errors gracefully** with proper cleanup ## Monitoring Different Data Types **Monitor account changes:** ```javascript theme={null} const request = { accounts: { myAccounts: { account: ["YourAccountAddress"], owner: [], filters: [] } }, slots: {}, transactions: {}, transactionsStatus: {}, entry: {}, blocks: {}, blocksMeta: {}, accountsDataSlice: [], ping: undefined, commitment: CommitmentLevel.CONFIRMED, }; stream.on("data", (data) => { if (data?.account) { const account = data.account.account; console.log("Account Update:", account.pubkey); console.log("Lamports:", account.lamports); } }); ``` **Monitor network slots:** ```javascript theme={null} const request = { accounts: {}, slots: { slotSubscribe: {} }, transactions: {}, transactionsStatus: {}, entry: {}, blocks: {}, blocksMeta: {}, accountsDataSlice: [], ping: undefined, commitment: CommitmentLevel.CONFIRMED, }; stream.on("data", (data) => { if (data?.slot) { console.log("Slot:", data.slot.slot); console.log("Parent:", data.slot.parentSlot); console.log("Status:", data.slot.status); } }); ``` **Monitor blocks:** ```javascript theme={null} const request = { accounts: {}, slots: {}, transactions: {}, transactionsStatus: {}, entry: {}, blocks: {}, blocksMeta: { blockMetaSubscribe: {} }, accountsDataSlice: [], ping: undefined, commitment: CommitmentLevel.CONFIRMED, }; stream.on("data", (data) => { if (data?.blockMeta) { console.log("Block:", data.blockMeta.slot); console.log("Transactions:", data.blockMeta.transactionCount); console.log("Block Hash:", data.blockMeta.blockhash); } }); ``` ## Environment Variables Store your credentials securely: ```bash theme={null} # .env file GRPC_ENDPOINT=https://grpc.solanatracker.io GRPC_API_KEY=your-api-key-here ``` ```javascript theme={null} require('dotenv').config(); const client = new Client( process.env.GRPC_ENDPOINT, process.env.GRPC_API_KEY, { "grpc.max_receive_message_length": 100 * 1024 * 1024, } ); ``` ## What's Next? Monitor DEX transactions and program activity Track token holders and account changes Build a Pump.fun trading bot Optimize for production ## Common Questions ### Which endpoint should I use? **Choose based on your location:** * **Europe/Global:** `https://grpc.solanatracker.io` * **North America:** `https://grpc-us.solanatracker.io` Test both endpoints and use the one with lower latency from your deployment region. Aim for \~1ms latency by co-locating in the same data center. ### How do I handle reconnections? Wrap your connection logic in a retry function: ```javascript theme={null} async function connectWithRetry(maxRetries = 5) { for (let i = 0; i < maxRetries; i++) { try { return await getClient(); } catch (e) { console.log(`Retry ${i + 1}/${maxRetries}...`); await new Promise(resolve => setTimeout(resolve, 1000 * Math.pow(2, i))); } } throw new Error("Max retries exceeded"); } ``` ### What commitment level should I use? **Commitment levels:** * **PROCESSED** - Fastest, but can be rolled back * **CONFIRMED** - Balanced speed and finality (recommended) * **FINALIZED** - Slowest, but guaranteed finalized For most applications, `CONFIRMED` is the best choice. ### How do I filter transactions? Use the filter fields in your subscribe request: ```javascript theme={null} transactions: { myTransactions: { vote: false, // Exclude vote transactions failed: false, // Exclude failed transactions accountInclude: ["Address1"], // Must include any of these (OR) accountRequired: ["Address2"], // Must include all of these (AND) accountExclude: ["Address3"], // Must not include any of these } } ``` ## Support & Resources Get help with your implementation [support@solanatracker.io](mailto:support@solanatracker.io) Complete protobuf definitions and protocol specs [View Repository](https://github.com/rpcpool/yellowstone-grpc) # Slot & Block Monitoring Source: https://docs.solanatracker.io/yellowstone-grpc/slot-block-monitoring Watch Solana blocks in real-time - track network health, block production, and transaction volume ## Overview Slot and block monitoring lets you watch Solana's network in real-time. See when blocks are produced, track network health, and monitor transaction volume as it happens. **Start here first:** Complete the [Quickstart Guide](/yellowstone-grpc/quickstart) to set up your stream manager. ## What You Can Monitor **Watch the network heartbeat** Slots are 400ms time windows. Watch them advance to see network health: ```typescript theme={null} const subscribeRequest: SubscribeRequest = { slots: { slotSubscribe: { filterByCommitment: false // Get all commitment levels } }, commitment: CommitmentLevel.CONFIRMED }; ``` **What you'll see:** * Slot number * Parent slot * Status (processed, confirmed, or finalized) **Good for:** Network health dashboards, timing analysis, detecting problems **Get everything in a block** See complete blocks with all transactions and account updates: ```typescript theme={null} const subscribeRequest: SubscribeRequest = { blocks: { blockSubscribe: { accountInclude: [], includeTransactions: true, includeAccounts: true, includeEntries: false } }, commitment: CommitmentLevel.CONFIRMED }; ``` **What you'll see:** * Block info * All transactions * Account changes * Timing data **Lots of data:** Full blocks are big. Use filters to reduce the amount of data. **Just the basics** Get block info without all the transaction details: ```typescript theme={null} const subscribeRequest: SubscribeRequest = { blocksMeta: { blockMetaSubscribe: {} }, commitment: CommitmentLevel.CONFIRMED }; ``` **What you'll see:** * Block hash * Slot number * Block height * Transaction count * Block rewards **Lightweight:** Much less data than full blocks ## Real Examples ### Example 1: Basic Slot Monitor Watch slots and track network performance: ```typescript theme={null} import { StreamManager } from './yellowstone'; import { CommitmentLevel, SubscribeRequest } from "@triton-one/yellowstone-grpc"; async function monitorSlots() { const streamManager = new StreamManager( "https://grpc.solanatracker.io", process.env.GRPC_API_TOKEN || "your-api-key", handleSlotUpdate ); const subscribeRequest: SubscribeRequest = { slots: { slotSubscribe: { filterByCommitment: false } }, commitment: CommitmentLevel.PROCESSED }; console.log("Starting slot monitoring..."); await streamManager.connect(subscribeRequest); } const slotStats = { totalSlots: 0, lastSlot: 0, lastTimestamp: Date.now(), slotTimes: [] as number[], skippedSlots: 0 }; function handleSlotUpdate(data: any): void { if (data.slot) { const slot = data.slot; const currentTime = Date.now(); slotStats.totalSlots++; console.log(`\n[Slot #${slotStats.totalSlots}]`); console.log(` Slot Number: ${slot.slot}`); console.log(` Parent Slot: ${slot.parentSlot}`); console.log(` Status: ${slot.status.toUpperCase()}`); // Calculate timing if we have a previous slot if (slotStats.lastSlot > 0) { const slotDiff = slot.slot - slotStats.lastSlot; const timeDiff = currentTime - slotStats.lastTimestamp; if (slotDiff === 1) { // Normal progression slotStats.slotTimes.push(timeDiff); // Keep last 100 slots if (slotStats.slotTimes.length > 100) { slotStats.slotTimes.shift(); } const avgSlotTime = slotStats.slotTimes.reduce((a, b) => a + b, 0) / slotStats.slotTimes.length; console.log(` Time Since Last: ${timeDiff}ms`); console.log(` Average (100 slots): ${avgSlotTime.toFixed(1)}ms`); // Alert on slow slots if (timeDiff > 800) { console.log(` ⚠️ SLOW SLOT: Expected ~400ms, got ${timeDiff}ms`); } } else if (slotDiff > 1) { // Skipped slots const skipped = slotDiff - 1; slotStats.skippedSlots += skipped; console.log(` ⚠️ SKIPPED ${skipped} SLOT(S)`); console.log(` Total Skipped: ${slotStats.skippedSlots}`); } } slotStats.lastSlot = slot.slot; slotStats.lastTimestamp = currentTime; // Summary every 100 slots if (slotStats.totalSlots % 100 === 0) { printSlotSummary(); } } } function printSlotSummary(): void { const avgSlotTime = slotStats.slotTimes.length > 0 ? slotStats.slotTimes.reduce((a, b) => a + b, 0) / slotStats.slotTimes.length : 0; const skipRate = (slotStats.skippedSlots / slotStats.totalSlots * 100).toFixed(2); console.log(`\n=== Slot Summary ===`); console.log(` Total Slots: ${slotStats.totalSlots}`); console.log(` Skipped: ${slotStats.skippedSlots} (${skipRate}%)`); console.log(` Average Slot Time: ${avgSlotTime.toFixed(1)}ms`); console.log(` Expected: ~400ms per slot`); } monitorSlots().catch(console.error); ``` ### Example 2: Block Metadata Monitor Track blocks and transaction volume: ```typescript theme={null} async function monitorBlocks() { const streamManager = new StreamManager( "https://grpc.solanatracker.io", process.env.GRPC_API_TOKEN || "your-api-key", handleBlockMeta ); const subscribeRequest: SubscribeRequest = { blocksMeta: { blockMetaSubscribe: {} }, commitment: CommitmentLevel.CONFIRMED }; console.log("Monitoring blocks..."); await streamManager.connect(subscribeRequest); } const blockStats = { totalBlocks: 0, totalTransactions: 0, totalRewards: 0, highActivityBlocks: 0, blockTimes: [] as number[], lastBlockTime: 0 }; function handleBlockMeta(data: any): void { if (data.blockMeta) { const meta = data.blockMeta; blockStats.totalBlocks++; blockStats.totalTransactions += meta.transactionCount || 0; console.log(`\n[Block #${blockStats.totalBlocks}]`); console.log(` Slot: ${meta.slot}`); console.log(` Block Height: ${meta.blockHeight}`); console.log(` Block Hash: ${meta.blockhash?.slice(0, 16)}...`); console.log(` Transactions: ${meta.transactionCount || 0}`); // Track block timing if (meta.blockTime) { const blockTime = new Date(meta.blockTime * 1000); console.log(` Block Time: ${blockTime.toISOString()}`); if (blockStats.lastBlockTime > 0) { const timeDiff = meta.blockTime - blockStats.lastBlockTime; blockStats.blockTimes.push(timeDiff); if (blockStats.blockTimes.length > 50) { blockStats.blockTimes.shift(); } console.log(` Time Since Last Block: ${timeDiff}s`); } blockStats.lastBlockTime = meta.blockTime; } // Track rewards if (meta.rewards && meta.rewards.length > 0) { const totalReward = meta.rewards.reduce((sum: number, r: any) => sum + (r.lamports || 0), 0); blockStats.totalRewards += totalReward; console.log(` Block Rewards: ${(totalReward / 1e9).toFixed(6)} SOL`); console.log(` Reward Count: ${meta.rewards.length}`); // Show top rewards const sortedRewards = [...meta.rewards] .sort((a: any, b: any) => (b.lamports || 0) - (a.lamports || 0)) .slice(0, 3); sortedRewards.forEach((reward: any, idx: number) => { console.log(` ${idx + 1}. ${reward.pubkey?.slice(0, 8)}... ${(reward.lamports / 1e9).toFixed(6)} SOL (${reward.rewardType || 'unknown'})`); }); } // Flag high-activity blocks if (meta.transactionCount > 3000) { blockStats.highActivityBlocks++; console.log(` 🔥 HIGH ACTIVITY: ${meta.transactionCount} transactions`); } // Summary every 50 blocks if (blockStats.totalBlocks % 50 === 0) { printBlockSummary(); } } } function printBlockSummary(): void { const avgTxPerBlock = blockStats.totalBlocks > 0 ? (blockStats.totalTransactions / blockStats.totalBlocks).toFixed(1) : 0; const avgBlockTime = blockStats.blockTimes.length > 0 ? (blockStats.blockTimes.reduce((a, b) => a + b, 0) / blockStats.blockTimes.length).toFixed(1) : 0; const totalRewardsSOL = (blockStats.totalRewards / 1e9).toFixed(4); console.log(`\n=== Block Summary ===`); console.log(` Total Blocks: ${blockStats.totalBlocks}`); console.log(` Total Transactions: ${blockStats.totalTransactions}`); console.log(` Avg Tx/Block: ${avgTxPerBlock}`); console.log(` High Activity Blocks: ${blockStats.highActivityBlocks}`); console.log(` Avg Block Time: ${avgBlockTime}s`); console.log(` Total Rewards: ${totalRewardsSOL} SOL`); } monitorBlocks().catch(console.error); ``` ### Example 3: Filtered Block Monitor Watch blocks that contain specific program activity: ```typescript theme={null} // Replace with programs you want to monitor const TOKEN_PROGRAM = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"; const SYSTEM_PROGRAM = "11111111111111111111111111111111"; async function monitorFilteredBlocks() { const streamManager = new StreamManager( "https://grpc.solanatracker.io", process.env.GRPC_API_TOKEN || "your-api-key", handleFilteredBlock ); const subscribeRequest: SubscribeRequest = { blocks: { filteredBlocks: { accountInclude: [TOKEN_PROGRAM, SYSTEM_PROGRAM], // Your programs here includeTransactions: true, includeAccounts: false, includeEntries: false } }, commitment: CommitmentLevel.CONFIRMED }; console.log("Monitoring filtered blocks..."); await streamManager.connect(subscribeRequest); } const filteredStats = { totalBlocks: 0, totalTransactions: 0, successfulTx: 0, failedTx: 0, totalFees: 0, programCounts: new Map() }; function handleFilteredBlock(data: any): void { if (data.block) { const block = data.block; filteredStats.totalBlocks++; console.log(`\n[Filtered Block #${filteredStats.totalBlocks}]`); console.log(` Slot: ${block.slot}`); console.log(` Block Hash: ${block.blockhash?.slice(0, 16)}...`); console.log(` Transactions in Block: ${block.transactions?.length || 0}`); if (block.transactions) { let blockTxCount = 0; let blockFees = 0; let successCount = 0; let failCount = 0; block.transactions.forEach((tx: any) => { blockTxCount++; filteredStats.totalTransactions++; const success = !tx.meta?.err; if (success) { successCount++; filteredStats.successfulTx++; } else { failCount++; filteredStats.failedTx++; } const fee = tx.meta?.fee || 0; blockFees += fee; filteredStats.totalFees += fee; // Track program usage const accountKeys = tx.transaction?.message?.accountKeys || []; const instructions = tx.transaction?.message?.instructions || []; instructions.forEach((ix: any) => { const programId = accountKeys[ix.programIdIndex]; if (programId) { const count = filteredStats.programCounts.get(programId) || 0; filteredStats.programCounts.set(programId, count + 1); } }); }); console.log(` Successful Tx: ${successCount}`); console.log(` Failed Tx: ${failCount}`); console.log(` Block Fees: ${(blockFees / 1e9).toFixed(6)} SOL`); console.log(` Avg Fee: ${(blockFees / Math.max(blockTxCount, 1) / 1e9).toFixed(6)} SOL`); // Show program distribution in this block const programsInBlock = new Map(); block.transactions.forEach((tx: any) => { const keys = tx.transaction?.message?.accountKeys || []; const instructions = tx.transaction?.message?.instructions || []; instructions.forEach((ix: any) => { const programId = keys[ix.programIdIndex]; if (programId) { const count = programsInBlock.get(programId) || 0; programsInBlock.set(programId, count + 1); } }); }); console.log(` Programs Used:`); Array.from(programsInBlock.entries()) .sort((a, b) => b[1] - a[1]) .slice(0, 3) .forEach(([program, count]) => { const name = program === TOKEN_PROGRAM ? "Token Program" : program === SYSTEM_PROGRAM ? "System Program" : program.slice(0, 12) + "..."; console.log(` - ${name}: ${count} calls`); }); } // Summary every 20 blocks if (filteredStats.totalBlocks % 20 === 0) { printFilteredSummary(); } } } function printFilteredSummary(): void { const avgTxPerBlock = filteredStats.totalBlocks > 0 ? (filteredStats.totalTransactions / filteredStats.totalBlocks).toFixed(1) : 0; const successRate = filteredStats.totalTransactions > 0 ? (filteredStats.successfulTx / filteredStats.totalTransactions * 100).toFixed(1) : 0; console.log(`\n=== Filtered Block Summary ===`); console.log(` Blocks Processed: ${filteredStats.totalBlocks}`); console.log(` Total Transactions: ${filteredStats.totalTransactions}`); console.log(` Avg Tx/Block: ${avgTxPerBlock}`); console.log(` Success Rate: ${successRate}%`); console.log(` Total Fees: ${(filteredStats.totalFees / 1e9).toFixed(4)} SOL`); console.log(`\n Most Used Programs:`); Array.from(filteredStats.programCounts.entries()) .sort((a, b) => b[1] - a[1]) .slice(0, 5) .forEach(([program, count]) => { const name = program === TOKEN_PROGRAM ? "Token Program" : program === SYSTEM_PROGRAM ? "System Program" : program.slice(0, 12) + "..."; console.log(` ${name}: ${count} instructions`); }); } monitorFilteredBlocks().catch(console.error); ``` ## Understanding the Data ```typescript theme={null} { slot: number; // Which slot parentSlot: number; // Previous slot status: string; // "processed", "confirmed", or "finalized" } ``` **Slots happen every \~400ms** **Status levels:** * **Processed:** Just happened * **Confirmed:** Majority of network agrees * **Finalized:** Can't be undone ```typescript theme={null} { slot: number; blockHeight: number; blockhash: string; parentBlockhash: string; blockTime: number; // Unix timestamp transactionCount: number; rewards: Array<{ pubkey: string; lamports: number; rewardType: string; // "fee", "rent", "voting", "staking" }>; } ``` **Block time:** When the block was made **Rewards:** What validators earned ```typescript theme={null} { slot: number; parentSlot: number; blockhash: string; previousBlockhash: string; transactions: Transaction[]; // All transactions accounts: AccountUpdate[]; // Account changes entries: Entry[]; // Entries (optional) } ``` **Warning:** Full blocks can be several MB ## How Much Data? **Very light** * Minimal data usage * Real-time network pulse * Easy to process * Great for dashboards **Medium** * Moderate data usage * Block-level insights * Transaction counts * Good for analytics **Heavy** * High data usage * Everything included * Needs strong hardware * Use filters! **Smart choice** * Use accountInclude * Turn off what you don't need * Focus on specific programs * Best balance ## What You Can Build **Track network performance** * Slot timing dashboards * Congestion alerts * Consensus tracking * Validator stats * Skip rate alerts **Collect blockchain data** * Transaction volume charts * Fee tracking * Block size analysis * Activity patterns * Reward tracking **Keep apps in sync** * Slot-based updates * Block confirmations * Network state sync * Timing sync ## Common Problems **Problem:** You see gaps in slot numbers **Why it happens:** * Connection dropped * Validator went down * Your code is too slow **Fix it:** * Track gaps and alert * Add catch-up code * Check your connection **Problem:** Full blocks are overwhelming **Fix it:** * Use metadata instead * Add account filters * Turn off unnecessary data * Process async ## Best Practices **Tips for production:** * **Start small** - Use metadata before full blocks * **Filter everything** - Use accountInclude to reduce data * **Watch timing** - Track slots to spot problems * **Handle gaps** - Code for missing slots * **Don't block** - Process data async * **Match commitment** - Pick the right level for your needs * **Track yourself** - Monitor your own performance * **Set alerts** - Get notified of problems ## Common Questions A slot is a 400ms time window. Solana processes transactions in slots. One slot = one potential block (though some slots get skipped). Start with metadata. It's way lighter and has most of what you need. Only use full blocks if you need every transaction detail. Validators sometimes miss their turn to produce a block. This is normal and the network handles it automatically. A 5% skip rate is typical. A busy block can be several MB. That's why filtering is important - you don't want to download gigabytes of data you don't need. * **Processed** (\~400ms): Just happened, might change * **Confirmed** (\~1 second): Supermajority agrees, probably won't change * **Finalized** (\~13 seconds): Locked in, can't change ## Next Steps Even more detailed monitoring Watch specific accounts See it in action Get started # Transaction Monitoring Source: https://docs.solanatracker.io/yellowstone-grpc/transaction-monitoring Watch Solana transactions in real-time - track swaps, transfers, and program interactions instantly ## Overview Transaction monitoring lets you watch Solana transactions as they happen. Track swaps, transfers, success/failures, program calls, and token changes - all in real-time. **You'll need:** Run `npm install @triton-one/yellowstone-grpc` first ## Installation ```bash theme={null} npm install @triton-one/yellowstone-grpc ``` ## Complete Working Example Here's a ready-to-use transaction monitor that you can customize: ```javascript theme={null} const Client = require("@triton-one/yellowstone-grpc").default; const { CommitmentLevel } = require("@triton-one/yellowstone-grpc"); // Example program IDs - replace with programs you want to monitor const TOKEN_PROGRAM = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"; const SYSTEM_PROGRAM = "11111111111111111111111111111111"; // 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!"); } }; // Transaction statistics const txStats = { totalTransactions: 0, successfulTx: 0, failedTx: 0, totalFees: 0, totalComputeUnits: 0, programCounts: new Map(), lastReportTime: Date.now() }; // Calculate SOL balance changes function calculateSolChanges(meta) { const changes = []; if (!meta.preBalances || !meta.postBalances) return changes; meta.preBalances.forEach((preBalance, index) => { const postBalance = meta.postBalances[index] || 0; const change = (postBalance - preBalance) / 1e9; // Convert lamports to SOL if (Math.abs(change) > 0.000001) { // Ignore tiny changes changes.push({ accountIndex: index, change: change }); } }); return changes; } // Calculate token balance changes function calculateTokenChanges(meta) { const changes = []; if (!meta.preTokenBalances || !meta.postTokenBalances) return changes; meta.preTokenBalances.forEach((preBalance, index) => { const postBalance = meta.postTokenBalances[index]; if (preBalance && postBalance && preBalance.mint === postBalance.mint) { const preAmount = preBalance.uiTokenAmount?.uiAmount || 0; const postAmount = postBalance.uiTokenAmount?.uiAmount || 0; const change = postAmount - preAmount; if (change !== 0) { changes.push({ mint: preBalance.mint, change, decimals: preBalance.uiTokenAmount?.decimals || 0 }); } } }); return changes; } (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 transactions stream.on("data", (data) => { if (data?.transaction?.transaction) { const tx = data.transaction.transaction; const slot = data.transaction.slot; txStats.totalTransactions++; const success = !tx.meta?.err; if (success) { txStats.successfulTx++; } else { txStats.failedTx++; } const fee = tx.meta?.fee || 0; const computeUnits = tx.meta?.computeUnitsConsumed || 0; txStats.totalFees += fee; txStats.totalComputeUnits += computeUnits; // Track which programs were used const accountKeys = tx.transaction?.message?.accountKeys || []; const instructions = tx.transaction?.message?.instructions || []; instructions.forEach((ix) => { const programId = accountKeys[ix.programIdIndex]; if (programId) { const count = txStats.programCounts.get(programId) || 0; txStats.programCounts.set(programId, count + 1); } }); console.log(`\n[Transaction #${txStats.totalTransactions}]`); console.log(` Signature: ${tx.signature}`); console.log(` Slot: ${slot}`); console.log(` Status: ${success ? '✅ SUCCESS' : '❌ FAILED'}`); console.log(` Fee: ${(fee / 1e9).toFixed(6)} SOL`); console.log(` Compute Units: ${computeUnits.toLocaleString()}`); console.log(` Instructions: ${instructions.length}`); // Show SOL balance changes const solChanges = calculateSolChanges(tx.meta); if (solChanges.length > 0) { console.log(` SOL Changes:`); solChanges.forEach(change => { console.log(`Account ${change.accountIndex}: ${change.change > 0 ? '+' : ''}${change.change.toFixed(6)} SOL`); }); } // Show token balance changes const tokenChanges = calculateTokenChanges(tx.meta); if (tokenChanges.length > 0) { console.log(` Token Changes:`); tokenChanges.forEach(change => { console.log(`${change.mint.slice(0, 8)}... ${change.change > 0 ? '+' : ''}${change.change.toFixed(6)}`); }); } // Show program IDs used if (accountKeys.length > 0) { const uniquePrograms = new Set(); instructions.forEach((ix) => { const programId = accountKeys[ix.programIdIndex]; if (programId) uniquePrograms.add(programId); }); if (uniquePrograms.size > 0) { console.log(` Programs Called:`); uniquePrograms.forEach(program => { const name = program === TOKEN_PROGRAM ? "Token Program" : program === SYSTEM_PROGRAM ? "System Program" : program.slice(0, 12) + "..."; console.log(` - ${name}`); }); } } // Show error details for failed transactions if (!success && tx.meta?.err) { console.log(` Error: ${JSON.stringify(tx.meta.err)}`); if (tx.meta?.logMessages && tx.meta.logMessages.length > 0) { console.log(` Last Log: ${tx.meta.logMessages[tx.meta.logMessages.length - 1]}`); } } // Report statistics every 5 minutes const now = Date.now(); if (now - txStats.lastReportTime > 300000) { const avgFee = txStats.totalTransactions > 0 ? txStats.totalFees / txStats.totalTransactions / 1e9 : 0; const successRate = txStats.totalTransactions > 0 ? (txStats.successfulTx / txStats.totalTransactions * 100) : 0; console.log(`\n=== Transaction Statistics Report ===`); console.log(` Total Transactions: ${txStats.totalTransactions}`); console.log(` Successful: ${txStats.successfulTx}`); console.log(` Failed: ${txStats.failedTx}`); console.log(` Success Rate: ${successRate.toFixed(2)}%`); console.log(` Total Fees: ${(txStats.totalFees / 1e9).toFixed(4)} SOL`); console.log(` Average Fee: ${avgFee.toFixed(6)} SOL`); console.log(` Avg Compute Units: ${(txStats.totalComputeUnits / txStats.totalTransactions).toFixed(0)}`); console.log(`\n Most Used Programs:`); Array.from(txStats.programCounts.entries()) .sort((a, b) => b[1] - a[1]) .slice(0, 5) .forEach(([program, count]) => { const name = program === TOKEN_PROGRAM ? "Token Program" : program === SYSTEM_PROGRAM ? "System Program" : program.slice(0, 12) + "..."; console.log(` ${name}: ${count} transactions`); }); txStats.lastReportTime = now; } } }); // Subscribe to transactions // Customize the accountInclude array with programs you want to monitor const request = { accounts: {}, slots: {}, transactions: { allTransactions: { vote: false, failed: false, signature: undefined, accountInclude: [TOKEN_PROGRAM, SYSTEM_PROGRAM], // Add your program IDs here accountExclude: [], accountRequired: [], }, }, transactionsStatus: {}, entry: {}, blocks: {}, blocksMeta: {}, accountsDataSlice: [], 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 transactions\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 - Total: ${txStats.totalTransactions}, Success: ${txStats.successfulTx}, Failed: ${txStats.failedTx}`); process.exit(0); }); ``` ## How to Filter Transactions **Watch transactions using specific programs:** ```javascript theme={null} const request = { // ... other fields transactions: { myTransactions: { vote: false, failed: false, accountInclude: [ "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", "11111111111111111111111111111111" ], accountExclude: [], accountRequired: [], } }, commitment: CommitmentLevel.CONFIRMED, }; ``` **Watch transactions affecting certain accounts:** ```javascript theme={null} const request = { // ... other fields transactions: { walletTransactions: { vote: false, failed: true, // Include failures accountInclude: [ "YourWalletAddress", "YourTokenMintAddress" ], accountExclude: [], accountRequired: [], } }, commitment: CommitmentLevel.CONFIRMED, }; ``` **Combine multiple filters:** ```javascript theme={null} const request = { // ... other fields transactions: { filteredTransactions: { vote: false, failed: false, accountInclude: ["TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"], accountRequired: ["YourProgramId"], // Must include accountExclude: ["Vote111111111111111111111111111111111111111"], } }, commitment: CommitmentLevel.CONFIRMED, }; ``` ## More Examples ### Example 2: Track Failed Transactions Find out why transactions fail: ```javascript theme={null} const failureStats = { total: 0, errorTypes: 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?.transaction?.transaction?.meta?.err) { const tx = data.transaction.transaction; const slot = data.transaction.slot; failureStats.total++; const errorType = JSON.stringify(tx.meta.err); const count = failureStats.errorTypes.get(errorType) || 0; failureStats.errorTypes.set(errorType, count + 1); console.log(`\n[Failed Transaction #${failureStats.total}]`); console.log(` Signature: ${tx.signature}`); console.log(` Slot: ${slot}`); console.log(` Error: ${errorType}`); console.log(` Fee Paid: ${(tx.meta.fee / 1e9).toFixed(6)} SOL`); // Log messages for debugging if (tx.meta?.logMessages) { console.log(` Last 5 Log Messages:`); tx.meta.logMessages.slice(-5).forEach((msg) => { console.log(` ${msg}`); }); } // Report failure patterns every 20 failures if (failureStats.total % 20 === 0) { console.log(`\n=== Failure Analysis ===`); console.log(` Total Failures: ${failureStats.total}`); console.log(`\n Top Error Types:`); Array.from(failureStats.errorTypes.entries()) .sort((a, b) => b[1] - a[1]) .slice(0, 5) .forEach(([error, count]) => { const percentage = (count / failureStats.total * 100).toFixed(1); console.log(` ${error}: ${count} (${percentage}%)`); }); } } }); const request = { accounts: {}, slots: {}, transactions: { failedTransactions: { vote: false, failed: true, // Only failed transactions accountInclude: ["YourProgramId"], accountExclude: [], accountRequired: [], }, }, 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 failed transactions\n"); resolve(); } else { reject(err); } }); }); await streamClosed; })(); ``` ### Example 3: Watch Big Transfers Track large SOL transfers: ```javascript theme={null} const HIGH_VALUE_THRESHOLD = 10; // 10 SOL const highValueStats = { totalMonitored: 0, highValueCount: 0, totalVolume: 0, largestTransfer: 0, largestSignature: "" }; (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?.transaction?.transaction?.meta) { const tx = data.transaction.transaction; const slot = data.transaction.slot; highValueStats.totalMonitored++; const preBalances = tx.meta.preBalances || []; const postBalances = tx.meta.postBalances || []; // Calculate largest balance change let maxChange = 0; let changedAccountIndex = -1; preBalances.forEach((preBalance, index) => { const postBalance = postBalances[index] || 0; const change = Math.abs(postBalance - preBalance); if (change > maxChange) { maxChange = change; changedAccountIndex = index; } }); const changeInSOL = maxChange / 1e9; if (changeInSOL > HIGH_VALUE_THRESHOLD) { highValueStats.highValueCount++; highValueStats.totalVolume += changeInSOL; if (changeInSOL > highValueStats.largestTransfer) { highValueStats.largestTransfer = changeInSOL; highValueStats.largestSignature = tx.signature; } const accountKeys = tx.transaction?.message?.accountKeys || []; const changedAccount = accountKeys[changedAccountIndex]; console.log(`\n[High-Value Transaction #${highValueStats.highValueCount}]`); console.log(` Signature: ${tx.signature}`); console.log(` Slot: ${slot}`); console.log(` 💰 Transfer Amount: ${changeInSOL.toFixed(2)} SOL`); console.log(` Fee: ${(tx.meta.fee / 1e9).toFixed(6)} SOL`); console.log(` Changed Account: ${changedAccount || 'Unknown'}`); // Report summary every 10 high-value transactions if (highValueStats.highValueCount % 10 === 0) { console.log(`\n=== High-Value Summary ===`); console.log(` Monitored: ${highValueStats.totalMonitored} transactions`); console.log(` High-Value: ${highValueStats.highValueCount} transactions`); console.log(` Total Volume: ${highValueStats.totalVolume.toFixed(2)} SOL`); console.log(` Largest Transfer: ${highValueStats.largestTransfer.toFixed(2)} SOL`); } } // Log progress every 100 monitored transactions if (highValueStats.totalMonitored % 100 === 0) { console.log(`\n[Progress] Monitored ${highValueStats.totalMonitored} transactions, found ${highValueStats.highValueCount} high-value`); } } }); const request = { accounts: {}, slots: {}, transactions: { systemTransactions: { vote: false, failed: false, accountInclude: ["11111111111111111111111111111111"], // System Program accountExclude: [], accountRequired: [], }, }, 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 transactions with transfers > ${HIGH_VALUE_THRESHOLD} SOL\n`); resolve(); } else { reject(err); } }); }); await streamClosed; })(); ``` ## Understanding Filters **accountInclude:** Transaction must touch ANY of these accounts **accountRequired:** Transaction must touch ALL of these accounts **accountExclude:** Transaction must NOT touch any of these Transaction passes if: (include OR empty) AND (all required) AND NOT (any excluded) ## What You Can Build Monitor DEX transactions to find trading opportunities, track competitors, or analyze market activity in real-time. Watch specific wallets or programs to see every transaction they make. Perfect for portfolio tracking or whale watching. Track failed transactions to debug your program, understand user issues, or monitor network problems. Measure transaction volume, fees, and activity across programs to understand usage patterns and trends. Get instant notifications for high-value transfers, specific program calls, or suspicious activity. ## Tips for Better Performance CONFIRMED is usually best - fast but reliable Use specific program IDs to reduce data Don't block - handle transactions asynchronously Monitor your memory and CPU usage ## Common Questions It depends on your filters. Watching all transactions = huge data. Watching one program = manageable. Watching a specific wallet = very little. Always use filters! Yes! Just add them all to `accountInclude`. The transaction will match if it touches ANY of them. * **Include (OR):** Transaction touches ANY of these accounts * **Required (AND):** Transaction touches ALL of these accounts Use Include for "I want to see transactions involving A or B or C" Use Required for "I only want transactions that involve both A and B" Usually no - they're just noise. But set `failed: true` if you're debugging or analyzing failure patterns. Add the wallet address to `accountInclude`. You'll see every transaction that touches that wallet. Set `vote: false` in your filter. Vote transactions are validator consensus votes and usually aren't what you want. Compute units measure how much computation a transaction used. More complex transactions use more compute units and usually cost more. ## Next Steps Also watch account changes See transaction parsing in action Optimize your setup Get started fast