
Dex Pool Analysis
- 200 installs
- 257 repo stars
- Updated June 24, 2026
- agiprolabs/claude-trading-skills
dex-pool-analysis is a Claude Code skill that compares AMM pool mechanics across Raydium, Orca, and Meteora, including fee structures, pool types, creation patterns, and volume efficiency.
About
dex-pool-analysis is a Claude Code skill for comparing Solana AMM pool mechanics across Raydium V4/CLMM, Orca Whirlpool, and Meteora DLMM, including fee structures, capital efficiency, and program IDs. It also covers pool health metrics, creation patterns like PumpFun graduation, and best-pool selection for execution. A developer uses it when evaluating which pool to trade through or assessing pool-level risk on Solana.
- Compares AMM pool mechanics across Raydium, Orca Whirlpool, Meteora DLMM, and PumpSwap
- Documents fee structures, LP/protocol splits, and program IDs per DEX
- Covers pool health metrics and PumpFun graduation creation patterns
Dex Pool Analysis by the numbers
- 200 all-time installs (skills.sh)
- Ranked #469 of 1,106 Finance & Trading skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
dex-pool-analysis capabilities & compatibility
Free; analysis skill, though live pool data may need a Solana RPC or DEX API.
- Capabilities
- dex pool analysis · amm comparison · fee tier analysis · pool health metrics · venue selection
- Use cases
- trading · data analysis · research
- Runs
- Runs locally
- Pricing
- Free
What dex-pool-analysis says it does
AMM pool mechanics comparison across Raydium, Orca, and Meteora including fee structures, pool types, creation patterns, and volume efficiency
Uses the classic `xy = k` invariant with a fixed 0.25% swap fee.
Concentrated Liquidity Market Maker pools allow LPs to specify price ranges, improving capital efficiency by 10-100x compared to V4.
npx skills add https://github.com/agiprolabs/claude-trading-skills --skill dex-pool-analysisAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 200 |
|---|---|
| repo stars | ★ 257 |
| Last updated | June 24, 2026 |
| Repository | agiprolabs/claude-trading-skills ↗ |
What it does
Compare Solana AMM pool types, fees, and health metrics to pick execution venues and assess risk.
Who is it for?
Selecting the best Solana execution venue and assessing pool-level fees, capital efficiency, and risk.
Skip if: AMM formula derivation or impermanent-loss math, which the skill defers to the lp-math and impermanent-loss skills.
When should I use this skill?
You need to compare Solana DEX pools by type, fee tier, or health before trading through one.
What you get
A comparison of pool types, fees, and health metrics that informs venue selection and risk assessment.
By the numbers
- Compares 6+ Solana pool types across Raydium, Orca, Meteora, PumpSwap
- Raydium CLMM improves capital efficiency 10-100x over V4
- PumpFun graduates to PumpSwap at ~$69K market cap
Files
DEX Pool Analysis — Solana AMM Pool Mechanics & Comparison
Solana's DEX ecosystem spans multiple AMM designs: constant-product pools (Raydium V4), concentrated liquidity (Raydium CLMM, Orca Whirlpool), and bin-based liquidity (Meteora DLMM). Each pool type has distinct fee structures, capital efficiency characteristics, and risk profiles. Understanding these differences is essential for selecting the best execution venue, evaluating liquidity quality, and identifying pool-level risks.
This skill covers:
- Pool type mechanics and fee structures across Solana DEXes
- Pool health metrics (TVL, volume efficiency, fee APR, LP count)
- Pool creation patterns (PumpFun graduation, manual creation)
- Best pool selection for trade execution
- Pool age and risk assessment
Related skills: See lp-math for AMM formulas, liquidity-analysis for depth assessment, impermanent-loss for LP risk, slippage-modeling for execution cost.
---
1. Pool Types on Solana
Raydium V4 (Constant Product)
The most common pool type for newly launched tokens. Uses the classic xy = k invariant with a fixed 0.25% swap fee. Integrated with OpenBook (formerly Serum) for combined AMM + orderbook liquidity.
Program ID: 675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8
Fee: 0.25% per swap (0.22% to LPs, 0.03% to RAY buyback)Key characteristics:
- Full-range liquidity (infinite price range)
- Simple LP provisioning — deposit both tokens in equal value
- Lower capital efficiency than concentrated liquidity
- OpenBook market ID required for pool creation
Raydium CLMM (Concentrated Liquidity)
Concentrated Liquidity Market Maker pools allow LPs to specify price ranges, improving capital efficiency by 10-100x compared to V4.
Program ID: CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK
Fee tiers: 0.01%, 0.05%, 0.25%, 1%, 2%
Tick spacing: 1, 10, 60, 120, 240 (corresponding to fee tiers)Key characteristics:
- LPs choose min/max price for their position
- Positions represented as NFTs
- Higher fee income per dollar deposited (when in range)
- Risk of position going out of range (no fees earned)
- Multiple fee tiers for different volatility profiles
Orca Whirlpool (Concentrated Liquidity)
Orca's concentrated liquidity implementation, dominant for major token pairs (SOL/USDC, SOL/USDT).
Program ID: whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc
Fee tiers: 0.01%, 0.02%, 0.04%, 0.05%, 0.16%, 0.30%, 0.65%, 1%, 2%
Tick spacing: 1, 2, 4, 8, 16, 64, 128, 256, 512 (varies by fee)Key characteristics:
- Positions as NFTs (similar to Uniswap V3)
- Wide fee tier selection for granular control
- Strong SDK and developer tooling
- Dominant for blue-chip Solana pairs
Meteora DLMM (Dynamic Liquidity Market Maker)
Bin-based liquidity where each bin holds a fixed price. LPs distribute liquidity across bins using strategy modes.
Program ID: LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo
Fee: Dynamic (base fee + variable fee based on volatility)
Bin step: 1-100 basis points per binKey characteristics:
- Discrete price bins instead of continuous ticks
- Dynamic fees that increase during high volatility
- Strategy modes: Spot, Curve, Bid-Ask
- Zero slippage within a single bin
- Extremely capital efficient for stablecoin pairs
Meteora Dynamic Pools
Multi-token pools with single-sided deposit capability and volatility-adjusted fees.
Program ID: Eo7WjKq67rjJQSZxS6z3YkapzY3eMj6Xy8X5EQVn5UaB
Fee: Volatility-based dynamic feeKey characteristics:
- Single-sided deposits allowed
- Dynamic fee based on recent price volatility
- Multi-token pool support
- Simpler LP experience than concentrated liquidity
PumpSwap (PumpFun AMM)
PumpFun's native AMM for tokens that graduate from the bonding curve.
Program ID: PSwapMdSai8tjrEXcxFeQth87xC4rRsa4VA5mhGhXkP
Fee: 0.25% per swap (0.20% to LPs, 0.05% protocol)
Migration fee: 0 SOL (post-March 2025)Key characteristics:
- Constant-product (
xy = k) mechanics - Automatic migration from PumpFun bonding curve at ~$69K market cap
- Creator coin rewards (10% of protocol fees to coin creators)
---
2. Fee Structure Comparison
| DEX | Pool Type | Fee Range | LP Share | Protocol Share |
|---|---|---|---|---|
| Raydium V4 | Constant Product | 0.25% fixed | 0.22% | 0.03% (RAY) |
| Raydium CLMM | Concentrated | 0.01%–2% | ~84% | ~16% |
| Orca Whirlpool | Concentrated | 0.01%–2% | 87% | 13% |
| Meteora DLMM | Bin-based | Dynamic | 80% | 20% |
| Meteora Dynamic | Dynamic | Variable | ~80% | ~20% |
| PumpSwap | Constant Product | 0.25% fixed | 0.20% | 0.05% |
Fee tier selection guidance:
- 0.01%: Stablecoin pairs (USDC/USDT) — minimal price movement
- 0.05%: Correlated assets (mSOL/SOL, jitoSOL/SOL) — low volatility
- 0.25%–0.30%: Standard pairs (SOL/USDC) — moderate volatility
- 1%–2%: Volatile/meme tokens — high impermanent loss risk
---
3. Pool Creation Patterns
PumpFun Graduation Flow
Most new Solana meme tokens follow this lifecycle:
PumpFun Bonding Curve → ~$69K market cap → Migration → Raydium V4 or PumpSwap1. Token launches on PumpFun bonding curve 2. As buys push market cap to ~$69K, the bonding curve completes 3. Liquidity migrates automatically to either Raydium V4 or PumpSwap 4. Since March 2025, PumpFun defaults migration to PumpSwap (their own AMM) 5. Post-migration, additional pools may be created on other DEXes
Analysis implications:
- Pools created via PumpFun graduation have known initial liquidity (~$12K)
- Very new graduated pools carry higher rug risk
- Check if creator LP tokens are locked or burnable
Manual Pool Creation
Tokens not launched via PumpFun have pools created manually:
- Raydium V4 requires an OpenBook market + pool initialization
- Raydium CLMM, Orca, and Meteora allow direct pool creation
- Manual creation allows arbitrary initial liquidity amounts
---
4. Volume Efficiency (Volume/TVL Ratio)
Volume efficiency measures how actively a pool's liquidity is utilized:
volume_efficiency = volume_24h / tvl| V/TVL Ratio | Interpretation |
|---|---|
| > 5.0 | Very high turnover — likely wash trading or bot activity |
| 1.0–5.0 | Active trading — healthy, well-utilized pool |
| 0.1–1.0 | Moderate activity — normal for mid-cap tokens |
| < 0.1 | Low activity — stale or abandoned pool |
| 0.0 | No trades — dead pool |
Fee APR estimation from volume efficiency:
fee_apr = volume_efficiency * fee_rate * 365
# Example: V/TVL of 2.0 at 0.25% fee = 2.0 * 0.0025 * 365 = 182.5% APRThis is a theoretical maximum — actual LP returns depend on impermanent loss, position range (for concentrated liquidity), and fee share.
---
5. Pool Health Metrics
Core Metrics
pool_health = {
"tvl_usd": 150_000, # Total value locked
"volume_24h_usd": 300_000, # 24-hour trading volume
"volume_tvl_ratio": 2.0, # Volume efficiency
"fee_apr_estimate": 182.5, # Annualized fee rate (%)
"pool_age_hours": 720, # Time since creation
"lp_count_estimate": 45, # Number of LP positions
"tvl_trend_24h": -0.05, # TVL change (-5%)
"price_change_24h": 0.12, # Price change (+12%)
}Red Flags
Watch for these warning signs when evaluating pools:
| Red Flag | Threshold | Risk |
|---|---|---|
| Very new pool | < 24 hours old | Rug pull, unvetted token |
| Single LP | LP count = 1 | Creator can pull all liquidity |
| Declining TVL | > 20% drop in 24h | Liquidity flight |
| Zero volume | No trades in 6h+ | Dead or abandoned |
| Extreme V/TVL | > 10x | Wash trading, bot manipulation |
| Tiny TVL | < $1,000 | Massive slippage on any trade |
Health Score Algorithm
def compute_health_score(
tvl_usd: float,
volume_24h: float,
pool_age_hours: float,
lp_count: int,
tvl_change_24h: float,
) -> float:
"""Score from 0-100 indicating pool health.
Components (each 0-20):
- TVL adequacy: Is there enough liquidity?
- Volume efficiency: Is the pool actively traded?
- Maturity: How long has the pool existed?
- LP diversity: How many independent LPs?
- TVL stability: Is liquidity growing or shrinking?
"""
# TVL score (0-20): logarithmic scale, peaks at $1M+
tvl_score = min(20, max(0, 5 * math.log10(max(tvl_usd, 1)) - 10))
# Volume score (0-20): V/TVL ratio, sweet spot 0.5-3.0
v_tvl = volume_24h / max(tvl_usd, 1)
volume_score = min(20, max(0, v_tvl * 10)) if v_tvl < 5 else max(0, 20 - (v_tvl - 5) * 4)
# Age score (0-20): older = more trusted
age_score = min(20, pool_age_hours / 72 * 20) # Max at 72h
# LP diversity score (0-20)
lp_score = min(20, lp_count * 2) # Max at 10 LPs
# Stability score (0-20): penalize large negative TVL changes
stability_score = max(0, 20 + tvl_change_24h * 40) # -50% → 0, 0% → 20
return tvl_score + volume_score + age_score + lp_score + stability_score---
6. Best Pool Selection for Execution
When multiple pools exist for a token pair, select the best one for trade execution:
def rank_pools_for_execution(pools: list[dict], trade_size_usd: float) -> list[dict]:
"""Rank pools by execution quality for a given trade size.
Factors:
1. Sufficient TVL (trade size < 2% of TVL for acceptable slippage)
2. Active volume (recent trades confirm the pool is live)
3. Lowest fee tier (when liquidity is sufficient)
4. Pool type efficiency (concentrated > constant product for same TVL)
5. Pool health score (age, LP count, stability)
"""
for pool in pools:
size_ratio = trade_size_usd / max(pool["tvl_usd"], 1)
pool["estimated_slippage"] = size_ratio * 100 # Rough % estimate
# Prefer pools where trade is < 2% of TVL
pool["size_ok"] = size_ratio < 0.02
# Concentrated liquidity is more efficient
efficiency_mult = 1.0
if pool["pool_type"] in ("clmm", "whirlpool", "dlmm"):
efficiency_mult = 0.3 # ~3x less slippage per TVL dollar
pool["adjusted_slippage"] = pool["estimated_slippage"] * efficiency_mult
pool["execution_score"] = (
(1.0 / max(pool["adjusted_slippage"], 0.001)) * 0.5
+ pool.get("health_score", 50) * 0.3
+ (1.0 / max(pool["fee_rate"], 0.0001)) * 0.2
)
return sorted(pools, key=lambda p: p["execution_score"], reverse=True)---
7. Program IDs Quick Reference
PROGRAM_IDS = {
"raydium_v4": "675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8",
"raydium_clmm": "CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK",
"orca_whirlpool": "whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc",
"meteora_dlmm": "LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo",
"meteora_dynamic": "Eo7WjKq67rjJQSZxS6z3YkapzY3eMj6Xy8X5EQVn5UaB",
"pumpswap": "PSwapMdSai8tjrEXcxFeQth87xC4rRsa4VA5mhGhXkP",
"pumpfun_bonding": "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P",
}---
8. Integration Points
With liquidity-analysis
Use pool analysis to feed liquidity depth assessment. Pool type determines which liquidity model applies (constant product vs concentrated vs bin-based).
With lp-math
Pool type determines which math formulas apply. Raydium V4 uses xy = k, CLMM/Whirlpool use tick-based math, DLMM uses bin math. See lp-math for full derivations.
With slippage-modeling
Best pool selection directly feeds slippage estimation. Concentrated liquidity pools have different slippage curves than constant-product pools.
With jupiter-api
Jupiter aggregates across all pool types automatically. Pool analysis helps understand why Jupiter routes through specific pools and validate route quality.
---
9. Workflow Example
import httpx
# Step 1: Fetch all pools for a token from DexScreener
async def analyze_token_pools(token_mint: str) -> dict:
url = f"https://api.dexscreener.com/tokens/v1/solana/{token_mint}"
async with httpx.AsyncClient() as client:
resp = await client.get(url)
pools = resp.json()
results = []
for pool in pools:
dex = pool.get("dexId", "unknown")
pool_info = {
"dex": dex,
"pool_address": pool.get("pairAddress"),
"tvl_usd": pool.get("liquidity", {}).get("usd", 0),
"volume_24h": pool.get("volume", {}).get("h24", 0),
"age_hours": pool.get("pairCreatedAt", 0),
"fee_rate": estimate_fee_rate(dex),
"pool_type": classify_pool_type(dex),
}
pool_info["volume_efficiency"] = (
pool_info["volume_24h"] / max(pool_info["tvl_usd"], 1)
)
results.append(pool_info)
return {"token": token_mint, "pool_count": len(results), "pools": results}---
Files
References
references/pool_mechanics.md— Detailed mechanics for each pool type with program IDs and formulasreferences/pool_analysis_guide.md— Pool health metrics, red flags, volume efficiency, and best pool selection
Scripts
scripts/analyze_pools.py— Fetch and analyze all pools for a token, rank by execution qualityscripts/pool_monitor.py— Monitor pool metrics over time, detect liquidity events
---
This skill provides analysis tools and information for evaluating DEX pool characteristics. It does not provide financial advice or trading recommendations.
Pool Analysis Guide — Health Metrics, Red Flags & Best Pool Selection
Practical guide for evaluating DEX pool quality, detecting risks, and selecting optimal execution venues.
---
Pool Health Metrics
TVL (Total Value Locked)
Minimum TVL thresholds by trade size:
| Trade Size | Minimum TVL | Rationale |
|---|---|---|
| < $100 | $5,000 | < 2% impact |
| $100–$1K | $50,000 | Acceptable slippage |
| $1K–$10K | $250,000 | Professional execution |
| $10K–$100K | $1,000,000 | Institutional quality |
| > $100K | $5,000,000 | Deep liquidity required |
For concentrated liquidity pools, the relevant metric is liquidity at the current price, not total TVL. A $100K CLMM pool concentrated in a tight range can handle larger trades than a $500K constant-product pool.
Volume/TVL Ratio (Volume Efficiency)
volume_efficiency = volume_24h / tvl| Ratio | Classification | Interpretation |
|---|---|---|
| > 10.0 | Extreme | Almost certainly wash trading or bot loops |
| 5.0–10.0 | Very high | Suspicious — verify with tx analysis |
| 1.0–5.0 | High | Active, healthy pool |
| 0.3–1.0 | Moderate | Normal for mid-cap tokens |
| 0.05–0.3 | Low | Light trading, may have wide spreads |
| < 0.05 | Very low | Stale pool — check if still active |
Fee APR Estimate
# For constant-product pools
fee_apr = (volume_24h * fee_rate * 365) / tvl * 100
# For concentrated liquidity (position-specific)
fee_apr = (volume_24h * fee_rate * 365) / position_value * concentration_factor * 100Note: Fee APR does not account for impermanent loss. Net LP return = Fee APR - IL.
Pool Age
pool_age_hours = (current_time - pool_created_at) / 3600| Age | Risk Level | Notes |
|---|---|---|
| < 1 hour | Very high | Sniper bots active, volatile |
| 1–6 hours | High | Initial price discovery ongoing |
| 6–24 hours | Elevated | Settling but still risky |
| 1–7 days | Moderate | Established but young |
| > 7 days | Lower | Track record available |
| > 30 days | Base | Enough history for analysis |
LP Count
Number of distinct liquidity providers. Higher LP count indicates more distributed (safer) liquidity.
| LP Count | Risk |
|---|---|
| 1 | Critical — single entity controls all liquidity |
| 2–5 | High — few LPs, easy to rug |
| 5–20 | Moderate — reasonable distribution |
| 20–100 | Low — well distributed |
| > 100 | Very low — organic, mature pool |
---
Red Flags Checklist
Critical (Do Not Trade)
1. Single LP with > 90% of liquidity: Creator can rug by removing liquidity 2. Pool age < 5 minutes with > $50K TVL: Likely still in sniper phase 3. TVL dropped > 50% in last hour: Active liquidity removal in progress 4. Zero volume for 24+ hours on a token with market cap > $100K: Possible frozen/broken pool
Warning (Proceed with Caution)
5. V/TVL > 10: Probable wash trading — real liquidity may be much lower 6. Pool age < 24 hours: Limited price history, higher manipulation risk 7. TVL < $10,000: High slippage even on small trades 8. Only one pool exists: No alternative venue if pool is compromised 9. LP tokens not burned/locked: Creator can remove liquidity at any time
Informational
10. Multiple pools on different DEXes: Normal — compare for best execution 11. V/TVL < 0.1: Low activity but not necessarily dangerous 12. Fee tier mismatch (e.g., 0.01% fee on a meme token): Pool may not attract LPs
---
Pool Creation Patterns
PumpFun Graduation Flow
Bonding Curve Phase → ~$69K Market Cap → Migration → AMM PoolTimeline analysis: 1. T=0: Token deploys on PumpFun bonding curve 2. T=minutes to hours: Trading on bonding curve (no traditional LP) 3. T=graduation: Market cap hits ~$69K, migration triggered 4. T=graduation+seconds: Pool created on Raydium V4 or PumpSwap 5. T=graduation+minutes: Sniper bots front-run organic traders 6. T=graduation+1h: Initial volatility settling
Key checks after graduation:
- Was the migration to Raydium V4 or PumpSwap?
- Is initial LP burned? (Check burn transaction)
- How many unique wallets traded in first hour?
- Did creator wallets sell in first 10 minutes?
Organic Pool Creation
Non-PumpFun tokens may have pools created manually on any DEX:
- Check who created the pool (team wallet vs random)
- Verify initial liquidity amount is reasonable
- Look for simultaneous pool creation on multiple DEXes (professional launch)
---
Best Pool Selection Algorithm
When a token has multiple pools across DEXes, use this ranking process:
Step 1: Filter by Minimum Requirements
def passes_minimum(pool: dict, trade_size_usd: float) -> bool:
"""Check if pool meets minimum requirements for execution."""
min_tvl = trade_size_usd * 50 # Trade must be < 2% of TVL
return (
pool["tvl_usd"] >= min_tvl
and pool["volume_24h"] > 0 # Must have recent activity
and pool["pool_age_hours"] > 1 # Not brand new
)Step 2: Estimate Execution Cost
def estimate_execution_cost(pool: dict, trade_size_usd: float) -> float:
"""Estimate total cost: fee + slippage."""
fee_cost = trade_size_usd * pool["fee_rate"]
# Slippage depends on pool type
if pool["pool_type"] in ("clmm", "whirlpool", "dlmm"):
# Concentrated liquidity — lower slippage per TVL
slippage_pct = (trade_size_usd / pool["tvl_usd"]) * 0.3
else:
# Constant product — standard slippage
slippage_pct = trade_size_usd / pool["tvl_usd"]
slippage_cost = trade_size_usd * slippage_pct
return fee_cost + slippage_costStep 3: Score and Rank
def rank_pools(pools: list[dict], trade_size_usd: float) -> list[dict]:
"""Rank pools by total execution quality."""
valid = [p for p in pools if passes_minimum(p, trade_size_usd)]
for pool in valid:
cost = estimate_execution_cost(pool, trade_size_usd)
health = compute_health_score(pool)
# Lower cost is better (invert for scoring)
pool["cost_score"] = 1.0 / max(cost, 0.01)
pool["health_score"] = health
pool["total_score"] = pool["cost_score"] * 0.6 + health * 0.4
return sorted(valid, key=lambda p: p["total_score"], reverse=True)Step 4: Verify Route
For the top-ranked pool, verify:
- Pool has had a trade in the last 30 minutes (still active)
- Current price in pool matches other pools within 2% (no stale pricing)
- Sufficient balance in the pool for the quote token you want to receive
---
Practical Workflow: Token Pool Analysis
# 1. Fetch all pools for a token
pools = fetch_pools_from_dexscreener(token_mint)
# 2. Classify each pool
for pool in pools:
pool["pool_type"] = classify_by_dex(pool["dex_id"])
pool["health"] = compute_health_score(pool)
pool["flags"] = check_red_flags(pool)
# 3. Filter out critical-flag pools
safe_pools = [p for p in pools if not p["flags"]["critical"]]
# 4. Rank for execution
ranked = rank_pools(safe_pools, trade_size_usd=500)
# 5. Select best pool
best = ranked[0] if ranked else None---
This guide provides analytical frameworks for pool evaluation. It does not constitute financial advice or trading recommendations.
DEX Pool Mechanics — Solana AMM Comparison
Detailed mechanics for each major AMM pool type on Solana, including formulas, fee structures, and program IDs.
---
Raydium V4 (Constant Product)
Program ID: 675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8
Mechanics
- Uses the constant-product invariant:
x * y = k - Full-range liquidity — every LP position covers price 0 to infinity
- Integrated with OpenBook (Serum successor) for hybrid AMM + orderbook routing
Fee Structure
- Swap fee: 0.25% per trade
- LP share: 0.22% (88% of fee)
- Protocol share: 0.03% used for RAY token buyback
Pool Creation
- Requires an OpenBook market ID
- Initial liquidity set by creator (both tokens deposited in equal USD value)
- Pool creation fee: ~0.4 SOL (for OpenBook market + pool accounts)
Swap Formula
output = (reserve_out * amount_in * 9975) / (reserve_in * 10000 + amount_in * 9975)The 9975/10000 factor encodes the 0.25% fee.
Price Impact
price_impact = amount_in / (reserve_in + amount_in)For a $1,000 trade against a pool with $100,000 in reserves: ~1% price impact.
---
Raydium CLMM (Concentrated Liquidity)
Program ID: CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK
Mechanics
- Concentrated liquidity (Uniswap V3-style) on Solana
- LPs choose a price range [P_low, P_high] for their position
- Liquidity only active when current price is within range
- Position represented as an NFT
Fee Tiers and Tick Spacing
| Fee Tier | Tick Spacing | Use Case |
|---|---|---|
| 0.01% | 1 | Stablecoin pairs |
| 0.05% | 10 | Correlated assets (LSTs) |
| 0.25% | 60 | Standard pairs |
| 1.00% | 120 | Volatile pairs |
| 2.00% | 240 | Very volatile / meme tokens |
Price-Tick Relationship
price = 1.0001^tick
tick = log(price) / log(1.0001)Liquidity Concentration Factor
concentration = sqrt(P_high / P_low) / (sqrt(P_high / P_low) - 1)A position covering a +/-5% range has ~10x concentration vs full range. A +/-1% range has ~50x.
Capital Efficiency
For a given TVL, concentrated positions provide more liquidity depth at the current price:
effective_liquidity = tvl * concentration_factor---
Orca Whirlpool (Concentrated Liquidity)
Program ID: whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc
Mechanics
- Concentrated liquidity similar to Raydium CLMM
- Positions as NFTs with per-position fee tracking
- Mature SDK with strong TypeScript and Rust support
Fee Tiers
| Fee Tier | Tick Spacing | Typical Use |
|---|---|---|
| 0.01% | 1 | Stable pairs |
| 0.02% | 2 | Tightly correlated |
| 0.04% | 4 | LST pairs |
| 0.05% | 8 | Correlated assets |
| 0.16% | 16 | Mid-vol pairs |
| 0.30% | 64 | Standard pairs |
| 0.65% | 128 | Higher vol |
| 1.00% | 256 | Volatile pairs |
| 2.00% | 512 | Meme / high vol |
Fee Distribution
- LP share: ~87% of swap fees
- Protocol share: ~13% to Orca treasury
Key Differences from Raydium CLMM
- More fee tier options (9 vs 5)
- Different tick spacing mapping
- Separate reward mechanism for liquidity mining
- Generally higher TVL for major pairs (SOL/USDC, SOL/USDT)
---
Meteora DLMM (Dynamic Liquidity Market Maker)
Program ID: LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo
Mechanics
- Bin-based liquidity: each bin holds liquidity at a single discrete price
- Current active bin determines the current price
- LPs distribute tokens across multiple bins using strategy modes
- Zero slippage for trades that fit within a single bin
Bin System
bin_price = (1 + bin_step / 10000) ^ (bin_id - 8388608)bin_step: 1-100 basis points between adjacent binsbin_id: integer index, 8388608 is the "zero" bin (price = 1.0)
Strategy Modes
| Mode | Distribution | Best For |
|---|---|---|
| Spot | Uniform across bins | General purpose, balanced exposure |
| Curve | Gaussian/bell curve around current price | Concentrated around expected price |
| Bid-Ask | Heavy on outer bins | Market-making, earning on mean reversion |
Dynamic Fee
total_fee = base_fee + variable_fee
variable_fee = bin_step * volatility_accumulatorThe variable fee increases during high-volatility periods (many bin crossings in recent swaps) and decays over time. This protects LPs from adverse selection during volatile moves.
Fee Distribution
- LP share: ~80% of total fee
- Protocol share: ~20% to Meteora treasury
---
Meteora Dynamic Pools
Program ID: Eo7WjKq67rjJQSZxS6z3YkapzY3eMj6Xy8X5EQVn5UaB
Mechanics
- Constant-product base with dynamic fee adjustment
- Single-sided deposits permitted (the protocol rebalances internally)
- Fees adjust based on recent price volatility
Key Features
- Lower barrier for LPs (no price range selection required)
- Volatility-based fee: higher fees during volatile periods, lower during calm
- Virtual price mechanism for fair LP token valuation
---
PumpSwap
Program ID: PSwapMdSai8tjrEXcxFeQth87xC4rRsa4VA5mhGhXkP
Mechanics
- Standard constant-product AMM (
xy = k) - Designed as the destination for PumpFun bonding curve graduations
- Launched March 2025, replacing Raydium V4 as default migration target
Fee Structure
- Swap fee: 0.25% per trade
- LP share: 0.20% (80%)
- Protocol share: 0.05% (20%), with 10% of protocol fees going to coin creators
Migration Details
- Migration from bonding curve is automatic at ~$69K market cap
- Migration fee: 0 SOL (reduced from 6 SOL in original Raydium migration)
- Initial pool liquidity: ~85 SOL + all remaining tokens from bonding curve
---
Program ID Quick Reference
| DEX | Program ID |
|---|---|
| Raydium V4 | 675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8 |
| Raydium CLMM | CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK |
| Orca Whirlpool | whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc |
| Meteora DLMM | LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo |
| Meteora Dynamic | Eo7WjKq67rjJQSZxS6z3YkapzY3eMj6Xy8X5EQVn5UaB |
| PumpSwap | PSwapMdSai8tjrEXcxFeQth87xC4rRsa4VA5mhGhXkP |
| PumpFun Bonding | 6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P |
---
Comparison Summary
| Feature | Raydium V4 | Raydium CLMM | Orca Whirlpool | Meteora DLMM | PumpSwap |
|---|---|---|---|---|---|
| Model | xy=k | Concentrated | Concentrated | Bin-based | xy=k |
| Fee | 0.25% fixed | 0.01-2% | 0.01-2% | Dynamic | 0.25% fixed |
| Capital efficiency | 1x | 10-100x | 10-100x | 10-50x | 1x |
| LP complexity | Low | High | High | Medium | Low |
| Slippage model | Continuous | Tick-based | Tick-based | Bin-based | Continuous |
| Position type | LP tokens | NFT | NFT | NFT | LP tokens |
| Dynamic fee | No | No | No | Yes | No |
This reference provides technical information about pool mechanics. It does not constitute financial advice.
#!/usr/bin/env python3
"""Analyze all DEX pools for a Solana token and rank them by execution quality.
Fetches pool data from DexScreener (free, no API key required), computes
health scores, flags risks, and ranks pools for optimal trade execution.
Usage:
python scripts/analyze_pools.py <TOKEN_MINT>
python scripts/analyze_pools.py --demo
Dependencies:
uv pip install httpx
Environment Variables:
None required (DexScreener API is free and keyless).
"""
import argparse
import json
import math
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 ───────────────────────────────────────────────────
DEXSCREENER_BASE = "https://api.dexscreener.com"
REQUEST_TIMEOUT = 15.0
PROGRAM_IDS = {
"raydium_v4": "675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8",
"raydium_clmm": "CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK",
"orca_whirlpool": "whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc",
"meteora_dlmm": "LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo",
"meteora_dynamic": "Eo7WjKq67rjJQSZxS6z3YkapzY3eMj6Xy8X5EQVn5UaB",
"pumpswap": "PSwapMdSai8tjrEXcxFeQth87xC4rRsa4VA5mhGhXkP",
}
# Fee rates by DEX (used when DexScreener doesn't report fees)
DEFAULT_FEE_RATES: dict[str, float] = {
"raydium": 0.0025,
"raydium_clmm": 0.0025,
"orca": 0.003,
"meteora": 0.003,
"pumpswap": 0.0025,
}
# ── Data Models ─────────────────────────────────────────────────────
@dataclass
class PoolInfo:
"""Parsed pool information with computed metrics."""
pool_address: str
dex_id: str
dex_name: str
pool_type: str
base_token: str
quote_token: str
base_symbol: str
quote_symbol: str
price_usd: float
tvl_usd: float
volume_24h: float
volume_6h: float
volume_1h: float
price_change_24h: float
pool_age_hours: float
fee_rate: float
txn_count_24h: int
buys_24h: int
sells_24h: int
url: str
# Computed metrics
volume_efficiency: float = 0.0
fee_apr_estimate: float = 0.0
health_score: float = 0.0
execution_score: float = 0.0
red_flags: list[str] = field(default_factory=list)
# ── Pool Classification ────────────────────────────────────────────
def classify_pool_type(dex_id: str) -> str:
"""Classify pool type from DexScreener dex ID.
Args:
dex_id: DexScreener DEX identifier string.
Returns:
Pool type classification string.
"""
dex_lower = dex_id.lower()
if "raydium" in dex_lower and "clmm" in dex_lower:
return "clmm"
if "raydium" in dex_lower and "cpmm" in dex_lower:
return "cpmm"
if "raydium" in dex_lower:
return "constant_product"
if "orca" in dex_lower or "whirlpool" in dex_lower:
return "whirlpool"
if "meteora" in dex_lower and "dlmm" in dex_lower:
return "dlmm"
if "meteora" in dex_lower:
return "dynamic"
if "pump" in dex_lower:
return "constant_product"
return "unknown"
def estimate_fee_rate(dex_id: str) -> float:
"""Estimate the swap fee rate based on DEX identifier.
Args:
dex_id: DexScreener DEX identifier string.
Returns:
Estimated fee rate as a decimal (e.g., 0.0025 for 0.25%).
"""
dex_lower = dex_id.lower()
for key, rate in DEFAULT_FEE_RATES.items():
if key in dex_lower:
return rate
return 0.003 # Conservative default
# ── Health Scoring ──────────────────────────────────────────────────
def compute_health_score(pool: PoolInfo) -> float:
"""Compute a 0-100 health score for a pool.
Components (each 0-20 points):
- TVL adequacy: logarithmic scale, peaks at $1M+
- Volume efficiency: sweet spot 0.5-3.0 V/TVL ratio
- Pool maturity: older pools score higher (max at 72h)
- Transaction diversity: buy/sell balance
- TVL stability: penalize negative price change as proxy
Args:
pool: PoolInfo with all metrics populated.
Returns:
Health score from 0 to 100.
"""
# TVL score (0-20): log scale, peaks at $1M+
if pool.tvl_usd <= 0:
tvl_score = 0.0
else:
tvl_score = min(20.0, max(0.0, 5.0 * math.log10(pool.tvl_usd) - 10.0))
# Volume score (0-20): V/TVL sweet spot 0.5-3.0
v_tvl = pool.volume_efficiency
if v_tvl < 5.0:
volume_score = min(20.0, max(0.0, v_tvl * 10.0))
else:
volume_score = max(0.0, 20.0 - (v_tvl - 5.0) * 4.0)
# Age score (0-20): max at 72 hours
age_score = min(20.0, pool.pool_age_hours / 72.0 * 20.0)
# Transaction diversity score (0-20): balanced buys/sells
total_txns = pool.buys_24h + pool.sells_24h
if total_txns > 0:
buy_ratio = pool.buys_24h / total_txns
# Perfect balance = 0.5, score drops for imbalance
balance = 1.0 - abs(buy_ratio - 0.5) * 2.0
txn_score = min(20.0, balance * 15.0 + min(5.0, total_txns / 200.0 * 5.0))
else:
txn_score = 0.0
# Stability score (0-20): penalize extreme price moves
abs_change = abs(pool.price_change_24h)
stability_score = max(0.0, 20.0 - abs_change * 0.4)
return tvl_score + volume_score + age_score + txn_score + stability_score
def check_red_flags(pool: PoolInfo) -> list[str]:
"""Check pool for warning signs and risk indicators.
Args:
pool: PoolInfo with all metrics populated.
Returns:
List of red flag description strings.
"""
flags: list[str] = []
if pool.tvl_usd < 1_000:
flags.append("CRITICAL: TVL below $1,000 — extreme slippage risk")
elif pool.tvl_usd < 10_000:
flags.append("WARNING: TVL below $10,000 — high slippage on moderate trades")
if pool.pool_age_hours < 1:
flags.append("CRITICAL: Pool is less than 1 hour old — sniper risk")
elif pool.pool_age_hours < 24:
flags.append("WARNING: Pool is less than 24 hours old — limited history")
if pool.volume_efficiency > 10:
flags.append("WARNING: V/TVL > 10 — possible wash trading")
elif pool.volume_efficiency > 5:
flags.append("NOTICE: V/TVL > 5 — unusually high turnover")
if pool.volume_24h == 0 and pool.pool_age_hours > 6:
flags.append("WARNING: Zero 24h volume — pool may be dead")
total_txns = pool.buys_24h + pool.sells_24h
if total_txns > 0:
buy_ratio = pool.buys_24h / total_txns
if buy_ratio > 0.9:
flags.append("WARNING: >90% buys — possible coordinated pump")
elif buy_ratio < 0.1:
flags.append("WARNING: >90% sells — possible dump in progress")
if pool.price_change_24h < -50:
flags.append("CRITICAL: Price dropped >50% in 24h")
elif pool.price_change_24h < -20:
flags.append("WARNING: Price dropped >20% in 24h")
return flags
# ── Execution Ranking ───────────────────────────────────────────────
def estimate_slippage(pool: PoolInfo, trade_size_usd: float) -> float:
"""Estimate slippage percentage for a trade of the given size.
Concentrated liquidity pools have better capital efficiency, resulting
in lower slippage per TVL dollar.
Args:
pool: PoolInfo with pool type and TVL.
trade_size_usd: Size of the trade in USD.
Returns:
Estimated slippage as a percentage (e.g., 1.0 = 1%).
"""
if pool.tvl_usd <= 0:
return 100.0
base_slippage = (trade_size_usd / pool.tvl_usd) * 100.0
# Concentrated liquidity pools have better capital efficiency
if pool.pool_type in ("clmm", "whirlpool", "dlmm"):
return base_slippage * 0.3 # ~3x more efficient
return base_slippage
def rank_pools_for_execution(
pools: list[PoolInfo], trade_size_usd: float
) -> list[PoolInfo]:
"""Rank pools by execution quality for a given trade size.
Scoring weights:
- 50%: Execution cost (fee + estimated slippage) — lower is better
- 30%: Health score — higher is better
- 20%: Fee rate — lower is better
Args:
pools: List of PoolInfo objects to rank.
trade_size_usd: Trade size in USD for slippage estimation.
Returns:
Pools sorted by execution score, best first.
"""
for pool in pools:
slippage_pct = estimate_slippage(pool, trade_size_usd)
fee_cost = pool.fee_rate * 100.0 # As percentage
total_cost = slippage_pct + fee_cost
# Lower cost is better — invert for scoring
cost_score = 1.0 / max(total_cost, 0.001) * 10.0
# Normalize health (0-100 → 0-10)
health_norm = pool.health_score / 10.0
# Lower fee is better — invert for scoring
fee_score = 1.0 / max(pool.fee_rate, 0.0001)
pool.execution_score = cost_score * 0.5 + health_norm * 0.3 + fee_score * 0.0002
pools.sort(key=lambda p: p.execution_score, reverse=True)
return pools
# ── Data Fetching ───────────────────────────────────────────────────
def fetch_pools(token_mint: str) -> list[PoolInfo]:
"""Fetch all Solana pools for a token from DexScreener.
Args:
token_mint: Solana token mint address.
Returns:
List of PoolInfo objects.
Raises:
httpx.HTTPStatusError: On non-2xx response from DexScreener.
"""
url = f"{DEXSCREENER_BASE}/tokens/v1/solana/{token_mint}"
with httpx.Client(timeout=REQUEST_TIMEOUT) as client:
resp = client.get(url)
resp.raise_for_status()
raw_pools = resp.json()
if not isinstance(raw_pools, list):
print(f"Unexpected response format: {type(raw_pools)}")
return []
pools: list[PoolInfo] = []
now_ms = int(time.time() * 1000)
for p in raw_pools:
# Skip non-Solana pools
if p.get("chainId", "") != "solana":
continue
created_at = p.get("pairCreatedAt", now_ms)
age_hours = max(0, (now_ms - created_at) / 3_600_000)
liquidity = p.get("liquidity") or {}
volume = p.get("volume") or {}
price_change = p.get("priceChange") or {}
txns = p.get("txns") or {}
txns_24h = txns.get("h24") or {}
dex_id = p.get("dexId", "unknown")
pool_info = PoolInfo(
pool_address=p.get("pairAddress", ""),
dex_id=dex_id,
dex_name=p.get("dexId", "unknown"),
pool_type=classify_pool_type(dex_id),
base_token=p.get("baseToken", {}).get("address", ""),
quote_token=p.get("quoteToken", {}).get("address", ""),
base_symbol=p.get("baseToken", {}).get("symbol", "???"),
quote_symbol=p.get("quoteToken", {}).get("symbol", "???"),
price_usd=float(p.get("priceUsd", 0) or 0),
tvl_usd=float(liquidity.get("usd", 0) or 0),
volume_24h=float(volume.get("h24", 0) or 0),
volume_6h=float(volume.get("h6", 0) or 0),
volume_1h=float(volume.get("h1", 0) or 0),
price_change_24h=float(price_change.get("h24", 0) or 0),
pool_age_hours=age_hours,
fee_rate=estimate_fee_rate(dex_id),
txn_count_24h=int(txns_24h.get("buys", 0) or 0)
+ int(txns_24h.get("sells", 0) or 0),
buys_24h=int(txns_24h.get("buys", 0) or 0),
sells_24h=int(txns_24h.get("sells", 0) or 0),
url=p.get("url", ""),
)
# Compute derived metrics
pool_info.volume_efficiency = (
pool_info.volume_24h / pool_info.tvl_usd if pool_info.tvl_usd > 0 else 0.0
)
pool_info.fee_apr_estimate = (
pool_info.volume_efficiency * pool_info.fee_rate * 365 * 100
)
pool_info.health_score = compute_health_score(pool_info)
pool_info.red_flags = check_red_flags(pool_info)
pools.append(pool_info)
return pools
# ── Demo Data ───────────────────────────────────────────────────────
def generate_demo_pools() -> list[PoolInfo]:
"""Generate synthetic multi-pool data for demo mode.
Creates a realistic scenario: a token with pools across multiple DEXes
at different stages of maturity and health.
Returns:
List of synthetic PoolInfo objects.
"""
demo_pools_raw = [
{
"name": "Raydium V4 (Main)",
"pool_address": "DemoPool1111111111111111111111111111111111111",
"dex_id": "raydium",
"pool_type": "constant_product",
"tvl_usd": 250_000,
"volume_24h": 500_000,
"volume_6h": 120_000,
"volume_1h": 18_000,
"price_change_24h": 5.2,
"pool_age_hours": 720,
"fee_rate": 0.0025,
"buys_24h": 1200,
"sells_24h": 980,
},
{
"name": "Orca Whirlpool (CL)",
"pool_address": "DemoPool2222222222222222222222222222222222222",
"dex_id": "orca",
"pool_type": "whirlpool",
"tvl_usd": 180_000,
"volume_24h": 420_000,
"volume_6h": 100_000,
"volume_1h": 15_000,
"price_change_24h": 5.1,
"pool_age_hours": 480,
"fee_rate": 0.003,
"buys_24h": 800,
"sells_24h": 750,
},
{
"name": "Meteora DLMM",
"pool_address": "DemoPool3333333333333333333333333333333333333",
"dex_id": "meteora_dlmm",
"pool_type": "dlmm",
"tvl_usd": 85_000,
"volume_24h": 200_000,
"volume_6h": 55_000,
"volume_1h": 8_000,
"price_change_24h": 4.8,
"pool_age_hours": 168,
"fee_rate": 0.003,
"buys_24h": 400,
"sells_24h": 380,
},
{
"name": "Raydium CLMM",
"pool_address": "DemoPool4444444444444444444444444444444444444",
"dex_id": "raydium_clmm",
"pool_type": "clmm",
"tvl_usd": 60_000,
"volume_24h": 150_000,
"volume_6h": 35_000,
"volume_1h": 5_000,
"price_change_24h": 5.5,
"pool_age_hours": 96,
"fee_rate": 0.0025,
"buys_24h": 300,
"sells_24h": 280,
},
{
"name": "PumpSwap (New)",
"pool_address": "DemoPool5555555555555555555555555555555555555",
"dex_id": "pumpswap",
"pool_type": "constant_product",
"tvl_usd": 12_000,
"volume_24h": 180_000,
"volume_6h": 80_000,
"volume_1h": 25_000,
"price_change_24h": 45.0,
"pool_age_hours": 3,
"fee_rate": 0.0025,
"buys_24h": 2500,
"sells_24h": 500,
},
{
"name": "Dead Pool (Raydium V4)",
"pool_address": "DemoPool6666666666666666666666666666666666666",
"dex_id": "raydium",
"pool_type": "constant_product",
"tvl_usd": 500,
"volume_24h": 0,
"volume_6h": 0,
"volume_1h": 0,
"price_change_24h": -85.0,
"pool_age_hours": 2400,
"fee_rate": 0.0025,
"buys_24h": 0,
"sells_24h": 0,
},
]
pools: list[PoolInfo] = []
for d in demo_pools_raw:
pool = PoolInfo(
pool_address=d["pool_address"],
dex_id=d["dex_id"],
dex_name=d["name"],
pool_type=d["pool_type"],
base_token="DemoMint1111111111111111111111111111111111111",
quote_token="So11111111111111111111111111111111111111112",
base_symbol="DEMO",
quote_symbol="SOL",
price_usd=0.00042,
tvl_usd=d["tvl_usd"],
volume_24h=d["volume_24h"],
volume_6h=d["volume_6h"],
volume_1h=d["volume_1h"],
price_change_24h=d["price_change_24h"],
pool_age_hours=d["pool_age_hours"],
fee_rate=d["fee_rate"],
txn_count_24h=d["buys_24h"] + d["sells_24h"],
buys_24h=d["buys_24h"],
sells_24h=d["sells_24h"],
url=f"https://dexscreener.com/solana/{d['pool_address']}",
)
pool.volume_efficiency = (
pool.volume_24h / pool.tvl_usd if pool.tvl_usd > 0 else 0.0
)
pool.fee_apr_estimate = pool.volume_efficiency * pool.fee_rate * 365 * 100
pool.health_score = compute_health_score(pool)
pool.red_flags = check_red_flags(pool)
pools.append(pool)
return pools
# ── Display ─────────────────────────────────────────────────────────
def print_pool_summary(pool: PoolInfo, rank: int) -> None:
"""Print a formatted summary of a single pool.
Args:
pool: PoolInfo to display.
rank: Display rank number.
"""
print(f"\n{'='*70}")
print(f" #{rank} {pool.dex_name} — {pool.base_symbol}/{pool.quote_symbol}")
print(f"{'='*70}")
print(f" Pool Type: {pool.pool_type}")
print(f" Pool Address: {pool.pool_address[:20]}...")
print(f" Price (USD): ${pool.price_usd:.8f}")
print(f" TVL: ${pool.tvl_usd:,.0f}")
print(f" Volume (24h): ${pool.volume_24h:,.0f}")
print(f" Volume (1h): ${pool.volume_1h:,.0f}")
print(f" V/TVL Ratio: {pool.volume_efficiency:.2f}")
print(f" Fee Rate: {pool.fee_rate*100:.2f}%")
print(f" Fee APR Est: {pool.fee_apr_estimate:.1f}%")
print(f" Pool Age: {pool.pool_age_hours:.0f} hours ({pool.pool_age_hours/24:.1f} days)")
print(f" Txns (24h): {pool.txn_count_24h} (buys: {pool.buys_24h}, sells: {pool.sells_24h})")
print(f" Price Δ (24h): {pool.price_change_24h:+.1f}%")
print(f" Health Score: {pool.health_score:.1f}/100")
print(f" Exec Score: {pool.execution_score:.2f}")
if pool.red_flags:
print(f"\n Red Flags:")
for flag in pool.red_flags:
print(f" - {flag}")
else:
print(f"\n Red Flags: None")
def print_execution_recommendation(
pools: list[PoolInfo], trade_size_usd: float
) -> None:
"""Print execution recommendation based on ranked pools.
Args:
pools: Ranked list of pools (best first).
trade_size_usd: Trade size used for ranking.
"""
print(f"\n{'='*70}")
print(f" EXECUTION ANALYSIS (trade size: ${trade_size_usd:,.0f})")
print(f"{'='*70}")
viable = [p for p in pools if p.tvl_usd >= trade_size_usd * 50]
if not viable:
print("\n No pools have sufficient TVL for this trade size.")
print(f" Minimum TVL needed: ${trade_size_usd * 50:,.0f}")
print(" Consider reducing trade size or splitting across pools.")
return
best = viable[0]
slippage = estimate_slippage(best, trade_size_usd)
total_cost = slippage + best.fee_rate * 100
print(f"\n Recommended Pool: {best.dex_name}")
print(f" Pool Type: {best.pool_type}")
print(f" Est. Slippage: {slippage:.3f}%")
print(f" Fee: {best.fee_rate*100:.2f}%")
print(f" Total Est. Cost: {total_cost:.3f}% (${trade_size_usd * total_cost / 100:,.2f})")
if best.red_flags:
print(f"\n Caution — flags on recommended pool:")
for flag in best.red_flags:
print(f" - {flag}")
if len(viable) > 1:
print(f"\n Alternative pools ({len(viable)-1}):")
for alt in viable[1:3]:
alt_slip = estimate_slippage(alt, trade_size_usd)
alt_cost = alt_slip + alt.fee_rate * 100
print(f" - {alt.dex_name}: ~{alt_cost:.3f}% total cost")
def print_report(pools: list[PoolInfo], trade_size_usd: float) -> None:
"""Print full analysis report for all pools.
Args:
pools: List of PoolInfo objects (will be ranked internally).
trade_size_usd: Trade size for execution analysis.
"""
if not pools:
print("No pools found.")
return
print(f"\n{'#'*70}")
print(f" DEX POOL ANALYSIS REPORT")
print(f" Token: {pools[0].base_symbol}/{pools[0].quote_symbol}")
print(f" Pools found: {len(pools)}")
print(f"{'#'*70}")
# Rank pools for execution
ranked = rank_pools_for_execution(pools, trade_size_usd)
# Print each pool
for i, pool in enumerate(ranked, 1):
print_pool_summary(pool, i)
# Execution recommendation
print_execution_recommendation(ranked, trade_size_usd)
# Summary statistics
total_tvl = sum(p.tvl_usd for p in pools)
total_vol = sum(p.volume_24h for p in pools)
critical_flags = sum(
1 for p in pools for f in p.red_flags if f.startswith("CRITICAL")
)
print(f"\n{'='*70}")
print(f" SUMMARY")
print(f"{'='*70}")
print(f" Total TVL across pools: ${total_tvl:,.0f}")
print(f" Total Volume (24h): ${total_vol:,.0f}")
print(f" Aggregate V/TVL: {total_vol / max(total_tvl, 1):.2f}")
print(f" Pools with critical flags: {critical_flags}/{len(pools)}")
print(f" Pool types: {', '.join(sorted(set(p.pool_type for p in pools)))}")
print(f"\n Note: This analysis provides information only, not financial advice.")
print(f" Always verify pool data on-chain before executing trades.\n")
# ── Main ────────────────────────────────────────────────────────────
def main() -> None:
"""Entry point: parse arguments and run pool analysis."""
parser = argparse.ArgumentParser(
description="Analyze DEX pools for a Solana token"
)
parser.add_argument(
"token_mint",
nargs="?",
default=None,
help="Solana token mint address to analyze",
)
parser.add_argument(
"--demo",
action="store_true",
help="Run with synthetic demo data (no API calls)",
)
parser.add_argument(
"--trade-size",
type=float,
default=500.0,
help="Trade size in USD for execution analysis (default: 500)",
)
parser.add_argument(
"--json",
action="store_true",
help="Output results as JSON",
)
args = parser.parse_args()
if args.demo:
print("Running in demo mode with synthetic pool data...\n")
pools = generate_demo_pools()
elif args.token_mint:
print(f"Fetching pools for {args.token_mint}...")
try:
pools = fetch_pools(args.token_mint)
except httpx.HTTPStatusError as e:
print(f"API error: {e.response.status_code} — {e.response.text[:200]}")
sys.exit(1)
except httpx.RequestError as e:
print(f"Network error: {e}")
sys.exit(1)
else:
parser.print_help()
print("\nExamples:")
print(" python scripts/analyze_pools.py --demo")
print(" python scripts/analyze_pools.py <TOKEN_MINT_ADDRESS>")
sys.exit(1)
if args.json:
output = []
for p in pools:
output.append({
"pool_address": p.pool_address,
"dex": p.dex_name,
"pool_type": p.pool_type,
"tvl_usd": p.tvl_usd,
"volume_24h": p.volume_24h,
"volume_efficiency": round(p.volume_efficiency, 4),
"fee_apr_estimate": round(p.fee_apr_estimate, 2),
"health_score": round(p.health_score, 2),
"red_flags": p.red_flags,
})
print(json.dumps(output, indent=2))
else:
print_report(pools, args.trade_size)
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""Monitor DEX pool metrics over time and detect liquidity events.
Tracks TVL changes, volume trends, and new pool creation for a Solana token.
In demo mode, simulates a pool lifecycle from creation through maturity,
including liquidity addition, removal events, and volume spikes.
Usage:
python scripts/pool_monitor.py <TOKEN_MINT> --interval 60
python scripts/pool_monitor.py --demo
Dependencies:
uv pip install httpx
Environment Variables:
None required (DexScreener API is free and keyless).
"""
import argparse
import math
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 ───────────────────────────────────────────────────
DEXSCREENER_BASE = "https://api.dexscreener.com"
REQUEST_TIMEOUT = 15.0
# Alert thresholds
TVL_DROP_ALERT_PCT = -15.0 # Alert if TVL drops more than 15%
TVL_SPIKE_ALERT_PCT = 50.0 # Alert if TVL spikes more than 50%
VOLUME_SPIKE_MULT = 3.0 # Alert if volume > 3x average
NEW_POOL_ALERT = True # Alert on new pool detection
# ── Data Models ─────────────────────────────────────────────────────
@dataclass
class PoolSnapshot:
"""A point-in-time snapshot of pool metrics."""
timestamp: float
pool_address: str
dex_name: str
tvl_usd: float
volume_1h: float
volume_24h: float
price_usd: float
buys_1h: int
sells_1h: int
@dataclass
class PoolAlert:
"""An alert generated from pool monitoring."""
timestamp: float
pool_address: str
dex_name: str
alert_type: str # "tvl_drop", "tvl_spike", "volume_spike", "new_pool", "dead_pool"
severity: str # "critical", "warning", "info"
message: str
details: dict = field(default_factory=dict)
@dataclass
class MonitorState:
"""Tracking state for a monitored pool."""
pool_address: str
dex_name: str
snapshots: list[PoolSnapshot] = field(default_factory=list)
alerts: list[PoolAlert] = field(default_factory=list)
first_seen: float = 0.0
last_tvl: float = 0.0
avg_volume_1h: float = 0.0
# ── Alert Detection ────────────────────────────────────────────────
def detect_alerts(
state: MonitorState, current: PoolSnapshot
) -> list[PoolAlert]:
"""Compare current snapshot against state to detect alerts.
Checks for:
- TVL drops exceeding threshold
- TVL spikes exceeding threshold
- Volume spikes vs rolling average
- Dead pools (zero activity)
Args:
state: Current monitoring state for this pool.
current: Latest pool snapshot.
Returns:
List of new alerts detected.
"""
alerts: list[PoolAlert] = []
if state.last_tvl > 0:
tvl_change_pct = ((current.tvl_usd - state.last_tvl) / state.last_tvl) * 100
# TVL drop alert
if tvl_change_pct < TVL_DROP_ALERT_PCT:
severity = "critical" if tvl_change_pct < -50 else "warning"
alerts.append(PoolAlert(
timestamp=current.timestamp,
pool_address=current.pool_address,
dex_name=current.dex_name,
alert_type="tvl_drop",
severity=severity,
message=f"TVL dropped {tvl_change_pct:.1f}% "
f"(${state.last_tvl:,.0f} -> ${current.tvl_usd:,.0f})",
details={
"previous_tvl": state.last_tvl,
"current_tvl": current.tvl_usd,
"change_pct": tvl_change_pct,
},
))
# TVL spike alert
if tvl_change_pct > TVL_SPIKE_ALERT_PCT:
alerts.append(PoolAlert(
timestamp=current.timestamp,
pool_address=current.pool_address,
dex_name=current.dex_name,
alert_type="tvl_spike",
severity="info",
message=f"TVL increased {tvl_change_pct:.1f}% "
f"(${state.last_tvl:,.0f} -> ${current.tvl_usd:,.0f})",
details={
"previous_tvl": state.last_tvl,
"current_tvl": current.tvl_usd,
"change_pct": tvl_change_pct,
},
))
# Volume spike vs average
if state.avg_volume_1h > 0 and current.volume_1h > 0:
volume_mult = current.volume_1h / state.avg_volume_1h
if volume_mult > VOLUME_SPIKE_MULT:
alerts.append(PoolAlert(
timestamp=current.timestamp,
pool_address=current.pool_address,
dex_name=current.dex_name,
alert_type="volume_spike",
severity="warning",
message=f"Volume spike: {volume_mult:.1f}x average "
f"(${current.volume_1h:,.0f} vs avg ${state.avg_volume_1h:,.0f})",
details={
"current_volume_1h": current.volume_1h,
"avg_volume_1h": state.avg_volume_1h,
"multiplier": volume_mult,
},
))
# Dead pool detection
if (
len(state.snapshots) >= 3
and all(s.volume_1h == 0 for s in state.snapshots[-3:])
and current.volume_1h == 0
):
alerts.append(PoolAlert(
timestamp=current.timestamp,
pool_address=current.pool_address,
dex_name=current.dex_name,
alert_type="dead_pool",
severity="warning",
message="Pool has had zero volume for 3+ consecutive checks",
details={"consecutive_zero_checks": len(state.snapshots)},
))
return alerts
def update_state(state: MonitorState, snapshot: PoolSnapshot) -> None:
"""Update monitoring state with a new snapshot.
Args:
state: Current monitoring state to update.
snapshot: New snapshot to incorporate.
"""
state.snapshots.append(snapshot)
state.last_tvl = snapshot.tvl_usd
# Rolling average of 1h volume (last 10 snapshots)
recent = state.snapshots[-10:]
volumes = [s.volume_1h for s in recent if s.volume_1h > 0]
state.avg_volume_1h = sum(volumes) / len(volumes) if volumes else 0.0
# ── Data Fetching ───────────────────────────────────────────────────
def fetch_pool_snapshots(token_mint: str) -> list[PoolSnapshot]:
"""Fetch current pool data from DexScreener and create snapshots.
Args:
token_mint: Solana token mint address.
Returns:
List of PoolSnapshot objects, one per pool.
Raises:
httpx.HTTPStatusError: On non-2xx response.
"""
url = f"{DEXSCREENER_BASE}/tokens/v1/solana/{token_mint}"
now = time.time()
with httpx.Client(timeout=REQUEST_TIMEOUT) as client:
resp = client.get(url)
resp.raise_for_status()
raw_pools = resp.json()
if not isinstance(raw_pools, list):
return []
snapshots: list[PoolSnapshot] = []
for p in raw_pools:
if p.get("chainId", "") != "solana":
continue
volume = p.get("volume") or {}
liquidity = p.get("liquidity") or {}
txns = p.get("txns") or {}
txns_1h = txns.get("h1") or {}
snapshots.append(PoolSnapshot(
timestamp=now,
pool_address=p.get("pairAddress", ""),
dex_name=p.get("dexId", "unknown"),
tvl_usd=float(liquidity.get("usd", 0) or 0),
volume_1h=float(volume.get("h1", 0) or 0),
volume_24h=float(volume.get("h24", 0) or 0),
price_usd=float(p.get("priceUsd", 0) or 0),
buys_1h=int(txns_1h.get("buys", 0) or 0),
sells_1h=int(txns_1h.get("sells", 0) or 0),
))
return snapshots
# ── Demo Simulation ─────────────────────────────────────────────────
def simulate_pool_lifecycle() -> list[tuple[float, list[PoolSnapshot]]]:
"""Simulate a token's pool lifecycle for demo mode.
Simulates 20 time steps covering:
- T0-T2: Pool creation on Raydium V4 (PumpFun graduation)
- T3-T5: Early trading with high volume, TVL growth
- T6-T8: Orca Whirlpool pool created (new pool event)
- T9-T12: Stable period, normal trading
- T13-T15: Liquidity removal event on Raydium (rug warning)
- T16-T18: Volume dies on Raydium, moves to Orca
- T19: Final state
Returns:
List of (timestamp, snapshots) tuples representing time steps.
"""
random.seed(42) # Reproducible demo
base_time = time.time()
steps: list[tuple[float, list[PoolSnapshot]]] = []
# Pool lifecycle parameters
raydium_tvl_curve = [
12_000, 18_000, 35_000, 55_000, 80_000, # Growth phase
95_000, 100_000, 105_000, 110_000, 108_000, # Stable
105_000, 100_000, 95_000, 40_000, 25_000, # Rug event
15_000, 8_000, 5_000, 3_000, 2_000, # Decline
]
# Orca pool appears at step 6
orca_tvl_curve = [
0, 0, 0, 0, 0, 0,
50_000, 65_000, 75_000, 80_000, # Creation + growth
85_000, 90_000, 95_000, 100_000, 110_000, # Absorbs volume
120_000, 130_000, 135_000, 140_000, 145_000, # Dominant
]
for step in range(20):
t = base_time + step * 3600 # 1 hour between steps
snapshots: list[PoolSnapshot] = []
# Raydium V4 pool
ray_tvl = raydium_tvl_curve[step]
ray_vol_base = ray_tvl * random.uniform(0.3, 1.5)
if step >= 13:
ray_vol_base *= 0.2 # Volume dies after rug
snapshots.append(PoolSnapshot(
timestamp=t,
pool_address="RayPool1111111111111111111111111111111111111",
dex_name="raydium",
tvl_usd=ray_tvl * random.uniform(0.95, 1.05),
volume_1h=ray_vol_base * random.uniform(0.02, 0.08),
volume_24h=ray_vol_base,
price_usd=0.00042 * (1 + step * 0.02 + random.uniform(-0.05, 0.05)),
buys_1h=int(random.uniform(10, 80)),
sells_1h=int(random.uniform(8, 70)),
))
# Orca Whirlpool (appears at step 6)
orca_tvl = orca_tvl_curve[step]
if orca_tvl > 0:
orca_vol_base = orca_tvl * random.uniform(0.5, 2.0)
snapshots.append(PoolSnapshot(
timestamp=t,
pool_address="OrcaPool2222222222222222222222222222222222222",
dex_name="orca_whirlpool",
tvl_usd=orca_tvl * random.uniform(0.95, 1.05),
volume_1h=orca_vol_base * random.uniform(0.03, 0.08),
volume_24h=orca_vol_base,
price_usd=0.00042 * (1 + step * 0.02 + random.uniform(-0.05, 0.05)),
buys_1h=int(random.uniform(15, 90)),
sells_1h=int(random.uniform(12, 85)),
))
steps.append((t, snapshots))
return steps
# ── Display ─────────────────────────────────────────────────────────
def format_timestamp(ts: float) -> str:
"""Format a Unix timestamp to a readable string.
Args:
ts: Unix timestamp.
Returns:
Formatted time string.
"""
return time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(ts))
def print_snapshot_report(
snapshots: list[PoolSnapshot], states: dict[str, MonitorState], step: int
) -> None:
"""Print a monitoring report for the current time step.
Args:
snapshots: Current pool snapshots.
states: Monitoring states keyed by pool address.
step: Current step number.
"""
if not snapshots:
print(f" Step {step}: No pool data available")
return
ts = snapshots[0].timestamp
print(f"\n{'─'*60}")
print(f" Step {step} | {format_timestamp(ts)}")
print(f"{'─'*60}")
total_tvl = 0.0
total_vol = 0.0
for snap in snapshots:
total_tvl += snap.tvl_usd
total_vol += snap.volume_1h
state = states.get(snap.pool_address)
tvl_change = ""
if state and state.last_tvl > 0 and len(state.snapshots) > 0:
prev = state.last_tvl
change_pct = ((snap.tvl_usd - prev) / prev) * 100
tvl_change = f" ({change_pct:+.1f}%)"
print(
f" {snap.dex_name:20s} | "
f"TVL: ${snap.tvl_usd:>10,.0f}{tvl_change:>10s} | "
f"Vol(1h): ${snap.volume_1h:>8,.0f} | "
f"Txns: {snap.buys_1h + snap.sells_1h:>4d}"
)
print(f" {'':20s} | Total TVL: ${total_tvl:>10,.0f} | Total Vol: ${total_vol:>8,.0f}")
def print_alert(alert: PoolAlert) -> None:
"""Print a formatted alert message.
Args:
alert: PoolAlert to display.
"""
severity_prefix = {
"critical": "!! CRITICAL",
"warning": "! WARNING ",
"info": "i INFO ",
}
prefix = severity_prefix.get(alert.severity, " UNKNOWN ")
print(f"\n [{prefix}] {format_timestamp(alert.timestamp)}")
print(f" Pool: {alert.dex_name} ({alert.pool_address[:16]}...)")
print(f" Type: {alert.alert_type}")
print(f" {alert.message}")
def print_monitoring_summary(states: dict[str, MonitorState]) -> None:
"""Print final monitoring summary with all alerts.
Args:
states: All monitoring states.
"""
all_alerts: list[PoolAlert] = []
for state in states.values():
all_alerts.extend(state.alerts)
print(f"\n{'='*60}")
print(f" MONITORING SUMMARY")
print(f"{'='*60}")
print(f" Pools monitored: {len(states)}")
print(f" Total alerts: {len(all_alerts)}")
# Count by severity
by_severity: dict[str, int] = {}
by_type: dict[str, int] = {}
for a in all_alerts:
by_severity[a.severity] = by_severity.get(a.severity, 0) + 1
by_type[a.alert_type] = by_type.get(a.alert_type, 0) + 1
if by_severity:
print(f"\n By severity:")
for sev, count in sorted(by_severity.items()):
print(f" {sev}: {count}")
if by_type:
print(f"\n By type:")
for atype, count in sorted(by_type.items()):
print(f" {atype}: {count}")
# Per-pool summary
print(f"\n Per-pool summary:")
for addr, state in states.items():
if state.snapshots:
first = state.snapshots[0]
last = state.snapshots[-1]
tvl_change = (
((last.tvl_usd - first.tvl_usd) / first.tvl_usd * 100)
if first.tvl_usd > 0 else 0
)
print(
f" {state.dex_name:20s} | "
f"TVL: ${first.tvl_usd:>10,.0f} -> ${last.tvl_usd:>10,.0f} "
f"({tvl_change:+.1f}%) | "
f"Alerts: {len(state.alerts)}"
)
print(f"\n Note: This monitoring report is for informational purposes only.")
print(f" It does not constitute financial advice.\n")
# ── Monitor Loop ────────────────────────────────────────────────────
def run_monitor(token_mint: str, interval_seconds: int, max_checks: int) -> None:
"""Run the pool monitor in live mode.
Args:
token_mint: Solana token mint address.
interval_seconds: Seconds between checks.
max_checks: Maximum number of checks before stopping.
"""
states: dict[str, MonitorState] = {}
known_pools: set[str] = set()
print(f"Monitoring pools for {token_mint}")
print(f"Interval: {interval_seconds}s | Max checks: {max_checks}")
print(f"Press Ctrl+C to stop.\n")
for check_num in range(1, max_checks + 1):
try:
snapshots = fetch_pool_snapshots(token_mint)
except (httpx.HTTPStatusError, httpx.RequestError) as e:
print(f" Fetch error at check {check_num}: {e}")
time.sleep(interval_seconds)
continue
# Detect new pools
for snap in snapshots:
if snap.pool_address not in known_pools:
known_pools.add(snap.pool_address)
if check_num > 1 and NEW_POOL_ALERT:
alert = PoolAlert(
timestamp=snap.timestamp,
pool_address=snap.pool_address,
dex_name=snap.dex_name,
alert_type="new_pool",
severity="info",
message=f"New pool detected on {snap.dex_name} "
f"with TVL ${snap.tvl_usd:,.0f}",
)
print_alert(alert)
if snap.pool_address in states:
states[snap.pool_address].alerts.append(alert)
# Initialize state if needed
if snap.pool_address not in states:
states[snap.pool_address] = MonitorState(
pool_address=snap.pool_address,
dex_name=snap.dex_name,
first_seen=snap.timestamp,
last_tvl=snap.tvl_usd,
)
# Detect alerts and update states
for snap in snapshots:
state = states[snap.pool_address]
alerts = detect_alerts(state, snap)
for alert in alerts:
print_alert(alert)
state.alerts.append(alert)
update_state(state, snap)
# Print report
print_snapshot_report(snapshots, states, check_num)
if check_num < max_checks:
try:
time.sleep(interval_seconds)
except KeyboardInterrupt:
print("\n\nMonitoring stopped by user.")
break
print_monitoring_summary(states)
def run_demo() -> None:
"""Run the pool monitor with simulated lifecycle data.
Simulates 20 hours of pool activity including pool creation,
liquidity events, and volume shifts.
"""
print("Running demo simulation: Token pool lifecycle over 20 time steps")
print("Simulating: PumpFun graduation -> growth -> liquidity event -> decline\n")
lifecycle = simulate_pool_lifecycle()
states: dict[str, MonitorState] = {}
known_pools: set[str] = set()
for step, (ts, snapshots) in enumerate(lifecycle, 1):
# Detect new pools
for snap in snapshots:
if snap.pool_address not in known_pools:
known_pools.add(snap.pool_address)
if step > 1:
alert = PoolAlert(
timestamp=snap.timestamp,
pool_address=snap.pool_address,
dex_name=snap.dex_name,
alert_type="new_pool",
severity="info",
message=f"New pool detected on {snap.dex_name} "
f"with TVL ${snap.tvl_usd:,.0f}",
)
print_alert(alert)
states.setdefault(snap.pool_address, MonitorState(
pool_address=snap.pool_address,
dex_name=snap.dex_name,
first_seen=snap.timestamp,
)).alerts.append(alert)
if snap.pool_address not in states:
states[snap.pool_address] = MonitorState(
pool_address=snap.pool_address,
dex_name=snap.dex_name,
first_seen=snap.timestamp,
last_tvl=snap.tvl_usd,
)
# Detect alerts
for snap in snapshots:
state = states[snap.pool_address]
alerts = detect_alerts(state, snap)
for alert in alerts:
print_alert(alert)
state.alerts.append(alert)
update_state(state, snap)
# Print step report
print_snapshot_report(snapshots, states, step)
print_monitoring_summary(states)
# ── Main ────────────────────────────────────────────────────────────
def main() -> None:
"""Entry point: parse arguments and run pool monitor."""
parser = argparse.ArgumentParser(
description="Monitor DEX pool metrics and detect liquidity events"
)
parser.add_argument(
"token_mint",
nargs="?",
default=None,
help="Solana token mint address to monitor",
)
parser.add_argument(
"--demo",
action="store_true",
help="Run with simulated lifecycle data (no API calls)",
)
parser.add_argument(
"--interval",
type=int,
default=60,
help="Seconds between checks in live mode (default: 60)",
)
parser.add_argument(
"--max-checks",
type=int,
default=60,
help="Maximum number of checks before stopping (default: 60)",
)
args = parser.parse_args()
if args.demo:
run_demo()
elif args.token_mint:
run_monitor(args.token_mint, args.interval, args.max_checks)
else:
parser.print_help()
print("\nExamples:")
print(" python scripts/pool_monitor.py --demo")
print(" python scripts/pool_monitor.py <TOKEN_MINT> --interval 30")
sys.exit(1)
if __name__ == "__main__":
main()
Related skills
FAQ
What AMM pool types exist on Solana?
Constant-product (Raydium V4, PumpSwap), concentrated liquidity (Raydium CLMM, Orca Whirlpool), and bin-based (Meteora DLMM), each with distinct fee structures.
What fee does Raydium V4 charge?
0.25% per swap, split 0.22% to LPs and 0.03% to RAY buyback.