Ask an LLM "what's trending in crypto right now?" and you get a confident answer from its training data. Useless. Markets move by the minute.

So I gave Claude two tools, fetch_json for market APIs (CoinGecko, DexScreener) and fetch_page for announcements and docs, and let it decide what to read. The result is a small agent that answers questions like:

"What's trending on CoinGecko right now? Give me price and 24h change for the top 3, and check DexScreener for their liquidity."

with live numbers and the URLs it used.

The annoying part wasn't the agent. It was getting the data. Public crypto APIs rate-limit per IP, and exchange and announcement pages love to 403 anything that comes from a cloud server. The fix is boring and effective: send the requests through a proxy pool and retry blocked ones after a short back-off. I'll show a free one below.

How it works

  1. You ask a question.
  2. Claude decides which API or page it needs and calls fetch_json(url) or fetch_page(url).
  3. The script fetches it through the proxy. A 403/429/503 is retried after a back-off, and on a rotating pool each retry leaves from a new IP.
  4. Pages are stripped of script, style, nav and footer, then truncated, so the context window stays small. JSON is truncated too.
  5. Claude reads the results, maybe follows a link or calls another endpoint, then answers and cites its sources.

A max_steps cap keeps a curious agent from crawling the whole internet on your API bill.

The code

"""A crypto research agent: reads market APIs and web pages through a proxy. pip install anthropic requests beautifulsoup4 export ANTHROPIC_API_KEY=... python crypto_agent.py "Which of today's top 5 CoinGecko gainers trade on DexScreener with >$1M liquidity?" Proxy (PROXY_URL): default http://ws.solproxy.net:8501 free pool, no signup (1 GB/day per IP) free acct http://free:TOKEN@ws.solproxy.net:8501 free account, 25 GB/month paid http://USERNAME:TOKEN@ws.solproxy.net:8500 rotating residential """ import json import os import sys import time from urllib.parse import urljoin import anthropic import requests from bs4 import BeautifulSoup PROXY_URL = os.getenv("PROXY_URL", "http://ws.solproxy.net:8501") # "" = no proxy PROXIES = {"http": PROXY_URL, "https": PROXY_URL} if PROXY_URL else None HEADERS = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " "(KHTML, like Gecko) Chrome/130.0 Safari/537.36", "Accept-Language": "en-US,en;q=0.9"} BLOCKED = {403, 429, 503} def _get(url: str): """GET with retries on blocks/rate limits. Each retry goes out through the proxy again (a new exit IP on a rotating pool) after a short back-off.""" last = None for attempt in range(3): try: r = requests.get(url, headers=HEADERS, proxies=PROXIES, timeout=30) except requests.RequestException as e: last = f"network error: {e}" else: if r.status_code not in BLOCKED: return r, None last = f"HTTP {r.status_code} (blocked/rate-limited)" time.sleep(1.5 * (attempt + 1)) return None, last def fetch_json(url: str, max_chars: int = 12000) -> dict: """Call a public JSON API (CoinGecko, DexScreener, an exchange's REST API).""" r, err = _get(url) if err: return {"url": url, "error": err} if r.status_code >= 400: return {"url": url, "error": f"HTTP {r.status_code}"} try: data = r.json() except ValueError: return {"url": url, "error": "response is not JSON"} return {"url": url, "json": json.dumps(data)[:max_chars]} def fetch_page(url: str, max_chars: int = 8000) -> dict: """Download a page and return its title, readable text and links.""" r, err = _get(url) if err: return {"url": url, "error": err} if r.status_code >= 400: return {"url": url, "error": f"HTTP {r.status_code}"} soup = BeautifulSoup(r.text, "html.parser") for tag in soup(["script", "style", "noscript", "svg", "nav", "footer"]): tag.decompose() text = " ".join(soup.get_text(" ").split()) links = [] for a in soup.find_all("a", href=True): href = urljoin(url, a["href"]) if href.startswith("http") and href not in links: links.append(href) return {"url": url, "title": soup.title.string.strip() if soup.title and soup.title.string else "", "text": text[:max_chars], "links": links[:40]} TOOLS = [{ "name": "fetch_page", "description": "Fetch a web page and return its title, main text (truncated) and up to " "40 absolute links found on it. Use the links to go deeper.", "input_schema": { "type": "object", "properties": {"url": {"type": "string", "description": "Absolute http(s) URL"}}, "required": ["url"], "additionalProperties": False, }, "strict": True, }, { "name": "fetch_json", "description": "Call a public JSON API and return the body (truncated). Useful endpoints: " "https://api.coingecko.com/api/v3/search/trending, " "https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&order=market_cap_desc&per_page=50, " "https://api.coingecko.com/api/v3/simple/price?ids=bitcoin,ethereum&vs_currencies=usd&include_24hr_change=true, " "https://api.dexscreener.com/latest/dex/search?q=<token symbol or address>.", "input_schema": { "type": "object", "properties": {"url": {"type": "string", "description": "Absolute https URL of the API call"}}, "required": ["url"], "additionalProperties": False, }, "strict": True, }] TOOL_FUNCS = {"fetch_page": fetch_page, "fetch_json": fetch_json} SYSTEM = ("You are a crypto market research agent. Prefer fetch_json on public APIs for " "numbers (prices, volume, liquidity) and fetch_page for announcements, docs and " "news. Make as few calls as you need, stop as soon as you can answer, give the " "numbers with the time you read them, and cite every URL you used. " "This is research, not financial advice: say so if asked what to buy.") def run(question: str, max_steps: int = 12) -> str: client = anthropic.Anthropic() messages = [{"role": "user", "content": question}] for _ in range(max_steps): response = client.beta.messages.create( model="claude-opus-5-5", max_tokens=16000, system=SYSTEM, tools=TOOLS, messages=messages, betas=["server-side-fallback-2026-07-01"], fallbacks="default", ) if response.stop_reason in ("end_turn", "refusal", "max_tokens"): return "".join(b.text for b in response.content if b.type == "text") messages.append({"role": "assistant", "content": response.content}) if response.stop_reason == "pause_turn": continue results = [] for block in response.content: if block.type == "tool_use": print(f" -> {block.name} {block.input['url']}", file=sys.stderr) page = TOOL_FUNCS[block.name](block.input["url"]) results.append({"type": "tool_result", "tool_use_id": block.id, "content": json.dumps(page), "is_error": "error" in page}) messages.append({"role": "user", "content": results}) return "Stopped: step limit reached." if __name__ == "__main__": print(run(" ".join(sys.argv[1:]) or "What is trending on CoinGecko right now, and what are " "the price and 24h change of the top 3 trending coins?")) 

