
Dexscreener Api
- 207 installs
- 257 repo stars
- Updated June 24, 2026
- agiprolabs/claude-trading-skills
dexscreener-api is a Claude Code skill that queries the free, no-auth DexScreener API for multi-chain DEX pair data: prices, volume, liquidity, transactions, and token profiles.
About
dexscreener-api is a Claude Code skill that queries the DexScreener API for DEX pair data across 80+ chains without an API key: prices, volume, liquidity, transaction counts, and token profiles. A developer uses it for quick token lookups, cross-chain price comparison, and lightweight monitoring of new or boosted tokens. It documents the search, pair, and token endpoints and the full pair response schema.
- Queries DexScreener for DEX pair data across 80+ chains with no API key
- Returns prices, volume, liquidity, transaction counts, and token profiles/boosts
- Documents search, pair, and token endpoints plus response schema
Dexscreener Api by the numbers
- 207 all-time installs (skills.sh)
- Ranked #455 of 1,106 Finance & Trading skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
dexscreener-api capabilities & compatibility
Free; no API key or headers required.
- Capabilities
- dex pair data · token price lookup · liquidity data · cross chain comparison · boosted token monitoring
- Use cases
- data analysis · research · trading
- Runs
- Runs locally
- Pricing
- Free
What dexscreener-api says it does
Free, no-auth multi-chain DEX pair data — prices, volume, liquidity, transactions, and token profiles
DexScreener provides DEX pair data across 80+ chains with **no API key required**.
Rate limits: ~300 req/min for DEX data endpoints, 60 req/min for profile/boost endpoints.
npx skills add https://github.com/agiprolabs/claude-trading-skills --skill dexscreener-apiAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 207 |
|---|---|
| repo stars | ★ 257 |
| Last updated | June 24, 2026 |
| Repository | agiprolabs/claude-trading-skills ↗ |
What it does
Query DexScreener for multi-chain DEX pair prices, volume, liquidity, and token profiles without a key.
Who is it for?
Quick token lookups, cross-chain price comparison, and lightweight monitoring of new or boosted tokens.
Skip if: Heavy or high-frequency use beyond the documented ~300 req/min DEX and 60 req/min profile limits.
When should I use this skill?
You need DexScreener pair prices, liquidity, or boost data across chains inside an agent.
What you get
Working DexScreener queries for pair prices, liquidity, volume, and boosted-token discovery.
By the numbers
- Covers 80+ chains
- Rate limits ~300 req/min (DEX) and 60 req/min (profile/boost)
- Search returns ~30 pairs across all chains
Files
DexScreener API — Free Multi-Chain DEX Data
DexScreener provides DEX pair data across 80+ chains with no API key required. Prices, volume, liquidity, transaction counts, and token profiles — all free. Best for quick lookups, cross-chain comparison, and lightweight monitoring.
Quick Start
No authentication needed. Just make requests:
import httpx
# Search for a token
resp = httpx.get("https://api.dexscreener.com/latest/dex/search", params={"q": "BONK"})
pairs = resp.json()["pairs"]
for p in pairs[:3]:
print(f"{p['baseToken']['symbol']}/{p['quoteToken']['symbol']} on {p['chainId']}: ${p['priceUsd']}")# Or with curl
curl "https://api.dexscreener.com/latest/dex/search?q=BONK"Base URL
https://api.dexscreener.com
No API key, no headers required. Rate limits: ~300 req/min for DEX data endpoints, 60 req/min for profile/boost endpoints.
Core Endpoints
Search Pairs
# Search by token name, symbol, or address (returns ~30 pairs across all chains)
GET /latest/dex/search?q=BONK
GET /latest/dex/search?q=So11111111111111111111111111111111111111112Get Pairs by Chain + Pair Address
# Single pair
GET /latest/dex/pairs/solana/PAIR_ADDRESS
# Multiple pairs (comma-separated)
GET /latest/dex/pairs/solana/PAIR1,PAIR2,PAIR3Get Pairs by Token Address
# All pairs containing this token across all chains
GET /latest/dex/tokens/TOKEN_MINT_ADDRESS
# Chain-specific (v1)
GET /token-pairs/v1/solana/TOKEN_MINT
# Multiple tokens on one chain (v1)
GET /tokens/v1/solana/TOKEN1,TOKEN2,TOKEN3Token Profiles & Boosts
# Latest token profiles (60 req/min)
GET /token-profiles/latest/v1
# Latest boosted tokens
GET /token-boosts/latest/v1
# Top boosted tokens (sorted by total boost amount)
GET /token-boosts/top/v1
# Token orders (ads, profile claims)
GET /orders/v1/solana/TOKEN_ADDRESS
# Community takeovers
GET /community-takeovers/latest/v1Pair Response Schema
{
"chainId": "solana",
"dexId": "raydium",
"url": "https://dexscreener.com/solana/PAIR_ADDR",
"pairAddress": "3nMFwZX...",
"labels": ["CLMM"],
"baseToken": { "address": "So11...", "name": "Wrapped SOL", "symbol": "SOL" },
"quoteToken": { "address": "EPjF...", "name": "USD Coin", "symbol": "USDC" },
"priceNative": "86.08",
"priceUsd": "86.08",
"txns": {
"m5": { "buys": 25, "sells": 18 },
"h1": { "buys": 916, "sells": 1282 },
"h6": { "buys": 11309, "sells": 12661 },
"h24": { "buys": 27974, "sells": 31475 }
},
"volume": { "m5": 41680, "h1": 6773038, "h6": 71628073, "h24": 167164493 },
"priceChange": { "m5": -0.01, "h1": -0.36, "h6": 0.82, "h24": 0.25 },
"liquidity": { "usd": 28168478, "base": 232348, "quote": 8167805 },
"fdv": 8171740,
"marketCap": 8171740,
"pairCreatedAt": 1688106058000,
"info": {
"imageUrl": "https://cdn.dexscreener.com/...",
"websites": [{ "url": "https://...", "label": "Website" }],
"socials": [{ "url": "https://x.com/...", "type": "twitter" }]
}
}Key notes:
priceNativeandpriceUsdare strings (preserves precision)pairCreatedAtis milliseconds (divide by 1000 for unix timestamp)labels:"CLMM"(concentrated),"DLMM"(dynamic),"wp"(whirlpool)infois optional — only present if the token profile has been claimedfdv/marketCapmay be absent for tokens without supply data
Common Patterns
Quick Token Lookup
def lookup_token(address: str) -> dict | None:
"""Get the best pair for a token by liquidity."""
resp = httpx.get(f"https://api.dexscreener.com/latest/dex/tokens/{address}")
pairs = resp.json().get("pairs", [])
if not pairs:
return None
# Sort by liquidity (highest first)
pairs.sort(key=lambda p: p.get("liquidity", {}).get("usd", 0), reverse=True)
return pairs[0]Cross-Chain Price Check
def find_best_price(symbol: str) -> list[dict]:
"""Find a token across all chains and compare prices."""
resp = httpx.get("https://api.dexscreener.com/latest/dex/search", params={"q": symbol})
pairs = resp.json().get("pairs", [])
results = []
for p in pairs:
if p["baseToken"]["symbol"].upper() == symbol.upper():
results.append({
"chain": p["chainId"],
"dex": p["dexId"],
"price": float(p.get("priceUsd", 0)),
"liquidity": p.get("liquidity", {}).get("usd", 0),
"volume_24h": p.get("volume", {}).get("h24", 0),
})
return sorted(results, key=lambda x: x["liquidity"], reverse=True)Monitor New Tokens via Boosts
def get_newly_boosted() -> list[dict]:
"""Get tokens that were recently boosted (paid promotion)."""
resp = httpx.get("https://api.dexscreener.com/token-boosts/latest/v1")
return [
{
"chain": t["chainId"],
"address": t["tokenAddress"],
"boost_amount": t.get("amount", 0),
"total_boost": t.get("totalAmount", 0),
"description": t.get("description", ""),
}
for t in resp.json()
]Buy/Sell Ratio Analysis
def analyze_pressure(pair: dict) -> dict:
"""Analyze buy/sell pressure from transaction counts."""
txns = pair.get("txns", {})
result = {}
for tf in ["m5", "h1", "h6", "h24"]:
data = txns.get(tf, {})
buys = data.get("buys", 0)
sells = data.get("sells", 0)
total = buys + sells
result[tf] = {
"buys": buys, "sells": sells, "total": total,
"buy_ratio": buys / total if total > 0 else 0.5,
}
return resultSupported Chains
Major chains: solana, ethereum, base, arbitrum, bsc, polygon, avalanche, optimism, zksync, scroll, linea, tron, near, aptos, ton, sui, hyperliquid
80+ chains total. The chainId matches the URL path on dexscreener.com.
Limitations
- No OHLCV/candle data — Use Birdeye or SolanaTracker for historical candles
- No pagination — Search returns ~30 results max
- No wallet/trader data — Use Birdeye or Helius for wallet analysis
- No token security data — Use Birdeye
token_securityendpoint - Snapshot data only — Current state, not historical time series
- No WebSocket — Polling only
When to Use DexScreener vs Alternatives
| Need | Use |
|---|---|
| Quick free token lookup | DexScreener |
| Cross-chain comparison | DexScreener |
| Historical OHLCV for backtesting | Birdeye or SolanaTracker |
| Wallet/trader analysis | Helius or SolanaTracker |
| Real-time streaming | Yellowstone gRPC or Birdeye WebSocket |
| Token security check | Birdeye or SolanaTracker risk score |
Files
References
references/endpoints.md— Complete endpoint listing with response schemasreferences/error_handling.md— Rate limits, error codes, best practices
Scripts
scripts/token_lookup.py— Look up any token across all chains with liquidity analysisscripts/boost_monitor.py— Monitor newly boosted/promoted tokens
DexScreener API — Endpoints Reference
All endpoints use base URL https://api.dexscreener.com. No authentication required.
---
DEX Data Endpoints (~300 req/min)
Search Pairs
- Endpoint:
GET /latest/dex/search - Parameters:
q(required) — token name, symbol, or address - Response:
{ "schemaVersion": "1.0.0", "pairs": [...] }— up to ~30 pairs - Notes: Searches across all 80+ chains. Results sorted by liquidity.
curl "https://api.dexscreener.com/latest/dex/search?q=BONK"Get Pairs by Chain + Pair Address
- Endpoint:
GET /latest/dex/pairs/{chainId}/{pairAddresses} - Parameters:
chainId(path) — chain identifier (e.g.,solana,ethereum)pairAddresses(path) — single or comma-separated pair addresses (max 30)- Response:
{ "schemaVersion": "1.0.0", "pairs": [...] }
curl "https://api.dexscreener.com/latest/dex/pairs/solana/PAIR_ADDRESS"
# Multiple pairs
curl "https://api.dexscreener.com/latest/dex/pairs/solana/PAIR1,PAIR2,PAIR3"Get Pairs by Token Address (Legacy)
- Endpoint:
GET /latest/dex/tokens/{tokenAddresses} - Parameters:
tokenAddresses(path) — single or comma-separated token addresses (max 30) - Response:
{ "schemaVersion": "1.0.0", "pairs": [...] } - Notes: Returns all pairs containing the token across all chains.
curl "https://api.dexscreener.com/latest/dex/tokens/So11111111111111111111111111111111111111112"Get Token Pairs (v1)
- Endpoint:
GET /token-pairs/v1/{chainId}/{tokenAddress} - Parameters:
chainId(path) — chain identifiertokenAddress(path) — single token address- Response: Array of pair objects for that token on the specified chain.
curl "https://api.dexscreener.com/token-pairs/v1/solana/TOKEN_MINT"Get Tokens (v1)
- Endpoint:
GET /tokens/v1/{chainId}/{tokenAddresses} - Parameters:
chainId(path) — chain identifiertokenAddresses(path) — comma-separated token addresses (max 30)- Response: Array of pair objects.
curl "https://api.dexscreener.com/tokens/v1/solana/TOKEN1,TOKEN2"---
Token Profile & Boost Endpoints (~60 req/min)
Latest Token Profiles
- Endpoint:
GET /token-profiles/latest/v1 - Response: Array of token profiles with metadata, links, and images.
[
{
"chainId": "solana",
"tokenAddress": "TOKEN...",
"icon": "https://...",
"header": "https://...",
"description": "...",
"links": [
{ "label": "Website", "type": "website", "url": "https://..." },
{ "label": "Twitter", "type": "twitter", "url": "https://x.com/..." }
]
}
]Latest Boosted Tokens
- Endpoint:
GET /token-boosts/latest/v1 - Response: Array of recently boosted tokens.
[
{
"chainId": "solana",
"tokenAddress": "TOKEN...",
"amount": 500,
"totalAmount": 2500,
"icon": "https://...",
"description": "..."
}
]Top Boosted Tokens
- Endpoint:
GET /token-boosts/top/v1 - Response: Same schema as latest, sorted by
totalAmountdescending.
Token Orders
- Endpoint:
GET /orders/v1/{chainId}/{tokenAddress} - Response: Array of orders (ads, profile claims) for a token.
[
{
"type": "tokenProfile",
"status": "approved",
"paymentTimestamp": 1700000000
}
]Community Takeovers
- Endpoint:
GET /community-takeovers/latest/v1 - Response: Array of tokens with recent community takeover activity.
---
Pair Response Schema
Every pair object returned by DEX data endpoints:
| Field | Type | Description |
|---|---|---|
chainId | string | Chain identifier |
dexId | string | DEX name (raydium, orca, uniswap, etc.) |
url | string | DexScreener URL for this pair |
pairAddress | string | On-chain pair/pool address |
labels | string[] | Pool type labels: "CLMM", "DLMM", "wp" |
baseToken | object | { address, name, symbol } |
quoteToken | object | { address, name, symbol } |
priceNative | string | Price in quote token (string for precision) |
priceUsd | string | USD price (string for precision) |
txns | object | Transaction counts by timeframe |
volume | object | Volume in USD: { m5, h1, h6, h24 } |
priceChange | object | % change: { m5, h1, h6, h24 } |
liquidity | object | { usd, base, quote } |
fdv | number | Fully diluted valuation (may be absent) |
marketCap | number | Market cap (may be absent) |
pairCreatedAt | number | Millisecond timestamp |
info | object | Optional: { imageUrl, websites[], socials[] } |
---
Supported Chains
Major: solana, ethereum, base, arbitrum, bsc, polygon, avalanche, optimism, zksync, scroll, linea, tron, near, aptos, ton, sui, hyperliquid
80+ chains total. The chainId value matches the URL slug on dexscreener.com.
DexScreener API — Error Handling & Rate Limits
Rate Limits
| Endpoint Group | Limit | Scope |
|---|---|---|
DEX data (/latest/dex/*, /tokens/v1/*, /token-pairs/v1/*) | ~300 req/min | Per IP |
Token profiles & boosts (/token-profiles/*, /token-boosts/*) | ~60 req/min | Per IP |
Orders & takeovers (/orders/*, /community-takeovers/*) | ~60 req/min | Per IP |
No rate limit headers are returned. Limits are enforced silently — you get HTTP 429 when exceeded.
HTTP Status Codes
| Code | Meaning | Action |
|---|---|---|
| 200 | Success | Process response |
| 400 | Bad request | Check parameters, address format |
| 404 | Not found | Pair/token doesn't exist or has no DEX listings |
| 429 | Rate limited | Back off, wait 10-30 seconds |
| 500 | Server error | Retry after 5 seconds, max 3 attempts |
| 503 | Service unavailable | Retry after 30 seconds |
Error Response Format
DexScreener doesn't return structured error bodies. Non-200 responses may have empty bodies or plain text. Always check status code first.
Retry Strategy
import time
import httpx
def dexscreener_get(url: str, max_retries: int = 3) -> dict:
"""GET with exponential backoff for rate limits."""
for attempt in range(max_retries):
resp = httpx.get(url, timeout=15.0)
if resp.status_code == 200:
return resp.json()
if resp.status_code == 429:
wait = 10.0 * (attempt + 1)
print(f"Rate limited, waiting {wait}s...")
time.sleep(wait)
continue
if resp.status_code >= 500:
time.sleep(5.0 * (attempt + 1))
continue
# 4xx (not 429) — don't retry
resp.raise_for_status()
raise RuntimeError(f"Failed after {max_retries} retries: {url}")Common Gotchas
1. String prices, not numbers
priceNative and priceUsd are strings. Always cast:
price = float(pair.get("priceUsd", "0"))2. Missing fields
fdv,marketCap— absent for tokens without supply datainfo— only present if token profile has been claimedliquidity— may beNoneor{"usd": 0}for dead pools
Always use .get() with defaults:
liq = pair.get("liquidity", {}).get("usd", 0) or 03. Millisecond timestamps
pairCreatedAt is milliseconds, not seconds:
from datetime import datetime, timezone
created = datetime.fromtimestamp(pair["pairCreatedAt"] / 1000, tz=timezone.utc)4. No pagination
Search returns ~30 results max. If you need exhaustive results, use the token address endpoint instead of search.
5. Duplicate pairs across DEXes
A token may have pairs on Raydium, Orca, and Meteora simultaneously. Filter by dexId or sort by liquidity to find the primary pair:
pairs.sort(key=lambda p: p.get("liquidity", {}).get("usd", 0) or 0, reverse=True)
primary = pairs[0]6. Cross-chain ambiguity
Searching by symbol (e.g., "USDC") returns pairs from many chains. Filter by chainId:
solana_pairs = [p for p in pairs if p["chainId"] == "solana"]Batch Request Optimization
When looking up multiple tokens, use comma-separated addresses instead of individual requests:
# Bad: 10 requests
for addr in addresses:
resp = httpx.get(f"https://api.dexscreener.com/latest/dex/tokens/{addr}")
# Good: 1 request (max 30 addresses)
joined = ",".join(addresses[:30])
resp = httpx.get(f"https://api.dexscreener.com/tokens/v1/solana/{joined}")No Historical Data
DexScreener provides snapshot data only. For historical OHLCV, use:
- Birdeye — Solana OHLCV with pagination
- SolanaTracker — 1-second resolution OHLCV
- CoinGecko — Long-term historical (years)
#!/usr/bin/env python3
"""Monitor newly boosted and promoted tokens on DexScreener.
Fetches latest token boosts, top boosts, and community takeovers.
Useful for discovering newly promoted tokens and tracking paid
promotion activity across chains.
Usage:
python scripts/boost_monitor.py
CHAIN_FILTER="solana" python scripts/boost_monitor.py
Dependencies:
uv pip install httpx
"""
import os
import sys
import time
from typing import Optional
import httpx
# ── Configuration ───────────────────────────────────────────────────
CHAIN_FILTER = os.getenv("CHAIN_FILTER", "") # e.g., "solana" to filter
BASE_URL = "https://api.dexscreener.com"
# ── API Functions ───────────────────────────────────────────────────
def dexscreener_get(url: str, max_retries: int = 3) -> list | dict:
"""Make a GET request to DexScreener with retry.
Args:
url: Full URL to request.
max_retries: Maximum retry attempts.
Returns:
Parsed JSON response (list or dict).
Raises:
RuntimeError: After exhausting retries.
"""
for attempt in range(max_retries):
try:
resp = httpx.get(url, timeout=15.0)
if resp.status_code == 200:
return resp.json()
if resp.status_code == 429:
wait = 15.0 * (attempt + 1)
print(f" Rate limited, waiting {wait}s...")
time.sleep(wait)
continue
if resp.status_code >= 500:
time.sleep(5.0 * (attempt + 1))
continue
resp.raise_for_status()
except httpx.TimeoutException:
if attempt < max_retries - 1:
time.sleep(3.0)
continue
raise
raise RuntimeError(f"Failed after {max_retries} retries: {url}")
# ── Data Fetching ───────────────────────────────────────────────────
def fetch_latest_boosts() -> list[dict]:
"""Fetch recently boosted tokens.
Returns:
List of boost entries with chain, address, amounts.
"""
data = dexscreener_get(f"{BASE_URL}/token-boosts/latest/v1")
if not isinstance(data, list):
return []
return data
def fetch_top_boosts() -> list[dict]:
"""Fetch top boosted tokens sorted by total boost amount.
Returns:
List of boost entries sorted by totalAmount descending.
"""
data = dexscreener_get(f"{BASE_URL}/token-boosts/top/v1")
if not isinstance(data, list):
return []
return data
def fetch_latest_profiles() -> list[dict]:
"""Fetch latest token profiles (claimed/updated).
Returns:
List of token profile entries.
"""
data = dexscreener_get(f"{BASE_URL}/token-profiles/latest/v1")
if not isinstance(data, list):
return []
return data
def fetch_community_takeovers() -> list[dict]:
"""Fetch latest community takeover activity.
Returns:
List of CTO entries.
"""
data = dexscreener_get(f"{BASE_URL}/community-takeovers/latest/v1")
if not isinstance(data, list):
return []
return data
def enrich_with_pair_data(tokens: list[dict], limit: int = 5) -> list[dict]:
"""Enrich boost entries with pair data (price, liquidity).
Args:
tokens: List of boost/profile entries.
limit: Maximum tokens to look up (to stay within rate limits).
Returns:
Enriched entries with pair data added.
"""
enriched = []
for token in tokens[:limit]:
chain = token.get("chainId", "")
address = token.get("tokenAddress", "")
if not chain or not address:
enriched.append(token)
continue
try:
data = dexscreener_get(
f"{BASE_URL}/tokens/v1/{chain}/{address}"
)
pairs = data if isinstance(data, list) else data.get("pairs", [])
if pairs:
# Sort by liquidity
pairs.sort(
key=lambda p: (p.get("liquidity") or {}).get("usd", 0) or 0,
reverse=True,
)
best = pairs[0]
token["_pair"] = {
"price": float(best.get("priceUsd", "0") or "0"),
"liquidity": (best.get("liquidity") or {}).get("usd", 0) or 0,
"volume_24h": (best.get("volume") or {}).get("h24", 0) or 0,
"symbol": best.get("baseToken", {}).get("symbol", "?"),
"dex": best.get("dexId", "?"),
"pair_count": len(pairs),
}
time.sleep(1.0) # respect profile endpoint rate limit
except Exception as e:
print(f" Warning: couldn't enrich {address[:12]}...: {e}")
enriched.append(token)
return enriched
# ── Display ─────────────────────────────────────────────────────────
def format_usd(value: float) -> str:
"""Format a USD value for display."""
if value >= 1_000_000:
return f"${value / 1e6:.2f}M"
elif value >= 1_000:
return f"${value / 1e3:.1f}K"
return f"${value:.2f}"
def print_boosts(boosts: list[dict], title: str) -> None:
"""Print a formatted table of boosted tokens.
Args:
boosts: List of boost entries (optionally enriched).
title: Section title.
"""
if not boosts:
print(f"\n No {title.lower()} found.")
return
print(f"\n{'='*65}")
print(f" {title}")
print(f"{'='*65}")
for i, b in enumerate(boosts, 1):
chain = b.get("chainId", "?")
address = b.get("tokenAddress", "?")
amount = b.get("amount", 0)
total = b.get("totalAmount", 0)
desc = b.get("description", "")[:40]
pair = b.get("_pair", {})
symbol = pair.get("symbol", "")
price = pair.get("price", 0)
liq = pair.get("liquidity", 0)
vol = pair.get("volume_24h", 0)
symbol_str = f" ({symbol})" if symbol else ""
print(f"\n #{i} — {chain}{symbol_str}")
print(f" Address: {address}")
if total:
print(f" Boost: {amount} (total: {total})")
if desc:
print(f" Desc: {desc}")
if pair:
price_str = f"${price:.8f}" if price < 0.01 else f"${price:.4f}"
print(f" Price: {price_str}")
print(f" Liq: {format_usd(liq)} | Vol 24h: {format_usd(vol)}")
print(f" DEX: {pair.get('dex', '?')} | Pairs: {pair.get('pair_count', 0)}")
def print_profiles(profiles: list[dict]) -> None:
"""Print latest token profiles.
Args:
profiles: List of profile entries.
"""
if not profiles:
print("\n No new profiles found.")
return
print(f"\n{'='*65}")
print(f" Latest Token Profiles")
print(f"{'='*65}")
for i, p in enumerate(profiles[:10], 1):
chain = p.get("chainId", "?")
address = p.get("tokenAddress", "?")
desc = p.get("description", "")[:50]
links = p.get("links", [])
link_types = [l.get("type", "?") for l in links[:3]]
print(f"\n #{i} — {chain}")
print(f" Address: {address}")
if desc:
print(f" Desc: {desc}")
if link_types:
print(f" Links: {', '.join(link_types)}")
# ── Main ────────────────────────────────────────────────────────────
def main() -> None:
"""Run boost monitor and display results."""
print("DexScreener Boost Monitor")
print("=" * 65)
# Fetch all data
print("\nFetching latest boosts...")
latest = fetch_latest_boosts()
time.sleep(1.0)
print("Fetching top boosts...")
top = fetch_top_boosts()
time.sleep(1.0)
print("Fetching latest profiles...")
profiles = fetch_latest_profiles()
time.sleep(1.0)
print("Fetching community takeovers...")
ctos = fetch_community_takeovers()
# Apply chain filter
if CHAIN_FILTER:
latest = [b for b in latest if b.get("chainId") == CHAIN_FILTER]
top = [b for b in top if b.get("chainId") == CHAIN_FILTER]
profiles = [p for p in profiles if p.get("chainId") == CHAIN_FILTER]
ctos = [c for c in ctos if c.get("chainId") == CHAIN_FILTER]
print(f"\nFiltered to chain: {CHAIN_FILTER}")
# Summary
print(f"\nFound: {len(latest)} latest boosts, {len(top)} top boosts, "
f"{len(profiles)} profiles, {len(ctos)} CTOs")
# Enrich top boosts with pair data
if top:
print("\nEnriching top boosts with pair data...")
top = enrich_with_pair_data(top, limit=5)
# Display
print_boosts(top[:10], "Top Boosted Tokens")
print_boosts(latest[:10], "Latest Boosts")
print_profiles(profiles)
if ctos:
print(f"\n{'='*65}")
print(f" Community Takeovers: {len(ctos)} active")
print(f"{'='*65}")
for c in ctos[:5]:
print(f" - {c.get('chainId', '?')}: {c.get('tokenAddress', '?')[:20]}...")
# Chain distribution
all_chains = [b.get("chainId") for b in latest + top if b.get("chainId")]
if all_chains:
from collections import Counter
chain_counts = Counter(all_chains).most_common(10)
print(f"\n{'='*65}")
print(f" Boost Activity by Chain")
print(f"{'='*65}")
for chain, count in chain_counts:
bar = "█" * min(count, 30)
print(f" {chain:<15} {count:>4} {bar}")
print()
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""Look up any token across all chains using DexScreener (no auth required).
Fetches all DEX pairs for a token, sorts by liquidity, and displays a
comprehensive summary including price, volume, liquidity, buy/sell
pressure, and cross-DEX comparison.
Usage:
python scripts/token_lookup.py
TOKEN_ADDRESS="So11111111111111111111111111111111111111112" python scripts/token_lookup.py
TOKEN_SYMBOL="BONK" python scripts/token_lookup.py
Dependencies:
uv pip install httpx
"""
import os
import sys
import time
from datetime import datetime, timezone
from typing import Optional
import httpx
# ── Configuration ───────────────────────────────────────────────────
TOKEN_ADDRESS = os.getenv("TOKEN_ADDRESS", "")
TOKEN_SYMBOL = os.getenv("TOKEN_SYMBOL", "")
CHAIN_FILTER = os.getenv("CHAIN_FILTER", "") # e.g., "solana" to limit results
BASE_URL = "https://api.dexscreener.com"
if not TOKEN_ADDRESS and not TOKEN_SYMBOL:
TOKEN_ADDRESS = "So11111111111111111111111111111111111111112" # SOL
# ── API Functions ───────────────────────────────────────────────────
def dexscreener_get(url: str, params: Optional[dict] = None, max_retries: int = 3) -> dict:
"""Make a GET request to DexScreener with retry logic.
Args:
url: Full URL to request.
params: Query parameters.
max_retries: Maximum retry attempts.
Returns:
Parsed JSON response.
Raises:
RuntimeError: After exhausting retries.
"""
for attempt in range(max_retries):
try:
resp = httpx.get(url, params=params, timeout=15.0)
if resp.status_code == 200:
return resp.json()
if resp.status_code == 429:
wait = 10.0 * (attempt + 1)
print(f" Rate limited, waiting {wait}s...")
time.sleep(wait)
continue
if resp.status_code >= 500:
time.sleep(5.0 * (attempt + 1))
continue
resp.raise_for_status()
except httpx.TimeoutException:
if attempt < max_retries - 1:
time.sleep(3.0)
continue
raise
raise RuntimeError(f"Failed after {max_retries} retries: {url}")
def lookup_by_address(address: str) -> list[dict]:
"""Look up all pairs for a token address.
Args:
address: Token mint/contract address.
Returns:
List of pair dicts sorted by liquidity.
"""
data = dexscreener_get(f"{BASE_URL}/latest/dex/tokens/{address}")
pairs = data.get("pairs") or []
pairs.sort(key=lambda p: (p.get("liquidity") or {}).get("usd", 0) or 0, reverse=True)
return pairs
def lookup_by_symbol(symbol: str) -> list[dict]:
"""Search for pairs by token symbol.
Args:
symbol: Token symbol (e.g., "BONK").
Returns:
List of pair dicts matching the symbol, sorted by liquidity.
"""
data = dexscreener_get(f"{BASE_URL}/latest/dex/search", params={"q": symbol})
pairs = data.get("pairs") or []
# Filter to exact symbol match
matched = [
p for p in pairs
if p.get("baseToken", {}).get("symbol", "").upper() == symbol.upper()
]
matched.sort(key=lambda p: (p.get("liquidity") or {}).get("usd", 0) or 0, reverse=True)
return matched if matched else pairs
# ── Analysis ────────────────────────────────────────────────────────
def analyze_buy_sell_pressure(pair: dict) -> dict:
"""Analyze buy/sell transaction ratios across timeframes.
Args:
pair: Single pair response dict.
Returns:
Dict with buy ratios by timeframe.
"""
txns = pair.get("txns", {})
result = {}
for tf in ["m5", "h1", "h6", "h24"]:
data = txns.get(tf, {})
buys = data.get("buys", 0)
sells = data.get("sells", 0)
total = buys + sells
result[tf] = {
"buys": buys,
"sells": sells,
"total": total,
"buy_ratio": round(buys / total, 3) if total > 0 else 0.5,
}
return result
def format_usd(value: float) -> str:
"""Format a USD value for display."""
if value >= 1_000_000_000:
return f"${value / 1e9:.2f}B"
elif value >= 1_000_000:
return f"${value / 1e6:.2f}M"
elif value >= 1_000:
return f"${value / 1e3:.2f}K"
return f"${value:.2f}"
# ── Display ─────────────────────────────────────────────────────────
def print_pair_summary(pair: dict, rank: int = 1) -> None:
"""Print summary for a single pair.
Args:
pair: Pair response dict.
rank: Display rank number.
"""
base = pair.get("baseToken", {})
quote = pair.get("quoteToken", {})
symbol = base.get("symbol", "?")
name = base.get("name", "Unknown")
price_usd = float(pair.get("priceUsd", "0") or "0")
liq = (pair.get("liquidity") or {}).get("usd", 0) or 0
vol_24h = (pair.get("volume") or {}).get("h24", 0) or 0
fdv = pair.get("fdv", 0) or 0
mcap = pair.get("marketCap", 0) or 0
chain = pair.get("chainId", "?")
dex = pair.get("dexId", "?")
labels = pair.get("labels", [])
pool_type = f" [{', '.join(labels)}]" if labels else ""
created_ms = pair.get("pairCreatedAt", 0)
created_str = ""
if created_ms:
created_dt = datetime.fromtimestamp(created_ms / 1000, tz=timezone.utc)
created_str = created_dt.strftime("%Y-%m-%d")
print(f"\n{'─'*55}")
print(f" #{rank} — {symbol}/{quote.get('symbol', '?')} on {dex} ({chain}){pool_type}")
print(f"{'─'*55}")
print(f" Name: {name}")
print(f" Price: {'${:.8f}'.format(price_usd) if price_usd < 0.01 else '${:.4f}'.format(price_usd)}")
print(f" Liquidity: {format_usd(liq)}")
print(f" Volume 24h: {format_usd(vol_24h)}")
if mcap:
print(f" Market Cap: {format_usd(mcap)}")
if fdv and fdv != mcap:
print(f" FDV: {format_usd(fdv)}")
if created_str:
print(f" Created: {created_str}")
print(f" Pair: {pair.get('pairAddress', '?')[:20]}...")
# Price changes
changes = pair.get("priceChange", {})
if changes:
parts = []
for tf in ["m5", "h1", "h6", "h24"]:
pct = changes.get(tf)
if pct is not None:
parts.append(f"{tf}: {pct:+.2f}%")
if parts:
print(f" Changes: {' | '.join(parts)}")
# Buy/sell pressure
pressure = analyze_buy_sell_pressure(pair)
h1 = pressure.get("h1", {})
h24 = pressure.get("h24", {})
if h1.get("total", 0) > 0:
print(f" Txns 1h: {h1['buys']}B / {h1['sells']}S (buy ratio: {h1['buy_ratio']:.0%})")
if h24.get("total", 0) > 0:
print(f" Txns 24h: {h24['buys']}B / {h24['sells']}S (buy ratio: {h24['buy_ratio']:.0%})")
def print_cross_dex_comparison(pairs: list[dict]) -> None:
"""Print a comparison table across DEXes.
Args:
pairs: List of pair dicts, already sorted by liquidity.
"""
if len(pairs) < 2:
return
# Filter to same chain for meaningful comparison
chains = set(p.get("chainId") for p in pairs[:10])
if len(chains) == 1:
print(f"\n{'='*55}")
print(f" Cross-DEX Comparison ({list(chains)[0]})")
print(f"{'='*55}")
else:
print(f"\n{'='*55}")
print(f" Cross-Chain Comparison")
print(f"{'='*55}")
print(f" {'DEX':<15} {'Chain':<10} {'Price':>12} {'Liquidity':>12} {'Vol 24h':>12}")
print(f" {'─'*15} {'─'*10} {'─'*12} {'─'*12} {'─'*12}")
for p in pairs[:10]:
dex = p.get("dexId", "?")[:14]
chain = p.get("chainId", "?")[:9]
price = float(p.get("priceUsd", "0") or "0")
liq = (p.get("liquidity") or {}).get("usd", 0) or 0
vol = (p.get("volume") or {}).get("h24", 0) or 0
price_str = f"${price:.6f}" if price < 0.01 else f"${price:.4f}"
print(f" {dex:<15} {chain:<10} {price_str:>12} {format_usd(liq):>12} {format_usd(vol):>12}")
# ── Main ────────────────────────────────────────────────────────────
def main() -> None:
"""Run token lookup and display results."""
if TOKEN_ADDRESS:
print(f"Looking up token: {TOKEN_ADDRESS}")
pairs = lookup_by_address(TOKEN_ADDRESS)
else:
print(f"Searching for: {TOKEN_SYMBOL}")
pairs = lookup_by_symbol(TOKEN_SYMBOL)
if CHAIN_FILTER:
pairs = [p for p in pairs if p.get("chainId") == CHAIN_FILTER]
if not pairs:
print("No pairs found.")
sys.exit(1)
# Primary pair details
base_symbol = pairs[0].get("baseToken", {}).get("symbol", "?")
total_liq = sum((p.get("liquidity") or {}).get("usd", 0) or 0 for p in pairs)
print(f"\nFound {len(pairs)} pairs for {base_symbol}")
print(f"Total liquidity across all pairs: {format_usd(total_liq)}")
# Show top 3 pairs
for i, pair in enumerate(pairs[:3], 1):
print_pair_summary(pair, rank=i)
# Cross-DEX comparison
if len(pairs) >= 2:
print_cross_dex_comparison(pairs)
print()
if __name__ == "__main__":
main()
Related skills
FAQ
Does DexScreener need an API key?
No. The API needs no key or headers; rate limits are ~300 req/min for DEX data and 60 req/min for profile/boost endpoints.
How many chains does it cover?
80+ chains, with search returning around 30 pairs across all chains.