@solana-tracker/data-api
Version:
Official Solana Tracker Data API client for accessing Solana Data
1,419 lines (1,160 loc) • 67.8 kB
Markdown
# Solana Tracker - Data API SDK
Official JavaScript/TypeScript client for the [Solana Tracker Data API](https://www.solanatracker.io/data-api).
[](https://badge.fury.io/js/@solana-tracker%2Fdata-api)
## Features (Summary)
- Full TypeScript support with detailed interfaces for all API responses
- **PnL v2**: REST endpoints under `/v2/pnl` (leaderboards, token traders, wallet analytics, wallet-summary batch, batch positions) and Datastream rooms `pnl:{wallet}`, `pnl:{wallet}:{token}`, `pnl:{wallet}:summary` — scoped to meme / tradable tokens, not SOL. Includes `pnlMode` (strict/adjusted/raw), unified wallet identity (KOL, bot, pool, developer, hacker, spam-dusting, exchange, SNS primary `.sol` domain, platform tags), always-on token enrichment, opt-in holder enrichment, full position rows on `wallet/positions` and `tokens/:mint/traders`, and `summary.timing.avgHoldTimeSecs` on the wallet overview
- **Jupiter DCA**: REST endpoints under `/dca/*` (programs, wallet orders, single order, token flow/buyers/sellers/users, trading pair) and Datastream rooms `dca:jupiter[:event][:scope]` for opened, filled, closed, deposit, withdraw, collected_fee, and live position snapshots — scoped per token, wallet, or DCA account
- Comprehensive coverage of all Solana Tracker Data API endpoints
- Real-time data streaming via WebSocket (Datastream)
- Built-in error handling with specific error types
- Compatible with both Node.js and browser environments
- Enhanced search with 60+ filter parameters including holder distribution, social media, fees, Coin Communities chat activity, and more
- Cursor-based pagination for efficient deep searches
- Top performers endpoint - get the best performing tokens launched today
- Token filtering for overview endpoints (Memescope / Pulse overview) (graduated, graduating, latest)
- Paginated token holders endpoint for efficient holder data retrieval (5000 per page limit)
- Aggregated price updates across all pools for a token via WebSocket (min, median, max, average, top pools setc.)
- Smart primary pool routing - automatically switches to new main pool (price-by-token, token:{token}:primary rooms)
- (Global Fees) Platform and network fees tracking via WebSocket and API
- Developer/creator holdings tracking via WebSocket
- Top 10 holders monitoring with real-time percentage updates
- Live stats subscriptions for tokens and pools
- Primary pool subscriptions for token updates
- Wallet balance subscription API
- Snipers and insiders tracking via WebSocket
- Support for all pool types including launchpad and meteora curve pools (Shows which platform token is released on, Moonshot, Bonk, Jupiter Studio etc)
## Installation
Install the package using npm:
```bash
npm install @solana-tracker/data-api
```
Or with yarn:
```bash
yarn add @solana-tracker/data-api
```
## Quick Start
```typescript
import { Client } from '@solana-tracker/data-api';
// Initialize the client with your API key
const client = new Client({
apiKey: 'YOUR_API_KEY',
});
// Fetch token information
const fetchTokenInfo = async () => {
try {
const tokenInfo = await client.getTokenInfo(
'4k3Dyjzvzp8eMZWUXbBCjEvwSkkk59S5iCNLY3QrkX6R'
);
console.log('Token info:', tokenInfo);
} catch (error) {
console.error('Error:', error);
}
};
fetchTokenInfo();
```
## Examples
Runnable samples live in the [`examples/`](examples/) folder. Copy a file, set your API key or WebSocket URL, and run with `npx ts-node` (or compile with TypeScript) after installing the package.
| File | What it demonstrates |
|------|----------------------|
| [`examples/tokens.ts`](examples/tokens.ts) | Token info, holders, trending, search |
| [`examples/prices.ts`](examples/prices.ts) | Price and multi-price endpoints |
| [`examples/wallets.ts`](examples/wallets.ts) | Wallet balances and portfolio |
| [`examples/trades.ts`](examples/trades.ts) | Token and pool trades |
| [`examples/charts.ts`](examples/charts.ts) | OHLCV and holder/snipers charts |
| [`examples/stats.ts`](examples/stats.ts) | Token and pool stats |
| [`examples/bundlers.ts`](examples/bundlers.ts) | Bundler-related API usage |
| [`examples/profit-loss.ts`](examples/profit-loss.ts) | **Legacy** PnL endpoints (`getWalletPnL`, `getTokenPnL`, first buyers, top traders) |
| [`examples/pnl-v2.ts`](examples/pnl-v2.ts) | **PnL v2 REST**: leaderboards, token traders, wallet overview/history/performance/risk/chart, wallet-summary batch, batch positions (examples use a meme mint, not SOL) |
| [`examples/pnl-v2-datastream.ts`](examples/pnl-v2-datastream.ts) | **PnL v2 Datastream**: `subscribe.pnl.position`, `.wallet`, `.summary` with `tradeUpdate`, `balanceUpdate`, `priceUpdate`, and wallet summary payloads |
| [`examples/dca.ts`](examples/dca.ts) | **Jupiter DCA REST**: `getDcaPrograms`, wallet/orders, single order, token flow/buyers/sellers/users, pair |
| [`examples/dca-datastream.ts`](examples/dca-datastream.ts) | **Jupiter DCA Datastream**: `subscribe.dca.all`, `.opened`, `.filled`, `.closed`, `.deposit`, `.withdraw`, `.collectedFee`, `.position`, `.token().buyers()/sellers()`, `.wallet`, `.order` |
| [`examples/datastream.ts`](examples/datastream.ts) | WebSocket subscriptions (prices, txs, stats, volume, etc.) |
| [`examples/utils.ts`](examples/utils.ts) | Shared helpers (`handleError`, formatting) |
## What's New
### Version Updates
#### Latest Features:
1. **Multiple Markets & Launchpads**: `market` and `launchpad` now accept comma-separated values or arrays on both `/search` and `/deployer`:
```typescript
// Search across multiple markets
const results = await client.searchTokens({
market: ['raydium', 'orca', 'pumpfun'],
launchpad: ['pumpfun', 'boop'],
minLiquidity: 10000,
});
// Filter deployer tokens by market and launchpad
const deployerTokens = await client.getTokensByDeployer('walletAddress', {
market: ['raydium', 'pumpfun'],
launchpad: 'pumpfun',
});
```
2. **Full Token Format**: `format=full` returns results as full token objects (same shape as `/tokens/:token`) on both `/search` and `/deployer`:
```typescript
// Search with full token details (max limit: 100)
const fullResults = await client.searchTokens({
query: 'TRUMP',
format: 'full',
limit: 50,
});
// fullResults.data items are TokenDetailResponse objects (token, pools, events, risk, etc.)
// Deployer tokens with full details
const fullDeployer = await client.getTokensByDeployer('walletAddress', {
format: 'full',
limit: 50,
});
```
3. **Deployer Endpoint Enhancements**: The `/deployer` endpoint now supports `launchpad`, `market`, and `format` filters via a params object (backward compatible with positional args).
4. **Pool Creation Data**: New and graduated pool messages now include `creation` data (creator, created_tx, created_time) inside the pool object.
5. **Top Performers Endpoint**: Get the best performing tokens launched today:
```typescript
const topPerformers = await client.getTopPerformers('1h');
// Valid timeframes: '5m', '15m', '30m', '1h', '6h', '12h', '24h'
console.log('Top performers:', topPerformers.map(t => t.token.symbol));
```
2. **Enhanced Token Overview with Advanced Filtering**:
- Filter latest, graduating, and graduated tokens by liquidity, market cap, risk score, and specific markets
- Support for spam reduction and holder count filtering
- Backward compatible with simple limit parameter
2. **Paginated Token Holders**:
- Efficient pagination through large holder lists
- Support for up to 5000 holders per request
- Cursor-based navigation for seamless data retrieval
3. **Aggregated Price Updates (WebSocket)**:
- Get accurate price data aggregated across all pools
- Median, average, min, and max prices
- Top pools by liquidity automatically identified
- Replaces deprecated `price.token()` method
4. **Smart Primary Pool Routing**:
- Automatic switching to new primary pools when liquidity migrates
- Works for both `token:primary` and `price-by-token` subscriptions
- No code changes needed - happens automatically
5. **Enhanced Search with 60+ Filters**:
- Holder distribution filters (top10, dev, insiders, snipers)
- Social media filters (twitter, telegram, discord, etc.)
- Fees filters (total fees, trading fees, priority tips)
- Volume filters across multiple timeframes
- Token characteristic filters (LP burn, authorities, curve percentage)
- Cursor-based pagination for efficient deep searches
6. **Live Stats Subscriptions**: Subscribe to real-time statistics for tokens and pools across all timeframes (1m, 5m, 15m, 30m, 1h, 4h, 24h) using `.stats.token()` and `.stats.pool()` methods
7. **Total Stats Rooms**: Subscribe to direct total stats objects with `.stats.total.token()` and `.stats.total.pool()`
8. **Volume Rooms**: Subscribe to high-frequency USD volume aggregation via `.volume.pool()` and `.volume.token()`
9. **Developer Holdings Tracking**: Monitor developer/creator wallet holdings in real-time with `.dev.holding()` method
10. **Top 10 Holders Monitoring**: Track the top 10 holders and their combined percentage of token supply with `.top10()` method
11. **Global Fees Tracking**: Monitor platform and network fees via WebSocket and API
## Real-Time Data Streaming (Premium plan or higher only)
The library includes a `Datastream` class for real-time data updates with an improved, intuitive API:
```typescript
import { Datastream } from '@solana-tracker/data-api';
// Initialize the Datastream with your API key
const dataStream = new Datastream({
wsUrl: 'YOUR_WS_URL',
});
// Connect to the WebSocket server
dataStream.connect();
// Handle connection events
dataStream.on('connected', () => console.log('Connected to datastream'));
dataStream.on('disconnected', () =>
console.log('Disconnected from datastream')
);
dataStream.on('error', (error) => console.error('Datastream error:', error));
// Example 1: Subscribe to latest tokens with chained listener
dataStream.subscribe.latest().on((tokenData) => {
console.log('New token created:', tokenData.token.name);
});
// Example 2: NEW - Get accurate aggregated price across all pools
const tokenAddress = '6p6xgHyF7AeE6TZkSmFsko444wqoP15icUSqi2jfGiPN'; // TRUMP token
dataStream.subscribe.price.aggregated(tokenAddress).on((priceData) => {
console.log(`Aggregated price: $${priceData.aggregated.median}`);
console.log(`Price range: $${priceData.aggregated.min} - $${priceData.aggregated.max}`);
console.log(`Active pools: ${priceData.aggregated.poolCount}`);
console.log('Top pools by liquidity:', priceData.topPools);
});
// Example 3: Subscribe to primary pool updates (automatically switches to new main pool)
dataStream.subscribe.token(tokenAddress).primary().on((poolUpdate) => {
console.log('Primary pool update (automatically switches to new main pool):');
console.log(`Price: $${poolUpdate.price.usd}`);
console.log(`Liquidity: $${poolUpdate.liquidity.usd}`);
console.log(`Pool: ${poolUpdate.poolId}`);
});
// Example 4: Subscribe to token transactions with stored subscription reference
const txSubscription = dataStream.subscribe.tx
.token(tokenAddress)
.on((transaction) => {
console.log(`Transaction type: ${transaction.type}`);
console.log(`Amount: ${transaction.amount}`);
console.log(`Price: $${transaction.priceUsd}`);
});
// Later, unsubscribe from transactions
txSubscription.unsubscribe();
// Example 5: Monitor holder count for a token
dataStream.subscribe.holders(tokenAddress).on((holderData) => {
console.log(`Total holders: ${holderData.total}`);
});
// Example 6: Watch for wallet transactions
const walletAddress = 'YourWalletAddressHere';
dataStream.subscribe.tx.wallet(walletAddress).on((walletTx) => {
console.log(`${walletTx.type === 'buy' ? 'Bought' : 'Sold'} token`);
console.log(`Volume: ${walletTx.volume} USD`);
});
// Example 7: Subscribe to curve percentage updates
dataStream.subscribe.curvePercentage('pumpfun', 30).on((data) => {
console.log(`Token ${data.token.symbol} reached 30% on Pump.fun`);
console.log(`Market cap: ${data.pools[0].marketCap.usd}`);
});
// Different markets and percentages
dataStream.subscribe.curvePercentage('meteora-curve', 75).on((data) => {
console.log(`Meteora token at 75%: ${data.token.name}`);
});
// Example 8: NEW - Monitor snipers for a token
dataStream.subscribe.snipers(tokenAddress).on((sniperUpdate) => {
console.log(`Sniper wallet: ${sniperUpdate.wallet}`);
console.log(`Token amount: ${sniperUpdate.tokenAmount.toLocaleString()}`);
console.log(`Percentage: ${sniperUpdate.percentage.toFixed(2)}%`);
console.log(
`Total snipers hold: ${sniperUpdate.totalSniperPercentage.toFixed(2)}%`
);
});
// Example 9: NEW - Monitor insiders for a token
dataStream.subscribe.insiders(tokenAddress).on((insiderUpdate) => {
console.log(`Insider wallet: ${insiderUpdate.wallet}`);
console.log(`Token amount: ${insiderUpdate.tokenAmount.toLocaleString()}`);
console.log(`Percentage: ${insiderUpdate.percentage.toFixed(2)}%`);
console.log(
`Total insiders hold: ${insiderUpdate.totalInsiderPercentage.toFixed(2)}%`
);
});
// Example 10: NEW - Monitor wallet balance changes (new API location)
const walletAddress = 'YourWalletAddressHere';
// Watch all token balance changes for a wallet
dataStream.subscribe
.wallet(walletAddress)
.balance()
.on((balanceUpdate) => {
console.log(`Balance update for wallet ${balanceUpdate.wallet}`);
console.log(`Token: ${balanceUpdate.token}`);
console.log(`New balance: ${balanceUpdate.amount}`);
});
// Watch specific token balance for a wallet
dataStream.subscribe
.wallet(walletAddress)
.tokenBalance('tokenMint')
.on((balanceUpdate) => {
console.log(`Token balance changed to: ${balanceUpdate.amount}`);
});
// Example 11: NEW - Subscribe to live stats for tokens and pools
// Get real-time statistics across all timeframes (1m, 5m, 15m, 30m, 1h, 4h, 24h)
dataStream.subscribe.stats.token(tokenAddress).on((stats) => {
console.log('Live token stats update:');
// Access specific timeframe stats
if (stats['24h']) {
console.log('24h Stats:');
console.log(` Volume: $${stats['24h'].volume.total.toLocaleString()}`);
console.log(` Buys: ${stats['24h'].buys}, Sells: ${stats['24h'].sells}`);
console.log(` Unique wallets: ${stats['24h'].wallets}`);
console.log(
` Price change: ${stats['24h'].priceChangePercentage.toFixed(2)}%`
);
}
if (stats['1h']) {
console.log('1h Stats:');
console.log(
` Buyers: ${stats['1h'].buyers}, Sellers: ${stats['1h'].sellers}`
);
console.log(` Buy volume: $${stats['1h'].volume.buys.toLocaleString()}`);
console.log(` Sell volume: $${stats['1h'].volume.sells.toLocaleString()}`);
}
// Iterate through all available timeframes
Object.entries(stats).forEach(([timeframe, data]) => {
console.log(
`${timeframe}: ${data.transactions} txns, ${data.wallets} wallets`
);
});
});
// Subscribe to live stats for a specific pool
dataStream.subscribe.stats.pool('poolId').on((stats) => {
console.log('Pool stats update:');
if (stats['5m']) {
console.log('Last 5 minutes:');
console.log(` Transactions: ${stats['5m'].transactions}`);
console.log(` Volume: $${stats['5m'].volume.total.toLocaleString()}`);
console.log(` Price: $${stats['5m'].price}`);
}
});
// Example 12: NEW - Monitor developer/creator holdings for a token
dataStream.subscribe
.token(tokenAddress)
.dev.holding()
.on((devUpdate) => {
console.log(`Developer ${devUpdate.creator} holdings update:`);
console.log(` Amount: ${devUpdate.amount}`);
console.log(` Percentage: ${devUpdate.percentage.toFixed(4)}%`);
console.log(` Previous: ${devUpdate.previousPercentage.toFixed(4)}%`);
const change = devUpdate.percentage - devUpdate.previousPercentage;
console.log(` Change: ${change > 0 ? '+' : ''}${change.toFixed(4)}%`);
});
// Example 13: NEW - Monitor top 10 holders for a token
dataStream.subscribe
.token(tokenAddress)
.top10()
.on((top10Update) => {
console.log(
`Top 10 holders control ${top10Update.totalPercentage.toFixed(2)}% of supply`
);
if (top10Update.previousPercentage !== null) {
const change =
top10Update.totalPercentage - top10Update.previousPercentage;
console.log(
`Change from previous: ${change > 0 ? '+' : ''}${change.toFixed(2)}%`
);
}
console.log('Top 10 holders:');
top10Update.holders.forEach((holder, index) => {
console.log(
` #${index + 1}: ${holder.address.slice(0, 8)}... - ${holder.percentage.toFixed(2)}%`
);
});
});
// Example 14: NEW - Monitor platform and network fees for a token
dataStream.subscribe
.token(tokenAddress)
.fees()
.on((feesUpdate) => {
console.log(
`Total fees accumulated: ${feesUpdate.fees.total.toFixed(6)} SOL`
);
console.log(
`MEV/Priority tips: ${feesUpdate.fees.totalTips.toFixed(6)} SOL`
);
console.log('Transaction tx', feesUpdate.tx);
// Individual platforms (not all will be present)
if (feesUpdate.fees.photon !== undefined) {
console.log(`Photon fees: ${feesUpdate.fees.photon.toFixed(6)} SOL`);
}
if (feesUpdate.fees.axiom !== undefined) {
console.log(`Axiom fees: ${feesUpdate.fees.axiom.toFixed(6)} SOL`);
}
if (feesUpdate.fees.bullx !== undefined) {
console.log(`BullX fees: ${feesUpdate.fees.bullx.toFixed(6)} SOL`);
}
if (feesUpdate.fees.jito !== undefined) {
console.log(`Jito tips: ${feesUpdate.fees.fees.jito.toFixed(6)} SOL`);
}
if (feesUpdate.fees.network !== undefined) {
console.log(`Network fees: ${feesUpdate.fees.network.toFixed(6)} SOL`);
}
});
```
Available subscription methods:
```typescript
// Token and pool updates
dataStream.subscribe.latest(); // Latest tokens and pools
dataStream.subscribe.token(tokenAddress); // Token changes (all pools - default)
dataStream.subscribe.token(tokenAddress).all(); // Token changes (all pools - explicit)
dataStream.subscribe.token(tokenAddress).primary(); // Token changes (primary pool only - auto-switches to new main pool)
// Developer and Top Holders tracking
dataStream.subscribe.token(tokenAddress).dev.holding(); // Developer holdings updates
dataStream.subscribe.token(tokenAddress).top10(); // Top 10 holders updates
dataStream.subscribe.token(tokenAddress).fees();
dataStream.subscribe.pool(poolId); // Pool changes
// Price updates
dataStream.subscribe.price.aggregated(tokenAddress); // NEW: Aggregated price across all pools (RECOMMENDED)
dataStream.subscribe.price.token(tokenAddress); // DEPRECATED: Token price (auto-switches to new main pool)
dataStream.subscribe.price.allPoolsForToken(tokenAddress); // All price updates for a token
dataStream.subscribe.price.pool(poolId); // Pool price
// Transactions
dataStream.subscribe.tx.token(tokenAddress); // Token transactions
dataStream.subscribe.tx.pool(tokenAddress, poolId); // Pool transactions
dataStream.subscribe.tx.wallet(walletAddress); // Wallet transactions
// Wallet balance updates (NEW location)
dataStream.subscribe.wallet(walletAddress).balance(); // All token balance changes
dataStream.subscribe.wallet(walletAddress).tokenBalance(tokenAddress); // Specific token balance
// Live statistics (NEW)
dataStream.subscribe.stats.token(tokenAddress); // Live stats for a token
dataStream.subscribe.stats.pool(poolId); // Live stats for a pool
dataStream.subscribe.stats.total.token(tokenAddress); // Direct total stats object for a token
dataStream.subscribe.stats.total.pool(poolId); // Direct total stats object for a pool
// Volume rooms (NEW)
dataStream.subscribe.volume.pool(poolId); // USD volume per pool (flush ~50ms)
dataStream.subscribe.volume.token(tokenAddress); // USD volume per token (cross-pool deduplicated, flush ~50ms)
// PnL v2 (meme / tradable mints — not SOL)
dataStream.subscribe.pnl.position(walletAddress, tokenAddress); // tradeUpdate + balanceUpdate + throttled priceUpdate for one position
dataStream.subscribe.pnl.wallet(walletAddress); // all position updates for wallet
dataStream.subscribe.pnl.summary(walletAddress); // wallet summary aggregates
// Pump.fun stages
dataStream.subscribe.graduating(); // Graduating tokens
dataStream.subscribe.graduated(); // Graduated tokens
// Metadata and holders
dataStream.subscribe.metadata(tokenAddress); // Token metadata
dataStream.subscribe.holders(tokenAddress); // Holder updates
// Curve percentage updates
dataStream.subscribe.curvePercentage(market, percentage); // Market options: 'launchpad', 'pumpfun', 'boop', 'meteora-curve'
// Snipers and Insiders tracking
dataStream.subscribe.snipers(tokenAddress); // Track sniper wallets
dataStream.subscribe.insiders(tokenAddress); // Track insider wallets
```
Each subscription method returns a response object with:
- `room`: The subscription channel name
- `on()`: Method to attach a listener with proper TypeScript types
- Returns an object with `unsubscribe()` method for easy cleanup
### Smart Primary Pool Routing
The `token:primary` and `price-by-token` subscriptions now automatically switch to the new primary pool when liquidity migrates:
```typescript
// Subscribe to primary pool - automatically switches when main pool changes
dataStream.subscribe.token(tokenAddress).primary().on((poolUpdate) => {
console.log('Primary pool update (auto-switches to new main pool):');
console.log(`Pool ID: ${poolUpdate.poolId}`);
console.log(`Liquidity: $${poolUpdate.liquidity.usd}`);
// When liquidity moves to a new pool, you'll automatically get updates from the new pool
});
// DEPRECATED: price.token() also auto-switches but use price.aggregated() instead
dataStream.subscribe.price.token(tokenAddress).on((priceData) => {
console.log('Price from main pool (auto-switches):');
console.log(`Price: $${priceData.price}`);
console.log(`Pool: ${priceData.pool}`);
});
// RECOMMENDED: Use aggregated price for accurate data across all pools
dataStream.subscribe.price.aggregated(tokenAddress).on((priceData) => {
console.log('Aggregated price data:');
console.log(`Median: $${priceData.aggregated.median}`);
console.log(`Average: $${priceData.aggregated.average}`);
console.log(`Range: $${priceData.aggregated.min} - $${priceData.aggregated.max}`);
});
```
### Migration Guide for Balance Updates
The wallet balance subscriptions have been moved to a more intuitive location:
```typescript
// OLD (deprecated - will show warning)
dataStream.subscribe.tx.wallet(walletAddress).balance().on(callback);
dataStream.subscribe.tx
.wallet(walletAddress)
.tokenBalance(tokenAddress)
.on(callback);
// NEW (recommended)
dataStream.subscribe.wallet(walletAddress).balance().on(callback);
dataStream.subscribe
.wallet(walletAddress)
.tokenBalance(tokenAddress)
.on(callback);
// Note: Wallet transactions remain under .tx namespace
dataStream.subscribe.tx.wallet(walletAddress).on(callback); // Still the correct way for transactions
```
### Migration Guide for Price Updates
The `price.token()` method is deprecated in favor of `price.aggregated()`:
```typescript
// OLD (deprecated - will show warning)
dataStream.subscribe.price.token(tokenAddress).on(callback);
// NEW (recommended - more accurate across all pools)
dataStream.subscribe.price.aggregated(tokenAddress).on(callback);
// For specific pool price (still valid)
dataStream.subscribe.price.pool(poolId).on(callback);
```
## WebSocket Data Stream
The `Datastream` class provides real-time access to Solana Tracker data:
### Events
The Datastream extends the standard EventEmitter interface, allowing you to listen for various events:
```typescript
// Connection events
dataStream.on('connected', () => console.log('Connected to WebSocket server'));
dataStream.on('disconnected', (socketType) =>
console.log(`Disconnected: ${socketType}`)
);
dataStream.on('reconnecting', (attempt) =>
console.log(`Reconnecting: attempt ${attempt}`)
);
dataStream.on('error', (error) => console.error('Error:', error));
// Data events - Standard approach
dataStream.on('latest', (data) => console.log('New token:', data));
dataStream.on(`price:aggregated:${tokenAddress}`, (data) =>
console.log('Aggregated price update:', data)
); // NEW
dataStream.on(`price-by-token:${tokenAddress}`, (data) =>
console.log('Price update (deprecated):', data)
);
dataStream.on(`transaction:${tokenAddress}`, (data) =>
console.log('New transaction:', data)
);
dataStream.on(`token:${tokenAddress}`, (data) =>
console.log('Token update (all pools):', data)
);
dataStream.on(`token:${tokenAddress}:primary`, (data) =>
console.log('Token update (primary pool - auto-switches):', data)
);
dataStream.on(`sniper:${tokenAddress}`, (data) =>
console.log('Sniper update:', data)
);
dataStream.on(`insider:${tokenAddress}`, (data) =>
console.log('Insider update:', data)
);
dataStream.on(`stats:token:${tokenAddress}`, (data) =>
console.log('Token stats:', data)
);
dataStream.on(`stats:pool:${poolId}`, (data) =>
console.log('Pool stats:', data)
);
// Developer and top holders events
dataStream.on(`dev_holding:${tokenAddress}`, (data) =>
console.log('Dev holding update:', data)
);
dataStream.on(`top10:${tokenAddress}`, (data) =>
console.log('Top 10 holders update:', data)
);
dataStream.on(`fees:${tokenAddress}`, (data) => console.log('Fees update:', data));
// New approach - Chain .on() directly to subscription
dataStream.subscribe.latest().on((data) => console.log('New token:', data));
dataStream.subscribe.price
.aggregated(tokenAddress)
.on((data) => console.log('Aggregated price:', data)); // NEW
dataStream.subscribe.tx
.token(tokenAddress)
.on((data) => console.log('Transaction:', data));
dataStream.subscribe
.snipers(tokenAddress)
.on((data) => console.log('Sniper:', data));
dataStream.subscribe
.insiders(tokenAddress)
.on((data) => console.log('Insider:', data));
dataStream.subscribe.stats
.token(tokenAddress)
.on((data) => console.log('Stats:', data));
dataStream.subscribe.stats
.pool(poolId)
.on((data) => console.log('Pool stats:', data));
```
## API Documentation
The library provides methods for all endpoints in the Solana Tracker Data API.
### Token Endpoints
```typescript
// Get token information
const tokenInfo = await client.getTokenInfo('tokenAddress');
// Get token by pool address
const tokenByPool = await client.getTokenByPool('poolAddress');
// Get token holders
const tokenHolders = await client.getTokenHolders('tokenAddress');
// NEW: Get token holders with pagination (up to 5000 per request)
const paginatedHolders = await client.getTokenHoldersPaginated(
'tokenAddress',
100, // limit (optional, default: 100, max: 5000)
'cursorFromPreviousRequest' // cursor (optional)
);
// Access the data
console.log('Total holders:', paginatedHolders.total);
console.log('Holders in this page:', paginatedHolders.accounts.length);
console.log('Has more pages:', paginatedHolders.hasMore);
// Get next page
if (paginatedHolders.hasMore) {
const nextPage = await client.getTokenHoldersPaginated(
'tokenAddress',
100,
paginatedHolders.cursor
);
}
// Get top token holders
const topHolders = await client.getTopHolders('tokenAddress');
// Get all-time high price for a token
const athPrice = await client.getAthPrice('tokenAddress');
// Get tokens by deployer wallet
const deployerTokens = await client.getTokensByDeployer('walletAddress');
// Get tokens by deployer with filters (NEW)
const filteredDeployerTokens = await client.getTokensByDeployer('walletAddress', {
page: 1,
limit: 100,
market: ['raydium', 'pumpfun'],
launchpad: 'pumpfun',
});
// Get deployer tokens with full token details (NEW)
const fullDeployerTokens = await client.getTokensByDeployer('walletAddress', {
format: 'full',
limit: 50, // max 100 when format=full
});
// fullDeployerTokens.tokens are TokenDetailResponse objects
// Get latest tokens
const latestTokens = await client.getLatestTokens(100);
// Get information about multiple tokens (UPDATED: Now returns MultiTokensResponse)
const multipleTokens = await client.getMultipleTokens([
'So11111111111111111111111111111111111111112',
'4k3Dyjzvzp8eMZWUXbBCjEvwSkkk59S5iCNLY3QrkX6R',
]);
// Access tokens like: multipleTokens.tokens['tokenAddress']
// Get trending tokens
const trendingTokens = await client.getTrendingTokens('1h');
// Get top performing tokens by price change percentage
const topPerformers = await client.getTopPerformers('1h');
// Valid timeframes: '5m', '15m', '30m', '1h', '6h', '12h', '24h'
// Get tokens by volume
const volumeTokens = await client.getTokensByVolume('24h');
// NEW: Get token overview with advanced filtering
// Backward compatible - simple limit still works
const simpleOverview = await client.getTokenOverview(50);
// NEW: Advanced filtering with markets and risk scoring
const filteredOverview = await client.getTokenOverview({
limit: 100,
minCurve: 60,
minHolders: 100,
reduceSpam: true,
minMarketCap: 100000,
markets: ['pumpfun', 'raydium', 'meteora'],
minRiskScore: 50,
maxRiskScore: 80,
});
// Returns: { latest: [...], graduating: [...], graduated: [...] }
// NEW: Get graduated tokens with filtering and pagination
const graduatedTokens = await client.getGraduatedTokens({
limit: 100,
page: 1,
reduceSpam: true,
minLiquidity: 50000,
markets: ['raydium'],
minMarketCap: 1000000,
});
// NEW: Get graduating tokens with filtering
const graduatingTokens = await client.getGraduatingTokens({
limit: 50,
minCurve: 70,
maxCurve: 95,
minHolders: 50,
maxHolders: 500,
minMarketCap: 50000,
markets: ['pumpfun', 'moonshot'],
});
```
### Token Overview Filtering Options
All token overview endpoints (`getTokenOverview`, `getGraduatedTokens`, `getGraduatingTokens`) support these filters:
```typescript
interface TokenOverviewParams {
// Basic parameters
limit?: number; // Max results (default: 100, max: 500)
// Overview-specific
minCurve?: number; // Min curve percentage for graduating tokens
minHolders?: number; // Min number of holders for graduating
maxHolders?: number; // Max number of holders for graduating
reduceSpam?: boolean; // Filter out quick graduated tokens
// Graduated-specific
page?: number; // Page number for pagination
// Graduating-specific
minCurve?: number; // Min curve percentage (default: 40)
maxCurve?: number; // Max curve percentage (default: 100)
// Shared filters
minLiquidity?: number; // Minimum liquidity in USD
maxLiquidity?: number; // Maximum liquidity in USD
minMarketCap?: number; // Minimum market cap in USD
maxMarketCap?: number; // Maximum market cap in USD
markets?: string[]; // Array of markets: ['pumpfun', 'raydium', 'meteora', etc]
minRiskScore?: number; // Minimum risk score (0-100)
maxRiskScore?: number; // Maximum risk score (0-100)
rugged?: boolean; // Filter by rugged status
}
```
**Example: Find safe, high-liquidity graduated tokens**
```typescript
const safeTokens = await client.getGraduatedTokens({
limit: 50,
reduceSpam: true,
minLiquidity: 100000,
minMarketCap: 500000,
markets: ['raydium', 'meteora'],
maxRiskScore: 50,
rugged: false,
});
```
**Example: Monitor tokens about to graduate**
```typescript
const nearGraduation = await client.getGraduatingTokens({
minCurve: 85,
maxCurve: 99,
minHolders: 200,
minMarketCap: 100000,
markets: ['pumpfun'],
});
```
### Advanced Token Search (Enhanced!)
The `searchTokens` method now supports **60+ filter parameters** for precise token discovery:
```typescript
// Basic search with query
const basicSearch = await client.searchTokens({
query: 'TRUMP',
limit: 50,
sortBy: 'marketCapUsd',
sortOrder: 'desc',
});
// Search by exact symbol
const symbolSearch = await client.searchTokens({
symbol: 'TRUMP',
minLiquidity: 1000000,
});
// Filter by holder distribution - Find tokens with decentralized ownership
const decentralizedTokens = await client.searchTokens({
maxDev: 5, // Dev holds max 5%
maxTop10: 20, // Top 10 holders max 20%
maxInsiders: 10, // Insiders max 10%
maxSnipers: 5, // Snipers max 5%
minHolders: 1000, // At least 1000 holders
minLiquidity: 50000,
sortBy: 'holders',
sortOrder: 'desc',
});
// Find graduating Pump.fun tokens
const graduatingTokens = await client.searchTokens({
market: 'pumpfun',
minCurvePercentage: 80,
maxCurvePercentage: 99,
sortBy: 'curvePercentage',
sortOrder: 'desc',
});
// Search across multiple markets (NEW)
const multiMarketTokens = await client.searchTokens({
market: ['raydium', 'orca', 'pumpfun'],
minLiquidity: 50000,
sortBy: 'volume_24h',
sortOrder: 'desc',
});
// Filter by multiple launchpads (NEW)
const launchpadTokens = await client.searchTokens({
launchpad: ['pumpfun', 'boop'],
minMarketCap: 100000,
});
// Get full token objects from search (NEW)
const fullTokenSearch = await client.searchTokens({
query: 'TRUMP',
format: 'full',
limit: 50, // max 100 when format=full
});
// fullTokenSearch.data items include: token, pools, events, risk, buys, sells, txns, holders
// Search by social media presence
const tokensWithTwitter = await client.searchTokens({
twitter: 'https://x.com/realDonaldTrump',
minLiquidity: 10000,
});
// Find highly active tokens by fees
const activeTokens = await client.searchTokens({
minFeesTotal: 100, // At least 100 SOL in total fees
minFeesTrading: 50, // At least 50 SOL in trading fees
sortBy: 'fees.total',
sortOrder: 'desc',
limit: 20,
});
// Coin Communities — tokens with live community chat activity
const communityTokens = await client.searchTokens({
hasCoinCommunity: true,
sortBy: 'communityMessages',
sortOrder: 'desc',
limit: 50,
});
// At least 10 community messages and min $20k liquidity
const activeCommunityTokens = await client.searchTokens({
hasCoinCommunity: true,
minCommunityMessages: 10,
minLiquidity: 20000,
});
// Active pump.fun tokens with community chat
const pumpfunCommunity = await client.searchTokens({
market: 'pumpfun',
hasCoinCommunity: true,
sortBy: 'communityMessages',
sortOrder: 'desc',
});
// Each result includes hasCoinCommunity and communityMessages on the pool row
// Filter by volume across different timeframes
const highVolumeTokens = await client.searchTokens({
minVolume_24h: 100000, // At least $100k volume in 24h
minVolume_1h: 10000, // At least $10k volume in 1h
minLiquidity: 50000,
sortBy: 'volume_24h',
sortOrder: 'desc',
});
// Filter by transaction activity
const popularTokens = await client.searchTokens({
minBuys: 100,
minSells: 50,
minTotalTransactions: 150,
minHolders: 500,
sortBy: 'totalTransactions',
sortOrder: 'desc',
});
// Find tokens with specific token characteristics
const safeTokens = await client.searchTokens({
freezeAuthority: 'null', // No freeze authority
mintAuthority: 'null', // No mint authority
lpBurn: 100, // 100% LP burned
minLiquidity: 25000,
sortBy: 'liquidityUsd',
sortOrder: 'desc',
});
// Search by creator wallet
const creatorTokens = await client.searchTokens({
creator: 'CreatorWalletAddress',
sortBy: 'createdAt',
sortOrder: 'desc',
});
// Cursor-based pagination (faster for deep pagination)
let allResults = [];
let cursor = undefined;
do {
const results = await client.searchTokens({
minLiquidity: 5000,
limit: 100,
cursor: cursor,
});
allResults = allResults.concat(results.data);
cursor = results.nextCursor;
} while (cursor && results.hasMore);
console.log(`Found ${allResults.length} tokens`);
// Complex multi-filter search
const customSearch = await client.searchTokens({
// Liquidity and Market Cap
minLiquidity: 100000,
maxLiquidity: 10000000,
minMarketCap: 500000,
maxMarketCap: 50000000,
// Holder Distribution
minHolders: 500,
maxTop10: 30,
maxDev: 10,
maxInsiders: 15,
maxSnipers: 5,
// Volume (24h)
minVolume_24h: 50000,
// Transaction Activity
minBuys: 50,
minTotalTransactions: 100,
// Token Characteristics
freezeAuthority: 'null',
mintAuthority: 'null',
lpBurn: 100,
// Fees (indicates active trading)
minFeesTotal: 10,
// Sorting and Pagination
sortBy: 'volume_24h',
sortOrder: 'desc',
limit: 50,
showPriceChanges: true,
});
```
#### Search Parameters Reference
**Search & Pagination:**
- `query` - Search term for token symbol, name, or address
- `symbol` - Search for tokens with exact symbol match
- `page` - Page number for offset-based pagination (1-based)
- `limit` - Results per page (default: 100, max: 500)
- `cursor` - Cursor for cursor-based pagination (faster for deep searches)
- `sortBy` - Field to sort by (see sortable fields below)
- `sortOrder` - Sort order: `'asc'` or `'desc'`
- `showAllPools` - Return all pools for each token (default: false)
- `showPriceChanges` - Include price change data in response (default: false)
**Sortable Fields:**
- Market data: `liquidityUsd`, `marketCapUsd`, `priceUsd`, `volume`, `volume_5m`, `volume_15m`, `volume_30m`, `volume_1h`, `volume_6h`, `volume_12h`, `volume_24h`
- Holder distribution: `top10`, `dev`, `insiders`, `snipers`, `holders`
- Trading activity: `buys`, `sells`, `totalTransactions`
- Fees: `fees.total`, `fees.totalTrading`, `fees.totalTips`
- Other: `createdAt`, `lpBurn`, `curvePercentage`
**Creation Filters:**
- `minCreatedAt` - Minimum creation date (unix timestamp in ms)
- `maxCreatedAt` - Maximum creation date (unix timestamp in ms)
**Liquidity & Market Cap Filters:**
- `minLiquidity` - Minimum liquidity in USD
- `maxLiquidity` - Maximum liquidity in USD (capped at $100B)
- `minMarketCap` - Minimum market cap in USD
- `maxMarketCap` - Maximum market cap in USD (capped at $5T)
**Volume Filters:**
- `minVolume` / `maxVolume` - General volume filters
- `volumeTimeframe` - Timeframe for volume: `'5m'`, `'15m'`, `'30m'`, `'1h'`, `'6h'`, `'12h'`, `'24h'`
- Timeframe-specific: `minVolume_5m`, `maxVolume_5m`, `minVolume_15m`, `maxVolume_15m`, `minVolume_30m`, `maxVolume_30m`, `minVolume_1h`, `maxVolume_1h`, `minVolume_6h`, `maxVolume_6h`, `minVolume_12h`, `maxVolume_12h`, `minVolume_24h`, `maxVolume_24h`
**Transaction Filters:**
- `minBuys` / `maxBuys` - Filter by number of buy transactions
- `minSells` / `maxSells` - Filter by number of sell transactions
- `minTotalTransactions` / `maxTotalTransactions` - Filter by total transactions
**Holder Filters:**
- `minHolders` / `maxHolders` - Filter by total holder count
- `minTop10` / `maxTop10` - Filter by % held by top 10 holders (0-100)
- `minDev` / `maxDev` - Filter by % held by developer (0-100)
- `minInsiders` / `maxInsiders` - Filter by % held by insiders (0-100)
- `minSnipers` / `maxSnipers` - Filter by % held by snipers (0-100)
**Token Characteristics:**
- `lpBurn` - LP token burn percentage (0-100)
- `market` - Market identifier or array of markets (e.g., `'pumpfun'`, `'raydium'`, or `['raydium', 'orca', 'pumpfun']`)
- `freezeAuthority` - Freeze authority address (use `'null'` for none)
- `mintAuthority` - Mint authority address (use `'null'` for none)
- `deployer` - Deployer wallet address
- `creator` - Token creator wallet address
- `status` - Token status (`'graduating'`, `'graduated'`, `'default'`)
- `minCurvePercentage` / `maxCurvePercentage` - Bonding curve % (0-100)
**Social Media Filters (exact match):**
- `twitter` - Twitter/X profile URL
- `telegram` - Telegram channel/group URL
- `discord` - Discord server invite URL
- `website` - Official website URL
- `facebook` - Facebook page URL
- `instagram` - Instagram profile URL
- `youtube` - YouTube channel URL
- `reddit` - Reddit community URL
- `tiktok` - TikTok profile URL
- `github` - GitHub repository URL
**Launchpad Filter:**
- `launchpad` - Launchpad name or array of names (e.g., `'pumpfun'`, or `['pumpfun', 'boop']`)
**Format:**
- `format` - Set to `'full'` to return full token objects (same shape as `/tokens/:token`). When `format=full`, max limit is capped at 100.
**Fees Filters (in SOL):**
- `minFeesTotal` / `maxFeesTotal` - Total fees paid
- `minFeesTrading` / `maxFeesTrading` - Trading fees paid
- `minFeesTips` / `maxFeesTips` - Priority fees/tips paid
**Coin Communities Filters:**
- `hasCoinCommunity` - `true` for tokens with at least one monitored community message; `false` for tokens with zero messages
- `minCommunityMessages` / `maxCommunityMessages` - Filter by live message count range (inclusive)
- Sort by `sortBy=communityMessages` with `sortOrder=asc|desc`
#### Search Response
The search response includes pagination information and detailed token data:
```typescript
// Default response returns SearchResult items
// With format: 'full', returns TokenDetailResponse items
interface SearchResponse<T = SearchResult> {
status: string;
data: T[]; // Array of token results (SearchResult or TokenDetailResponse when format=full)
total?: number; // Total number of results
pages?: number; // Total number of pages
page?: number; // Current page number
nextCursor?: string; // Cursor for next page (cursor-based pagination)
hasMore?: boolean; // Whether there are more results
}
interface SearchResult {
// Basic token info
name: string;
symbol: string;
mint: string;
image?: string;
decimals: number;
// Pool and market info
poolAddress: string;
market: string;
quoteToken: string;
// Price and liquidity
liquidityUsd: number;
marketCapUsd: number;
priceUsd: number;
// Trading activity
buys: number;
sells: number;
totalTransactions: number;
volume: number;
volume_5m: number;
volume_15m: number;
volume_30m: number;
volume_1h: number;
volume_6h: number;
volume_12h: number;
volume_24h: number;
// Holder information
holders: number;
top10?: number; // % held by top 10 holders
dev?: number; // % held by developer
insiders?: number; // % held by insiders
snipers?: number; // % held by snipers
// Verification
jupiter?: boolean; // Jupiter verification status
verified?: boolean; // General verification status
// Token characteristics
lpBurn: number;
freezeAuthority: string | null;
mintAuthority: string | null;
deployer: string;
status: string;
createdAt: number;
lastUpdated: number;
// Social media
socials?: {
twitter?: string;
telegram?: string;
discord?: string;
website?: string;
facebook?: string;
instagram?: string;
youtube?: string;
reddit?: string;
tiktok?: string;
github?: string;
};
// Coin Communities (live chat message counts)
hasCoinCommunity?: boolean;
communityMessages?: number;
// Fees information
fees?: {
total?: number; // Total fees in SOL
totalTrading?: number; // Trading fees in SOL
totalTips?: number; // Priority fees/tips in SOL
};
// Token creation details
tokenDetails?: {
creator: string;
tx: string;
time: number;
};
}
```
### Price Endpoints
```typescript
// Get token price
const tokenPrice = await client.getPrice('tokenAddress', true); // Include price changes
// Get historic price information
const priceHistory = await client.getPriceHistory('tokenAddress');
// Get price at a specific timestamp
const timestampPrice = await client.getPriceAtTimestamp(
'tokenAddress',
1690000000
);
// Get price range (lowest/highest in time range)
const priceRange = await client.getPriceRange(
'tokenAddress',
1690000000,
1695000000
);
// Get price using POST method
const postedPrice = await client.postPrice('tokenAddress');
// Get multiple token prices
const multiplePrices = await client.getMultiplePrices([
'So11111111111111111111111111111111111111112',
'4k3Dyjzvzp8eMZWUXbBCjEvwSkkk59S5iCNLY3QrkX6R',
]);
// Get multiple token prices using POST
const postedMultiplePrices = await client.postMultiplePrices([
'So11111111111111111111111111111111111111112',
'4k3Dyjzvzp8eMZWUXbBCjEvwSkkk59S5iCNLY3QrkX6R',
]);
```
### Wallet Endpoints
```typescript
// Get basic wallet information
const walletBasic = await client.getWalletBasic('walletAddress');
// Get all tokens in a wallet
const wallet = await client.getWallet('walletAddress');
// Get wallet tokens with pagination
const walletPage = await client.getWalletPage('walletAddress', 2);
// Get wallet portfolio chart data with historical values and PnL
const walletChart = await client.getWalletChart('walletAddress');
console.log('24h PnL:', walletChart.pnl['24h']);
console.log('30d PnL:', walletChart.pnl['30d']);
console.log('Chart data points:', walletChart.chartData.length);
// Get wallet trades
const walletTrades = await client.getWalletTrades(
'walletAddress',
undefined,
true,
true,
false
);
```
### Trade Endpoints
```typescript
// Get trades for a token
const tokenTrades = await client.getTokenTrades('tokenAddress');
// Get trades for a specific token and pool
const poolTrades = await client.getPoolTrades('tokenAddress', 'poolAddress');
// Get trades for a specific token, pool, and wallet
const userPoolTrades = await client.getUserPoolTrades(
'tokenAddress',
'poolAddress',
'walletAddress'
);
// Get trades for a specific token and wallet
const userTokenTrades = await client.getUserTokenTrades(
'tokenAddress',
'walletAddress'
);
```
### Chart Endpoints
```typescript
// Get OHLCV data for a token - NEW: Now supports object syntax
// Method 1: Object syntax (recommended for multiple parameters)
const chartData = await client.getChartData({
tokenAddress: 'tokenAddress',
type: '1h',
timeFrom: 1690000000,
timeTo: 1695000000,
marketCap: false,
removeOutliers: true,
dynamicPools: true, // NEW: Dynamic pool selection
timezone: 'current', // NEW: Use current timezone or specify like 'America/New_York'
fastCache: true, // NEW: Enable fast cache for better performance
currency: 'usd', // NEW: Currency for price data - 'usd' (default), 'eur', or 'sol'
});
// Method 2: Traditional syntax (still supported)
const chartData = await client.getChartData(
'tokenAddress',
'1h',
1690000000,
1695000000,
false, // marketCap
true, // removeOutliers
true, // dynamicPools
'current', // timezone
true, // fastCache
'eur' // currency
);
// Get chart in different currencies
const chartInEur = await client.getChartData({
tokenAddress: 'tokenAddress',
type: '1h',
currency: 'eur',
});
const chartInSol = await client.getChartData({
tokenAddress: 'tokenAddress',
type: '1h',
currency: 'sol',
});
// Get OHLCV data for a specific token and pool
const poolChartData = await client.getPoolChartData({
tokenAddress: 'tokenAddress',
poolAddress: 'poolAddress',
type: '15m',
timezone: 'UTC',
fastCache: false,
currency: 'usd', // Also supports currency option
});
// Get holder count chart data
const holdersChart = await client.getHoldersChart('tokenAddress', '1d');
// Get snipers percentage chart data
const snipersChart = await client.getSnipersChart('tokenAddress', '1d');
// Get insiders percentage chart data
const insidersChart = await client.getInsidersChart('tokenAddress', '1d');
// Get bundlers percentage chart data
const bundlersChart = await client.getBundlersChart('tokenAddress', '1d');
```
### PnL Endpoints
```typescript
// Get PnL data for all positions of a wallet
const walletPnL = await client.getWalletPnL('walletAddress', true, true, false);
// Get the first 100 buyers of a token with PnL data
const firstBuyers = await client.getFirstBuyers('tokenAddress');
// Get PnL data for a specific token in a wallet - NEW: holdingCheck parameter
const tokenPnL = await client.getTokenPnL(
'walletAddress',
'tokenAddress',
true
);
// Can also use object syntax
const tokenPnL = await client.getTokenPnL({
wallet: 'walletAddress',
tokenAddress: 'tokenAddress',
holdingCheck: true,
});
```
### PnL v2 REST (`/v2/pnl`)
PnL v2 adds typed methods on `Client` for leaderboards, per-token traders, wallet analytics, and batch position lookups. Types are exported from the package with the `PnlV2` prefix (for example `PnlV2WalletOverviewResponse`, `PnlV2KOLLeaderboardParams`).
**Token scope:** PnL v2 targets meme and tradable SPL positions (for example pump-style tokens). It is **not** for tracking SOL (native or wrapped `So111...`).
Wallet endpoints may return a **queued** payload while the wallet is being indexed: check for `queued === true` and `indexed === false` (see `PnlV2WalletQueued`).
#### `pnlMode` parameter
Endpoints that accept `pnlMode` control how positions flagged by the invalid-PnL heuristic are counted toward realized PnL:
| Mode | Behaviour |
|------|-----------|
| `strict` (default) | Flagged positions contribute **0** |
| `adjusted` | Flagged positions get a cost-basis-capped value |
| `raw` | No adjustment — `realized_pnl` passed through as-is |
Accepted on: top traders (`/v2/pnl/leaderboard/top`), wallet overview (`/v2/pnl/wallets/:wallet`), wallet positions, wallet-token position, and all three position batch endpoints. The server also accepts the aliases `pnl_mode` and `mode`.
```typescript
import type { PnlMode } from '@solana-tracker/data-api';
const mode: PnlMode = 'adjusted';
const top = await client.getPnlV2TopTraders({ days: 90, pnlMode: mode, limit: 50 });
// top.pagination.pnlMode → 'adjusted'
// top.traders[0].pnlAdjustments → { mode, invalidPnl, adjustedCorrection }
// top.traders[0].period.realizedRaw → gross PnL before filtering
// top.traders[0].period.days → { profitable, losing, maxSinglePnl, winRate }
```
#### Unified identity
Every wallet-scoped response now carries an `identity` field (top-level or per-row). It can include multiple tags and pluggable sources (`kol`, `bot`, `pool`, `developer`, `hacker`, `spam_dusting`, `exchange`, SNS, platform tags, `arbitr