
Whale Tracking
- 233 installs
- 257 repo stars
- Updated June 24, 2026
- agiprolabs/claude-trading-skills
whale-tracking is a Claude Code skill that monitors large Solana wallets to detect accumulation, distribution, and smart-money signals before they appear in price.
About
A Claude Code skill that tracks large Solana wallets to detect accumulation, distribution, and smart-money movements before they show up in price action. It classifies wallets by absolute and relative thresholds, scores accumulation versus distribution with on-chain heuristics, and manages a whale watchlist. Developers use it as an on-chain alpha and risk-assessment source.
- Monitors large Solana wallets for accumulation and distribution
- Scores accumulation vs distribution with on-chain heuristics
- Generates smart-money signals before they show up in price
Whale Tracking by the numbers
- 233 all-time installs (skills.sh)
- Ranked #399 of 1,106 Finance & Trading skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
whale-tracking capabilities & compatibility
Free skill; needs a Solana data source. Helius free tier cited at 100K credits/mo, Birdeye at 100 req/min
- Capabilities
- whale tracking · wallet profiling · token holder analysis · smart money signals · accumulation detection
- Use cases
- data analysis · research · trading
- Pricing
- Bring your own API key
What whale-tracking says it does
Whale tracking monitors the on-chain behavior of large wallets to detect accumulation, distribution, and smart money movements before they become visible in price action.
A single large wallet selling 5% of a token's supply can crash the price 30-50% on thin Solana DEX liquidity.
Accumulation is when a whale builds a position over time, often trying to minimize price impact.
npx skills add https://github.com/agiprolabs/claude-trading-skills --skill whale-trackingAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 233 |
|---|---|
| repo stars | ★ 257 |
| Last updated | June 24, 2026 |
| Repository | agiprolabs/claude-trading-skills ↗ |
What it does
Monitor large Solana wallets and detect accumulation or distribution before it fully affects a token's price.
Who is it for?
Catching large-holder accumulation or distribution on Solana tokens as an early trading signal.
Skip if: Non-Solana chains or executing trades automatically.
When should I use this skill?
You want an early warning that a whale is buying or dumping a token.
What you get
Scored accumulation and distribution signals for large wallets on a token.
- Scored accumulation/distribution signals and a whale watchlist for a token
By the numbers
- 4 whale size tiers (Retail to Mega-whale)
- Accumulation and distribution scoring thresholds at score >= 4
Files
Whale Tracking for Solana Tokens
Whale tracking monitors the on-chain behavior of large wallets to detect accumulation, distribution, and smart money movements before they become visible in price action. On Solana, where token ownership is highly concentrated and whale transactions can move markets instantly, tracking large holders is one of the highest-signal alpha sources available.
Why Whale Tracking Matters
A single large wallet selling 5% of a token's supply can crash the price 30-50% on thin Solana DEX liquidity. Conversely, a known profitable wallet accumulating a new token often precedes major price runs. Whale tracking converts on-chain transparency into actionable intelligence.
Key use cases:
- Early warning: Detect large holder sells before the price impact fully propagates
- Smart money following: Identify wallets with strong track records and monitor their new positions
- Accumulation detection: Spot gradual buying by whales who split orders to avoid detection
- Distribution detection: Catch insiders or early investors offloading positions
- Risk assessment: Evaluate token concentration risk before entering a position
What Constitutes a Whale
Whale classification depends on context. A wallet holding $50K of a $1M market cap token is a whale; the same $50K in SOL is not. Use relative and absolute thresholds:
Absolute Thresholds
| Category | Trade Size | Portfolio Size | Typical Behavior |
|---|---|---|---|
| Retail | < 10 SOL | < 100 SOL | Reactive, follows trends |
| Mid-size | 10-100 SOL | 100-1,000 SOL | Mixed strategies |
| Whale | 100-1,000 SOL | 1,000-10,000 SOL | Informed, moves markets |
| Mega-whale | > 1,000 SOL | > 10,000 SOL | Market makers, funds, insiders |
Relative Thresholds (Per Token)
| Metric | Threshold | Significance |
|---|---|---|
| % of supply held | > 2% | Significant holder |
| % of daily volume | > 5% single trade | Market-moving transaction |
| Top N holders | Top 20 | Core holder group |
| Concentration ratio | Top 10 hold > 50% | High concentration risk |
Use both absolute and relative metrics. A 50 SOL trade in a $200K market cap token is whale-level; the same trade in a $50M token is retail.
Accumulation vs Distribution Patterns
Accumulation Signals
Accumulation is when a whale builds a position over time, often trying to minimize price impact.
DCA pattern (Dollar-Cost Averaging):
- Multiple buys of similar size over hours or days
- Buys at regular intervals (e.g., every 30 minutes)
- Position grows steadily without large single transactions
Dip buying:
- Buys concentrated during price dips
- Larger-than-usual purchases when price drops > 10%
- Position increases during periods of general selling
Multi-wallet accumulation:
- New wallets funded from the same source
- Each wallet buys small amounts of the same token
- Positions later consolidated into a primary wallet
Detection heuristics:
accumulation_score = 0
if buy_count > sell_count * 2: accumulation_score += 2
if avg_buy_size > avg_sell_size: accumulation_score += 1
if position_change_7d > 0: accumulation_score += 1
if buys_during_dips > buys_on_pump: accumulation_score += 2
if dca_pattern_detected: accumulation_score += 2
# Score >= 4 = likely accumulatingDistribution Signals
Distribution is when a whale reduces or exits a position, often gradually to avoid crashing the price.
Gradual selling:
- Multiple sells over days, each < 5% of position
- Sells increase in frequency over time
- Position shrinks steadily
Transfer to exchange:
- Tokens transferred to known exchange deposit addresses
- Large transfers to Binance, OKX, Bybit hot wallets
- Often precedes selling by hours or days
Rapid exit:
- Single large market sell (> 20% of position)
- Often triggers cascading liquidations
- Visible as large red candles with high volume
Detection heuristics:
distribution_score = 0
if sell_count > buy_count * 2: distribution_score += 2
if position_change_7d < 0: distribution_score += 1
if transfers_to_exchanges > 0: distribution_score += 3
if sell_frequency_increasing: distribution_score += 2
if position_pct_remaining < 50: distribution_score += 1
# Score >= 4 = likely distributingWhale Watchlist Management
Maintain a watchlist of wallets worth tracking. Sources for discovering whale wallets:
Discovery Methods
1. Top holders per token: Query getTokenLargestAccounts for any token of interest 2. Large transaction monitoring: Watch for trades > 100 SOL on key tokens 3. Profitable trader rankings: Use SolanaTracker or Birdeye top trader endpoints 4. Known fund wallets: Public wallet addresses of crypto funds and DAOs 5. Cross-referencing: Wallets that appear in top holders of multiple successful tokens
Watchlist Structure
Each watchlist entry should track:
whale_entry = {
"address": "WhaLe...",
"label": "Smart money #47", # Human-readable label
"discovered": "2026-01-15", # When added to watchlist
"discovery_reason": "top_trader", # How they were found
"win_rate": 0.72, # Historical trade win rate
"avg_pnl": 3.4, # Average PnL multiplier
"tokens_tracked": 12, # Number of tokens held
"last_active": "2026-03-09", # Last on-chain activity
"tags": ["dex_trader", "sniper"], # Classification tags
}Wallet Classification Tags
| Tag | Description |
|---|---|
sniper | Buys tokens within minutes of launch |
dex_trader | Primarily trades on DEXes |
accumulator | Builds positions gradually |
flipper | Short hold times, quick profit-taking |
insider | Connected to token teams (funded by deployer) |
fund | Institutional or fund wallet |
market_maker | Provides liquidity, two-sided flow |
Cross-Token Analysis
Whale tracking becomes most powerful when you analyze whale behavior across multiple tokens simultaneously.
Convergence Signals
When multiple tracked whales buy the same token independently, the signal strength compounds:
| Whale Count | Signal | Confidence |
|---|---|---|
| 1 whale buying | Informational | Low |
| 2-3 whales buying | Notable | Medium |
| 4+ whales buying | Strong convergence | High |
| Whales + volume spike | Confirmed momentum | Very high |
Cross-Token Flow Analysis
Track where whale capital flows:
Token A (selling) → SOL → Token B (buying)
If multiple whales rotate from A to B:
- Bearish for Token A (smart money exiting)
- Bullish for Token B (smart money entering)Data Sources
Whale tracking on Solana uses multiple data sources. See references/data_sources.md for complete details.
| Source | Use Case | Auth Required |
|---|---|---|
| Helius | Transaction history, webhooks | Yes (free tier available) |
| SolanaTracker | Top traders, wallet PnL | Yes |
| Birdeye | Token holder rankings | Yes (free tier available) |
| Solana RPC | Token accounts, signatures | No (public endpoints) |
Helius Webhooks for Real-Time Tracking
Helius webhooks enable real-time whale alerts without polling:
# Webhook configuration for whale wallet monitoring
webhook_config = {
"webhookURL": "https://your-server.com/whale-alerts",
"transactionTypes": ["SWAP", "TRANSFER"],
"accountAddresses": [
"WhaLe1...", # Tracked whale wallets
"WhaLe2...",
],
"webhookType": "enhanced", # Parsed transaction data
}Signal Generation
Convert whale activity into trading signals. Whale signals are one input to a broader decision framework, not standalone trading triggers.
Signal Types
Whale Buy Signal:
- Whale buys > 100 SOL of a token
- Confidence increases with: whale track record, buy size, number of whales buying
- Weaken signal if: token is very new (< 24h), whale is known flipper, high concentration risk
Whale Sell Signal:
- Whale sells > 25% of position or > 100 SOL
- Confidence increases with: multiple whales selling, transfers to exchanges, increasing sell frequency
- Weaken signal if: whale is taking partial profit after large gain, whale is known rebalancer
Accumulation Signal:
- Whale accumulation score >= 4 (see scoring above)
- Strongest when: multiple whales accumulating same token, accumulation during price downtrend
- Time horizon: days to weeks (accumulation is a slow signal)
Distribution Signal:
- Whale distribution score >= 4
- Strongest when: team/insider wallets distributing, post-unlock distribution, increasing sell pace
- Time horizon: hours to days (distribution can accelerate quickly)
Signal Scoring
def compute_whale_signal(whale_activity: dict) -> dict:
"""Combine whale activity indicators into a composite signal."""
score = 0.0
# Trade direction: +1 for buy, -1 for sell
direction = 1 if whale_activity["is_buy"] else -1
# Size factor: larger trades = stronger signal
size_sol = whale_activity["size_sol"]
if size_sol > 500:
score += direction * 3
elif size_sol > 100:
score += direction * 2
else:
score += direction * 1
# Whale quality: better track record = stronger signal
win_rate = whale_activity["whale_win_rate"]
score *= (0.5 + win_rate) # 0.5x to 1.5x multiplier
# Convergence: multiple whales = stronger signal
whale_count = whale_activity["concurrent_whale_count"]
score *= (1 + 0.3 * (whale_count - 1))
return {
"score": round(score, 2),
"direction": "bullish" if score > 0 else "bearish",
"confidence": "high" if abs(score) > 5 else "medium" if abs(score) > 2 else "low",
}Integration with Other Skills
Whale tracking works best when combined with other analysis:
| Skill | Integration |
|---|---|
token-holder-analysis | Identify concentration risk, insider wallets |
liquidity-analysis | Estimate price impact of whale trades |
solana-onchain | Wallet profiling, transaction history |
slippage-modeling | Predict slippage for whale-sized trades |
risk-management | Factor whale concentration into position sizing |
helius-api | Transaction fetching, webhook setup |
Files
References
references/detection_methods.md— Accumulation/distribution detection algorithms, whale classification, alert thresholds and scoring systemsreferences/data_sources.md— Complete guide to Helius, SolanaTracker, Birdeye, and on-chain data sources for whale tracking
Scripts
scripts/track_whales.py— Fetches top holders for a token, classifies whale activity as accumulating/distributing/holding, prints a whale report. Run with--demofor synthetic data mode.scripts/whale_alerts.py— Monitors a watchlist of whale wallets for new large transactions, classifies trades, and prints alerts. Run with--demofor simulated whale trades.
Limitations and Caveats
- Privacy wallets: Whales using multiple wallets or mixers can evade tracking
- Misleading signals: Whales may intentionally create visible accumulation to attract followers, then dump
- Latency: By the time you detect a whale buy, the price impact may already be priced in
- False positives: Internal transfers between a whale's own wallets look like buys/sells
- Exchange wallets: Centralized exchange hot wallets show massive flows that are not individual whale activity
- Not financial advice: Whale activity is informational input for analysis, not a standalone trading recommendation
Whale Tracking Data Sources
Complete guide to the APIs and on-chain methods used for whale tracking on Solana.
Source Comparison
| Source | Real-Time | Auth | Free Tier | Best For |
|---|---|---|---|---|
| Helius | Yes (webhooks) | API key | 100K credits/mo | Transaction history, webhooks |
| SolanaTracker | Polling | API key | Limited | Top traders, PnL rankings |
| Birdeye | Polling | API key | 100 req/min | Token holder rankings |
| Solana RPC | Polling | None (public) | Unlimited | Token accounts, raw signatures |
Helius
Helius provides parsed transaction data and webhook-based real-time monitoring. It is the primary data source for whale tracking.
getSignaturesForAddress
Fetch recent transaction signatures for a wallet:
GET https://api.helius.xyz/v0/addresses/{address}/transactions?api-key={KEY}Parameters:
address(path): Wallet addressapi-key(query): Your Helius API keylimit(query): Max results (default 100, max 1000)before(query): Signature to paginate beforetype(query): Filter by type (e.g.,SWAP,TRANSFER)
Response (array of enhanced transactions):
[
{
"signature": "5UfD...",
"timestamp": 1709856000,
"type": "SWAP",
"source": "JUPITER",
"tokenTransfers": [
{
"fromUserAccount": "WhaLe...",
"toUserAccount": "Pool...",
"mint": "So11...",
"tokenAmount": 150.5
},
{
"fromUserAccount": "Pool...",
"toUserAccount": "WhaLe...",
"mint": "EPjF...",
"tokenAmount": 45000.0
}
],
"nativeTransfers": [...],
"description": "WhaLe... swapped 150.5 SOL for 45000 TOKEN"
}
]Rate limit: Varies by plan. Free tier: ~10 req/sec.
Webhooks for Real-Time Monitoring
Create a webhook to receive instant notifications for whale activity:
POST https://api.helius.xyz/v0/webhooks?api-key={KEY}Request body:
{
"webhookURL": "https://your-server.com/whale-webhook",
"transactionTypes": ["SWAP", "TRANSFER"],
"accountAddresses": ["WhaLe1...", "WhaLe2..."],
"webhookType": "enhanced",
"encoding": "jsonParsed"
}Webhook types:
enhanced— Parsed transaction with human-readable descriptions (recommended)raw— Full raw transaction datadiscord— Formatted for Discord webhooks
Key considerations:
- Webhooks fire within seconds of transaction confirmation
- You can monitor up to 100 addresses per webhook on the free tier
- Enhanced type provides token transfer details pre-parsed
- Webhook delivery includes retry on failure (3 attempts)
Helius DAS API
The Digital Asset Standard API provides token balance snapshots:
POST https://mainnet.helius-rpc.com/?api-key={KEY}{
"jsonrpc": "2.0",
"id": 1,
"method": "getAssetsByOwner",
"params": {
"ownerAddress": "WhaLe...",
"page": 1,
"limit": 1000,
"displayOptions": {
"showFungible": true
}
}
}Returns all tokens held by a wallet with current balances. Useful for building a whale portfolio snapshot.
SolanaTracker
SolanaTracker provides pre-computed trader rankings and PnL data.
Top Traders Per Token
GET https://data.solanatracker.io/top-traders/{mint}Headers: x-api-key: {SOLANATRACKER_API_KEY}
Response:
{
"wallets": [
{
"wallet": "WhaLe...",
"pnl": 12500.0,
"pnlPercent": 340.5,
"bought": 5000.0,
"sold": 17500.0,
"volume": 22500.0,
"tradeCount": 15,
"holding": 0.0
}
]
}Wallet PnL and Trade History
GET https://data.solanatracker.io/pnl/{wallet}Returns overall PnL stats for a wallet across all tokens traded.
GET https://data.solanatracker.io/wallet/{wallet}/tradesReturns recent trades with token, direction, size, and timestamp.
Top Traders Discovery
GET https://data.solanatracker.io/top-traders/all?period=7d&limit=50Returns the most profitable wallets over a time period. Useful for discovering new whales to track.
Birdeye
Birdeye provides token-centric holder and trader data.
Token Top Traders
GET https://public-api.birdeye.so/defi/v2/tokens/top_tradersHeaders: X-API-KEY: {BIRDEYE_API_KEY}, x-chain: solana
Parameters:
address(query): Token mint addresstime_frame(query):24h,7d,30dsort_type(query):volume,pnloffset,limit: Pagination
Response includes: wallet address, buy/sell volume, trade count, PnL.
Token Holder Distribution
GET https://public-api.birdeye.so/defi/v2/tokens/holderParameters:
address: Token mintoffset,limit: Pagination
Returns holder addresses sorted by balance, with percentage of supply.
Rate limit: 100 req/min (free), 1000 req/min (paid).
On-Chain (Solana RPC)
Direct RPC calls require no API key and work with any Solana RPC endpoint.
getTokenLargestAccounts
Fetch the top token holders directly from the chain:
{
"jsonrpc": "2.0",
"id": 1,
"method": "getTokenLargestAccounts",
"params": ["TokenMintAddress..."]
}Response:
{
"result": {
"value": [
{
"address": "TokenAccountAddress...",
"amount": "1000000000",
"decimals": 9,
"uiAmount": 1.0,
"uiAmountString": "1.0"
}
]
}
}Notes:
- Returns token account addresses, not wallet addresses
- You need
getAccountInfoto resolve the token account owner (wallet address) - Returns top 20 accounts by default
- Free, no authentication required
Resolving Token Account to Wallet
{
"jsonrpc": "2.0",
"id": 1,
"method": "getAccountInfo",
"params": [
"TokenAccountAddress...",
{"encoding": "jsonParsed"}
]
}The parsed.info.owner field contains the wallet address.
getSignaturesForAddress
Monitor recent transactions for a wallet:
{
"jsonrpc": "2.0",
"id": 1,
"method": "getSignaturesForAddress",
"params": [
"WalletAddress...",
{"limit": 20}
]
}Returns signature, slot, blockTime, and err (null if successful). Use each signature to fetch full transaction details via getTransaction.
Transaction Monitoring Pipeline
For whale tracking via raw RPC: (1) getTokenLargestAccounts for target token, (2) resolve each token account to wallet, (3) poll getSignaturesForAddress per whale, (4) fetch new transactions with getTransaction (jsonParsed), (5) parse token transfers to classify buy/sell/transfer.
Recommended Stack
| Component | Recommended | Fallback |
|---|---|---|
| Real-time alerts | Helius webhooks | Polling RPC signatures |
| Transaction history | Helius enhanced API | Solana RPC + manual parsing |
| Top trader discovery | SolanaTracker rankings | Birdeye top traders |
| Holder snapshots | Solana RPC (getTokenLargestAccounts) | Birdeye holder endpoint |
| Wallet PnL | SolanaTracker wallet PnL | Compute from trade history |
For a cost-effective setup, use free Solana RPC for holder snapshots and Helius free tier for enhanced transaction parsing. Upgrade to paid Helius ($49/mo) for webhook-based real-time tracking.
Whale Detection Methods
Algorithms and heuristics for identifying whale wallets, classifying their behavior as accumulation or distribution, and generating scored alerts.
Whale Classification
By Trade Size
Classify individual transactions by SOL value:
def classify_trade_size(sol_amount: float) -> str:
"""Classify a single trade by size."""
if sol_amount >= 1000:
return "mega_whale"
elif sol_amount >= 100:
return "whale"
elif sol_amount >= 10:
return "mid_size"
else:
return "retail"By Portfolio Value
Classify wallets by total holdings:
def classify_wallet(total_sol_value: float) -> str:
"""Classify a wallet by total portfolio value in SOL."""
if total_sol_value >= 10_000:
return "mega_whale"
elif total_sol_value >= 1_000:
return "whale"
elif total_sol_value >= 100:
return "mid_size"
else:
return "retail"By Token-Relative Holdings
For a specific token, classify by percentage of supply held:
| % of Supply | Classification | Risk Level |
|---|---|---|
| > 10% | Dominant whale | Critical |
| 5-10% | Major whale | High |
| 2-5% | Significant holder | Medium |
| 1-2% | Notable holder | Low |
| < 1% | Minor holder | Minimal |
By Influence Score
Combine multiple factors into an influence score:
def compute_influence_score(
pct_supply: float,
trade_count_30d: int,
avg_trade_sol: float,
win_rate: float,
follower_count: int,
) -> float:
"""Compute a 0-100 influence score for a whale wallet.
Args:
pct_supply: Percentage of token supply held (0-100).
trade_count_30d: Number of trades in the last 30 days.
avg_trade_sol: Average trade size in SOL.
win_rate: Historical win rate (0-1).
follower_count: Number of wallets that copy this wallet.
Returns:
Influence score from 0 to 100.
"""
supply_score = min(pct_supply * 5, 30) # Max 30 points
activity_score = min(trade_count_30d * 0.5, 20) # Max 20 points
size_score = min(avg_trade_sol / 50, 20) # Max 20 points
skill_score = win_rate * 15 # Max 15 points
social_score = min(follower_count * 0.5, 15) # Max 15 points
return round(supply_score + activity_score + size_score + skill_score + social_score, 1)Accumulation Detection
DCA Pattern Detection
Detect Dollar-Cost Averaging by looking for regular, similarly-sized buys:
import statistics
def detect_dca_pattern(
buy_timestamps: list[float],
buy_amounts: list[float],
min_buys: int = 4,
max_cv: float = 0.5,
) -> bool:
"""Detect DCA buying pattern.
Args:
buy_timestamps: Unix timestamps of buy transactions.
buy_amounts: SOL amounts of each buy.
min_buys: Minimum number of buys to consider.
max_cv: Maximum coefficient of variation for amounts.
Returns:
True if DCA pattern detected.
"""
if len(buy_timestamps) < min_buys:
return False
# Check amount consistency (low coefficient of variation)
mean_amt = statistics.mean(buy_amounts)
if mean_amt == 0:
return False
cv = statistics.stdev(buy_amounts) / mean_amt
if cv > max_cv:
return False
# Check time regularity (intervals should be somewhat consistent)
intervals = [
buy_timestamps[i + 1] - buy_timestamps[i]
for i in range(len(buy_timestamps) - 1)
]
mean_interval = statistics.mean(intervals)
if mean_interval == 0:
return False
interval_cv = statistics.stdev(intervals) / mean_interval
return interval_cv < 1.0 # Intervals within 1 CVDip Buying Detection
Calculate what fraction of buys occurred during price dips (drawdown > 10% from rolling high). Compare buy timestamps against price history, compute rolling high at each buy point, and check if price was in drawdown. A dip_buy_ratio > 0.5 is a strong accumulation signal.
Composite Accumulation Score
def compute_accumulation_score(
buy_count: int,
sell_count: int,
avg_buy_size: float,
avg_sell_size: float,
position_change_7d_pct: float,
dip_buy_ratio: float,
is_dca: bool,
) -> int:
"""Compute accumulation score from 0 to 10.
Score >= 4 indicates likely accumulation.
Score >= 7 indicates strong accumulation.
"""
score = 0
if buy_count > sell_count * 2:
score += 2
elif buy_count > sell_count:
score += 1
if avg_buy_size > avg_sell_size * 1.5:
score += 1
if position_change_7d_pct > 10:
score += 2
elif position_change_7d_pct > 0:
score += 1
if dip_buy_ratio > 0.5:
score += 2
elif dip_buy_ratio > 0.25:
score += 1
if is_dca:
score += 2
return min(score, 10)Distribution Detection
Exchange Transfer Detection
Known Solana exchange deposit addresses (partial list):
| Exchange | Hot Wallet Prefix | Notes |
|---|---|---|
| Binance | 5tzFkiKsc... | Multiple deposit addresses |
| OKX | JCnc... | Rotates addresses |
| Bybit | AC5R... | Rotates addresses |
Detection approach: 1. Maintain a list of known exchange wallet addresses 2. Flag any SPL token transfers to these addresses 3. Weight: transfer to exchange = strong distribution signal (+3 to distribution score)
Sell Frequency Analysis
def detect_increasing_sell_frequency(
sell_timestamps: list[float],
window_days: int = 7,
) -> bool:
"""Detect whether sell frequency is increasing over time.
Compares sell count in the most recent window to the prior window.
"""
if len(sell_timestamps) < 4:
return False
now = sell_timestamps[-1]
window_seconds = window_days * 86400
recent_cutoff = now - window_seconds
prior_cutoff = now - (2 * window_seconds)
recent_sells = sum(1 for t in sell_timestamps if t >= recent_cutoff)
prior_sells = sum(1 for t in sell_timestamps if prior_cutoff <= t < recent_cutoff)
return recent_sells > prior_sells * 1.5Composite Distribution Score
def compute_distribution_score(
buy_count: int,
sell_count: int,
position_change_7d_pct: float,
transfers_to_exchanges: int,
sell_frequency_increasing: bool,
position_pct_remaining: float,
) -> int:
"""Compute distribution score from 0 to 10.
Score >= 4 indicates likely distribution.
Score >= 7 indicates aggressive distribution.
"""
score = 0
if sell_count > buy_count * 2:
score += 2
elif sell_count > buy_count:
score += 1
if position_change_7d_pct < -20:
score += 2
elif position_change_7d_pct < 0:
score += 1
if transfers_to_exchanges > 0:
score += 3
if sell_frequency_increasing:
score += 2
if position_pct_remaining < 50:
score += 1
return min(score, 10)Alert Thresholds
Transaction-Level Alerts
| Alert Level | Trigger | Action |
|---|---|---|
| Info | Tracked whale any trade | Log activity |
| Warning | Whale trade > 100 SOL | Notify if subscribed |
| Critical | Whale sells > 25% of position | Immediate alert |
| Emergency | Multiple whales selling same token | Flash alert |
Scoring Alert Priority
Score by summing points: trade size (1-3 pts), whale influence (2-3 pts), concurrent whale count (0-3 pts), sell bonus (+1). Map total to priority: 8+ = critical, 5+ = high, 3+ = medium, else low.
Multi-Wallet Detection
Whales often split holdings across wallets. Detect linked wallets by:
1. Funding source: Wallets funded from the same parent wallet 2. Timing correlation: Wallets that trade the same tokens at similar times 3. Consolidation events: Multiple wallets sending tokens to a single address
When linked wallets are detected, treat their combined holdings as a single whale entity for accumulation/distribution scoring.
#!/usr/bin/env python3
"""Track whale wallets for a Solana token and classify their activity.
Fetches the top holders of a given token mint, identifies whale wallets
by balance threshold, checks their recent transaction activity, and
classifies each as accumulating, distributing, or holding.
Usage:
python scripts/track_whales.py --mint <TOKEN_MINT>
python scripts/track_whales.py --demo
Dependencies:
uv pip install httpx
Environment Variables:
HELIUS_API_KEY: Your Helius API key (optional in demo mode)
SOLANA_RPC_URL: Custom RPC endpoint (optional, defaults to public mainnet)
"""
import argparse
import json
import os
import random
import sys
import time
from dataclasses import dataclass, field
from typing import Optional
try:
import httpx
except ImportError:
print("Missing dependency. Install with: uv pip install httpx")
sys.exit(1)
# ── Configuration ───────────────────────────────────────────────────
HELIUS_API_KEY = os.getenv("HELIUS_API_KEY", "")
SOLANA_RPC_URL = os.getenv(
"SOLANA_RPC_URL", "https://api.mainnet-beta.solana.com"
)
HELIUS_RPC_URL = (
f"https://mainnet.helius-rpc.com/?api-key={HELIUS_API_KEY}"
if HELIUS_API_KEY
else ""
)
HELIUS_API_BASE = "https://api.helius.xyz/v0"
# Whale classification thresholds (in token units, relative)
WHALE_MIN_PCT_SUPPLY = 2.0 # Minimum % of supply to be considered a whale
WHALE_TOP_N = 20 # Track top N holders
# Activity classification thresholds
ACCUMULATION_THRESHOLD = 4 # Score >= this = accumulating
DISTRIBUTION_THRESHOLD = 4 # Score >= this = distributing
# ── Data Models ─────────────────────────────────────────────────────
@dataclass
class TokenHolder:
"""A token holder with balance and ownership data."""
token_account: str
wallet_address: str
balance: float
pct_supply: float
rank: int
@dataclass
class WhaleActivity:
"""Whale wallet activity summary."""
wallet: str
balance: float
pct_supply: float
rank: int
buy_count: int = 0
sell_count: int = 0
total_bought: float = 0.0
total_sold: float = 0.0
transfers_to_exchange: int = 0
classification: str = "holding"
accumulation_score: int = 0
distribution_score: int = 0
recent_transactions: list = field(default_factory=list)
# ── RPC Helpers ─────────────────────────────────────────────────────
def rpc_call(
client: httpx.Client,
method: str,
params: list,
rpc_url: Optional[str] = None,
) -> dict:
"""Make a JSON-RPC call to a Solana RPC endpoint.
Args:
client: httpx Client instance.
method: RPC method name.
params: Method parameters.
rpc_url: Override RPC URL.
Returns:
The 'result' field from the RPC response.
Raises:
RuntimeError: If the RPC call returns an error.
"""
url = rpc_url or HELIUS_RPC_URL or SOLANA_RPC_URL
payload = {
"jsonrpc": "2.0",
"id": 1,
"method": method,
"params": params,
}
resp = client.post(url, json=payload, timeout=30)
resp.raise_for_status()
data = resp.json()
if "error" in data:
raise RuntimeError(f"RPC error: {data['error']}")
return data.get("result", {})
def get_top_holders(
client: httpx.Client, mint: str
) -> list[TokenHolder]:
"""Fetch the top token holders for a given mint.
Uses getTokenLargestAccounts and resolves each token account
to its owning wallet address.
Args:
client: httpx Client instance.
mint: Token mint address.
Returns:
List of TokenHolder objects sorted by balance descending.
"""
result = rpc_call(client, "getTokenLargestAccounts", [mint])
accounts = result.get("value", [])
if not accounts:
print(f"No holders found for mint {mint}")
return []
total_supply = sum(
float(a.get("uiAmount", 0) or 0) for a in accounts
)
if total_supply == 0:
total_supply = 1.0 # Avoid division by zero
holders: list[TokenHolder] = []
for rank, acct in enumerate(accounts, 1):
token_account = acct["address"]
balance = float(acct.get("uiAmount", 0) or 0)
pct = (balance / total_supply) * 100
# Resolve token account to wallet address
wallet = resolve_token_account_owner(client, token_account)
holders.append(
TokenHolder(
token_account=token_account,
wallet_address=wallet,
balance=balance,
pct_supply=round(pct, 2),
rank=rank,
)
)
time.sleep(0.1) # Rate limit politeness
return holders
def resolve_token_account_owner(
client: httpx.Client, token_account: str
) -> str:
"""Resolve a token account address to its owner wallet.
Args:
client: httpx Client instance.
token_account: SPL token account address.
Returns:
Wallet address that owns the token account.
"""
try:
result = rpc_call(
client,
"getAccountInfo",
[token_account, {"encoding": "jsonParsed"}],
)
if result and result.get("value"):
parsed = result["value"]["data"]["parsed"]["info"]
return parsed.get("owner", token_account)
except Exception:
pass
return token_account # Fallback to token account address
def get_wallet_transactions(
client: httpx.Client, wallet: str, limit: int = 20
) -> list[dict]:
"""Fetch recent enhanced transactions for a wallet via Helius.
Falls back to basic RPC signatures if Helius key is not available.
Args:
client: httpx Client instance.
wallet: Wallet address.
limit: Maximum number of transactions.
Returns:
List of transaction dicts with type and transfer info.
"""
if HELIUS_API_KEY:
try:
url = (
f"{HELIUS_API_BASE}/addresses/{wallet}/transactions"
f"?api-key={HELIUS_API_KEY}&limit={limit}"
)
resp = client.get(url, timeout=30)
resp.raise_for_status()
return resp.json()
except Exception as e:
print(f" Helius API error for {wallet[:8]}...: {e}")
return []
else:
# Fallback: basic signatures (less detail)
try:
result = rpc_call(
client,
"getSignaturesForAddress",
[wallet, {"limit": limit}],
)
return [
{"signature": s["signature"], "type": "UNKNOWN"}
for s in (result if isinstance(result, list) else [])
]
except Exception as e:
print(f" RPC error for {wallet[:8]}...: {e}")
return []
# ── Activity Classification ────────────────────────────────────────
KNOWN_EXCHANGE_PREFIXES = [
"5tzFkiKsc", # Binance
"JCnc", # OKX
"AC5R", # Bybit
"9WzDX", # Coinbase
]
def classify_whale_activity(
holder: TokenHolder, transactions: list[dict]
) -> WhaleActivity:
"""Classify a whale's recent activity as accumulating, distributing, or holding.
Analyzes token transfers in recent transactions to determine
buy/sell counts and compute accumulation/distribution scores.
Args:
holder: The token holder information.
transactions: List of enhanced transaction dicts.
Returns:
WhaleActivity with classification and scores.
"""
activity = WhaleActivity(
wallet=holder.wallet_address,
balance=holder.balance,
pct_supply=holder.pct_supply,
rank=holder.rank,
)
for tx in transactions:
tx_type = tx.get("type", "UNKNOWN")
token_transfers = tx.get("tokenTransfers", [])
for transfer in token_transfers:
from_addr = transfer.get("fromUserAccount", "")
to_addr = transfer.get("toUserAccount", "")
amount = float(transfer.get("tokenAmount", 0) or 0)
if to_addr == holder.wallet_address:
activity.buy_count += 1
activity.total_bought += amount
elif from_addr == holder.wallet_address:
activity.sell_count += 1
activity.total_sold += amount
# Check for exchange transfers
for prefix in KNOWN_EXCHANGE_PREFIXES:
if to_addr.startswith(prefix):
activity.transfers_to_exchange += 1
break
# Compute accumulation score
acc = 0
if activity.buy_count > activity.sell_count * 2:
acc += 2
elif activity.buy_count > activity.sell_count:
acc += 1
avg_buy = (
activity.total_bought / activity.buy_count
if activity.buy_count
else 0
)
avg_sell = (
activity.total_sold / activity.sell_count
if activity.sell_count
else 0
)
if avg_buy > avg_sell * 1.5 and avg_sell > 0:
acc += 1
if activity.buy_count >= 4:
acc += 1 # Frequent buying
activity.accumulation_score = acc
# Compute distribution score
dist = 0
if activity.sell_count > activity.buy_count * 2:
dist += 2
elif activity.sell_count > activity.buy_count:
dist += 1
if activity.transfers_to_exchange > 0:
dist += 3
if activity.total_sold > activity.total_bought * 2:
dist += 2
activity.distribution_score = dist
# Classify
if acc >= ACCUMULATION_THRESHOLD and acc > dist:
activity.classification = "accumulating"
elif dist >= DISTRIBUTION_THRESHOLD and dist > acc:
activity.classification = "distributing"
else:
activity.classification = "holding"
activity.recent_transactions = transactions[:5]
return activity
# ── Demo Mode ───────────────────────────────────────────────────────
def generate_demo_data() -> list[WhaleActivity]:
"""Generate synthetic whale data for demonstration purposes.
Returns:
List of WhaleActivity objects with simulated data.
"""
random.seed(42)
demo_whales = [
("7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU", "accumulating"),
("DYw8jCTfwHNRJhhmFcbXvVDTqWMEVFBX6ZKUmG5CNSKK", "distributing"),
("HN7cABqLq46Es1jh92dQQisAq662SmxELLLsHHe4YWrH", "holding"),
("9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM", "accumulating"),
("5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1", "distributing"),
("FmHzNJkx6HqgjcC3kcEJU8hDUBfPcWwkm3u4MXJaxjms", "holding"),
("2AQdpHFicFaDB5xKs3oFZ8nSm3ALFVsGQ7LKRGBHVVWL", "accumulating"),
]
results: list[WhaleActivity] = []
total_supply = 1_000_000_000.0
for rank, (wallet, behavior) in enumerate(demo_whales, 1):
balance = random.uniform(10_000_000, 80_000_000)
pct = (balance / total_supply) * 100
if behavior == "accumulating":
buy_count = random.randint(8, 20)
sell_count = random.randint(0, 3)
total_bought = balance * random.uniform(0.3, 0.8)
total_sold = total_bought * random.uniform(0.0, 0.2)
acc_score = random.randint(5, 8)
dist_score = random.randint(0, 2)
elif behavior == "distributing":
buy_count = random.randint(0, 3)
sell_count = random.randint(8, 15)
total_sold = balance * random.uniform(0.3, 0.6)
total_bought = total_sold * random.uniform(0.0, 0.15)
acc_score = random.randint(0, 1)
dist_score = random.randint(5, 9)
else:
buy_count = random.randint(1, 4)
sell_count = random.randint(1, 4)
total_bought = balance * random.uniform(0.02, 0.1)
total_sold = total_bought * random.uniform(0.8, 1.2)
acc_score = random.randint(0, 2)
dist_score = random.randint(0, 2)
activity = WhaleActivity(
wallet=wallet,
balance=round(balance, 2),
pct_supply=round(pct, 2),
rank=rank,
buy_count=buy_count,
sell_count=sell_count,
total_bought=round(total_bought, 2),
total_sold=round(total_sold, 2),
transfers_to_exchange=random.randint(1, 3)
if behavior == "distributing"
else 0,
classification=behavior,
accumulation_score=acc_score,
distribution_score=dist_score,
)
results.append(activity)
return results
# ── Report Formatting ───────────────────────────────────────────────
def print_whale_report(
whales: list[WhaleActivity], mint: str, is_demo: bool = False
) -> None:
"""Print a formatted whale activity report.
Args:
whales: List of classified whale activities.
mint: Token mint address.
is_demo: Whether this is demo data.
"""
header = "WHALE ACTIVITY REPORT"
if is_demo:
header += " [DEMO MODE - SYNTHETIC DATA]"
print("\n" + "=" * 72)
print(f" {header}")
print(f" Token: {mint[:16]}...{mint[-8:]}" if len(mint) > 24 else f" Token: {mint}")
print(f" Whales tracked: {len(whales)}")
print("=" * 72)
# Summary counts
acc_count = sum(1 for w in whales if w.classification == "accumulating")
dist_count = sum(1 for w in whales if w.classification == "distributing")
hold_count = sum(1 for w in whales if w.classification == "holding")
print(f"\n Summary: {acc_count} accumulating | {dist_count} distributing | {hold_count} holding")
# Net signal
if acc_count > dist_count * 2:
net_signal = "STRONG ACCUMULATION"
elif acc_count > dist_count:
net_signal = "NET ACCUMULATION"
elif dist_count > acc_count * 2:
net_signal = "STRONG DISTRIBUTION"
elif dist_count > acc_count:
net_signal = "NET DISTRIBUTION"
else:
net_signal = "NEUTRAL"
print(f" Net signal: {net_signal}")
print("\n" + "-" * 72)
print(f" {'Rank':<5} {'Wallet':<16} {'Balance %':<10} {'Buys':<6} {'Sells':<6} {'Classification':<16} {'Score'}")
print("-" * 72)
for w in sorted(whales, key=lambda x: x.rank):
wallet_short = f"{w.wallet[:6]}...{w.wallet[-4:]}"
if w.classification == "accumulating":
score_str = f"Acc:{w.accumulation_score}"
elif w.classification == "distributing":
score_str = f"Dist:{w.distribution_score}"
else:
score_str = f"A:{w.accumulation_score}/D:{w.distribution_score}"
print(
f" {w.rank:<5} {wallet_short:<16} {w.pct_supply:<10.2f} "
f"{w.buy_count:<6} {w.sell_count:<6} {w.classification:<16} {score_str}"
)
print("-" * 72)
# Detailed view for accumulating and distributing whales
notable = [w for w in whales if w.classification != "holding"]
if notable:
print("\n NOTABLE WHALE DETAILS")
print("-" * 72)
for w in notable:
wallet_short = f"{w.wallet[:6]}...{w.wallet[-4:]}"
print(f"\n [{w.classification.upper()}] {wallet_short} (Rank #{w.rank})")
print(f" Balance: {w.balance:,.2f} tokens ({w.pct_supply:.2f}% of supply)")
print(f" Buys: {w.buy_count} trades | Total bought: {w.total_bought:,.2f}")
print(f" Sells: {w.sell_count} trades | Total sold: {w.total_sold:,.2f}")
if w.transfers_to_exchange > 0:
print(f" Exchange transfers: {w.transfers_to_exchange} (distribution signal)")
print(f" Accumulation score: {w.accumulation_score}/10")
print(f" Distribution score: {w.distribution_score}/10")
print("\n" + "=" * 72)
print(" Note: This is informational analysis only, not financial advice.")
print("=" * 72 + "\n")
# ── Main ────────────────────────────────────────────────────────────
def main() -> None:
"""Run whale tracking analysis."""
parser = argparse.ArgumentParser(
description="Track whale wallets for a Solana token"
)
parser.add_argument(
"--mint",
type=str,
help="Token mint address to analyze",
)
parser.add_argument(
"--demo",
action="store_true",
help="Run with synthetic demo data (no API keys required)",
)
parser.add_argument(
"--top-n",
type=int,
default=WHALE_TOP_N,
help=f"Number of top holders to track (default: {WHALE_TOP_N})",
)
args = parser.parse_args()
if args.demo:
print("Running in demo mode with synthetic data...")
demo_mint = "DemoTokenMint111111111111111111111111111111"
whales = generate_demo_data()
print_whale_report(whales, demo_mint, is_demo=True)
return
if not args.mint:
print("Error: --mint is required (or use --demo for synthetic data)")
sys.exit(1)
if not HELIUS_API_KEY:
print(
"Warning: HELIUS_API_KEY not set. Using public RPC with limited "
"transaction detail. Set HELIUS_API_KEY for enhanced data."
)
print(f"Fetching top holders for {args.mint[:16]}...")
with httpx.Client() as client:
# Step 1: Get top holders
holders = get_top_holders(client, args.mint)
if not holders:
print("No holders found. Check the mint address.")
sys.exit(1)
# Filter to whale-level holders
whale_holders = [
h
for h in holders[: args.top_n]
if h.pct_supply >= WHALE_MIN_PCT_SUPPLY
]
if not whale_holders:
print(
f"No holders found with >= {WHALE_MIN_PCT_SUPPLY}% of supply. "
f"Top holder has {holders[0].pct_supply:.2f}%."
)
# Fall back to top N
whale_holders = holders[: min(args.top_n, len(holders))]
print(f"Found {len(whale_holders)} whale wallets. Analyzing activity...")
# Step 2: Analyze each whale's activity
whale_activities: list[WhaleActivity] = []
for i, holder in enumerate(whale_holders):
short_addr = f"{holder.wallet_address[:8]}..."
print(f" [{i + 1}/{len(whale_holders)}] Analyzing {short_addr}")
transactions = get_wallet_transactions(
client, holder.wallet_address, limit=20
)
activity = classify_whale_activity(holder, transactions)
whale_activities.append(activity)
time.sleep(0.2) # Rate limit politeness
# Step 3: Print report
print_whale_report(whale_activities, args.mint)
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""Monitor whale wallets and generate alerts for large transactions.
Watches a configurable list of whale wallet addresses, checks for new
large transactions, classifies each as a buy or sell, estimates market
impact, and prints formatted alerts.
Usage:
python scripts/whale_alerts.py --demo
python scripts/whale_alerts.py --wallets wallet1,wallet2 --min-sol 100
Dependencies:
uv pip install httpx
Environment Variables:
HELIUS_API_KEY: Your Helius API key (optional in demo mode)
SOLANA_RPC_URL: Custom RPC endpoint (optional)
"""
import argparse
import json
import os
import random
import sys
import time
from dataclasses import dataclass
from datetime import datetime, timezone
from typing import Optional
try:
import httpx
except ImportError:
print("Missing dependency. Install with: uv pip install httpx")
sys.exit(1)
# ── Configuration ───────────────────────────────────────────────────
HELIUS_API_KEY = os.getenv("HELIUS_API_KEY", "")
SOLANA_RPC_URL = os.getenv(
"SOLANA_RPC_URL", "https://api.mainnet-beta.solana.com"
)
HELIUS_API_BASE = "https://api.helius.xyz/v0"
# Default alert thresholds
DEFAULT_MIN_SOL = 100.0 # Minimum trade size in SOL to trigger alert
SOL_PRICE_USD = 150.0 # Approximate SOL price for USD estimates
# Known exchange wallet prefixes for transfer classification
EXCHANGE_PREFIXES: dict[str, str] = {
"5tzFkiKsc": "Binance",
"JCnc": "OKX",
"AC5R": "Bybit",
"9WzDX": "Coinbase",
}
# ── Data Models ─────────────────────────────────────────────────────
@dataclass
class WhaleAlert:
"""A single whale transaction alert."""
wallet: str
wallet_label: str
signature: str
timestamp: float
action: str # "buy", "sell", "transfer_out", "transfer_in"
token_mint: str
token_symbol: str
token_amount: float
sol_value: float
usd_value: float
exchange_name: Optional[str]
impact_estimate: str # "low", "medium", "high", "extreme"
priority: str # "info", "warning", "critical"
# ── Helius API ──────────────────────────────────────────────────────
def fetch_wallet_transactions(
client: httpx.Client,
wallet: str,
limit: int = 10,
) -> list[dict]:
"""Fetch recent enhanced transactions for a wallet from Helius.
Args:
client: httpx Client instance.
wallet: Wallet address to query.
limit: Maximum transactions to return.
Returns:
List of enhanced transaction dicts.
Raises:
httpx.HTTPStatusError: On API errors.
"""
if not HELIUS_API_KEY:
return []
url = (
f"{HELIUS_API_BASE}/addresses/{wallet}/transactions"
f"?api-key={HELIUS_API_KEY}&limit={limit}"
)
try:
resp = client.get(url, timeout=30)
resp.raise_for_status()
return resp.json()
except httpx.HTTPStatusError as e:
print(f" API error for {wallet[:8]}...: {e.response.status_code}")
return []
except Exception as e:
print(f" Error fetching transactions for {wallet[:8]}...: {e}")
return []
def parse_transaction_to_alert(
tx: dict,
wallet: str,
wallet_label: str,
min_sol: float,
) -> Optional[WhaleAlert]:
"""Parse an enhanced transaction into a WhaleAlert if it meets thresholds.
Args:
tx: Enhanced transaction dict from Helius.
wallet: The tracked wallet address.
wallet_label: Human-readable label for the wallet.
min_sol: Minimum SOL value to generate an alert.
Returns:
WhaleAlert if the transaction is notable, None otherwise.
"""
tx_type = tx.get("type", "UNKNOWN")
signature = tx.get("signature", "unknown")
timestamp = tx.get("timestamp", 0)
token_transfers = tx.get("tokenTransfers", [])
native_transfers = tx.get("nativeTransfers", [])
# Check native SOL transfers
sol_moved = 0.0
for nt in native_transfers:
if nt.get("fromUserAccount") == wallet:
sol_moved -= nt.get("amount", 0) / 1e9
elif nt.get("toUserAccount") == wallet:
sol_moved += nt.get("amount", 0) / 1e9
# Check token transfers for the main token movement
token_mint = ""
token_symbol = ""
token_amount = 0.0
action = "unknown"
exchange_name = None
for tt in token_transfers:
from_addr = tt.get("fromUserAccount", "")
to_addr = tt.get("toUserAccount", "")
amount = float(tt.get("tokenAmount", 0) or 0)
mint = tt.get("mint", "")
if to_addr == wallet and amount > 0:
action = "buy" if tx_type == "SWAP" else "transfer_in"
token_mint = mint
token_amount = amount
elif from_addr == wallet and amount > 0:
action = "sell" if tx_type == "SWAP" else "transfer_out"
token_mint = mint
token_amount = amount
# Check if transfer to exchange
for prefix, name in EXCHANGE_PREFIXES.items():
if to_addr.startswith(prefix):
exchange_name = name
break
# Estimate SOL value (use native transfer as proxy)
sol_value = abs(sol_moved)
if sol_value < min_sol:
return None
usd_value = sol_value * SOL_PRICE_USD
# Impact estimate
if sol_value >= 1000:
impact = "extreme"
elif sol_value >= 500:
impact = "high"
elif sol_value >= 200:
impact = "medium"
else:
impact = "low"
# Alert priority
if sol_value >= 500 or exchange_name:
priority = "critical"
elif sol_value >= 200:
priority = "warning"
else:
priority = "info"
return WhaleAlert(
wallet=wallet,
wallet_label=wallet_label,
signature=signature,
timestamp=timestamp,
action=action,
token_mint=token_mint or "unknown",
token_symbol=token_symbol or mint_to_symbol(token_mint),
token_amount=token_amount,
sol_value=round(sol_value, 2),
usd_value=round(usd_value, 2),
exchange_name=exchange_name,
impact_estimate=impact,
priority=priority,
)
def mint_to_symbol(mint: str) -> str:
"""Map common token mints to symbols.
Args:
mint: Token mint address.
Returns:
Token symbol or shortened mint address.
"""
known = {
"So11111111111111111111111111111111111111112": "SOL",
"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v": "USDC",
"Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB": "USDT",
}
return known.get(mint, f"{mint[:6]}..." if mint else "UNKNOWN")
# ── Demo Mode ───────────────────────────────────────────────────────
def generate_demo_alerts() -> list[WhaleAlert]:
"""Generate 5 synthetic whale trade alerts for demonstration.
Returns:
List of WhaleAlert objects with simulated data.
"""
random.seed(123)
base_time = time.time()
demo_trades = [
{
"wallet": "7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU",
"label": "Smart Money Alpha",
"action": "buy",
"token": "BONK",
"mint": "DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263",
"token_amount": 25_000_000_000.0,
"sol_value": 350.0,
},
{
"wallet": "DYw8jCTfwHNRJhhmFcbXvVDTqWMEVFBX6ZKUmG5CNSKK",
"label": "Whale Distributor",
"action": "sell",
"token": "WIF",
"mint": "EKpQGSJtjMFqKZ9KQanSqYXRcF8fBopzLHYxdM65zcjm",
"token_amount": 500_000.0,
"sol_value": 800.0,
},
{
"wallet": "HN7cABqLq46Es1jh92dQQisAq662SmxELLLsHHe4YWrH",
"label": "Fund Wallet #3",
"action": "transfer_out",
"token": "JUP",
"mint": "JUPyiwrYJFskUPiHa7hkeR8VUtAeFoSYbKedZNsDvCN",
"token_amount": 2_000_000.0,
"sol_value": 600.0,
"exchange": "Binance",
},
{
"wallet": "9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM",
"label": "DCA Accumulator",
"action": "buy",
"token": "PYTH",
"mint": "HZ1JovNiVvGrGNiiYvEozEVgZ58xaU3RKwX8eACQBCt3",
"token_amount": 1_500_000.0,
"sol_value": 150.0,
},
{
"wallet": "5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1",
"label": "Known Insider",
"action": "sell",
"token": "RNDR",
"mint": "rndrizKT3MK1iimdxRdWabcF7Zg7AR5T4nud4EkHBof",
"token_amount": 100_000.0,
"sol_value": 1200.0,
},
]
alerts: list[WhaleAlert] = []
for i, trade in enumerate(demo_trades):
sol_val = trade["sol_value"]
usd_val = sol_val * SOL_PRICE_USD
if sol_val >= 1000:
impact = "extreme"
elif sol_val >= 500:
impact = "high"
elif sol_val >= 200:
impact = "medium"
else:
impact = "low"
exchange_name = trade.get("exchange")
if sol_val >= 500 or exchange_name:
priority = "critical"
elif sol_val >= 200:
priority = "warning"
else:
priority = "info"
alert = WhaleAlert(
wallet=trade["wallet"],
wallet_label=trade["label"],
signature=f"DemoSig{i + 1}{'x' * 60}",
timestamp=base_time - (i * 300), # 5 min apart
action=trade["action"],
token_mint=trade["mint"],
token_symbol=trade["token"],
token_amount=trade["token_amount"],
sol_value=sol_val,
usd_value=round(usd_val, 2),
exchange_name=exchange_name,
impact_estimate=impact,
priority=priority,
)
alerts.append(alert)
return alerts
# ── Alert Formatting ────────────────────────────────────────────────
PRIORITY_ICONS = {
"info": "[INFO]",
"warning": "[WARN]",
"critical": "[CRIT]",
}
ACTION_LABELS = {
"buy": "BOUGHT",
"sell": "SOLD",
"transfer_out": "TRANSFERRED OUT",
"transfer_in": "RECEIVED",
"unknown": "MOVED",
}
def format_alert(alert: WhaleAlert) -> str:
"""Format a single whale alert as a readable string.
Args:
alert: The whale alert to format.
Returns:
Multi-line formatted alert string.
"""
icon = PRIORITY_ICONS.get(alert.priority, "[???]")
action_label = ACTION_LABELS.get(alert.action, "ACTIVITY")
ts = datetime.fromtimestamp(alert.timestamp, tz=timezone.utc)
ts_str = ts.strftime("%Y-%m-%d %H:%M:%S UTC")
wallet_short = f"{alert.wallet[:6]}...{alert.wallet[-4:]}"
lines = [
f" {icon} {alert.wallet_label} ({wallet_short})",
f" Action: {action_label} {alert.token_amount:,.2f} {alert.token_symbol}",
f" Value: {alert.sol_value:,.2f} SOL (~${alert.usd_value:,.0f})",
f" Impact: {alert.impact_estimate.upper()}",
f" Time: {ts_str}",
f" Tx: {alert.signature[:16]}...",
]
if alert.exchange_name:
lines.insert(
3, f" Dest: {alert.exchange_name} (exchange deposit)"
)
return "\n".join(lines)
def print_alerts(
alerts: list[WhaleAlert], is_demo: bool = False
) -> None:
"""Print all whale alerts in a formatted report.
Args:
alerts: List of whale alerts to display.
is_demo: Whether these are demo alerts.
"""
header = "WHALE ALERTS"
if is_demo:
header += " [DEMO MODE - SIMULATED DATA]"
print("\n" + "=" * 72)
print(f" {header}")
print(f" Generated: {datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M:%S UTC')}")
print(f" Alerts: {len(alerts)}")
print("=" * 72)
# Summary
buys = [a for a in alerts if a.action == "buy"]
sells = [a for a in alerts if a.action in ("sell", "transfer_out")]
total_buy_sol = sum(a.sol_value for a in buys)
total_sell_sol = sum(a.sol_value for a in sells)
print(f"\n Buy alerts: {len(buys)} ({total_buy_sol:,.2f} SOL)")
print(f" Sell alerts: {len(sells)} ({total_sell_sol:,.2f} SOL)")
if total_buy_sol > total_sell_sol * 1.5:
print(" Net flow: BULLISH (more whale buying than selling)")
elif total_sell_sol > total_buy_sol * 1.5:
print(" Net flow: BEARISH (more whale selling than buying)")
else:
print(" Net flow: NEUTRAL")
# Print each alert
critical = [a for a in alerts if a.priority == "critical"]
warning = [a for a in alerts if a.priority == "warning"]
info = [a for a in alerts if a.priority == "info"]
if critical:
print("\n" + "-" * 72)
print(" CRITICAL ALERTS")
print("-" * 72)
for alert in critical:
print(format_alert(alert))
print()
if warning:
print("-" * 72)
print(" WARNING ALERTS")
print("-" * 72)
for alert in warning:
print(format_alert(alert))
print()
if info:
print("-" * 72)
print(" INFO ALERTS")
print("-" * 72)
for alert in info:
print(format_alert(alert))
print()
print("=" * 72)
print(" Note: This is informational analysis only, not financial advice.")
print("=" * 72 + "\n")
# ── Live Monitoring ─────────────────────────────────────────────────
def monitor_wallets(
wallets: dict[str, str],
min_sol: float,
poll_interval: int = 60,
max_checks: int = 5,
) -> list[WhaleAlert]:
"""Poll whale wallets for new transactions and generate alerts.
Args:
wallets: Dict mapping wallet address to label.
min_sol: Minimum SOL value to trigger an alert.
poll_interval: Seconds between polling cycles.
max_checks: Maximum number of polling cycles (0 = unlimited).
Returns:
All alerts generated across polling cycles.
"""
if not HELIUS_API_KEY:
print(
"Error: HELIUS_API_KEY required for live monitoring. "
"Use --demo for simulated alerts."
)
return []
all_alerts: list[WhaleAlert] = []
seen_signatures: set[str] = set()
check_count = 0
print(f"Monitoring {len(wallets)} whale wallets (min: {min_sol} SOL)...")
print(f"Polling every {poll_interval} seconds. Press Ctrl+C to stop.\n")
with httpx.Client() as client:
try:
while max_checks == 0 or check_count < max_checks:
check_count += 1
print(
f"[Check #{check_count}] "
f"{datetime.now(timezone.utc).strftime('%H:%M:%S UTC')}"
)
cycle_alerts: list[WhaleAlert] = []
for wallet, label in wallets.items():
txs = fetch_wallet_transactions(
client, wallet, limit=5
)
for tx in txs:
sig = tx.get("signature", "")
if sig in seen_signatures:
continue
seen_signatures.add(sig)
alert = parse_transaction_to_alert(
tx, wallet, label, min_sol
)
if alert:
cycle_alerts.append(alert)
time.sleep(0.2) # Rate limit
if cycle_alerts:
print(f" Found {len(cycle_alerts)} new alert(s)")
for alert in cycle_alerts:
print(format_alert(alert))
print()
all_alerts.extend(cycle_alerts)
else:
print(" No new whale activity above threshold")
if max_checks == 0 or check_count < max_checks:
time.sleep(poll_interval)
except KeyboardInterrupt:
print("\nMonitoring stopped by user.")
return all_alerts
# ── Main ────────────────────────────────────────────────────────────
def main() -> None:
"""Run whale alert monitoring."""
parser = argparse.ArgumentParser(
description="Monitor whale wallets for large transactions"
)
parser.add_argument(
"--demo",
action="store_true",
help="Run with simulated whale alerts (no API keys required)",
)
parser.add_argument(
"--wallets",
type=str,
help="Comma-separated wallet addresses to monitor",
)
parser.add_argument(
"--min-sol",
type=float,
default=DEFAULT_MIN_SOL,
help=f"Minimum SOL value to trigger alert (default: {DEFAULT_MIN_SOL})",
)
parser.add_argument(
"--interval",
type=int,
default=60,
help="Polling interval in seconds (default: 60)",
)
parser.add_argument(
"--max-checks",
type=int,
default=5,
help="Max polling cycles, 0 for unlimited (default: 5)",
)
args = parser.parse_args()
if args.demo:
print("Running in demo mode with simulated whale alerts...")
alerts = generate_demo_alerts()
print_alerts(alerts, is_demo=True)
return
if not args.wallets:
print(
"Error: --wallets required (comma-separated addresses) "
"or use --demo for simulated alerts"
)
sys.exit(1)
if not HELIUS_API_KEY:
print(
"Error: Set HELIUS_API_KEY environment variable for live monitoring."
)
sys.exit(1)
# Parse wallet list into dict (address -> short label)
wallet_list = [w.strip() for w in args.wallets.split(",") if w.strip()]
wallets = {
addr: f"Whale #{i + 1} ({addr[:6]}...)"
for i, addr in enumerate(wallet_list)
}
alerts = monitor_wallets(
wallets=wallets,
min_sol=args.min_sol,
poll_interval=args.interval,
max_checks=args.max_checks,
)
if alerts:
print(f"\nTotal alerts generated: {len(alerts)}")
else:
print("\nNo whale alerts generated during monitoring period.")
if __name__ == "__main__":
main()
Related skills
FAQ
What counts as a whale?
It depends on context: absolute thresholds (e.g. >100 SOL trades) plus relative ones like holding >2% of supply or >5% of daily volume in a single trade.
How does it tell accumulation from distribution?
It scores on-chain heuristics like buy/sell ratio, DCA patterns, dip buying, and transfers to exchange deposit addresses.