Coverage for api \ blockchain_endpoints.py: 0.00%
183 statements
« prev ^ index » next coverage.py v7.12.0, created at 2025-11-25 15:37 +0330
« prev ^ index » next coverage.py v7.12.0, created at 2025-11-25 15:37 +0330
1"""
2Blockchain Explorer API Endpoints
3Provides endpoints for Ethereum, BSC, and Tron blockchain data
4"""
6import os
7import logging
8import httpx
9from datetime import datetime, timedelta
10from typing import Optional, List, Dict, Any
11from fastapi import APIRouter, HTTPException, Query
12from pydantic import BaseModel
14from config import EXTERNAL_PROVIDERS, PROVIDER_FALLBACK_STRATEGY
16logger = logging.getLogger(__name__)
18router = APIRouter(prefix="/api/blockchain", tags=["blockchain"])
21# ============================================================================
22# Models
23# ============================================================================
25class BlockchainTransaction(BaseModel):
26 """Blockchain transaction model"""
27 tx_hash: str
28 from_address: str
29 to_address: str
30 value: float
31 value_usd: Optional[float] = None
32 timestamp: datetime
33 block_number: Optional[int] = None
34 blockchain: str
35 gas_used: Optional[int] = None
36 gas_price: Optional[float] = None
39# ============================================================================
40# Helper Functions
41# ============================================================================
43async def call_etherscan_api(
44 action: str,
45 params: Dict[str, Any] = None,
46 timeout: float = 10.0
47) -> Dict[str, Any]:
48 """Call Etherscan API"""
49 provider = EXTERNAL_PROVIDERS.get("etherscan")
50 if not provider or not provider.get("api_key"):
51 raise ValueError("Etherscan API key not configured")
53 base_url = provider["base_url"]
54 api_key = provider["api_key"]
56 params = params or {}
57 params.update({
58 "module": "proxy",
59 "action": action,
60 "apikey": api_key
61 })
63 async with httpx.AsyncClient(timeout=timeout) as client:
64 response = await client.get(base_url, params=params)
65 response.raise_for_status()
66 data = response.json()
68 if data.get("status") == "0" and "rate limit" in data.get("message", "").lower():
69 raise Exception("Rate limit exceeded")
71 return data
74async def call_bscscan_api(
75 action: str,
76 params: Dict[str, Any] = None,
77 timeout: float = 10.0
78) -> Dict[str, Any]:
79 """Call BSCScan API"""
80 provider = EXTERNAL_PROVIDERS.get("bscscan")
81 if not provider or not provider.get("api_key"):
82 raise ValueError("BSCScan API key not configured")
84 base_url = provider["base_url"]
85 api_key = provider["api_key"]
87 params = params or {}
88 params.update({
89 "module": "proxy",
90 "action": action,
91 "apikey": api_key
92 })
94 async with httpx.AsyncClient(timeout=timeout) as client:
95 response = await client.get(base_url, params=params)
96 response.raise_for_status()
97 data = response.json()
99 if data.get("status") == "0" and "rate limit" in data.get("message", "").lower():
100 raise Exception("Rate limit exceeded")
102 return data
105async def call_tronscan_api(
106 endpoint: str,
107 params: Dict[str, Any] = None,
108 timeout: float = 10.0
109) -> Dict[str, Any]:
110 """Call TronScan API"""
111 provider = EXTERNAL_PROVIDERS.get("tronscan")
112 if not provider or not provider.get("api_key"):
113 raise ValueError("TronScan API key not configured")
115 base_url = provider["base_url"]
116 api_key = provider["api_key"]
118 url = f"{base_url}/{endpoint}"
119 headers = {
120 "TRON-PRO-API-KEY": api_key
121 }
123 async with httpx.AsyncClient(timeout=timeout, headers=headers) as client:
124 response = await client.get(url, params=params or {})
125 response.raise_for_status()
126 return response.json()
129# ============================================================================
130# Ethereum Endpoints
131# ============================================================================
133@router.get("/ethereum/recent-transactions")
134async def get_ethereum_transactions(
135 limit: int = Query(default=20, ge=1, le=100),
136 min_value_usd: Optional[float] = Query(default=100000, description="Minimum transaction value in USD")
137):
138 """
139 Get recent large Ethereum transactions
141 Args:
142 limit: Maximum number of transactions to return
143 min_value_usd: Minimum transaction value in USD
144 """
145 try:
146 fallback_config = PROVIDER_FALLBACK_STRATEGY.get("etherscan", {})
148 # Get latest block
149 try:
150 block_data = await call_etherscan_api("eth_blockNumber")
151 latest_block = int(block_data.get("result", "0x0"), 16)
153 # Get recent blocks (approximate - we'll get transactions from last 100 blocks)
154 transactions = []
155 start_block = max(0, latest_block - 100)
157 for block_num in range(latest_block, start_block, -1):
158 if len(transactions) >= limit:
159 break
161 try:
162 block_data = await call_etherscan_api(
163 "eth_getBlockByNumber",
164 params={
165 "tag": hex(block_num),
166 "boolean": "true"
167 }
168 )
170 block_result = block_data.get("result", {})
171 txs = block_result.get("transactions", [])
173 for tx in txs[:10]: # Limit per block
174 if len(transactions) >= limit:
175 break
177 value_wei = int(tx.get("value", "0x0"), 16)
178 value_eth = value_wei / 1e18
180 # Rough USD estimate (ETH price ~$3000)
181 value_usd = value_eth * 3000
183 if value_usd >= min_value_usd:
184 transactions.append({
185 "tx_hash": tx.get("hash", ""),
186 "from_address": tx.get("from", ""),
187 "to_address": tx.get("to", ""),
188 "value": value_eth,
189 "value_usd": value_usd,
190 "block_number": block_num,
191 "timestamp": datetime.utcnow(), # Approximate
192 "blockchain": "ethereum"
193 })
194 except Exception as e:
195 logger.warning(f"Error fetching block {block_num}: {e}")
196 continue
198 return {
199 "transactions": transactions[:limit],
200 "count": len(transactions),
201 "source": "etherscan"
202 }
204 except ValueError as e:
205 return {
206 "error": str(e),
207 "transactions": [],
208 "count": 0
209 }
210 except Exception as e:
211 logger.error(f"Etherscan API error: {e}")
212 return {
213 "error": f"Failed to fetch transactions: {str(e)}",
214 "transactions": [],
215 "count": 0
216 }
218 except Exception as e:
219 logger.error(f"Ethereum transactions error: {e}", exc_info=True)
220 raise HTTPException(status_code=500, detail=str(e))
223@router.get("/ethereum/whale-addresses")
224async def get_ethereum_whales(
225 min_balance_eth: float = Query(default=10000, description="Minimum balance in ETH")
226):
227 """
228 Get Ethereum addresses with large balances (whales)
229 """
230 try:
231 # Note: This is a simplified version
232 # Real implementation would require tracking addresses over time
233 return {
234 "message": "Whale address tracking requires historical data",
235 "whales": [],
236 "count": 0
237 }
238 except Exception as e:
239 logger.error(f"Ethereum whales error: {e}")
240 raise HTTPException(status_code=500, detail=str(e))
243# ============================================================================
244# BSC Endpoints
245# ============================================================================
247@router.get("/bsc/whale-movements")
248async def get_bsc_whale_movements(
249 limit: int = Query(default=20, ge=1, le=100),
250 min_value_usd: Optional[float] = Query(default=1000000, description="Minimum transaction value in USD")
251):
252 """
253 Get large BSC transactions (whale movements)
254 """
255 try:
256 fallback_config = PROVIDER_FALLBACK_STRATEGY.get("bscscan", {})
258 try:
259 # Get latest block
260 block_data = await call_bscscan_api("eth_blockNumber")
261 latest_block = int(block_data.get("result", "0x0"), 16)
263 transactions = []
264 start_block = max(0, latest_block - 200) # Check more blocks for BSC
266 for block_num in range(latest_block, start_block, -1):
267 if len(transactions) >= limit:
268 break
270 try:
271 block_data = await call_bscscan_api(
272 "eth_getBlockByNumber",
273 params={
274 "tag": hex(block_num),
275 "boolean": "true"
276 }
277 )
279 block_result = block_data.get("result", {})
280 txs = block_result.get("transactions", [])
282 for tx in txs[:10]:
283 if len(transactions) >= limit:
284 break
286 value_wei = int(tx.get("value", "0x0"), 16)
287 value_bnb = value_wei / 1e18
289 # Rough USD estimate (BNB price ~$600)
290 value_usd = value_bnb * 600
292 if value_usd >= min_value_usd:
293 transactions.append({
294 "tx_hash": tx.get("hash", ""),
295 "from_address": tx.get("from", ""),
296 "to_address": tx.get("to", ""),
297 "value": value_bnb,
298 "value_usd": value_usd,
299 "block_number": block_num,
300 "timestamp": datetime.utcnow(),
301 "blockchain": "bsc"
302 })
303 except Exception as e:
304 logger.warning(f"Error fetching BSC block {block_num}: {e}")
305 continue
307 return {
308 "transactions": transactions[:limit],
309 "count": len(transactions),
310 "source": "bscscan"
311 }
313 except ValueError as e:
314 return {
315 "error": str(e),
316 "transactions": [],
317 "count": 0
318 }
319 except Exception as e:
320 logger.error(f"BSCScan API error: {e}")
321 return {
322 "error": f"Failed to fetch transactions: {str(e)}",
323 "transactions": [],
324 "count": 0
325 }
327 except Exception as e:
328 logger.error(f"BSC whale movements error: {e}", exc_info=True)
329 raise HTTPException(status_code=500, detail=str(e))
332# ============================================================================
333# Tron Endpoints
334# ============================================================================
336@router.get("/tron/large-transfers")
337async def get_tron_large_transfers(
338 limit: int = Query(default=20, ge=1, le=100),
339 min_value_usd: Optional[float] = Query(default=100000, description="Minimum transfer value in USD")
340):
341 """
342 Get large Tron transfers
343 """
344 try:
345 fallback_config = PROVIDER_FALLBACK_STRATEGY.get("tronscan", {})
347 try:
348 # TronScan API endpoint for large transfers
349 # Note: This is a simplified implementation
350 # Real TronScan API may have different endpoints
352 # For now, return a placeholder structure
353 return {
354 "message": "TronScan API integration in progress",
355 "transactions": [],
356 "count": 0,
357 "source": "tronscan"
358 }
360 except ValueError as e:
361 return {
362 "error": str(e),
363 "transactions": [],
364 "count": 0
365 }
366 except Exception as e:
367 logger.error(f"TronScan API error: {e}")
368 return {
369 "error": f"Failed to fetch transfers: {str(e)}",
370 "transactions": [],
371 "count": 0
372 }
374 except Exception as e:
375 logger.error(f"Tron large transfers error: {e}", exc_info=True)
376 raise HTTPException(status_code=500, detail=str(e))
379# ============================================================================
380# Combined Whale Activity
381# ============================================================================
383@router.get("/whales/activity")
384async def get_whale_activity(
385 limit: int = Query(default=50, ge=1, le=200),
386 min_amount_usd: float = Query(default=1000000, description="Minimum transaction amount in USD"),
387 hours: int = Query(default=24, ge=1, le=168, description="Time period in hours")
388):
389 """
390 Get combined whale activity from all blockchains
391 """
392 try:
393 all_transactions = []
395 # Get Ethereum transactions
396 try:
397 eth_data = await get_ethereum_transactions(limit=limit, min_value_usd=min_amount_usd)
398 if eth_data.get("transactions"):
399 all_transactions.extend(eth_data["transactions"])
400 except Exception as e:
401 logger.warning(f"Failed to get Ethereum transactions: {e}")
403 # Get BSC transactions
404 try:
405 bsc_data = await get_bsc_whale_movements(limit=limit, min_value_usd=min_amount_usd)
406 if bsc_data.get("transactions"):
407 all_transactions.extend(bsc_data["transactions"])
408 except Exception as e:
409 logger.warning(f"Failed to get BSC transactions: {e}")
411 # Get Tron transfers
412 try:
413 tron_data = await get_tron_large_transfers(limit=limit, min_value_usd=min_amount_usd)
414 if tron_data.get("transactions"):
415 all_transactions.extend(tron_data["transactions"])
416 except Exception as e:
417 logger.warning(f"Failed to get Tron transfers: {e}")
419 # Sort by value_usd descending
420 all_transactions.sort(key=lambda x: x.get("value_usd", 0), reverse=True)
422 return {
423 "transactions": all_transactions[:limit],
424 "count": len(all_transactions),
425 "total_volume_usd": sum(tx.get("value_usd", 0) for tx in all_transactions),
426 "by_blockchain": {
427 "ethereum": len([t for t in all_transactions if t.get("blockchain") == "ethereum"]),
428 "bsc": len([t for t in all_transactions if t.get("blockchain") == "bsc"]),
429 "tron": len([t for t in all_transactions if t.get("blockchain") == "tron"])
430 },
431 "timestamp": datetime.utcnow().isoformat()
432 }
434 except Exception as e:
435 logger.error(f"Whale activity error: {e}", exc_info=True)
436 raise HTTPException(status_code=500, detail=str(e))