Run it (free)

pip install anthropic requests beautifulsoup4 export ANTHROPIC_API_KEY=sk-ant-... python crypto_agent.py "What is trending on CoinGecko right now? Price and 24h change of the top 3, and their DexScreener liquidity." 

You'll see the agent's path on stderr:

 -> fetch_json https://api.coingecko.com/api/v3/search/trending -> fetch_json https://api.coingecko.com/api/v3/simple/price?ids=...&vs_currencies=usd&include_24hr_change=true -> fetch_json https://api.dexscreener.com/latest/dex/search?q=... 

About the proxy: by default the script uses http://ws.solproxy.net:8501, a free proxy pool you can use without signing up (1 GB per day per IP). A free account raises that to 25 GB a month (http://free:YOUR_TOKEN@ws.solproxy.net:8501). Set PROXY_URL="" to run from your own IP.

Questions worth trying

  • "Compare the 24h volume of the top 10 coins by market cap with their 7-day average. Which ones are spiking?"
  • "Search DexScreener for PEPE pairs and list the 3 with the most liquidity, with their DEX and chain."
  • "Read the latest listing announcements page of an exchange and tell me which new tokens are already on CoinGecko."
  • "What's the current price of BTC, ETH and SOL, and how far is each from its all-time high?"

Ideas to extend it

  • Schedule it. Run every hour and diff the answer: you get a cheap "what changed" bot for Telegram or Discord.
  • Add a cache (a dict keyed by URL with a 60 s TTL) so repeated questions don't burn rate limits.
  • Sticky IPs for anything with a session (logins, paginated dashboards). With SOLProxy add -sticky10m to the proxy username.
  • Be a good citizen. Rate limits exist for a reason: keep the back-off, cache aggressively, and use an official API key when you need volume.

This is a research tool, not financial advice.

🎁 20 free GB codes for readers

The proxy used here is SOLProxy. The free pool is fine for APIs. For pages that block hard, residential IPs work much better, and those are $0.80/GB pay-as-you-go with no subscription.

The first 20 readers get 1 GB of residential traffic free. Sign up and redeem one code each (each code works once):

  1. DEVTO-SYP3DF
  2. DEVTO-F3PQUW
  3. DEVTO-5ZTNCZ
  4. DEVTO-XBJYQV
  5. DEVTO-CZZ2CJ
  6. DEVTO-P3U7EF
  7. DEVTO-SNFFDE
  8. DEVTO-U6UDZD
  9. DEVTO-SCLN2T
  10. DEVTO-HDT8G7
  11. DEVTO-4WRWMK
  12. DEVTO-WE6NRS
  13. DEVTO-6A72RK
  14. DEVTO-365BES
  15. DEVTO-H2XFS4
  16. DEVTO-CH66AZ
  17. DEVTO-894KH7
  18. DEVTO-F4RJ42
  19. DEVTO-ATDHV9
  20. DEVTO-84M2DY

Redeem at Buy Traffic & Apply Promo in the dashboard, or open https://solproxy.net/dashboard?promo=YOUR-CODE. Then switch the script to PROXY_URL="http://USERNAME:TOKEN@ws.solproxy.net:8500".

Codes all gone? The free pool above still works, and there's a free proxy list updated every minute (also on GitHub).

What would you ask a crypto agent like this? Drop it in the comments and I'll run it.