
Token Holder Analysis
- 194 installs
- 257 repo stars
- Updated June 24, 2026
- agiprolabs/claude-trading-skills
token-holder-analysis is a Claude Code skill that measures Solana token ownership concentration and flags insider or bundler patterns as a pre-trade risk check.
About
A Claude Code skill that analyzes who holds a Solana token, how concentrated ownership is, and whether insider patterns suggest risk. It computes top-N holder percentage, Gini coefficient, HHI, and the Nakamoto coefficient, and detects bundler activity using data from Solana RPC, Helius, SolanaTracker, and Birdeye. Developers use it as a pre-trade safety check on token distribution risk.
- Computes concentration metrics: top-N %, Gini, HHI, and Nakamoto coefficient
- Detects insider and bundler patterns as a pre-trade safety check
- Pulls holder data from Solana RPC, Helius DAS, SolanaTracker, and Birdeye
Token Holder Analysis by the numbers
- 194 all-time installs (skills.sh)
- Ranked #481 of 1,106 Finance & Trading skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
token-holder-analysis capabilities & compatibility
Free skill; requires paid or free-tier API keys for Helius, SolanaTracker, or Birdeye
- Capabilities
- token holder analysis · wallet profiling · whale tracking · concentration scoring · insider detection
- Use cases
- data analysis · research · trading
- Pricing
- Bring your own API key
What token-holder-analysis says it does
Analyze who holds a token, how concentrated ownership is, and whether insider patterns suggest risk.
This is a critical pre-trade safety check — high concentration means a few wallets can crash the price.
Minimum number of holders needed to control >50% of supply.
npx skills add https://github.com/agiprolabs/claude-trading-skills --skill token-holder-analysisAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 194 |
|---|---|
| repo stars | ★ 257 |
| Last updated | June 24, 2026 |
| Repository | agiprolabs/claude-trading-skills ↗ |
What it does
Run a pre-trade concentration and insider-risk check on a Solana token before entering a position.
Who is it for?
Evaluating whether a Solana token's holder distribution poses a dump or manipulation risk before trading.
Skip if: Analyzing tokens on non-Solana chains or executing trades.
When should I use this skill?
You need to assess how concentrated a Solana token's ownership is before buying.
What you get
A concentration and insider-risk profile of the token's holder base.
- Concentration metrics and an insider/bundler risk assessment for a Solana token
By the numbers
- 4 named holder-data sources
- 4 concentration metrics (top-N, Gini, HHI, Nakamoto)
Files
Token Holder Analysis — Concentration, Distribution & Risk
Analyze who holds a token, how concentrated ownership is, and whether insider patterns suggest risk. This is a critical pre-trade safety check — high concentration means a few wallets can crash the price.
Quick Start
import httpx
import math
# Using Helius DAS API for holder data
HELIUS_KEY = os.getenv("HELIUS_API_KEY", "")
HELIUS = f"https://mainnet.helius-rpc.com/?api-key={HELIUS_KEY}"
# Or using SolanaTracker for holder + risk data
ST_KEY = os.getenv("SOLANATRACKER_API_KEY", "")
ST = "https://data.solanatracker.io"
# Get top holders via RPC
def get_top_holders(mint: str) -> list[dict]:
resp = httpx.post(HELIUS, json={
"jsonrpc": "2.0", "id": 1,
"method": "getTokenLargestAccounts",
"params": [mint],
})
return resp.json()["result"]["value"]
holders = get_top_holders("TOKEN_MINT")Data Sources
| Source | What It Provides | Auth |
|---|---|---|
Solana RPC (getTokenLargestAccounts) | Top 20 holders, supply | RPC key |
Helius DAS (getAsset, token accounts) | Parsed holder data, metadata | API key |
SolanaTracker (/tokens/{t}/holders/top) | Top 100 holders, bundler detection | API key |
Birdeye (/defi/token_security) | Top 10 %, creator balance, freeze/mint auth | API key |
Concentration Metrics
Top-N Holder Percentage
The simplest measure — what % of supply do the top N holders control?
def top_n_percentage(holders: list[dict], supply: int, n: int = 10) -> float:
"""Calculate percentage held by top N holders.
Args:
holders: Sorted list of holders (largest first).
supply: Total token supply.
n: Number of top holders.
Returns:
Percentage (0-100) held by top N.
"""
top_n_amount = sum(int(h.get("amount", 0)) for h in holders[:n])
return top_n_amount / supply * 100 if supply > 0 else 0Risk thresholds:
- Top 10 < 30%: Well distributed
- Top 10 30-50%: Moderate concentration
- Top 10 50-80%: High concentration — significant dump risk
- Top 10 > 80%: Extreme — likely controlled by a few wallets
Gini Coefficient
Measures inequality of token distribution (0 = perfectly equal, 1 = one holder owns everything).
def gini_coefficient(amounts: list[float]) -> float:
"""Calculate Gini coefficient for holder distribution.
Args:
amounts: List of holder amounts (any order).
Returns:
Gini coefficient between 0 and 1.
"""
if not amounts or all(a == 0 for a in amounts):
return 0.0
sorted_amounts = sorted(amounts)
n = len(sorted_amounts)
cumsum = sum((i + 1) * a for i, a in enumerate(sorted_amounts))
total = sum(sorted_amounts)
return (2 * cumsum) / (n * total) - (n + 1) / nInterpretation for crypto tokens:
- Gini < 0.6: Unusual, very well distributed
- Gini 0.6-0.8: Typical for established tokens
- Gini 0.8-0.95: Common for newer tokens
- Gini > 0.95: Extreme concentration, high risk
Herfindahl-Hirschman Index (HHI)
Measures market concentration — sum of squared market shares.
def hhi(amounts: list[float]) -> float:
"""Calculate HHI for holder concentration.
Args:
amounts: List of holder amounts.
Returns:
HHI value (0-10000). Higher = more concentrated.
"""
total = sum(amounts)
if total == 0:
return 0.0
shares = [a / total * 100 for a in amounts]
return sum(s ** 2 for s in shares)Interpretation:
- HHI < 1500: Competitive (unconcentrated)
- HHI 1500-2500: Moderately concentrated
- HHI > 2500: Highly concentrated
Nakamoto Coefficient
Minimum number of holders needed to control >50% of supply.
def nakamoto_coefficient(amounts: list[float]) -> int:
"""Calculate Nakamoto coefficient (holders needed for 51%).
Args:
amounts: Sorted list of holder amounts (largest first).
Returns:
Number of holders needed for majority control.
"""
total = sum(amounts)
if total == 0:
return 0
threshold = total * 0.51
cumulative = 0
for i, amount in enumerate(sorted(amounts, reverse=True)):
cumulative += amount
if cumulative >= threshold:
return i + 1
return len(amounts)Insider Detection Patterns
Bundler Detection
Bundlers use atomic transaction bundles (via Jito) to execute coordinated buys at token launch. Detection signals:
def detect_bundler_patterns(holders: list[dict], first_buyers: list[dict]) -> dict:
"""Identify potential bundler activity.
Args:
holders: Current top holders.
first_buyers: Early buyers from SolanaTracker /first-buyers endpoint.
Returns:
Bundler risk analysis.
"""
early_still_holding = [
b for b in first_buyers
if b.get("holdingAmount", 0) > 0
]
early_holder_pct = sum(
b.get("holdingPercentage", 0) for b in early_still_holding
)
return {
"early_buyers_count": len(first_buyers),
"still_holding_count": len(early_still_holding),
"early_holder_pct": round(early_holder_pct, 2),
"risk": "HIGH" if early_holder_pct > 20 else
"MODERATE" if early_holder_pct > 10 else "LOW",
}Developer Holdings
Creator wallet retention is a risk signal:
def check_developer_risk(token_data: dict) -> dict:
"""Check developer wallet holdings and authority.
Args:
token_data: Token info from SolanaTracker or Birdeye.
Returns:
Developer risk assessment.
"""
risk = token_data.get("risk", {})
flags = []
# Check creator balance (from Birdeye security endpoint)
creator_balance = token_data.get("creatorBalance", 0)
if creator_balance > 10:
flags.append(f"Creator holds {creator_balance:.1f}% of supply")
# Check mint authority
if token_data.get("mintAuthority") or token_data.get("ownerAddress"):
flags.append("Mint authority NOT renounced — supply can increase")
# Check freeze authority
if token_data.get("freezeAuthority") or token_data.get("freezeable"):
flags.append("Freeze authority enabled — tokens can be frozen")
return {
"flags": flags,
"risk_level": "HIGH" if len(flags) >= 2 else
"MODERATE" if len(flags) == 1 else "LOW",
}Sniper Detection
Snipers buy in the first few seconds/blocks after token creation:
def analyze_sniper_concentration(first_buyers: list[dict], total_supply: float) -> dict:
"""Analyze sniper impact on holder distribution.
Args:
first_buyers: First buyers data from SolanaTracker.
total_supply: Total token supply.
Returns:
Sniper concentration analysis.
"""
# Snipers typically buy in first 10 seconds
snipers = first_buyers[:10] # first N buyers are potential snipers
sniper_holding = sum(b.get("holdingAmount", 0) for b in snipers)
sniper_pct = sniper_holding / total_supply * 100 if total_supply > 0 else 0
return {
"sniper_count": len(snipers),
"sniper_holding_pct": round(sniper_pct, 2),
"sniper_still_holding": sum(1 for s in snipers if s.get("holdingAmount", 0) > 0),
"risk": "HIGH" if sniper_pct > 15 else
"MODERATE" if sniper_pct > 5 else "LOW",
}Complete Analysis Pipeline
def full_holder_analysis(mint: str) -> dict:
"""Run complete holder analysis for a token.
Combines RPC, SolanaTracker, and computed metrics.
"""
# 1. Get supply and top holders via RPC
supply_result = rpc_call("getTokenSupply", [mint])
total_supply = int(supply_result["result"]["value"]["amount"])
holders = get_top_holders(mint)
amounts = [int(h["amount"]) for h in holders]
# 2. Compute concentration metrics
metrics = {
"total_supply": total_supply,
"holder_count": len(holders),
"top_1_pct": top_n_percentage(holders, total_supply, 1),
"top_5_pct": top_n_percentage(holders, total_supply, 5),
"top_10_pct": top_n_percentage(holders, total_supply, 10),
"top_20_pct": top_n_percentage(holders, total_supply, 20),
"gini": round(gini_coefficient(amounts), 4),
"hhi": round(hhi(amounts), 1),
"nakamoto": nakamoto_coefficient(amounts),
}
# 3. Risk classification
t10 = metrics["top_10_pct"]
if t10 > 80:
metrics["risk"] = "EXTREME"
elif t10 > 50:
metrics["risk"] = "HIGH"
elif t10 > 30:
metrics["risk"] = "MODERATE"
else:
metrics["risk"] = "LOW"
return metricsRisk Classification Summary
| Metric | Low Risk | Moderate | High | Extreme |
|---|---|---|---|---|
| Top 10 % | <30% | 30-50% | 50-80% | >80% |
| Gini | <0.7 | 0.7-0.85 | 0.85-0.95 | >0.95 |
| HHI | <1500 | 1500-2500 | 2500-5000 | >5000 |
| Nakamoto | >10 | 5-10 | 2-4 | 1 |
| Mint Auth | Renounced | — | Active | Active + high dev % |
| Freeze Auth | Disabled | — | Enabled | Enabled + low liq |
Known Exclusions
When computing holder concentration, exclude these addresses which are programs/pools, not individual holders:
- DEX pool addresses (Raydium, Orca, Meteora pools)
- Token program vaults
- Bridge escrow accounts
- Known burn addresses
KNOWN_PROGRAMS = {
"5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1", # Raydium authority
"GThUX1Atko4tqhN2NaiTazWSeFWMuiUvfFnyJyUghFMJ", # Orca authority
# Add more as needed
}
def filter_real_holders(holders: list[dict]) -> list[dict]:
"""Remove known program/pool accounts from holder list."""
return [h for h in holders if h.get("address") not in KNOWN_PROGRAMS]Files
References
references/concentration_metrics.md— Mathematical formulas and derivations for Gini, HHI, Nakamotoreferences/insider_patterns.md— Bundler, sniper, and developer detection methodologyreferences/data_sources.md— How to fetch holder data from each API source
Scripts
scripts/analyze_holders.py— Full holder analysis: fetch holders, compute metrics, generate risk reportscripts/concentration_scanner.py— Scan multiple tokens for concentration risk
Concentration Metrics — Formulas & Interpretation
Top-N Holder Percentage
The simplest concentration measure. Sums the holdings of the N largest holders as a percentage of total supply.
Formula:
Top_N% = (Σ holdings[i] for i=1..N) / total_supply × 100Thresholds for Solana tokens:
| Top 10 % | Risk Level | Interpretation |
|---|---|---|
| < 20% | Very Low | Exceptionally well distributed |
| 20-30% | Low | Good distribution |
| 30-50% | Moderate | Typical for mid-cap tokens |
| 50-80% | High | Significant dump risk |
| > 80% | Extreme | Likely controlled by a few actors |
Important: Always exclude known program accounts (DEX pools, bridge escrows, burn addresses) from holder counts. Including pool liquidity as a "holder" skews the metric.
---
Gini Coefficient
Measures inequality of distribution. Originally from economics (income inequality), applied here to token holdings.
Formula:
G = (2 × Σ(i × x_i)) / (n × Σ(x_i)) - (n + 1) / n
where x_i are amounts sorted ascending, i is 1-indexed position, n is holder countEquivalent formula (via mean absolute difference):
G = Σ_i Σ_j |x_i - x_j| / (2 × n² × μ)
where μ is the mean holdingRange: 0 to 1
- 0 = perfectly equal (every holder has the same amount)
- 1 = maximally unequal (one holder owns everything)
Crypto-specific thresholds:
| Gini | Interpretation |
|---|---|
| < 0.5 | Rare — very egalitarian distribution |
| 0.5-0.7 | Well distributed for crypto |
| 0.7-0.85 | Typical for established tokens |
| 0.85-0.95 | Common for newer/smaller tokens |
| > 0.95 | Extreme concentration |
Limitations:
- Gini is sensitive to the number of holders included. Computing over top 20 holders (RPC limit) vs. all holders gives different values.
- For meaningful comparison, always compute over the same sample size.
- Gini doesn't distinguish between distributions with the same inequality but different shapes (e.g., one whale vs. several large holders).
---
Herfindahl-Hirschman Index (HHI)
Sum of squared market shares. Originally used for antitrust analysis (market concentration). More sensitive to large holders than Gini.
Formula:
HHI = Σ(s_i²)
where s_i = (holding_i / total_supply) × 100 (percentage share)Range: 0 to 10,000
- Minimum (n equal holders): HHI = 10000/n
- Maximum (one holder): HHI = 10,000
Thresholds (adapted from DOJ antitrust guidelines):
| HHI | Interpretation |
|---|---|
| < 1,500 | Unconcentrated (competitive) |
| 1,500-2,500 | Moderately concentrated |
| 2,500-5,000 | Highly concentrated |
| > 5,000 | Extremely concentrated |
Advantage over Gini: HHI is more sensitive to the presence of dominant holders. A distribution with one 50% holder and many small holders will have a much higher HHI than a distribution with several 10% holders, even if both have similar Gini values.
---
Nakamoto Coefficient
Minimum number of entities needed to control >50% of the system. Named after Satoshi Nakamoto.
Formula:
N = min k such that Σ(holding_i for top k holders) > 0.51 × total_supplyInterpretation:
| Nakamoto | Meaning |
|---|---|
| 1 | Single entity controls majority — maximum centralization |
| 2-3 | Small cartel can control the token |
| 4-10 | Moderate decentralization |
| > 10 | Good decentralization |
Notes:
- Lower bound for Nakamoto is 1. If the largest holder owns >50%, Nakamoto = 1.
- The 51% threshold can be adjusted (e.g., 33% for tokens where 1/3 can block governance).
- Nakamoto coefficient is particularly useful for governance tokens where voting power matters.
---
Shannon Entropy
Information-theoretic measure of distribution randomness. Not commonly used but provides additional perspective.
Formula:
H = -Σ(p_i × log2(p_i))
where p_i = holding_i / total_supplyRange: 0 to log2(n)
- 0 = one holder owns everything
- log2(n) = perfectly equal distribution
Higher entropy = more decentralized.
---
Combining Metrics
No single metric tells the full story. Use them together:
def comprehensive_risk(top_10_pct, gini, hhi, nakamoto):
"""Combine metrics into a single risk score."""
scores = {
"top10": 3 if top_10_pct > 80 else 2 if top_10_pct > 50 else 1 if top_10_pct > 30 else 0,
"gini": 3 if gini > 0.95 else 2 if gini > 0.85 else 1 if gini > 0.7 else 0,
"hhi": 3 if hhi > 5000 else 2 if hhi > 2500 else 1 if hhi > 1500 else 0,
"nakamoto": 3 if nakamoto <= 1 else 2 if nakamoto <= 3 else 1 if nakamoto <= 5 else 0,
}
total = sum(scores.values())
if total >= 9: return "EXTREME"
if total >= 6: return "HIGH"
if total >= 3: return "MODERATE"
return "LOW"Holder Analysis — Data Sources
Source Comparison
| Data Point | Solana RPC | Helius DAS | SolanaTracker | Birdeye |
|---|---|---|---|---|
| Top holders | Top 20 | Via token accts | Top 100 | Top 10 % |
| Total supply | Yes | Yes | Yes | Partial |
| Holder count | No | No | Yes | No |
| Mint authority | Via getAccountInfo | Via getAsset | In risk score | Yes |
| Freeze authority | Via getAccountInfo | Via getAsset | In risk score | Yes |
| Creator balance | No | No | Yes | Yes |
| Bundler detection | No | No | Yes | No |
| Sniper detection | No | No | Yes | No |
| Risk score | No | No | Yes (1-10) | Partial |
| Auth required | RPC key | API key | API key | API key |
| Cost | Free (public) | 50K/day free | €50/mo | Free tier |
Method 1: Solana RPC (Free, Basic)
Best for: Quick top-20 check, total supply, mint/freeze authority status.
# Top 20 holders
result = rpc_call("getTokenLargestAccounts", [mint])
holders = result["value"] # [{address, amount, decimals, uiAmount}]
# Total supply
result = rpc_call("getTokenSupply", [mint])
supply = result["value"] # {amount, decimals, uiAmount}
# Mint/freeze authority (from mint account data)
result = rpc_call("getAccountInfo", [mint, {"encoding": "jsonParsed"}])
mint_info = result["value"]["data"]["parsed"]["info"]
# mint_info["mintAuthority"] — None if renounced
# mint_info["freezeAuthority"] — None if disabledLimitations: Only top 20 holders. No holder count. No insider detection.
Method 2: Helius DAS API
Best for: Token metadata, parsed account data, fungible token details.
# Token metadata via DAS
resp = httpx.post(f"https://mainnet.helius-rpc.com/?api-key={KEY}", json={
"jsonrpc": "2.0", "id": 1,
"method": "getAsset",
"params": {"id": mint},
})
asset = resp.json()["result"]
# asset["authorities"] — mint/freeze authority info
# asset["token_info"]["supply"] — total supply
# asset["content"]["metadata"] — name, symbol, imageLimitations: Doesn't directly provide holder lists. Use with RPC for complete picture.
Method 3: SolanaTracker API (Most Complete)
Best for: Full holder analysis including bundlers, snipers, risk scoring.
HEADERS = {"x-api-key": ST_KEY}
# Top 100 holders
resp = httpx.get(f"https://data.solanatracker.io/tokens/{mint}/holders/top", headers=HEADERS)
holders = resp.json()
# Top 20 (lighter call)
resp = httpx.get(f"https://data.solanatracker.io/tokens/{mint}/holders/top20", headers=HEADERS)
# Bundler detection
resp = httpx.get(f"https://data.solanatracker.io/tokens/{mint}/bundlers", headers=HEADERS)
bundlers = resp.json()
# First buyers with PnL
resp = httpx.get(f"https://data.solanatracker.io/first-buyers/{mint}", headers=HEADERS)
first_buyers = resp.json()
# Full token info with risk score
resp = httpx.get(f"https://data.solanatracker.io/tokens/{mint}", headers=HEADERS)
token = resp.json()
risk_score = token["risk"]["score"] # 1-10Method 4: Birdeye API
Best for: Quick security check (mint/freeze authority, top 10 concentration).
# Token security info
resp = httpx.get("https://public-api.birdeye.so/defi/token_security",
headers={"X-API-KEY": BE_KEY, "x-chain": "solana"},
params={"address": mint})
security = resp.json()["data"]
# security["top10HolderPercent"]
# security["ownerAddress"] — mint authority (None if renounced)
# security["freezeable"]
# security["mutableMetadata"]
# security["creatorBalance"]Recommended Pipeline
For a thorough pre-trade holder analysis:
def full_holder_check(mint: str) -> dict:
"""Complete holder analysis using best available data sources."""
# 1. Quick check via RPC (free, always available)
supply = get_token_supply(mint)
top_20 = get_largest_accounts(mint)
# 2. Risk score from SolanaTracker (if key available)
if ST_KEY:
token_data = st_get(f"/tokens/{mint}")
risk_score = token_data.get("risk", {}).get("score", 0)
bundlers = st_get(f"/tokens/{mint}/bundlers")
# 3. Security check from Birdeye (if key available)
if BE_KEY:
security = birdeye_get("/defi/token_security", {"address": mint})
# 4. Compute concentration metrics from RPC data
amounts = [int(h["amount"]) for h in top_20]
metrics = compute_concentration(amounts, int(supply["amount"]))
return {
"supply": supply,
"top_holders": top_20,
"concentration": metrics,
"risk_score": risk_score,
"bundlers": bundlers,
"security": security,
}Caching Considerations
Holder data changes slowly compared to price data. Reasonable cache TTLs:
| Data | Cache TTL | Reason |
|---|---|---|
| Top holders | 5-15 minutes | Positions change via trades |
| Total supply | 1 hour | Rarely changes (unless mintable) |
| Mint/freeze authority | 1 hour | Almost never changes |
| Risk score | 15-30 minutes | Recalculated periodically |
| Bundler data | 1 hour | Historical, doesn't change |
Insider Detection Patterns
Overview
"Insider" in Solana token trading refers to wallets that have an unfair advantage — they bought before public availability, used transaction bundles for guaranteed early execution, or are connected to the token creator. Detecting these patterns is a critical pre-trade safety check.
Pattern 1: Bundler Detection
What is bundling?
Bundlers use Jito or similar services to submit multiple transactions atomically in a single block. This guarantees execution order, allowing coordinated buys at token launch.
Detection signals
1. Multiple buys in the same block/slot as token creation 2. Multiple wallets buying the exact same amount in the first slots 3. Wallets that share funding sources (funded from the same parent wallet) 4. High holding percentage from first-block buyers
Data sources
- SolanaTracker
/tokens/{token}/bundlers— pre-computed bundler detection - SolanaTracker
/first-buyers/{token}— first buyers with PnL - Helius Enhanced Transactions — parse early transactions for patterns
Risk interpretation
| Bundler Holding % | Risk |
|---|---|
| < 5% | Low — bundlers exited or hold small amounts |
| 5-15% | Moderate — some coordinated buying |
| 15-30% | High — significant coordinated control |
| > 30% | Extreme — likely orchestrated launch |
Pattern 2: Sniper Detection
What is sniping?
Snipers use automated bots to buy tokens within the first few seconds of liquidity being added. They achieve near-zero entry prices on bonding curves or DEX launches.
Detection signals
1. Buy transactions within first 1-10 seconds of pool creation 2. Wallet has many "first buy" patterns across different tokens 3. Bot-like transaction patterns (fixed amounts, consistent timing) 4. Currently holding large % of supply from initial snipe
Analysis approach
def is_sniper_wallet(wallet_trades: list[dict], token_created_at: int) -> bool:
"""Check if wallet sniped the token launch."""
first_buy = next(
(t for t in wallet_trades if t["type"] == "buy"),
None
)
if first_buy is None:
return False
# Bought within 10 seconds of creation
return first_buy["timestamp"] - token_created_at < 10Pattern 3: Developer / Creator Analysis
Risk factors
1. Creator still holds tokens — can dump at any time 2. Mint authority not renounced — creator can mint more tokens 3. Freeze authority active — creator can freeze token accounts 4. Creator wallet funded by known rug accounts 5. Creator deployed multiple tokens that failed (serial deployer)
Data sources
- Birdeye
/defi/token_security— mint authority, freeze authority, creator balance - SolanaTracker
/tokens/{token}— risk score includes these checks - SolanaTracker
/tokens/deployer/{addr}— all tokens by the same deployer - Direct RPC — check mint account for authority fields
Serial deployer detection
def check_deployer_history(deployer: str, api_key: str) -> dict:
"""Check deployer's track record."""
resp = httpx.get(
f"https://data.solanatracker.io/tokens/deployer/{deployer}",
headers={"x-api-key": api_key},
)
tokens = resp.json()
rugged = sum(1 for t in tokens if t.get("risk", {}).get("rugged"))
low_risk = sum(1 for t in tokens if t.get("risk", {}).get("score", 0) >= 7)
return {
"total_deployments": len(tokens),
"rugged_count": rugged,
"low_risk_count": low_risk,
"serial_rug": rugged > 3,
}Pattern 4: Connected Wallet Clusters
What to look for
- Wallets funded from the same source around token launch
- Wallets that only ever trade the same tokens
- Wallets that buy and sell in coordinated timing patterns
Analysis approach
This requires transaction history analysis: 1. For each top holder, trace their funding source (SOL origin) 2. Cluster wallets that share the same funding source 3. Flag clusters that collectively hold >10% of supply
def trace_funding_source(wallet: str, depth: int = 2) -> list[str]:
"""Trace SOL funding sources for a wallet.
Use Helius Enhanced Transactions to find incoming SOL transfers.
Returns list of funding source addresses.
"""
sources = []
resp = httpx.get(
f"https://api.helius.xyz/v0/addresses/{wallet}/transactions",
params={"api-key": HELIUS_KEY, "type": "TRANSFER"},
)
for tx in resp.json()[:20]:
for transfer in tx.get("nativeTransfers", []):
if transfer.get("toUserAccount") == wallet:
sources.append(transfer["fromUserAccount"])
return sourcesPattern 5: Wash Trading Detection
Signals
- Same wallet appearing as both buyer and seller (self-trades)
- High volume but low unique wallet count
- Volume spikes with no corresponding price movement
- Buy/sell ratio extremely close to 1.0 across many time periods
Quick check
def wash_trading_check(pair_data: dict) -> dict:
"""Quick wash trading heuristic from DexScreener data."""
txns = pair_data.get("txns", {}).get("h24", {})
buys = txns.get("buys", 0)
sells = txns.get("sells", 0)
total = buys + sells
volume = pair_data.get("volume", {}).get("h24", 0)
liquidity = pair_data.get("liquidity", {}).get("usd", 0)
flags = []
if total > 0 and abs(buys - sells) / total < 0.05:
flags.append("Buy/sell count suspiciously balanced")
if liquidity > 0 and volume / liquidity > 50:
flags.append(f"Volume/liquidity ratio extremely high ({volume/liquidity:.0f}x)")
return {"flags": flags, "suspicious": len(flags) > 0}Combining Insider Signals
Weight multiple signals for a composite insider risk score:
| Signal | Weight | Max Score |
|---|---|---|
| Bundler holding > 15% | High | 3 |
| Sniper holding > 10% | High | 3 |
| Creator holds > 10% | Medium | 2 |
| Mint authority active | High | 3 |
| Freeze authority active | Medium | 2 |
| Serial deployer (3+ rugs) | Critical | 4 |
| Connected wallet cluster | Medium | 2 |
| Wash trading signals | Low | 1 |
Score 0-3: Low insider risk Score 4-7: Moderate — proceed with caution Score 8-12: High — small positions only Score 13+: Extreme — avoid
#!/usr/bin/env python3
"""Full holder analysis for a Solana token.
Fetches top holders via RPC, computes concentration metrics (Gini, HHI,
Nakamoto coefficient), checks mint/freeze authority, and produces a
comprehensive risk report.
Usage:
python scripts/analyze_holders.py
TOKEN_ADDRESS="TokenMint..." python scripts/analyze_holders.py
Dependencies:
uv pip install httpx
Environment Variables:
SOLANA_RPC_URL: Solana RPC endpoint (default: public mainnet)
TOKEN_ADDRESS: Token mint address to analyze
SOLANATRACKER_API_KEY: Optional — enables bundler/sniper detection
"""
import os
import sys
import time
from typing import Any, Optional
import httpx
# ── Configuration ───────────────────────────────────────────────────
RPC_URL = os.getenv("SOLANA_RPC_URL", "https://api.mainnet-beta.solana.com")
ST_KEY = os.getenv("SOLANATRACKER_API_KEY", "")
TOKEN_ADDRESS = os.getenv(
"TOKEN_ADDRESS",
"DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263", # BONK
)
# ── RPC Helper ──────────────────────────────────────────────────────
def rpc_call(method: str, params: Optional[list] = None) -> dict[str, Any]:
"""Make a JSON-RPC call to Solana with retry."""
payload = {"jsonrpc": "2.0", "id": 1, "method": method, "params": params or []}
for attempt in range(3):
try:
resp = httpx.post(RPC_URL, json=payload, timeout=30.0)
if resp.status_code == 429:
time.sleep(2.0 * (attempt + 1))
continue
resp.raise_for_status()
data = resp.json()
if "error" in data:
raise RuntimeError(f"RPC error: {data['error'].get('message')}")
return data.get("result", {})
except httpx.TimeoutException:
if attempt < 2:
time.sleep(2.0)
continue
raise
raise RuntimeError(f"RPC {method} failed after retries")
def st_get(endpoint: str) -> Any:
"""Make a GET request to SolanaTracker API."""
if not ST_KEY:
return None
try:
resp = httpx.get(
f"https://data.solanatracker.io{endpoint}",
headers={"x-api-key": ST_KEY},
timeout=30.0,
)
if resp.status_code == 200:
return resp.json()
except Exception:
pass
return None
# ── Data Fetching ───────────────────────────────────────────────────
def get_supply_and_authority(mint: str) -> dict:
"""Get token supply and authority info.
Args:
mint: Token mint address.
Returns:
Dict with supply, decimals, mint_authority, freeze_authority.
"""
supply_result = rpc_call("getTokenSupply", [mint])
supply = supply_result.get("value", {})
# Get mint account for authority info
acct_result = rpc_call("getAccountInfo", [mint, {"encoding": "jsonParsed"}])
acct_data = acct_result.get("value", {})
mint_authority = None
freeze_authority = None
if acct_data:
parsed = acct_data.get("data", {}).get("parsed", {}).get("info", {})
mint_authority = parsed.get("mintAuthority")
freeze_authority = parsed.get("freezeAuthority")
return {
"total_amount": int(supply.get("amount", "0")),
"decimals": supply.get("decimals", 0),
"ui_amount": supply.get("uiAmount", 0),
"mint_authority": mint_authority,
"freeze_authority": freeze_authority,
}
def get_top_holders(mint: str) -> list[dict]:
"""Get top 20 holders via RPC."""
result = rpc_call("getTokenLargestAccounts", [mint])
return result.get("value", [])
# ── Concentration Metrics ───────────────────────────────────────────
def top_n_pct(amounts: list[int], total: int, n: int) -> float:
"""Percentage held by top N holders."""
return sum(amounts[:n]) / total * 100 if total > 0 else 0
def gini_coefficient(amounts: list[int]) -> float:
"""Gini coefficient (0=equal, 1=concentrated)."""
if not amounts or all(a == 0 for a in amounts):
return 0.0
sorted_a = sorted(amounts)
n = len(sorted_a)
total = sum(sorted_a)
if total == 0:
return 0.0
cumsum = sum((i + 1) * a for i, a in enumerate(sorted_a))
return (2 * cumsum) / (n * total) - (n + 1) / n
def hhi(amounts: list[int], total: int) -> float:
"""Herfindahl-Hirschman Index (0-10000)."""
if total == 0:
return 0.0
shares = [a / total * 100 for a in amounts]
return sum(s ** 2 for s in shares)
def nakamoto_coefficient(amounts: list[int]) -> int:
"""Minimum holders for >50% control."""
total = sum(amounts)
if total == 0:
return 0
threshold = total * 0.51
cumulative = 0
for i, a in enumerate(amounts):
cumulative += a
if cumulative >= threshold:
return i + 1
return len(amounts)
# ── Analysis ────────────────────────────────────────────────────────
def analyze_authority(supply_info: dict) -> list[str]:
"""Analyze mint and freeze authority status.
Args:
supply_info: From get_supply_and_authority().
Returns:
List of flag strings.
"""
flags = []
mint_auth = supply_info.get("mint_authority")
if mint_auth:
flags.append(f"[!!] MINT AUTHORITY ACTIVE: {mint_auth[:16]}...")
flags.append(" Supply can be increased — dilution risk")
else:
flags.append("[ok] Mint authority renounced")
freeze_auth = supply_info.get("freeze_authority")
if freeze_auth:
flags.append(f"[!!] FREEZE AUTHORITY ACTIVE: {freeze_auth[:16]}...")
flags.append(" Token accounts can be frozen")
else:
flags.append("[ok] Freeze authority disabled")
return flags
def analyze_bundlers(bundlers: list[dict]) -> list[str]:
"""Analyze bundler data from SolanaTracker.
Args:
bundlers: Bundler response list.
Returns:
List of flag strings.
"""
if not bundlers:
return ["[ok] No bundler activity detected"]
total_pct = sum(b.get("holdingPercentage", 0) for b in bundlers)
flags = [f"[i] {len(bundlers)} bundler wallets detected"]
flags.append(f" Combined holding: {total_pct:.1f}%")
if total_pct > 20:
flags.append("[!!] High bundler concentration — coordinated launch buying")
elif total_pct > 10:
flags.append("[!] Moderate bundler concentration")
return flags
# ── Display ─────────────────────────────────────────────────────────
def print_report(
mint: str,
supply_info: dict,
holders: list[dict],
amounts: list[int],
authority_flags: list[str],
bundler_flags: list[str],
risk_score: Optional[int],
) -> None:
"""Print comprehensive holder analysis report."""
total = supply_info["total_amount"]
decimals = supply_info["decimals"]
# Metrics
t1 = top_n_pct(amounts, total, 1)
t5 = top_n_pct(amounts, total, 5)
t10 = top_n_pct(amounts, total, 10)
t20 = top_n_pct(amounts, total, 20)
gini = gini_coefficient(amounts)
hhi_val = hhi(amounts, total)
naka = nakamoto_coefficient(amounts)
print(f"\n{'='*65}")
print(f"TOKEN HOLDER ANALYSIS")
print(f"{'='*65}")
print(f" Mint: {mint}")
print(f" Supply: {supply_info['ui_amount']:,.0f} ({decimals} decimals)")
if risk_score is not None:
print(f" Risk Score: {risk_score}/10 (SolanaTracker)")
# Authority
print(f"\n--- Authority Status ---")
for flag in authority_flags:
print(f" {flag}")
# Top holders
print(f"\n--- Top 20 Holders ---")
print(f" {'#':>3} {'Account':<20} {'Amount':>16} {'% Supply':>10}")
print(f" {'─'*3} {'─'*20} {'─'*16} {'─'*10}")
for i, h in enumerate(holders[:20], 1):
addr = h.get("address", "?")
short = addr[:8] + "..." + addr[-4:]
amt = int(h.get("amount", 0))
ui = h.get("uiAmount", 0)
pct = amt / total * 100 if total > 0 else 0
amt_str = f"{ui:,.2f}" if ui < 1e9 else f"{ui:,.0f}"
print(f" {i:>3} {short:<20} {amt_str:>16} {pct:>9.2f}%")
# Concentration metrics
print(f"\n--- Concentration Metrics ---")
print(f" Top 1: {t1:.2f}%")
print(f" Top 5: {t5:.2f}%")
print(f" Top 10: {t10:.2f}%")
print(f" Top 20: {t20:.2f}%")
print(f" Gini: {gini:.4f}")
print(f" HHI: {hhi_val:.1f}")
print(f" Nakamoto: {naka} holders for >50%")
# Bundlers
if bundler_flags:
print(f"\n--- Bundler Analysis ---")
for flag in bundler_flags:
print(f" {flag}")
# Overall risk
print(f"\n--- Overall Assessment ---")
# Concentration risk
if t10 > 80 or hhi_val > 5000:
conc_risk = "EXTREME"
elif t10 > 50 or hhi_val > 2500:
conc_risk = "HIGH"
elif t10 > 30 or hhi_val > 1500:
conc_risk = "MODERATE"
else:
conc_risk = "LOW"
print(f" Concentration: {conc_risk}")
# Authority risk
has_mint = supply_info.get("mint_authority") is not None
has_freeze = supply_info.get("freeze_authority") is not None
if has_mint and has_freeze:
auth_risk = "HIGH"
elif has_mint or has_freeze:
auth_risk = "MODERATE"
else:
auth_risk = "LOW"
print(f" Authority: {auth_risk}")
# Combined
risks = {"EXTREME": 4, "HIGH": 3, "MODERATE": 2, "LOW": 1}
combined = max(risks.get(conc_risk, 0), risks.get(auth_risk, 0))
overall = {4: "EXTREME", 3: "HIGH", 2: "MODERATE", 1: "LOW"}.get(combined, "UNKNOWN")
print(f" Overall: {overall}")
if overall == "EXTREME":
print("\n [!!] Token has critical risk factors. Avoid or use minimal size.")
elif overall == "HIGH":
print("\n [!] Significant risk factors present. Use small positions and tight stops.")
print()
# ── Main ────────────────────────────────────────────────────────────
def main() -> None:
"""Run holder analysis."""
print(f"Analyzing: {TOKEN_ADDRESS}")
print("Fetching supply and authority...")
supply_info = get_supply_and_authority(TOKEN_ADDRESS)
total = supply_info["total_amount"]
if total == 0:
print("Could not fetch token supply. Check the mint address.")
sys.exit(1)
time.sleep(0.5)
print("Fetching top holders...")
holders = get_top_holders(TOKEN_ADDRESS)
amounts = sorted([int(h.get("amount", 0)) for h in holders], reverse=True)
authority_flags = analyze_authority(supply_info)
# Optional: SolanaTracker enrichment
risk_score = None
bundler_flags = []
if ST_KEY:
print("Fetching SolanaTracker data...")
token_data = st_get(f"/tokens/{TOKEN_ADDRESS}")
if token_data:
risk_score = token_data.get("risk", {}).get("score")
time.sleep(0.5)
bundlers = st_get(f"/tokens/{TOKEN_ADDRESS}/bundlers")
if bundlers and isinstance(bundlers, list):
bundler_flags = analyze_bundlers(bundlers)
else:
print(" (Set SOLANATRACKER_API_KEY for bundler/sniper detection)")
print_report(
TOKEN_ADDRESS, supply_info, holders, amounts,
authority_flags, bundler_flags, risk_score,
)
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""Scan multiple tokens for holder concentration risk.
Takes a list of token mint addresses and computes concentration metrics
for each, producing a comparative summary table. Useful for screening
a watchlist of tokens before trading.
Usage:
python scripts/concentration_scanner.py
TOKENS="mint1,mint2,mint3" python scripts/concentration_scanner.py
Dependencies:
uv pip install httpx
Environment Variables:
SOLANA_RPC_URL: Solana RPC endpoint (default: public mainnet)
TOKENS: Comma-separated token mint addresses to scan
"""
import os
import sys
import time
from typing import Any, Optional
import httpx
# ── Configuration ───────────────────────────────────────────────────
RPC_URL = os.getenv("SOLANA_RPC_URL", "https://api.mainnet-beta.solana.com")
# Default: scan some well-known tokens
DEFAULT_TOKENS = ",".join([
"DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263", # BONK
"JUPyiwrYJFskUPiHa7hkeR8VUtAeFoSYbKedZNsDvCN", # JUP
"EKpQGSJtjMFqKZ9KQanSqYXRcF8fBopzLHYxdM65zcjm", # WIF
])
TOKENS = os.getenv("TOKENS", DEFAULT_TOKENS).split(",")
TOKENS = [t.strip() for t in TOKENS if t.strip()]
# ── RPC Helper ──────────────────────────────────────────────────────
def rpc_call(method: str, params: Optional[list] = None) -> dict[str, Any]:
"""Make a JSON-RPC call with retry."""
payload = {"jsonrpc": "2.0", "id": 1, "method": method, "params": params or []}
for attempt in range(3):
try:
resp = httpx.post(RPC_URL, json=payload, timeout=30.0)
if resp.status_code == 429:
time.sleep(3.0 * (attempt + 1))
continue
resp.raise_for_status()
data = resp.json()
if "error" in data:
return {}
return data.get("result", {})
except (httpx.TimeoutException, httpx.HTTPError):
if attempt < 2:
time.sleep(2.0)
continue
return {}
# ── Metrics ─────────────────────────────────────────────────────────
def gini(amounts: list[int]) -> float:
"""Gini coefficient."""
if not amounts or all(a == 0 for a in amounts):
return 0.0
s = sorted(amounts)
n = len(s)
total = sum(s)
if total == 0:
return 0.0
c = sum((i + 1) * a for i, a in enumerate(s))
return (2 * c) / (n * total) - (n + 1) / n
def hhi(amounts: list[int], total: int) -> float:
"""HHI index."""
if total == 0:
return 0.0
shares = [a / total * 100 for a in amounts]
return sum(s ** 2 for s in shares)
def nakamoto(amounts: list[int]) -> int:
"""Nakamoto coefficient."""
total = sum(amounts)
if total == 0:
return 0
threshold = total * 0.51
cumulative = 0
for i, a in enumerate(amounts):
cumulative += a
if cumulative >= threshold:
return i + 1
return len(amounts)
def risk_level(top10_pct: float, hhi_val: float) -> str:
"""Classify risk level."""
if top10_pct > 80 or hhi_val > 5000:
return "EXTREME"
if top10_pct > 50 or hhi_val > 2500:
return "HIGH"
if top10_pct > 30 or hhi_val > 1500:
return "MODERATE"
return "LOW"
# ── Scanner ─────────────────────────────────────────────────────────
def scan_token(mint: str) -> Optional[dict]:
"""Scan a single token for concentration metrics.
Args:
mint: Token mint address.
Returns:
Dict with metrics, or None on failure.
"""
# Get supply
supply_result = rpc_call("getTokenSupply", [mint])
supply_val = supply_result.get("value", {})
total = int(supply_val.get("amount", "0"))
if total == 0:
return None
time.sleep(0.3)
# Get holders
holder_result = rpc_call("getTokenLargestAccounts", [mint])
holders = holder_result.get("value", [])
if not holders:
return None
amounts = sorted([int(h.get("amount", 0)) for h in holders], reverse=True)
top1 = sum(amounts[:1]) / total * 100
top5 = sum(amounts[:5]) / total * 100
top10 = sum(amounts[:10]) / total * 100
top20 = sum(amounts[:20]) / total * 100
hhi_val = hhi(amounts, total)
# Get mint/freeze authority
acct_result = rpc_call("getAccountInfo", [mint, {"encoding": "jsonParsed"}])
acct_data = acct_result.get("value", {})
mint_auth = False
freeze_auth = False
if acct_data:
parsed = acct_data.get("data", {}).get("parsed", {}).get("info", {})
mint_auth = parsed.get("mintAuthority") is not None
freeze_auth = parsed.get("freezeAuthority") is not None
return {
"mint": mint,
"supply": supply_val.get("uiAmount", 0),
"decimals": supply_val.get("decimals", 0),
"holders_sampled": len(holders),
"top1_pct": round(top1, 2),
"top5_pct": round(top5, 2),
"top10_pct": round(top10, 2),
"top20_pct": round(top20, 2),
"gini": round(gini(amounts), 4),
"hhi": round(hhi_val, 1),
"nakamoto": nakamoto(amounts),
"mint_authority": mint_auth,
"freeze_authority": freeze_auth,
"risk": risk_level(top10, hhi_val),
}
# ── Display ─────────────────────────────────────────────────────────
def print_results(results: list[dict]) -> None:
"""Print comparative concentration table.
Args:
results: List of scan results.
"""
print(f"\n{'='*90}")
print(f"CONCENTRATION SCANNER — {len(results)} tokens analyzed")
print(f"{'='*90}")
# Summary table
print(f"\n {'Token':<16} {'Top1%':>6} {'Top5%':>6} {'Top10%':>7} {'Gini':>6} "
f"{'HHI':>7} {'Naka':>5} {'Mint':>5} {'Frz':>5} {'Risk':<8}")
print(f" {'─'*16} {'─'*6} {'─'*6} {'─'*7} {'─'*6} "
f"{'─'*7} {'─'*5} {'─'*5} {'─'*5} {'─'*8}")
for r in results:
mint_short = r["mint"][:6] + "..." + r["mint"][-4:]
mint_flag = "YES" if r["mint_authority"] else "no"
frz_flag = "YES" if r["freeze_authority"] else "no"
print(f" {mint_short:<16} {r['top1_pct']:>5.1f}% {r['top5_pct']:>5.1f}% "
f"{r['top10_pct']:>6.1f}% {r['gini']:>6.4f} {r['hhi']:>7.0f} "
f"{r['nakamoto']:>5} {mint_flag:>5} {frz_flag:>5} {r['risk']:<8}")
# Risk summary
risk_counts = {}
for r in results:
risk_counts[r["risk"]] = risk_counts.get(r["risk"], 0) + 1
print(f"\n--- Risk Distribution ---")
for level in ["EXTREME", "HIGH", "MODERATE", "LOW"]:
count = risk_counts.get(level, 0)
if count > 0:
bar = "█" * (count * 3)
print(f" {level:<10} {count:>3} {bar}")
# Flag tokens with authority risks
auth_risks = [r for r in results if r["mint_authority"] or r["freeze_authority"]]
if auth_risks:
print(f"\n--- Authority Warnings ---")
for r in auth_risks:
flags = []
if r["mint_authority"]:
flags.append("MINTABLE")
if r["freeze_authority"]:
flags.append("FREEZEABLE")
mint_short = r["mint"][:12] + "..."
print(f" [!] {mint_short}: {', '.join(flags)}")
print()
# ── Main ────────────────────────────────────────────────────────────
def main() -> None:
"""Run concentration scanner."""
print(f"Scanning {len(TOKENS)} tokens...")
print(f"RPC: {RPC_URL[:40]}...")
results = []
for i, mint in enumerate(TOKENS, 1):
print(f" [{i}/{len(TOKENS)}] {mint[:16]}...", end=" ")
result = scan_token(mint)
if result:
print(f"Top10: {result['top10_pct']:.1f}%, Risk: {result['risk']}")
results.append(result)
else:
print("FAILED")
time.sleep(0.5) # Rate limit courtesy
if not results:
print("No tokens could be analyzed.")
sys.exit(1)
# Sort by risk (worst first)
risk_order = {"EXTREME": 0, "HIGH": 1, "MODERATE": 2, "LOW": 3}
results.sort(key=lambda r: (risk_order.get(r["risk"], 9), -r["top10_pct"]))
print_results(results)
if __name__ == "__main__":
main()
Related skills
FAQ
Which chain does this skill support?
It analyzes Solana tokens, using Solana RPC, Helius DAS, SolanaTracker, and Birdeye as data sources.
What concentration metrics does it compute?
Top-N holder percentage, Gini coefficient, Herfindahl-Hirschman Index (HHI), and the Nakamoto coefficient.