|
|
|
|
|
""" |
|
|
BSCScan API Client - Binance Smart Chain blockchain explorer |
|
|
Official API: https://bscscan.com/apis |
|
|
""" |
|
|
|
|
|
import httpx |
|
|
import logging |
|
|
import os |
|
|
from typing import Dict, Any, List, Optional |
|
|
from datetime import datetime |
|
|
|
|
|
logger = logging.getLogger(__name__) |
|
|
|
|
|
|
|
|
BSCSCAN_API_KEY = os.getenv("BSCSCAN_KEY", "K62RKHGXTDCG53RU4MCG6XABIMJKTN19IT") |
|
|
|
|
|
|
|
|
class BSCScanClient: |
|
|
""" |
|
|
BSCScan API Client - BNB Smart Chain data |
|
|
""" |
|
|
|
|
|
def __init__(self, api_key: str = BSCSCAN_API_KEY): |
|
|
self.base_url = "https://api.bscscan.com/api" |
|
|
self.api_key = api_key |
|
|
self.timeout = 15.0 |
|
|
|
|
|
async def get_bnb_price(self) -> Dict[str, Any]: |
|
|
""" |
|
|
Get current BNB price in USD |
|
|
|
|
|
Returns: |
|
|
BNB price data |
|
|
""" |
|
|
try: |
|
|
async with httpx.AsyncClient(timeout=self.timeout) as client: |
|
|
response = await client.get( |
|
|
self.base_url, |
|
|
params={ |
|
|
"module": "stats", |
|
|
"action": "bnbprice", |
|
|
"apikey": self.api_key |
|
|
} |
|
|
) |
|
|
response.raise_for_status() |
|
|
data = response.json() |
|
|
|
|
|
if data.get("status") == "1": |
|
|
result_data = data.get("result", {}) |
|
|
price_usd = float(result_data.get("ethusd", 0)) |
|
|
|
|
|
result = { |
|
|
"symbol": "BNB", |
|
|
"price": price_usd, |
|
|
"currency": "USD", |
|
|
"timestamp": result_data.get("ethusd_timestamp", datetime.utcnow().isoformat()), |
|
|
"source": "BSCScan" |
|
|
} |
|
|
|
|
|
logger.info(f"β
BSCScan: Fetched BNB price: ${price_usd}") |
|
|
return result |
|
|
else: |
|
|
error_msg = data.get('message', 'Unknown error') |
|
|
|
|
|
|
|
|
if "NOTOK" in str(data.get('status', '')) or "Invalid API Key" in error_msg: |
|
|
logger.info(f"βΉοΈ BSCScan API key not configured or rate limited - using alternative providers") |
|
|
raise Exception(f"BSCScan unavailable") |
|
|
else: |
|
|
raise Exception(f"BSCScan API error: {error_msg}") |
|
|
|
|
|
except httpx.HTTPStatusError as e: |
|
|
logger.error(f"β BSCScan API HTTP error: {e.response.status_code}") |
|
|
raise |
|
|
except Exception as e: |
|
|
logger.error(f"β BSCScan API failed: {e}") |
|
|
raise |
|
|
|
|
|
async def get_bsc_supply(self) -> Dict[str, Any]: |
|
|
""" |
|
|
Get BNB total and circulating supply |
|
|
|
|
|
Returns: |
|
|
Supply data |
|
|
""" |
|
|
try: |
|
|
async with httpx.AsyncClient(timeout=self.timeout) as client: |
|
|
response = await client.get( |
|
|
self.base_url, |
|
|
params={ |
|
|
"module": "stats", |
|
|
"action": "bnbsupply", |
|
|
"apikey": self.api_key |
|
|
} |
|
|
) |
|
|
response.raise_for_status() |
|
|
data = response.json() |
|
|
|
|
|
if data.get("status") == "1": |
|
|
supply = float(data.get("result", 0)) |
|
|
|
|
|
logger.info(f"β
BSCScan: Fetched BNB supply: {supply}") |
|
|
return { |
|
|
"symbol": "BNB", |
|
|
"supply": supply, |
|
|
"source": "BSCScan", |
|
|
"timestamp": datetime.utcnow().isoformat() |
|
|
} |
|
|
else: |
|
|
raise Exception(f"BSCScan supply error: {data.get('message', 'Unknown error')}") |
|
|
|
|
|
except httpx.HTTPStatusError as e: |
|
|
logger.error(f"β BSCScan supply HTTP error: {e.response.status_code}") |
|
|
raise |
|
|
except Exception as e: |
|
|
logger.error(f"β BSCScan supply failed: {e}") |
|
|
raise |
|
|
|
|
|
async def get_gas_oracle(self) -> Dict[str, Any]: |
|
|
""" |
|
|
Get BSC gas oracle (gas prices) |
|
|
|
|
|
Returns: |
|
|
Gas price data |
|
|
""" |
|
|
try: |
|
|
async with httpx.AsyncClient(timeout=self.timeout) as client: |
|
|
response = await client.get( |
|
|
self.base_url, |
|
|
params={ |
|
|
"module": "gastracker", |
|
|
"action": "gasoracle", |
|
|
"apikey": self.api_key |
|
|
} |
|
|
) |
|
|
response.raise_for_status() |
|
|
data = response.json() |
|
|
|
|
|
if data.get("status") == "1": |
|
|
result_data = data.get("result", {}) |
|
|
|
|
|
logger.info(f"β
BSCScan: Fetched gas oracle data") |
|
|
return { |
|
|
"safe_gas_price": result_data.get("SafeGasPrice", "0"), |
|
|
"propose_gas_price": result_data.get("ProposeGasPrice", "0"), |
|
|
"fast_gas_price": result_data.get("FastGasPrice", "0"), |
|
|
"suggested_base_fee": result_data.get("suggestBaseFee", "0"), |
|
|
"gas_used_ratio": result_data.get("gasUsedRatio", "0"), |
|
|
"chain": "BSC", |
|
|
"source": "BSCScan", |
|
|
"timestamp": datetime.utcnow().isoformat() |
|
|
} |
|
|
else: |
|
|
raise Exception(f"BSCScan gas oracle error: {data.get('message', 'Unknown error')}") |
|
|
|
|
|
except httpx.HTTPStatusError as e: |
|
|
logger.error(f"β BSCScan gas oracle HTTP error: {e.response.status_code}") |
|
|
raise |
|
|
except Exception as e: |
|
|
logger.error(f"β BSCScan gas oracle failed: {e}") |
|
|
raise |
|
|
|
|
|
async def get_token_info(self, contract_address: str) -> Dict[str, Any]: |
|
|
""" |
|
|
Get BEP-20 token information |
|
|
|
|
|
Args: |
|
|
contract_address: Token contract address |
|
|
|
|
|
Returns: |
|
|
Token info |
|
|
""" |
|
|
try: |
|
|
async with httpx.AsyncClient(timeout=self.timeout) as client: |
|
|
response = await client.get( |
|
|
self.base_url, |
|
|
params={ |
|
|
"module": "token", |
|
|
"action": "tokeninfo", |
|
|
"contractaddress": contract_address, |
|
|
"apikey": self.api_key |
|
|
} |
|
|
) |
|
|
response.raise_for_status() |
|
|
data = response.json() |
|
|
|
|
|
if data.get("status") == "1": |
|
|
result_data = data.get("result", [{}])[0] |
|
|
|
|
|
logger.info(f"β
BSCScan: Fetched token info for {contract_address}") |
|
|
return { |
|
|
"contract_address": contract_address, |
|
|
"token_name": result_data.get("tokenName", ""), |
|
|
"symbol": result_data.get("symbol", ""), |
|
|
"decimals": result_data.get("decimals", ""), |
|
|
"total_supply": result_data.get("totalSupply", ""), |
|
|
"source": "BSCScan", |
|
|
"timestamp": datetime.utcnow().isoformat() |
|
|
} |
|
|
else: |
|
|
raise Exception(f"BSCScan token info error: {data.get('message', 'Unknown error')}") |
|
|
|
|
|
except httpx.HTTPStatusError as e: |
|
|
logger.error(f"β BSCScan token info HTTP error: {e.response.status_code}") |
|
|
raise |
|
|
except Exception as e: |
|
|
logger.error(f"β BSCScan token info failed: {e}") |
|
|
raise |
|
|
|
|
|
|
|
|
|
|
|
bscscan_client = BSCScanClient() |
|
|
|
|
|
|
|
|
|
|
|
async def fetch_bnb_price() -> float: |
|
|
"""Get BNB price from BSCScan""" |
|
|
try: |
|
|
data = await bscscan_client.get_bnb_price() |
|
|
return data.get("price", 0) |
|
|
except: |
|
|
return 0 |
|
|
|
|
|
|
|
|
async def fetch_bsc_gas_prices() -> Dict[str, Any]: |
|
|
"""Get BSC gas prices""" |
|
|
return await bscscan_client.get_gas_oracle() |
|
|
|
|
|
|
|
|
__all__ = ["BSCScanClient", "bscscan_client", "fetch_bnb_price", "fetch_bsc_gas_prices"] |
|
|
|