The application is receiving 403 Forbidden errors from GMGN.ai API endpoints. This is due to Cloudflare protection that blocks automated/bot requests.
https://gmgn.ai/api/v1/mutil_window_token_link_rug_vote/sol/{token}https://gmgn.ai/defi/quotation/v1/tokens/top_holders/sol/{token}https://gmgn.ai/api/v1/token_stat/sol/{token}https://gmgn.ai/api/v1/mutil_window_token_info
- Added modern browser User-Agent (Chrome 130)
- Added security headers:
Sec-Fetch-*,Sec-Ch-Ua-* - Better mimicking of real browser requests
- Implemented shared
aiohttp.ClientSessionfor connection reuse - Added connection limits and DNS caching
- Reduced overhead and improved performance
- Each API function now retries up to 2 times
- Progressive delays (1s, 2s) between retries
- Separate error logging for warnings vs final failures
- Graceful degradation when APIs fail
- Detailed logging showing which parts failed
- Informative error messages about Cloudflare protection
Despite improvements, GMGN.ai uses Cloudflare Bot Protection which:
- Detects automated requests through fingerprinting
- Requires JavaScript challenges
- May use TLS fingerprinting
- Tracks request patterns
# Add proxy support to session
connector = aiohttp.TCPConnector(
limit=10,
limit_per_host=5,
ttl_dns_cache=300
)
proxy = "http://your-proxy-service:port"
session = aiohttp.ClientSession(
connector=connector,
headers=headers
)
# In requests
async with session.get(url, proxy=proxy) as response:
...Pros:
- Most reliable for bypassing Cloudflare
- Can use residential proxies for better results
- Services: BrightData, Oxylabs, SmartProxy
Cons:
- Costs money ($50-500/month)
- Adds latency
pip install cloudscraper
# or
pip install curl_cffiimport cloudscraper
scraper = cloudscraper.create_scraper()
response = scraper.get(url)Pros:
- Free
- Handles Cloudflare challenges automatically
- Works with many protected sites
Cons:
- May not work with latest Cloudflare protection
- Synchronous (blocks async flow)
- Requires updates when Cloudflare changes
from playwright.async_api import async_playwright
async def get_token_data_via_browser(token: str):
async with async_playwright() as p:
browser = await p.chromium.launch(headless=True)
page = await browser.new_page()
await page.goto(f"https://gmgn.ai/sol/token/{token}")
# Extract data from page
await browser.close()Pros:
- Most reliable - acts like real user
- Can handle any JavaScript challenge
- Can scrape rendered data
Cons:
- Much slower (2-5 seconds per request)
- High resource usage
- Expensive at scale
Look for alternative sources for token data:
- Jupiter API - for price/swap data
- Helius API - for Solana token metadata
- Birdeye API - for DeFi analytics
- CoinGecko/CoinMarketCap - for basic token info
- Direct Solana RPC - for on-chain data
Pros:
- Legitimate API access
- Better rate limits
- More reliable
Cons:
- May not have exact same data as GMGN
- Some require API keys
- Different data structure
Reach out to GMGN.ai team for:
- Official API key
- IP whitelist
- Developer partnership
Pros:
- Legitimate access
- No workarounds needed
- Better support
Cons:
- May cost money
- May require business justification
- Takes time to get approval
- ✅ Already implemented: Better headers, retry logic, error handling
- Try cloudscraper library (quick to test)
- Add rate limiting between requests
- Implement rotating proxy service
- Or switch to alternative data sources (Helius, Birdeye)
- Contact GMGN for official API access
- Build hybrid solution using multiple data sources
The code now:
- Has better headers
- Retries failed requests
- Provides clear error messages
- Won't crash when API fails
However, 403 errors will likely continue until you implement one of the solutions above (proxies, cloudscraper, or alternative APIs).
pip install cloudscraper aiohttp-cloudscraperThen modify bot/utils/token.py:
import cloudscraper
from functools import lru_cache
@lru_cache(maxsize=1)
def get_scraper():
return cloudscraper.create_scraper()
async def get_top_holders(token: str):
scraper = get_scraper()
url = f"{GMGN_BASE_URL}/tokens/top_holders/sol/{token}"
# Use scraper instead of aiohttp
response = await asyncio.to_thread(
scraper.get,
url,
params=quote_params
)
if response.status_code == 200:
return response.json()
...This wraps sync cloudscraper in async context.
If you want me to implement any of these solutions, let me know which approach you prefer:
- Cloudscraper (easiest, test first)
- Proxy service (most reliable, costs money)
- Alternative APIs (different data, but legitimate)
- Browser automation (slowest, but works)