
Solanatracker Api
- 199 installs
- 257 repo stars
- Updated June 24, 2026
- agiprolabs/claude-trading-skills
solanatracker-api is a Claude Code skill for the SolanaTracker API, providing Solana token data, PnL, risk scores, 1-second OHLCV, wallet analytics, and Raptor swap execution.
About
solanatracker-api is a Claude Code skill documenting the SolanaTracker API for Solana token data and analytics. It covers token info with risk scoring, 1-second OHLCV charts, wallet PnL, holder and bundler analysis, top traders, a 30+ filter search, and the self-hosted Raptor DEX aggregator. A developer uses it to pull token analytics and execute swaps when building Solana trading tooling.
- Token data, risk scores, 1-second OHLCV, and wallet PnL endpoints
- Holder, bundler, sniper, and top-trader analytics
- Self-hosted Raptor DEX aggregator across 25+ DEXes
Solanatracker Api by the numbers
- 199 all-time installs (skills.sh)
- Ranked #472 of 1,106 Finance & Trading skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
solanatracker-api capabilities & compatibility
SolanaTracker plans from ~50 euros/mo (200K requests); Raptor free during public beta.
- Capabilities
- token data · risk scoring · wallet pnl · dex aggregation · ohlcv
- Use cases
- trading · data analysis · api development
- Runs
- Local or remote
- Pricing
- Paid
- Requires keys
- SOLANATRACKER_API_KEYXAPIKEYHEADER
What solanatracker-api says it does
SolanaTracker provides comprehensive Solana token data with unique features: **1-second OHLCV resolution**, **wallet PnL tracking**, **risk scoring**
Raptor is SolanaTracker's self-hosted DEX aggregator: 25+ DEXes, no rate limits, no API key, Yellowstone Jet TPU submission.
npx skills add https://github.com/agiprolabs/claude-trading-skills --skill solanatracker-apiAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 199 |
|---|---|
| repo stars | ★ 257 |
| Last updated | June 24, 2026 |
| Repository | agiprolabs/claude-trading-skills ↗ |
What it does
Fetch Solana token data, risk scores, PnL, and OHLCV from the SolanaTracker API for trading tools.
Who is it for?
Pulling Solana token analytics (risk, PnL, OHLCV, holders) and executing swaps for trading tools.
Skip if: Chains other than Solana or low-level chain reads that require direct RPC access.
When should I use this skill?
You need Solana token data, wallet PnL, or risk scoring from a hosted API instead of raw RPC.
What you get
Direct API access to token risk, PnL, 1-second OHLCV, holder analytics, and self-hosted swap execution.
- Token, PnL, and OHLCV data pulls from SolanaTracker
- Optional Raptor swap execution setup
By the numbers
- 1-second OHLCV resolution
- Raptor aggregates 25+ DEXes
- Search supports 30+ filters
Files
SolanaTracker API — Token Data, Analytics & Raptor DEX Aggregator
SolanaTracker provides comprehensive Solana token data with unique features: 1-second OHLCV resolution, wallet PnL tracking, risk scoring, bundler/sniper detection, and a self-hosted DEX aggregator (Raptor) supporting 25+ DEXes with no rate limits.
Quick Start
import httpx
API_KEY = os.getenv("SOLANATRACKER_API_KEY", "")
BASE = "https://data.solanatracker.io"
HEADERS = {"x-api-key": API_KEY}
# Token info with risk score, pools, holders
resp = httpx.get(f"{BASE}/tokens/{mint}", headers=HEADERS)
token = resp.json()
print(f"Risk: {token['risk']['score']}/10, Pools: {len(token['pools'])}")
# Wallet PnL
resp = httpx.get(f"{BASE}/pnl/{wallet}", headers=HEADERS)
pnl = resp.json()
print(f"Win rate: {pnl['summary']['winPercentage']}%")Authentication & Pricing
- API Key: From your SolanaTracker dashboard, passed as
x-api-keyheader - Plans: Starting ~€50/mo (200K requests), €200/mo (higher volume), Enterprise custom
- Raptor: Free during public beta (self-hosted, no rate limits)
Core Data Endpoints
Token Data
# Full token info (metadata, pools, risk, events, holders summary)
GET /tokens/{tokenAddress}
# New tokens (paginated, pages 1-10)
GET /tokens/latest?page=1
# Trending by volume (default: past hour)
GET /tokens/trending
GET /tokens/trending/{timeframe} # 5m, 15m, 30m, 1h, 4h, 12h, 24h
# Top performers
GET /tokens/top
# Graduated from bonding curves (pump.fun → Raydium)
GET /tokens/graduated
GET /tokens/graduating
# Multiple tokens in one request
GET /tokens/multi?tokens=ADDR1,ADDR2
# Deployer's tokens
GET /tokens/deployer/{deployerAddress}
# Token by pool address
GET /tokens/pool/{poolAddress}
# All-time high
GET /tokens/{token}/athPrice Data
# Current price with liquidity and market cap
GET /price?token={address}
# Price with change percentages
GET /price?token={address}&priceChanges=true
# Historic prices (3d, 5d, 7d, 14d, 30d snapshots)
GET /price/history?token={address}
# Multiple token prices
GET /price/multi?tokens=ADDR1,ADDR2OHLCV Charts (1-second resolution)
# Candle data — supports 1s resolution (unique to SolanaTracker)
GET /chart/{token}?type=1s&time_from=UNIX&time_to=UNIX
# Timeframes: 1s, 5s, 15s, 1m, 3m, 5m, 15m, 30m, 1h, 2h, 4h, 6h, 8h, 12h, 1d, 3d, 1w, 1mn
# Optional: currency=usd|sol|eur, removeOutliers=true, marketCap=true
# Specialized charts
GET /chart/{token}/bundlers # Bundler activity overlay
GET /chart/{token}/holders # Holder count overlay
GET /chart/{token}/insiders # Insider activity overlay
GET /chart/{token}/snipers # Sniper activity overlayWallet PnL
# Full wallet PnL across all tokens
GET /pnl/{wallet}
# Token-specific PnL
GET /pnl/{wallet}/{token}
# First buyers with PnL
GET /first-buyers/{token}
# Optional params: showHistoricPnL=true (1d/7d/30d), holdingCheck=truePnL response summary fields: realized, unrealized, total, totalInvested, averageBuyAmount, totalWins, totalLosses, winPercentage, lossPercentage
Wallet Data
# All tokens held by wallet
GET /wallet/{owner}
# Paginated wallet tokens
GET /wallet/{owner}/paginated
# Trade history
GET /wallet/{owner}/trades
# Portfolio chart over time
GET /wallet/{owner}/chartHolder Analysis
# Paginated holder list
GET /tokens/{token}/holders?page=1
# Top 100 holders
GET /tokens/{token}/holders/top
# Top 20 holders (lighter)
GET /tokens/{token}/holders/top20
# Bundler detection
GET /tokens/{token}/bundlersTop Traders
# Global top traders
GET /top-traders/all
GET /top-traders/all/paginated?page=1
# Token-specific top traders
GET /top-traders/{token}
# Optional: expandPnl=true, sortBy=total|winPercentageRisk Assessment
Risk is included in the /tokens/{token} response:
{
"risk": {
"score": 7, // 1-10 (10 = safest)
"rugged": false,
"jupiterVerified": true,
"risks": [
{ "name": "Top 10 holders own 45%", "level": "warn" },
{ "name": "Freeze authority enabled", "level": "danger" }
]
}
}Risk factors: sniper/insider concentration, developer holdings, bundler activity, top 10 holder %, mint/freeze authority, social presence, metadata completeness, liquidity.
Search (30+ filters)
GET /search?query=BONK&minLiquidity=10000&minRiskScore=5&market=raydium&sortBy=volume&limit=50
# Key filters: query, symbol, minLiquidity, maxLiquidity, minMarketCap, maxMarketCap,
# minVolume, maxVolume, hasImage, hasSocials, minHolders, maxHolders, minRiskScore,
# freezeAuthority, mintAuthority, deployer, market, sortBy, sortOrder, limit (max 500), pageRaptor — Self-Hosted DEX Aggregator
See the dedicated `raptor-dex` skill for complete Raptor documentation including setup, API reference, deployment guides, WebSocket streaming, and execution scripts.
Raptor is SolanaTracker's self-hosted DEX aggregator: 25+ DEXes, no rate limits, no API key, Yellowstone Jet TPU submission. GitHub | Docs
DataStream WebSocket
Real-time streaming via WebSocket for 30+ event types:
import websockets, json
async def stream():
uri = f"wss://datastream.solanatracker.io/{DATASTREAM_KEY}"
async with websockets.connect(uri) as ws:
# Subscribe to token trades
await ws.send(json.dumps({"type": "join", "room": f"transaction:{token_addr}"}))
# Also: price:{token}, holders:{token}, latest_tokens, graduated, etc.
async for msg in ws:
data = json.loads(msg)
if data.get("type") != "joined":
print(data)Key channels: transaction:{token}, price:{token}, holders:{token}, latest_tokens, graduated, graduating, bundlers:{token}, insiders:{token}, snipers:{token}, wallet_transaction:{wallet}
When to Use SolanaTracker vs Alternatives
| Need | Use |
|---|---|
| 1-second OHLCV candles | SolanaTracker |
| Wallet PnL tracking | SolanaTracker |
| Token risk scoring | SolanaTracker |
| Bundler/sniper detection | SolanaTracker |
| Self-hosted swap execution | Raptor |
| Free no-auth quick lookup | DexScreener |
| Real-time gRPC streaming | Yellowstone |
| Parsed transaction history | Helius |
| Cross-chain data | DexScreener or CoinGecko |
Files
References
references/data_api_endpoints.md— Complete Data API endpoint reference with parameters and response schemasreferences/raptor_setup.md— Raptor quick-start reference (full docs in theraptor-dexskill)references/risk_and_pnl.md— Risk scoring methodology and PnL calculation details- docs.solanatracker.io/llms-full.txt — Complete SolanaTracker documentation (13K+ lines, LLM-optimized) covering Data API, Swap API, Datastream WebSocket, Solana RPC, Yellowstone gRPC, SDK libraries, and all endpoint schemas. Fetch on demand rather than embedding.
Scripts
scripts/token_analysis.py— Fetch token data, risk score, holders, and tradesscripts/wallet_pnl.py— Wallet PnL analysis with win rate and trade breakdown
SolanaTracker Data API — Endpoints Reference
Base URL: https://data.solanatracker.io Authentication: x-api-key header
---
Token Endpoints
Get Token Info
- Endpoint:
GET /tokens/{tokenAddress} - Response: Full token object with metadata, pools, risk, events, holder summary
- Notes: Most comprehensive single-call endpoint. Includes risk score, all pools, and recent events.
curl -H "x-api-key: $KEY" "https://data.solanatracker.io/tokens/So11111111111111111111111111111111111111112"Latest Tokens
- Endpoint:
GET /tokens/latest - Parameters:
page(1-10) - Response: Array of newly created tokens
- Notes: Useful for monitoring new launches. Updates frequently.
Trending Tokens
- Endpoint:
GET /tokens/trendingorGET /tokens/trending/{timeframe} - Timeframes:
5m,15m,30m,1h,4h,12h,24h - Response: Top 100 tokens by volume for the timeframe
Graduated / Graduating
- Endpoint:
GET /tokens/graduated,GET /tokens/graduating - Response: Tokens that graduated from bonding curves (e.g., pump.fun → Raydium)
- Notes: Key signal for tokens transitioning to real liquidity pools.
Multiple Tokens
- Endpoint:
GET /tokens/multi - Parameters:
tokens— comma-separated addresses - Response: Array of token objects
Tokens by Deployer
- Endpoint:
GET /tokens/deployer/{deployerAddress} - Response: All tokens deployed by a specific wallet
---
Price Endpoints
Current Price
- Endpoint:
GET /price - Parameters:
token(required),priceChanges(optional, boolean) - Response:
{ price, liquidity, marketCap, priceChange5m, priceChange1h, ... }
Historic Prices
- Endpoint:
GET /price/history - Parameters:
token(required) - Response: Current price + snapshots at 3d, 5d, 7d, 14d, 30d intervals
Price at Timestamp
- Endpoint:
GET /price/history/timestamp - Parameters:
token,timestamp(unix seconds)
Price Range
- Endpoint:
GET /price/history/range - Parameters:
token,time_from,time_to - Response: Lowest and highest price in the range
Multi-Token Prices
- Endpoint:
GET /price/multi(GET) orPOST /price/multi(POST) - Parameters:
tokens— comma-separated addresses
---
Chart / OHLCV Endpoints
OHLCV Candles
- Endpoint:
GET /chart/{token} - Parameters:
type— Timeframe:1s,5s,15s,1m,3m,5m,15m,30m,1h,2h,4h,6h,8h,12h,1d,3d,1w,1mntime_from— Start unix timestamptime_to— End unix timestampcurrency—usd(default),sol,eurremoveOutliers— Boolean, filter anomalous candlesmarketCap— Boolean, return market cap instead of pricedynamicPools— Boolean, aggregate across poolsfastCache— Boolean, use cache for faster response
curl -H "x-api-key: $KEY" \
"https://data.solanatracker.io/chart/TOKEN?type=1m&time_from=1700000000&time_to=1700003600"Specialized Charts
GET /chart/{token}/bundlers— Bundler activity overlayGET /chart/{token}/holders— Holder count overlayGET /chart/{token}/insiders— Insider activity overlayGET /chart/{token}/snipers— Sniper activity overlay
---
PnL Endpoints
Wallet PnL
- Endpoint:
GET /pnl/{wallet} - Parameters:
showHistoricPnL(bool),holdingCheck(bool),hideDetails(bool) - Response: Summary + per-token breakdown
{
"summary": {
"realized": 1234.56,
"unrealized": 567.89,
"total": 1802.45,
"totalInvested": 5000.00,
"averageBuyAmount": 50.00,
"totalWins": 42,
"totalLosses": 18,
"winPercentage": 70.0,
"lossPercentage": 30.0
},
"tokens": [ /* per-token PnL details */ ]
}Token-Specific PnL
- Endpoint:
GET /pnl/{wallet}/{token} - Response: PnL for a specific token position
First Buyers
- Endpoint:
GET /first-buyers/{token} - Response: First buyers of a token with full PnL data
- Notes: Useful for identifying early-entry wallets and their current P&L.
---
Wallet Endpoints
Wallet Holdings
- Endpoint:
GET /wallet/{owner} - Response: All tokens held with pool, risk, and event data
Wallet Trades
- Endpoint:
GET /wallet/{owner}/trades - Response: Trade history with buy/sell amounts, prices, timestamps
Portfolio Chart
- Endpoint:
GET /wallet/{owner}/chart - Response: Portfolio value over time
---
Top Traders
Global Top Traders
- Endpoint:
GET /top-traders/allorGET /top-traders/all/paginated?page=1 - Parameters:
expandPnl(bool),sortBy(totalorwinPercentage)
Token Top Traders
- Endpoint:
GET /top-traders/{token} - Parameters: Same as global
---
Holder Endpoints
Paginated Holders
- Endpoint:
GET /tokens/{token}/holders?page=1
Top Holders
- Endpoint:
GET /tokens/{token}/holders/top— Top 100 - Endpoint:
GET /tokens/{token}/holders/top20— Top 20
Bundler Detection
- Endpoint:
GET /tokens/{token}/bundlers - Response: Wallets identified as bundlers (batch transaction senders)
---
Search
- Endpoint:
GET /search - Key parameters:
query,symbol,minLiquidity,maxLiquidity,minMarketCap,maxMarketCap,minVolume,maxVolume,hasImage,hasSocials,minHolders,maxHolders,minRiskScore,freezeAuthority,mintAuthority,deployer,market,sortBy,sortOrder,limit(max 500),page,cursor
curl -H "x-api-key: $KEY" \
"https://data.solanatracker.io/search?minLiquidity=50000&minRiskScore=5&market=raydium&sortBy=volume&limit=20"---
Trade / Event Endpoints
Token Trades
GET /trades/{token}— All tradesGET /trades/{token}/{wallet}— Wallet-specific tradesGET /trades/pool/{pool}— Pool-specific trades
Token Events
GET /events/{token}— Price change events by timeframeGET /events/pool/{pool}— Pool-specific events
---
Account Management
GET /credits— Remaining API creditsGET /subscription— Current subscription info
Raptor DEX Aggregator — Setup & Deployment
Raptor is a self-hosted Rust binary by SolanaTracker that aggregates swap quotes across 25+ Solana DEXes. Free during public beta.
Quick Start
# Clone the binary repo
git clone https://github.com/solanatracker/raptor-binary
cd raptor-binary
# Run with minimum required config
RPC_URL="https://your-solana-rpc.com" \
YELLOWSTONE_ENDPOINT="https://your-yellowstone-grpc.com" \
./raptor
# Default: listens on 0.0.0.0:8080Requirements: Solana RPC endpoint + Yellowstone gRPC endpoint (for pool indexing and Jet TPU transaction submission) + signature file in the working directory.
Signature File
Raptor requires a signature file in the same directory where the binary runs. This file authenticates your instance with the SolanaTracker backend.
# The signature file must be present alongside the raptor binary
ls raptor-binary/
# raptor ← the binary
# signature ← required auth file (provided by SolanaTracker)
# If you move the binary, copy the signature file too
cp raptor /opt/raptor/
cp signature /opt/raptor/
cd /opt/raptor && ./raptorWithout the signature file, Raptor will fail to start or fail to submit transactions. The file is included in the raptor-binary repo clone.
Environment Variables
| Variable | Required | Default | Description |
|---|---|---|---|
RPC_URL | Yes | — | Solana RPC endpoint |
YELLOWSTONE_ENDPOINT | Yes | — | Yellowstone gRPC endpoint |
BIND_ADDR | No | 0.0.0.0:8080 | Listen address |
INCLUDE_DEXES | No | All | Comma-separated DEX filter |
SKIP_POOL_INDEXER_WAIT | No | false | Skip initial pool index sync |
Execution Flow
1. GET /quote → Best route across DEXes
2. POST /swap → Unsigned transaction
3. Sign transaction locally (your key, never sent to Raptor)
4. POST /send-transaction → Submit via Yellowstone Jet TPU
5. GET /transaction/{sig} → Confirm statusStep 1: Get Quote
import httpx
RAPTOR = "http://localhost:8080"
resp = httpx.get(f"{RAPTOR}/quote", params={
"inputMint": "So11111111111111111111111111111111111111112", # SOL
"outputMint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", # USDC
"amount": 1_000_000_000, # 1 SOL in lamports
"slippageBps": 50,
})
quote = resp.json()
print(f"Output: {quote['amountOut']} lamports")
print(f"Price impact: {quote['priceImpact']}%")
print(f"Route: {len(quote['routePlan'])} hops")Step 2: Build Swap Transaction
resp = httpx.post(f"{RAPTOR}/swap", json={
"userPublicKey": "YOUR_WALLET_PUBKEY",
"quoteResponse": quote,
"wrapUnwrapSol": True,
"txVersion": "v0",
"priorityFee": "auto",
"maxPriorityFee": 100_000, # max priority fee in lamports
})
swap = resp.json()
# swap["swapTransaction"] is base64-encoded unsigned transactionStep 3: Sign Locally
from solders.transaction import VersionedTransaction
from solders.keypair import Keypair
import base64
tx_bytes = base64.b64decode(swap["swapTransaction"])
tx = VersionedTransaction.from_bytes(tx_bytes)
keypair = Keypair.from_base58_string(os.getenv("PRIVATE_KEY"))
signed_tx = tx.sign([keypair])
signed_b64 = base64.b64encode(bytes(signed_tx)).decode()Step 4: Submit Transaction
resp = httpx.post(f"{RAPTOR}/send-transaction", json={
"transaction": signed_b64,
})
result = resp.json()
print(f"Signature: {result['signature']}")
# Raptor retries for up to 30 seconds or until confirmedStep 5: Check Status
resp = httpx.get(f"{RAPTOR}/transaction/{result['signature']}")
status = resp.json()
# status: pending | confirmed | failed | expiredDEX Filter Configuration
Limit which DEXes Raptor queries. Useful for targeting specific pool types:
# Only bonding curve DEXes
INCLUDE_DEXES="pumpfun,moonit,heaven,raydium-launchlab,meteora-curve" ./raptor
# Only major AMMs
INCLUDE_DEXES="raydium,orca,meteora" ./raptorAll supported DEXes: raydium (AMM/CLMM/CPMM), meteora (DLMM/Dynamic), orca (Whirlpool v1/v2), pancakeswap, pumpfun, pumpswap, heaven, moonit, boopfun, humidifi, tessera, solfi, alphaq, zerofi, bisonfi, goonfi, fluxbeam, and more.
Deployment Options
Bare Metal / VPS
# Ensure signature file is in the same directory as the binary
ls ./raptor ./signature # both must exist
chmod +x raptor
RPC_URL="..." YELLOWSTONE_ENDPOINT="..." ./raptorDocker
FROM ubuntu:22.04
COPY raptor /usr/local/bin/raptor
COPY signature /usr/local/bin/signature
RUN chmod +x /usr/local/bin/raptor
WORKDIR /usr/local/bin
CMD ["raptor"]docker run -e RPC_URL="..." -e YELLOWSTONE_ENDPOINT="..." -p 8080:8080 raptorFly.io
# fly.toml
app = "my-raptor"
primary_region = "ewr" # East US for low latency
[build]
dockerfile = "Dockerfile"
[env]
RPC_URL = "https://your-rpc.com"
YELLOWSTONE_ENDPOINT = "https://your-yellowstone.com"
INCLUDE_DEXES = "raydium,orca,meteora,pumpfun"
[[services]]
internal_port = 8080
protocol = "tcp"
[[services.ports]]
port = 443
handlers = ["tls", "http"]
[[vm]]
size = "shared-cpu-2x"
memory = "4gb"fly deployKey Differences from Jupiter
1. Self-hosted: Runs on your infrastructure, no external API dependency 2. No rate limits: Limited only by your hardware 3. Separate on-chain program: Uses RaptorD5o... program, not Jupiter's 4. Jet TPU submission: Transactions submitted via Yellowstone, not standard RPC 5. Private key stays local: Signing happens on your machine, never sent to Raptor 6. DEX filtering: Choose exactly which DEXes to include
Troubleshooting
- Slow startup: Initial pool indexing can take 1-2 minutes. Use
SKIP_POOL_INDEXER_WAIT=trueto serve requests during indexing (quotes may be incomplete). - Quote returns 0 output: Token may not have liquidity on any included DEX. Check
INCLUDE_DEXESfilter. - Transaction fails: Check slippage, priority fee, and block height. Raptor retries automatically for 30 seconds.
- Health check fails: Verify RPC and Yellowstone endpoints are reachable.
SolanaTracker — Risk Scoring & PnL Calculation
Risk Score
SolanaTracker assigns each token a risk score from 1 (highest risk) to 10 (safest), evaluating multiple on-chain and metadata factors.
Risk Factors Evaluated
| Factor | Level | Description |
|---|---|---|
| Mint authority enabled | danger | Token supply can be increased |
| Freeze authority enabled | danger | Tokens can be frozen in wallets |
| Top 10 holders own >50% | warn | High concentration risk |
| Top 10 holders own >80% | danger | Extreme concentration |
| Low holder count (<100) | warn | Limited distribution |
| No social media links | warn | Possible low-effort token |
| Incomplete metadata | warn | Missing name, symbol, or image |
| Bundler activity detected | warn | Coordinated buy patterns |
| High sniper concentration | warn | Early buyers hold large % |
| Developer holds >10% | warn | Creator retention risk |
| Low liquidity (<$10K) | danger | Difficult to exit |
| Not Jupiter verified | info | Not vetted by Jupiter |
Risk Response Schema
{
"risk": {
"score": 7,
"rugged": false,
"jupiterVerified": true,
"risks": [
{
"name": "Top 10 holders own 45%",
"description": "High holder concentration",
"level": "warn",
"score": -1
},
{
"name": "Freeze authority enabled",
"description": "Token accounts can be frozen",
"level": "danger",
"score": -2
}
]
}
}Using Risk Scores
def assess_risk(token_data: dict) -> str:
"""Classify token risk level from SolanaTracker data."""
risk = token_data.get("risk", {})
score = risk.get("score", 0)
rugged = risk.get("rugged", False)
if rugged:
return "RUGGED — avoid"
if score >= 8:
return "LOW RISK"
if score >= 5:
return "MODERATE RISK"
if score >= 3:
return "HIGH RISK"
return "EXTREME RISK"
def has_danger_flags(token_data: dict) -> list[str]:
"""Extract danger-level risk flags."""
risks = token_data.get("risk", {}).get("risks", [])
return [r["name"] for r in risks if r.get("level") == "danger"]PnL Calculation
SolanaTracker tracks realized and unrealized PnL per wallet across all Solana tokens.
PnL Summary Fields
| Field | Type | Description |
|---|---|---|
realized | float | Profit/loss from closed positions (SOL) |
unrealized | float | Open position value vs. cost basis (SOL) |
total | float | realized + unrealized |
totalInvested | float | Total SOL spent on buys |
averageBuyAmount | float | Mean buy transaction size |
totalWins | int | Positions closed in profit |
totalLosses | int | Positions closed at a loss |
winPercentage | float | Win rate (0-100) |
lossPercentage | float | Loss rate (0-100) |
Token-Level PnL
Each token in the breakdown includes:
| Field | Type | Description |
|---|---|---|
tokenAddress | string | Token mint |
totalBought | float | Total tokens bought |
totalSold | float | Total tokens sold |
totalBuyValue | float | Total SOL spent buying |
totalSellValue | float | Total SOL received selling |
realized | float | Sell value - buy value for sold tokens |
unrealized | float | Current value of held tokens - cost basis |
holdingAmount | float | Currently held tokens |
holdingValue | float | Current value of holdings |
PnL Analysis Patterns
def classify_trader(pnl_summary: dict) -> str:
"""Classify trader skill level from PnL data."""
win_pct = pnl_summary.get("winPercentage", 0)
total = pnl_summary.get("total", 0)
invested = pnl_summary.get("totalInvested", 0)
trades = pnl_summary.get("totalWins", 0) + pnl_summary.get("totalLosses", 0)
if trades < 10:
return "INSUFFICIENT DATA"
roi = total / invested if invested > 0 else 0
if win_pct >= 60 and roi > 0.5:
return "STRONG PERFORMER"
if win_pct >= 50 and roi > 0:
return "PROFITABLE"
if win_pct >= 40:
return "MARGINAL"
return "UNPROFITABLE"Historical PnL
With showHistoricPnL=true, the response includes:
pnl_1d— Last 24 hourspnl_7d— Last 7 dayspnl_30d— Last 30 days
Each interval has the same summary structure, enabling trend analysis:
def pnl_trend(pnl: dict) -> str:
"""Check if trader is improving or declining."""
d1 = pnl.get("pnl_1d", {}).get("total", 0)
d7 = pnl.get("pnl_7d", {}).get("total", 0)
d30 = pnl.get("pnl_30d", {}).get("total", 0)
if d1 > 0 and d7 > 0 and d30 > 0:
return "CONSISTENTLY PROFITABLE"
if d1 > 0 and d7 > 0:
return "RECENTLY PROFITABLE"
if d1 < 0 and d7 < 0:
return "DECLINING"
return "MIXED"First Buyers Analysis
The /first-buyers/{token} endpoint combines early entry detection with PnL:
def analyze_first_buyers(token: str, api_key: str) -> dict:
"""Analyze first buyers and their outcomes."""
resp = httpx.get(
f"https://data.solanatracker.io/first-buyers/{token}",
headers={"x-api-key": api_key},
)
buyers = resp.json()
profitable = sum(1 for b in buyers if b.get("realized", 0) > 0)
total = len(buyers)
return {
"total_first_buyers": total,
"profitable_count": profitable,
"profitable_pct": round(profitable / total * 100, 1) if total > 0 else 0,
"still_holding": sum(1 for b in buyers if b.get("holdingAmount", 0) > 0),
"total_realized": sum(b.get("realized", 0) for b in buyers),
}Bundler Detection
Bundlers are wallets that use atomic bundles to execute coordinated buys (often at launch). The /tokens/{token}/bundlers endpoint identifies these:
[
{
"wallet": "ADDR...",
"bundleCount": 5,
"totalBought": 15000000,
"holdingAmount": 12000000,
"holdingPercentage": 2.4
}
]High bundler concentration at launch is a risk signal — it suggests coordinated buying that may precede a dump.
#!/usr/bin/env python3
"""Analyze a Solana token using SolanaTracker Data API.
Fetches token info, risk assessment, top holders, and recent trades
to produce a comprehensive screening report. Includes risk flags,
holder concentration, and trading activity analysis.
Usage:
python scripts/token_analysis.py
TOKEN_ADDRESS="TokenMint..." python scripts/token_analysis.py
Dependencies:
uv pip install httpx
Environment Variables:
SOLANATRACKER_API_KEY: Your SolanaTracker API key
TOKEN_ADDRESS: Token mint to analyze (default: SOL)
"""
import os
import sys
import time
from typing import Optional
import httpx
# ── Configuration ───────────────────────────────────────────────────
API_KEY = os.getenv("SOLANATRACKER_API_KEY", "")
if not API_KEY:
print("Set SOLANATRACKER_API_KEY environment variable")
print(" Get a key at https://www.solanatracker.io/data-api")
sys.exit(1)
TOKEN_ADDRESS = os.getenv(
"TOKEN_ADDRESS", "So11111111111111111111111111111111111111112"
)
BASE_URL = "https://data.solanatracker.io"
HEADERS = {"x-api-key": API_KEY}
# ── API Helper ──────────────────────────────────────────────────────
def st_get(endpoint: str, params: Optional[dict] = None) -> dict | list:
"""Make a GET request to SolanaTracker API with retry.
Args:
endpoint: API path (e.g., '/tokens/...').
params: Query parameters.
Returns:
Parsed JSON response.
Raises:
RuntimeError: On persistent API errors.
"""
for attempt in range(3):
try:
resp = httpx.get(
f"{BASE_URL}{endpoint}",
headers=HEADERS,
params=params or {},
timeout=30.0,
)
if resp.status_code == 429:
wait = 5.0 * (attempt + 1)
print(f" Rate limited, waiting {wait}s...")
time.sleep(wait)
continue
if resp.status_code == 403:
print(" Access denied — check API key and subscription tier")
return {}
resp.raise_for_status()
return resp.json()
except httpx.TimeoutException:
if attempt < 2:
time.sleep(3.0)
continue
raise
return {}
# ── Data Fetching ───────────────────────────────────────────────────
def fetch_token_info(address: str) -> dict:
"""Fetch full token info including risk, pools, and events.
Args:
address: Token mint address.
Returns:
Token info dict.
"""
data = st_get(f"/tokens/{address}")
return data if isinstance(data, dict) else {}
def fetch_top_holders(address: str) -> list[dict]:
"""Fetch top 20 holders for a token.
Args:
address: Token mint address.
Returns:
List of holder dicts.
"""
data = st_get(f"/tokens/{address}/holders/top20")
return data if isinstance(data, list) else []
def fetch_price_with_changes(address: str) -> dict:
"""Fetch current price with change percentages.
Args:
address: Token mint address.
Returns:
Price data dict.
"""
data = st_get("/price", {"token": address, "priceChanges": "true"})
return data if isinstance(data, dict) else {}
def fetch_top_traders(address: str) -> list[dict]:
"""Fetch top traders for a token.
Args:
address: Token mint address.
Returns:
List of top trader dicts.
"""
data = st_get(f"/top-traders/{address}")
return data if isinstance(data, list) else []
def fetch_bundlers(address: str) -> list[dict]:
"""Fetch bundler wallets for a token.
Args:
address: Token mint address.
Returns:
List of bundler dicts.
"""
data = st_get(f"/tokens/{address}/bundlers")
return data if isinstance(data, list) else []
# ── Analysis ────────────────────────────────────────────────────────
def analyze_risk(token_data: dict) -> list[str]:
"""Analyze risk factors and return formatted flags.
Args:
token_data: Full token info response.
Returns:
List of risk flag strings.
"""
risk = token_data.get("risk", {})
flags = []
if not risk:
flags.append("[?] Risk data unavailable")
return flags
score = risk.get("score", 0)
rugged = risk.get("rugged", False)
verified = risk.get("jupiterVerified", False)
if rugged:
flags.append("[!!] TOKEN HAS BEEN RUGGED")
if verified:
flags.append("[ok] Jupiter verified")
else:
flags.append("[i] Not Jupiter verified")
flags.append(f"[{'ok' if score >= 7 else '!' if score >= 4 else '!!'}] "
f"Risk score: {score}/10")
for r in risk.get("risks", []):
level = r.get("level", "info")
name = r.get("name", "Unknown risk")
prefix = {"danger": "[!!]", "warn": "[!]", "info": "[i]"}.get(level, "[?]")
flags.append(f" {prefix} {name}")
return flags
def analyze_holders(holders: list[dict]) -> dict:
"""Analyze holder concentration.
Args:
holders: List of top holder dicts.
Returns:
Concentration analysis dict.
"""
if not holders:
return {"count": 0}
total_pct = sum(h.get("percentage", 0) or h.get("pct", 0) for h in holders)
top_holder_pct = holders[0].get("percentage", 0) or holders[0].get("pct", 0) if holders else 0
return {
"count": len(holders),
"top_holder_pct": round(top_holder_pct, 2),
"top_10_pct": round(sum(
h.get("percentage", 0) or h.get("pct", 0) for h in holders[:10]
), 2),
"top_20_pct": round(total_pct, 2),
}
# ── Display ─────────────────────────────────────────────────────────
def format_usd(value: float) -> str:
"""Format a USD value for display."""
if not value:
return "$0"
if abs(value) >= 1_000_000_000:
return f"${value / 1e9:.2f}B"
elif abs(value) >= 1_000_000:
return f"${value / 1e6:.2f}M"
elif abs(value) >= 1_000:
return f"${value / 1e3:.2f}K"
return f"${value:.2f}"
def print_report(
address: str,
token_data: dict,
price_data: dict,
risk_flags: list[str],
holder_analysis: dict,
bundlers: list[dict],
) -> None:
"""Print formatted token analysis report.
Args:
address: Token mint address.
token_data: Full token info.
price_data: Price with changes.
risk_flags: Analyzed risk flags.
holder_analysis: Holder concentration data.
bundlers: Bundler wallet list.
"""
# Extract token metadata
token = token_data.get("token", token_data)
name = token.get("name", "Unknown")
symbol = token.get("symbol", "?")
print(f"\n{'='*60}")
print(f"TOKEN ANALYSIS: {symbol} ({name})")
print(f"{'='*60}")
print(f" Mint: {address}")
# Price data
print(f"\n--- Price & Market ---")
price = price_data.get("price", 0)
if price:
print(f" Price: {'${:.8f}'.format(price) if price < 0.01 else '${:.4f}'.format(price)}")
liq = price_data.get("liquidity", 0)
mcap = price_data.get("marketCap", 0)
if liq:
print(f" Liquidity: {format_usd(liq)}")
if mcap:
print(f" Market Cap: {format_usd(mcap)}")
# Price changes
changes = {
"5m": price_data.get("priceChange5m"),
"1h": price_data.get("priceChange1h"),
"6h": price_data.get("priceChange6h"),
"24h": price_data.get("priceChange24h"),
}
change_parts = [f"{tf}: {v:+.2f}%" for tf, v in changes.items() if v is not None]
if change_parts:
print(f" Changes: {' | '.join(change_parts)}")
# Pools
pools = token_data.get("pools", [])
if pools:
print(f"\n--- Pools ({len(pools)}) ---")
for p in pools[:5]:
pool_liq = p.get("liquidity", {})
pool_usd = pool_liq.get("usd", 0) if isinstance(pool_liq, dict) else 0
dex = p.get("market", p.get("dexId", "?"))
print(f" {dex:<15} Liq: {format_usd(pool_usd)}")
# Risk assessment
print(f"\n--- Risk Assessment ---")
for flag in risk_flags:
print(f" {flag}")
# Holder concentration
if holder_analysis.get("count", 0) > 0:
print(f"\n--- Holder Concentration ---")
print(f" Top holder: {holder_analysis['top_holder_pct']:.1f}%")
print(f" Top 10: {holder_analysis['top_10_pct']:.1f}%")
print(f" Top 20: {holder_analysis['top_20_pct']:.1f}%")
if holder_analysis["top_10_pct"] > 80:
print(f" [!!] EXTREME CONCENTRATION — top 10 hold >80%")
elif holder_analysis["top_10_pct"] > 50:
print(f" [!] High concentration — top 10 hold >50%")
# Bundlers
if bundlers:
total_bundler_pct = sum(b.get("holdingPercentage", 0) for b in bundlers)
print(f"\n--- Bundler Activity ---")
print(f" Bundlers detected: {len(bundlers)}")
print(f" Total holding: {total_bundler_pct:.1f}%")
if total_bundler_pct > 10:
print(f" [!] Significant bundler concentration")
# Overall
print(f"\n--- Assessment ---")
risk_score = token_data.get("risk", {}).get("score", 0)
if token_data.get("risk", {}).get("rugged"):
print(" STATUS: RUGGED — do not trade")
elif risk_score >= 7:
print(f" STATUS: LOW RISK (score {risk_score}/10)")
elif risk_score >= 4:
print(f" STATUS: MODERATE RISK (score {risk_score}/10)")
else:
print(f" STATUS: HIGH RISK (score {risk_score}/10)")
if liq and liq < 10_000:
print(" LIQUIDITY: DANGEROUSLY LOW (<$10K)")
elif liq and liq < 50_000:
print(" LIQUIDITY: LOW (<$50K)")
# ── Main ────────────────────────────────────────────────────────────
def main() -> None:
"""Run token analysis report."""
print(f"Analyzing token: {TOKEN_ADDRESS}")
print("Fetching token info...")
token_data = fetch_token_info(TOKEN_ADDRESS)
if not token_data:
print("Could not fetch token data. Check the address and API key.")
sys.exit(1)
print("Fetching price...")
price_data = fetch_price_with_changes(TOKEN_ADDRESS)
time.sleep(0.5)
print("Fetching holders...")
holders = fetch_top_holders(TOKEN_ADDRESS)
time.sleep(0.5)
print("Fetching bundlers...")
bundlers = fetch_bundlers(TOKEN_ADDRESS)
# Analyze
risk_flags = analyze_risk(token_data)
holder_analysis = analyze_holders(holders)
# Report
print_report(
TOKEN_ADDRESS, token_data, price_data,
risk_flags, holder_analysis, bundlers,
)
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""Analyze wallet PnL using SolanaTracker Data API.
Fetches wallet PnL data including win rate, realized/unrealized profit,
and per-token breakdown. Useful for evaluating wallet quality before
copy-trading or for tracking your own performance.
Usage:
python scripts/wallet_pnl.py
WALLET_ADDRESS="WalletPubkey..." python scripts/wallet_pnl.py
Dependencies:
uv pip install httpx
Environment Variables:
SOLANATRACKER_API_KEY: Your SolanaTracker API key
WALLET_ADDRESS: Wallet to analyze
"""
import os
import sys
import time
from typing import Optional
import httpx
# ── Configuration ───────────────────────────────────────────────────
API_KEY = os.getenv("SOLANATRACKER_API_KEY", "")
if not API_KEY:
print("Set SOLANATRACKER_API_KEY environment variable")
print(" Get a key at https://www.solanatracker.io/data-api")
sys.exit(1)
# Example: a known active wallet, or set your own
WALLET_ADDRESS = os.getenv("WALLET_ADDRESS", "")
if not WALLET_ADDRESS:
print("Set WALLET_ADDRESS environment variable")
print(" Example: WALLET_ADDRESS=\"YourWallet...\" python scripts/wallet_pnl.py")
sys.exit(1)
BASE_URL = "https://data.solanatracker.io"
HEADERS = {"x-api-key": API_KEY}
# ── API Helper ──────────────────────────────────────────────────────
def st_get(endpoint: str, params: Optional[dict] = None) -> dict | list:
"""Make a GET request to SolanaTracker API with retry.
Args:
endpoint: API path.
params: Query parameters.
Returns:
Parsed JSON response.
"""
for attempt in range(3):
try:
resp = httpx.get(
f"{BASE_URL}{endpoint}",
headers=HEADERS,
params=params or {},
timeout=30.0,
)
if resp.status_code == 429:
wait = 5.0 * (attempt + 1)
print(f" Rate limited, waiting {wait}s...")
time.sleep(wait)
continue
if resp.status_code == 403:
print(" Access denied — check API key")
return {}
resp.raise_for_status()
return resp.json()
except httpx.TimeoutException:
if attempt < 2:
time.sleep(3.0)
continue
raise
return {}
# ── Data Fetching ───────────────────────────────────────────────────
def fetch_pnl(wallet: str, historic: bool = True) -> dict:
"""Fetch wallet PnL with optional historic intervals.
Args:
wallet: Wallet public key.
historic: Include 1d/7d/30d PnL intervals.
Returns:
PnL data dict with summary and token breakdown.
"""
params = {}
if historic:
params["showHistoricPnL"] = "true"
data = st_get(f"/pnl/{wallet}", params)
return data if isinstance(data, dict) else {}
def fetch_wallet_trades(wallet: str) -> list[dict]:
"""Fetch recent trade history for a wallet.
Args:
wallet: Wallet public key.
Returns:
List of trade dicts.
"""
data = st_get(f"/wallet/{wallet}/trades")
return data if isinstance(data, list) else []
def fetch_wallet_holdings(wallet: str) -> list[dict]:
"""Fetch current token holdings for a wallet.
Args:
wallet: Wallet public key.
Returns:
List of held token dicts.
"""
data = st_get(f"/wallet/{wallet}")
return data if isinstance(data, list) else []
# ── Analysis ────────────────────────────────────────────────────────
def classify_trader(summary: dict) -> str:
"""Classify trader skill level from PnL summary.
Args:
summary: PnL summary dict.
Returns:
Classification string.
"""
win_pct = summary.get("winPercentage", 0)
total = summary.get("total", 0)
invested = summary.get("totalInvested", 0)
trades = summary.get("totalWins", 0) + summary.get("totalLosses", 0)
if trades < 10:
return "INSUFFICIENT DATA"
roi = total / invested if invested > 0 else 0
if win_pct >= 60 and roi > 0.5:
return "STRONG PERFORMER"
if win_pct >= 50 and roi > 0:
return "PROFITABLE"
if win_pct >= 40:
return "MARGINAL"
return "UNPROFITABLE"
def analyze_token_breakdown(tokens: list[dict]) -> dict:
"""Analyze per-token PnL distribution.
Args:
tokens: List of per-token PnL entries.
Returns:
Analysis summary dict.
"""
if not tokens:
return {}
winners = [t for t in tokens if t.get("realized", 0) > 0]
losers = [t for t in tokens if t.get("realized", 0) < 0]
holding = [t for t in tokens if t.get("holdingAmount", 0) > 0]
best_trade = max(tokens, key=lambda t: t.get("realized", 0), default={})
worst_trade = min(tokens, key=lambda t: t.get("realized", 0), default={})
avg_win = (
sum(t.get("realized", 0) for t in winners) / len(winners)
if winners else 0
)
avg_loss = (
sum(t.get("realized", 0) for t in losers) / len(losers)
if losers else 0
)
return {
"total_tokens_traded": len(tokens),
"winners": len(winners),
"losers": len(losers),
"still_holding": len(holding),
"avg_win_sol": round(avg_win, 4),
"avg_loss_sol": round(avg_loss, 4),
"best_trade_sol": round(best_trade.get("realized", 0), 4),
"best_trade_token": best_trade.get("tokenAddress", "?")[:12] + "...",
"worst_trade_sol": round(worst_trade.get("realized", 0), 4),
"worst_trade_token": worst_trade.get("tokenAddress", "?")[:12] + "...",
"profit_factor": round(
abs(sum(t.get("realized", 0) for t in winners)) /
abs(sum(t.get("realized", 0) for t in losers))
if losers and sum(t.get("realized", 0) for t in losers) != 0
else float("inf"),
2,
),
}
# ── Display ─────────────────────────────────────────────────────────
def format_sol(value: float) -> str:
"""Format a SOL value for display."""
if abs(value) >= 1000:
return f"{value:,.1f} SOL"
return f"{value:.4f} SOL"
def print_report(
wallet: str,
pnl_data: dict,
classification: str,
token_breakdown: dict,
holdings: list[dict],
) -> None:
"""Print formatted wallet PnL report.
Args:
wallet: Wallet address.
pnl_data: Full PnL response.
classification: Trader classification.
token_breakdown: Per-token analysis.
holdings: Current holdings.
"""
summary = pnl_data.get("summary", {})
print(f"\n{'='*60}")
print(f"WALLET PnL ANALYSIS")
print(f"{'='*60}")
print(f" Wallet: {wallet}")
print(f" Class: {classification}")
# Summary
print(f"\n--- PnL Summary ---")
print(f" Realized: {format_sol(summary.get('realized', 0))}")
print(f" Unrealized: {format_sol(summary.get('unrealized', 0))}")
print(f" Total: {format_sol(summary.get('total', 0))}")
print(f" Total Invested: {format_sol(summary.get('totalInvested', 0))}")
print(f" Avg Buy Size: {format_sol(summary.get('averageBuyAmount', 0))}")
invested = summary.get("totalInvested", 0)
total = summary.get("total", 0)
if invested > 0:
roi = total / invested * 100
print(f" ROI: {roi:+.1f}%")
# Win/loss
wins = summary.get("totalWins", 0)
losses = summary.get("totalLosses", 0)
total_trades = wins + losses
print(f"\n--- Win/Loss ---")
print(f" Wins: {wins}")
print(f" Losses: {losses}")
print(f" Total Trades: {total_trades}")
print(f" Win Rate: {summary.get('winPercentage', 0):.1f}%")
# Token breakdown
if token_breakdown:
print(f"\n--- Trade Analysis ---")
print(f" Tokens Traded: {token_breakdown['total_tokens_traded']}")
print(f" Still Holding: {token_breakdown['still_holding']}")
print(f" Avg Win: {format_sol(token_breakdown['avg_win_sol'])}")
print(f" Avg Loss: {format_sol(token_breakdown['avg_loss_sol'])}")
print(f" Best Trade: {format_sol(token_breakdown['best_trade_sol'])} ({token_breakdown['best_trade_token']})")
print(f" Worst Trade: {format_sol(token_breakdown['worst_trade_sol'])} ({token_breakdown['worst_trade_token']})")
pf = token_breakdown["profit_factor"]
pf_str = f"{pf:.2f}" if pf != float("inf") else "∞"
print(f" Profit Factor: {pf_str}")
# Historic PnL trends
for period, label in [("pnl_1d", "1 Day"), ("pnl_7d", "7 Days"), ("pnl_30d", "30 Days")]:
hist = pnl_data.get(period, {})
if hist:
h_total = hist.get("total", 0)
h_wins = hist.get("totalWins", 0)
h_losses = hist.get("totalLosses", 0)
h_trades = h_wins + h_losses
if h_trades > 0:
if period == "pnl_1d":
print(f"\n--- Historic PnL ---")
win_rate = hist.get("winPercentage", 0)
print(f" {label:>7}: {format_sol(h_total):>16} "
f"({h_trades} trades, {win_rate:.0f}% win)")
# Current holdings summary
if holdings:
print(f"\n--- Current Holdings ({len(holdings)} tokens) ---")
# Sort by value if available
for h in holdings[:5]:
token = h.get("token", h)
symbol = token.get("symbol", "?") if isinstance(token, dict) else "?"
amount = h.get("amount", h.get("balance", 0))
value = h.get("value", h.get("valueUsd", 0))
if value:
print(f" {symbol:<10} Value: ${value:,.2f}")
elif amount:
print(f" {symbol:<10} Amount: {amount}")
print()
# ── Main ────────────────────────────────────────────────────────────
def main() -> None:
"""Run wallet PnL analysis."""
print(f"Analyzing wallet: {WALLET_ADDRESS}")
print("Fetching PnL data...")
pnl_data = fetch_pnl(WALLET_ADDRESS, historic=True)
if not pnl_data:
print("Could not fetch PnL data. Check the wallet address and API key.")
sys.exit(1)
summary = pnl_data.get("summary", {})
classification = classify_trader(summary)
print("Fetching holdings...")
holdings = fetch_wallet_holdings(WALLET_ADDRESS)
# Analyze token breakdown
tokens = pnl_data.get("tokens", [])
token_breakdown = analyze_token_breakdown(tokens)
# Report
print_report(WALLET_ADDRESS, pnl_data, classification, token_breakdown, holdings)
if __name__ == "__main__":
main()
Related skills
FAQ
What is unique about SolanaTracker's data?
It offers 1-second OHLCV resolution, wallet PnL tracking, token risk scoring, bundler and sniper detection, and a self-hosted DEX aggregator (Raptor) across 25+ DEXes with no rate limits.
How do you authenticate?
With an API key from the SolanaTracker dashboard, passed as an x-api-key header; plans start around 50 euros per month.