Learn how to build a Moonshot memecoin tracker using CoinMarketCap DEX API for new token discovery on Solana.
Moonshot is a memecoin launchpad built by DEX Screener, one of the most used token analytics platforms in crypto.
Tokens launch on Moonshot's bonding curve at 0.02 SOL. As buyers accumulate, the price climbs along the curve. When the token reaches the 500 SOL threshold (~$73,000 market cap), it graduates liquidity migrates to Raydium, LP tokens are burned, and the token enters public DEX trading.
The graduation moment is where most of the early opportunity lives. Traders who identify strong tokens in the first minutes after graduation — before momentum is visible to most — capture the asymmetric entry.
CoinMarketCap API solves the signal problem. The moment a Moonshot token graduates and its Raydium pool is seeded, the CMC DEX API begins indexing it. You can then filter by pool age, liquidity depth, security flags, and transaction flow — before the crowd notices.
- CoinMarketCap DEX API powers the signal engine
- Solana RPC and Moonshot's official API handle bonding curve state and graduation events
Why Use CoinMarketCap API for a Moonshot Memecoin Tracker?
Moonshot graduates tokens. CoinMarketCap tells you which ones are worth tracking.
Instead of monitoring every graduation blindly, you can use CoinMarketCap to:
- discover newly graduated tokens the moment they appear on Raydium
- filter pools by age using pubAt to isolate recent launches
- validate liquidity depth before entering
- run security checks for honeypots, transfer restrictions, and hidden taxes
- detect whale transaction flow in the first minutes of trading
- track sub-minute candle momentum on new pools
- apply macro regime filters to avoid launching into adverse conditions
System Architecture
CoinMarketCap DEX API (Signal Layer)
├─ New Token Discovery (graduated Moonshot pools on Raydium)
├─ Pool Validation (liquidity, age via pubAt)
├─ Security Checks (honeypot, taxes — may be empty for new tokens)
├─ Transaction Flow (whale detection)
├─ Sub-minute Candles (first-minutes momentum)
└─ Macro Regime (fear/greed, altcoin season)
↓
Memecoin Signal Engine
↓
Solana RPC / Moonshot API (Validation Layer)
↓
Bonding Curve State + Graduation Events + On-Chain Safety
Architecture Clarification
The CoinMarketCap API acts strictly as an off-chain Signal Layer for memecoin discovery, liquidity validation, security screening, and market regime filtering. It is not a bonding curve monitor, graduation event oracle, or real-time launchpad feed.
Real bonding curve progress, graduation thresholds, LP burn status, and on-chain token safety must be validated directly on-chain via Solana RPC or Moonshot's official API. CMC data has cache delays that make it unsuitable as a sole trigger for latency-sensitive memecoin trading. Post-graduation indexing may take minutes before a new token appears in CMC endpoints.
Plan Note
The primary discovery endpoints (/v1/dex/new/list, /v1/dex/meme/list, /v1/dex/tokens/trending/list) require a paid CMC plan. This guide covers both paid endpoints and a Basic-plan fallback using /v4/dex/spot-pairs/latest.
Project Setup
Python Dependencies
import os
import time
import datetime
import requests
import pandas as pd
import numpy as np
Environment Variables
CMC_API_KEY = os.getenv("CMC_API_KEY")
CMC_BASE_URL = "https://pro-api.coinmarketcap.com"
Headers
HEADERS = {
"Accept": "application/json",
"Content-Type": "application/json",
"X-CMC_PRO_API_KEY": CMC_API_KEY,
}
Config
# Solana platform ID — resolve dynamically, do not hardcode
SOLANA_PLATFORM_ID = None # set by get_solana_platform_id()
# Pool age filter — only track pools newer than this
MAX_POOL_AGE_HOURS = 6
# Minimum liquidity to consider a graduation real
MIN_LIQUIDITY_USD = 10_000
Step 1: Resolve Solana Platform ID
def get_solana_platform_id():
url = f"{CMC_BASE_URL}/v1/dex/platform/list"
r = requests.get(url, headers=HEADERS)
r.raise_for_status()
for p in r.json()["data"]:
if p["n"].lower() == "solana": # field is "n", not "name"
return str(p["id"])
Use the returned ID as platformIds in all POST discovery calls.
Step 2: Discover Newly Graduated Tokens
Paid Plan
def fetch_new_solana_tokens(platform_id):
url = f"{CMC_BASE_URL}/v1/dex/new/list"
body = {"platformIds": platform_id, "limit": 50}
r = requests.post(url, headers=HEADERS, json=body)
r.raise_for_status()
return r.json()["data"]
def fetch_meme_tokens(platform_id):
url = f"{CMC_BASE_URL}/v1/dex/meme/list"
body = {"platformIds": platform_id, "limit": 50}
r = requests.post(url, headers=HEADERS, json=body)
r.raise_for_status()
return r.json()["data"]
Tokens graduated from Moonshot are indexed here almost immediately after their Raydium pool is seeded.
Basic Plan Fallback
def fetch_new_pairs_fallback():
url = f"{CMC_BASE_URL}/v4/dex/spot-pairs/latest"
params = {
"network_slug": "solana",
"dex_slug": "raydium",
"sort": "volume_24h",
"sort_dir": "desc",
}
r = requests.get(url, headers=HEADERS, params=params)
r.raise_for_status()
return r.json()["data"]
This surfaces active Raydium pairs sorted by volume. It does not isolate brand-new launches, but high-volume new tokens rise to the top quickly.
Step 3: Validate Pool Liquidity and Age
Not every graduated token is worth tracking. Filter by liquidity depth and pool age.
Endpoint
GET /v1/dex/token/pools
def fetch_token_pools(token_address):
url = f"{CMC_BASE_URL}/v1/dex/token/pools"
params = {
"address": token_address,
"platform": "solana"
}
r = requests.get(url, headers=HEADERS, params=params)
r.raise_for_status()
return r.json()["data"]
Key fields:
- exn — DEX name
- liqUsd — liquidity in USD, returned as a string — always cast to float
- v24 — 24h volume
- addr — pool address
- pubAt — pool publish timestamp (UNIX seconds) — use to filter by pool age
Note: lr and br are in the schema but frequently absent in live data. Do not rely on them.
def is_pool_valid(pool):
liq = float(pool.get("liqUsd") or 0)
if liq < MIN_LIQUIDITY_USD:
return False
pub_at = pool.get("pubAt")
if pub_at:
pub_at_sec = int(pub_at) / 1000 # pubAt is milliseconds — convert to seconds
age_hours = (time.time() - pub_at_sec) / 3600
if age_hours > MAX_POOL_AGE_HOURS:
return False
return True
def get_best_pool(pools):
valid = [p for p in pools if is_pool_valid(p)]
return max(valid, key=lambda p: float(p.get("liqUsd") or 0)) if valid else None
Step 4: Run Security and Rug Checks
Endpoint
GET /v1/dex/security/detail
def check_token_security(token_address):
url = f"{CMC_BASE_URL}/v1/dex/security/detail"
params = {
"platformName": "solana",
"address": token_address
}
r = requests.get(url, headers=HEADERS, params=params)
r.raise_for_status()
results = r.json()["data"] # data is a list
return results[0] if results else {}
Security data comes from Go+. For tokens with only minutes or hours on Raydium, the audit may not yet be processed - securityItems will be empty or absent. Always parse defensively:
def parse_security_flags(sec):
# Boolean flags inside securityItems[] — match by riskCode
items = sec.get("securityItems") or []
risk_map = {
item["riskCode"]: item.get("isHit", False)
for item in items
if item.get("riskCode")
}
honeypot = risk_map.get("honeypot", False)
transfer_pausable = risk_map.get("transfer_pausable", False)
transfer_pausable = risk_map.get("transfer_pausable", False)
# Taxes at root level — not inside securityItems
buy_tax = 0
sell_tax = 0
try:
buy_tax = float(sec.get("buyTax", 0) or 0)
sell_tax = float(sec.get("sellTax", 0) or 0)
except (TypeError, ValueError):
pass
return {
"honeypot": honeypot,
"transfer_pausable": transfer_pausable,
"buy_tax": buy_tax,
"sell_tax": sell_tax,
"combined_tax": buy_tax + sell_tax,
"scan_available": bool(items), # False = token too new for Go+ scan
}
If scan_available is False, treat the token as unverified — not necessarily malicious, but unscreened. Adjust your risk tolerance accordingly.
Step 5: Detect Whale Transaction Flow
Endpoint
GET /v1/dex/tokens/transactions
def fetch_transactions(token_address, min_volume=10_000):
url = f"{CMC_BASE_URL}/v1/dex/tokens/transactions"
params = {
"address": token_address,
"platform": "solana", # "solana" only — "sol" returns 400
"minVolume": min_volume
}
r = requests.get(url, headers=HEADERS, params=params)
r.raise_for_status()
return r.json()["data"]
Returns raw swap records. Key fields: tx (hash), v (volume USD), t0a (base asset), t1a (quote asset). Use minVolume to isolate whale-sized entries in the first minutes of trading.
Step 6: Track Sub-Minute Momentum
Endpoint
GET /v1/k-line/candles
def fetch_candles(token_address, interval="1min"):
url = f"{CMC_BASE_URL}/v1/k-line/candles"
params = {
"platform": "solana",
"address": token_address,
"interval": interval # 1s, 5s, 30s, 1min, 3min
}
r = requests.get(url, headers=HEADERS, params=params)
r.raise_for_status()
return r.json()["data"]
Each candle is a positional array of 7 elements — not a dict:
Index
Field
[0]
open
[1]
high
[2]
low
[3]
close
[4]
volume
[5]
timestamp, UNIX milliseconds — divide by 1000 for seconds
[6]
traders, unique trader count
def parse_candle(c):
return {
"open": c[0],
"high": c[1],
"low": c[2],
"close": c[3],
"volume": c[4],
"timestamp": c[5],
"traders": c[6],
"datetime": datetime.datetime.fromtimestamp(c[5] / 1000), # ms → seconds
}
A rising traders count alongside a volume spike on the first 1-minute candles is the strongest momentum signal for a newly graduated token.
Step 7: Build the Memecoin Signal Score
def compute_memecoin_score(pool, security_flags, candles):
score = 0
# Liquidity
liq = float(pool.get("liqUsd") or 0)
if liq > 100_000: score += 30
elif liq > 30_000: score += 15
# Pool age — younger pools score higher
pub_at = pool.get("pubAt")
if pub_at:
pub_at_sec = int(pub_at) / 1000 # pubAt is milliseconds — convert to seconds
age_hours = (time.time() - pub_at_sec) / 3600
if age_hours < 1: score += 25
elif age_hours < 3: score += 15
elif age_hours < 6: score += 5
# Security
if security_flags.get("honeypot") or security_flags.get("transfer_pausable"):
return -100 # hard reject
combined_tax = security_flags.get("combined_tax", 0)
if combined_tax == 0: score += 20
elif combined_tax < 5: score += 10
elif combined_tax > 10: score -= 25
if not security_flags.get("scan_available"):
score -= 10 # unscreened penalty, not a disqualifier
# Momentum — candles are positional arrays, traders at index [6]
if candles:
latest = candles[-1]
traders = latest[6] if len(latest) > 6 else 0
traders = traders or 0 # c[6] can be None in live data
if traders > 50: score += 25
elif traders > 20: score += 15
elif traders > 10: score += 8
return score
def should_track(score, threshold=40):
return score >= threshold
Step 8: Apply Macro Regime Filters
Memecoin momentum is highly correlated with macro risk appetite.
Endpoints
GET /v3/fear-and-greed/latest
GET /v1/altcoin-season-index/latest
def fetch_macro_regime():
fg_url = f"{CMC_BASE_URL}/v3/fear-and-greed/latest"
as_url = f"{CMC_BASE_URL}/v1/altcoin-season-index/latest"
fg = requests.get(fg_url, headers=HEADERS).json()["data"]
as_idx = requests.get(as_url, headers=HEADERS).json()["data"]
return {
"fear_greed_value": fg.get("value"),
"fear_greed_classification": fg.get("value_classification"),
"altcoin_index": as_idx.get("altcoin_index"),
}
def is_regime_favorable(regime):
fg = regime.get("fear_greed_value") or 0
as_i = regime.get("altcoin_index") or 0
# Memecoins need strong risk-on conditions
return fg > 55 and as_i >= 60
Step 9: Minimal End-to-End Flow
def run_moonshot_tracker(platform_id):
# 1. Macro regime — poll every 15 min
regime = fetch_macro_regime()
if not is_regime_favorable(regime):
return {"skipped": "unfavorable regime", "regime": regime}
# 2. Discover new tokens
try:
tokens = fetch_new_solana_tokens(platform_id)
except Exception:
tokens = fetch_new_pairs_fallback() # Basic plan fallback
results = []
for token in tokens:
addr = token.get("addr") or token.get("base_asset_contract_address")
if not addr:
continue
# 3. Pool validation
try:
pools = fetch_token_pools(addr)
pool = get_best_pool(pools)
except Exception:
continue
if not pool:
continue
# 4. Security check
try:
sec_raw = check_token_security(addr)
sec_flags = parse_security_flags(sec_raw)
except Exception:
sec_flags = {
"honeypot": False, "transfer_pausable": False,
"combined_tax": 0, "scan_available": False,
}
if sec_flags["honeypot"] or sec_flags["transfer_pausable"]:
continue
# 5. Momentum
try:
candles = fetch_candles(addr, interval="1min")
except Exception:
candles = []
# 6. Score
score = compute_memecoin_score(pool, sec_flags, candles)
if should_track(score):
results.append({
"address": addr,
"symbol": token.get("symbol") or token.get("base_asset_symbol"),
"score": score,
"pool": pool.get("addr"),
"liqUsd": pool.get("liqUsd"),
"pubAt": pool.get("pubAt"),
"combined_tax": sec_flags["combined_tax"],
"scan_available": sec_flags["scan_available"],
})
return {
"signals": sorted(results, key=lambda x: -x["score"]),
"regime": regime,
}
Rate Limits and Polling
CoinMarketCap API is REST-only. There is no WebSocket streaming.
Cache Intervals
Endpoint Group
Cache Interval
Discovery, /v1/dex/new/list, /v1/dex/meme/list
60 seconds
Pool data, /v1/dex/token/pools
60 seconds
DEX pairs, /v4/dex/spot-pairs/latest
60 seconds
Candles, /v1/k-line/candles
60 seconds
Fear & Greed, Altcoin Season
15 minutes
Best Practices
- poll every 60 seconds — faster polling returns cached data and wastes credits
- cache responses locally between polls
- use exponential backoff for HTTP 429 errors, rate reset at 60 seconds
def request_with_backoff(fn, retries=3, base_delay=2):
for attempt in range(retries):
try:
return fn()
except requests.exceptions.HTTPError as e:
if e.response.status_code == 429:
time.sleep(base_delay ** attempt)
else:
raise
raise Exception("Max retries exceeded")
Common Mistakes
Using p["name"] on platform list
The platform name field is "n", not "name". Using p["name"] raises a KeyError.
Not casting liqUsd to float
/v1/dex/token/pools returns liqUsd as a string. Comparing directly with >= raises a TypeError. Always cast: float(pool.get("liqUsd") or 0).
Expecting security scans on brand-new tokens
Go+ takes time to index new contracts. For tokens with minutes or hours on Raydium, securityItems will be empty. Use scan_available to track whether a scan exists — and adjust risk tolerance, not disqualify automatically.
Using "sol" as the platform value
/v1/dex/tokens/transactions only accepts "solana". The abbreviated form returns 400.
Calling .get() on candle arrays
Candles are positional arrays. Use candle[6] for traders — not candle.get("traders").
Treating CMC as a bonding curve monitor
CMC does not track bonding curve progress or graduation events. The DEX API only indexes a token after it graduates and its pool is seeded on Raydium. For real-time graduation detection, use Solana RPC directly.
Using Core API for memecoin discovery
Newly graduated tokens will not appear in /v3/cryptocurrency/quotes/latest or /v3/cryptocurrency/listings/latest for hours or days — the Core API requires manual review or sustained volume thresholds. Always use the DEX API family for memecoin discovery.
Passing tag="memes" as a query parameter
Returns a 400 error. The tag parameter only accepts "all", "defi", or "filesharing". Filter meme tags locally by inspecting the tags array in the response.
Final Thoughts
Moonshot tokens live and die in minutes. The edge is not speed — it is signal quality in the first window after graduation.
CoinMarketCap DEX API gives you a structured layer to filter the noise: liquidity depth, pool age, security status, whale flow, and momentum — all before you interact with any token on-chain.
The key separation:
- CoinMarketCap identifies which graduated tokens are worth tracking
- Solana RPC and Moonshot's API monitor bonding curve state and graduation events
Better filters lead to better memecoin decisions.
Next Steps
- refine MAX_POOL_AGE_HOURS and MIN_LIQUIDITY_USD thresholds based on live results
- add auto-exit signals using trailing candle momentum
- integrate Moonshot's official API for real-time graduation event detection
- track score-to-outcome correlation to improve signal weights
- store graduated token snapshots locally to build historical reference data
