|
|
"""
|
|
|
TronScan Provider - TRON blockchain transaction data
|
|
|
|
|
|
Provides:
|
|
|
- TRON address transaction history
|
|
|
- TRC-20 token transfers
|
|
|
- Account information
|
|
|
- Contract data
|
|
|
|
|
|
API Documentation: https://docs.tronscan.org/
|
|
|
"""
|
|
|
|
|
|
from __future__ import annotations
|
|
|
from typing import Any, Dict, List, Optional
|
|
|
|
|
|
from .base import BaseProvider, create_success_response, create_error_response
|
|
|
|
|
|
|
|
|
class TronscanProvider(BaseProvider):
|
|
|
"""TronScan REST API provider for TRON blockchain data"""
|
|
|
|
|
|
|
|
|
API_KEY = "7ae72726-bffe-4e74-9c33-97b761eeea21"
|
|
|
|
|
|
def __init__(self, api_key: Optional[str] = None):
|
|
|
super().__init__(
|
|
|
name="tronscan",
|
|
|
base_url="https://apilist.tronscanapi.com/api",
|
|
|
api_key=api_key or self.API_KEY,
|
|
|
timeout=10.0,
|
|
|
cache_ttl=30.0
|
|
|
)
|
|
|
|
|
|
def _get_default_headers(self) -> Dict[str, str]:
|
|
|
"""Get headers with TronScan API key"""
|
|
|
return {
|
|
|
"Accept": "application/json",
|
|
|
"User-Agent": "HF-Crypto-Data-Engine/1.0",
|
|
|
"TRON-PRO-API-KEY": self.api_key
|
|
|
}
|
|
|
|
|
|
async def get_transactions(
|
|
|
self,
|
|
|
address: str,
|
|
|
start: int = 0,
|
|
|
limit: int = 50,
|
|
|
sort: str = "-timestamp"
|
|
|
) -> Dict[str, Any]:
|
|
|
"""
|
|
|
Get list of transactions for a TRON address.
|
|
|
|
|
|
Args:
|
|
|
address: TRON address (starts with 'T')
|
|
|
start: Starting index for pagination
|
|
|
limit: Number of transactions to fetch
|
|
|
sort: Sort order ('-timestamp' for descending)
|
|
|
|
|
|
Returns:
|
|
|
Standardized response with transaction list
|
|
|
"""
|
|
|
if not address:
|
|
|
return create_error_response(
|
|
|
self.name,
|
|
|
"Invalid TRON address",
|
|
|
"Address is required"
|
|
|
)
|
|
|
|
|
|
|
|
|
if not address.startswith("T"):
|
|
|
return create_error_response(
|
|
|
self.name,
|
|
|
"Invalid TRON address format",
|
|
|
"TRON address should start with 'T'"
|
|
|
)
|
|
|
|
|
|
params = {
|
|
|
"address": address,
|
|
|
"start": start,
|
|
|
"limit": min(limit, 50),
|
|
|
"sort": sort
|
|
|
}
|
|
|
|
|
|
response = await self.get("transaction", params=params)
|
|
|
|
|
|
if not response.get("success"):
|
|
|
return response
|
|
|
|
|
|
data = response.get("data", {})
|
|
|
|
|
|
|
|
|
if isinstance(data, dict):
|
|
|
transactions = data.get("data", [])
|
|
|
total = data.get("total", 0)
|
|
|
else:
|
|
|
transactions = data if isinstance(data, list) else []
|
|
|
total = len(transactions)
|
|
|
|
|
|
return create_success_response(
|
|
|
self.name,
|
|
|
{
|
|
|
"address": address,
|
|
|
"chain": "tron",
|
|
|
"transactions": self._format_transactions(transactions),
|
|
|
"count": len(transactions),
|
|
|
"total": total
|
|
|
}
|
|
|
)
|
|
|
|
|
|
def _format_transactions(self, transactions: List[Dict]) -> List[Dict]:
|
|
|
"""Format TRON transaction data for clean output"""
|
|
|
formatted = []
|
|
|
for tx in transactions:
|
|
|
|
|
|
raw_amount = tx.get("amount", 0)
|
|
|
try:
|
|
|
amount = int(raw_amount) if raw_amount else 0
|
|
|
except (ValueError, TypeError):
|
|
|
amount = 0
|
|
|
|
|
|
formatted.append({
|
|
|
"hash": tx.get("hash") or tx.get("txID"),
|
|
|
"block": tx.get("block"),
|
|
|
"timestamp": tx.get("timestamp"),
|
|
|
"ownerAddress": tx.get("ownerAddress"),
|
|
|
"toAddress": tx.get("toAddress"),
|
|
|
"contractType": tx.get("contractType"),
|
|
|
"confirmed": tx.get("confirmed", False),
|
|
|
"result": tx.get("result"),
|
|
|
"amount": amount,
|
|
|
"amountTrx": amount / 1e6 if amount else 0,
|
|
|
"fee": tx.get("fee", 0),
|
|
|
"contractData": tx.get("contractData")
|
|
|
})
|
|
|
return formatted
|
|
|
|
|
|
async def get_trc20_transfers(
|
|
|
self,
|
|
|
address: str,
|
|
|
start: int = 0,
|
|
|
limit: int = 50,
|
|
|
contract_address: Optional[str] = None
|
|
|
) -> Dict[str, Any]:
|
|
|
"""
|
|
|
Get TRC-20 token transfer events for a TRON address.
|
|
|
|
|
|
Args:
|
|
|
address: TRON address
|
|
|
start: Starting index
|
|
|
limit: Number of results
|
|
|
contract_address: Optional filter by token contract
|
|
|
"""
|
|
|
if not address or not address.startswith("T"):
|
|
|
return create_error_response(
|
|
|
self.name,
|
|
|
"Invalid TRON address",
|
|
|
"Address must start with 'T'"
|
|
|
)
|
|
|
|
|
|
params = {
|
|
|
"address": address,
|
|
|
"start": start,
|
|
|
"limit": min(limit, 50),
|
|
|
"sort": "-timestamp"
|
|
|
}
|
|
|
|
|
|
if contract_address:
|
|
|
params["contract_address"] = contract_address
|
|
|
|
|
|
response = await self.get("token_trc20/transfers", params=params)
|
|
|
|
|
|
if not response.get("success"):
|
|
|
return response
|
|
|
|
|
|
data = response.get("data", {})
|
|
|
|
|
|
if isinstance(data, dict):
|
|
|
transfers = data.get("token_transfers", [])
|
|
|
total = data.get("total", 0)
|
|
|
else:
|
|
|
transfers = data if isinstance(data, list) else []
|
|
|
total = len(transfers)
|
|
|
|
|
|
return create_success_response(
|
|
|
self.name,
|
|
|
{
|
|
|
"address": address,
|
|
|
"chain": "tron",
|
|
|
"transfers": self._format_token_transfers(transfers),
|
|
|
"count": len(transfers),
|
|
|
"total": total
|
|
|
}
|
|
|
)
|
|
|
|
|
|
def _format_token_transfers(self, transfers: List[Dict]) -> List[Dict]:
|
|
|
"""Format TRC-20 token transfer data"""
|
|
|
formatted = []
|
|
|
for tx in transfers:
|
|
|
decimals = int(tx.get("decimals", 6))
|
|
|
quant = int(tx.get("quant", 0) or 0)
|
|
|
formatted.append({
|
|
|
"hash": tx.get("transaction_id"),
|
|
|
"block": tx.get("block"),
|
|
|
"timestamp": tx.get("block_ts"),
|
|
|
"from": tx.get("from_address"),
|
|
|
"to": tx.get("to_address"),
|
|
|
"quant": str(quant),
|
|
|
"tokenValue": quant / (10 ** decimals) if decimals else quant,
|
|
|
"tokenName": tx.get("tokenInfo", {}).get("tokenName"),
|
|
|
"tokenSymbol": tx.get("tokenInfo", {}).get("tokenAbbr"),
|
|
|
"tokenDecimal": decimals,
|
|
|
"contractAddress": tx.get("contract_address"),
|
|
|
"confirmed": tx.get("confirmed", False)
|
|
|
})
|
|
|
return formatted
|
|
|
|
|
|
async def get_account_info(self, address: str) -> Dict[str, Any]:
|
|
|
"""Get account information and balance for a TRON address"""
|
|
|
if not address or not address.startswith("T"):
|
|
|
return create_error_response(
|
|
|
self.name,
|
|
|
"Invalid TRON address",
|
|
|
"Address must start with 'T'"
|
|
|
)
|
|
|
|
|
|
params = {"address": address}
|
|
|
|
|
|
response = await self.get("accountv2", params=params)
|
|
|
|
|
|
if not response.get("success"):
|
|
|
return response
|
|
|
|
|
|
data = response.get("data", {})
|
|
|
|
|
|
if not data:
|
|
|
return create_error_response(
|
|
|
self.name,
|
|
|
"Account not found",
|
|
|
f"No data found for address {address}"
|
|
|
)
|
|
|
|
|
|
balance = data.get("balance", 0)
|
|
|
return create_success_response(
|
|
|
self.name,
|
|
|
{
|
|
|
"address": address,
|
|
|
"chain": "tron",
|
|
|
"balance": balance,
|
|
|
"balance_trx": balance / 1e6,
|
|
|
"bandwidth": data.get("bandwidth", {}),
|
|
|
"energy": data.get("energy", {}),
|
|
|
"totalFrozen": data.get("totalFrozen", 0),
|
|
|
"totalFrozenV2": data.get("totalFrozenV2", 0),
|
|
|
"tokens": data.get("withPriceTokens", [])[:10],
|
|
|
"transactions": data.get("transactions", 0)
|
|
|
}
|
|
|
)
|
|
|
|
|
|
async def get_token_list(
|
|
|
self,
|
|
|
start: int = 0,
|
|
|
limit: int = 20,
|
|
|
order_by: str = "-volume24hInTrx"
|
|
|
) -> Dict[str, Any]:
|
|
|
"""Get list of TRC-20 tokens sorted by volume"""
|
|
|
params = {
|
|
|
"start": start,
|
|
|
"limit": min(limit, 50),
|
|
|
"order": order_by,
|
|
|
"filter": "trc20"
|
|
|
}
|
|
|
|
|
|
response = await self.get("tokens/overview", params=params)
|
|
|
|
|
|
if not response.get("success"):
|
|
|
return response
|
|
|
|
|
|
data = response.get("data", {})
|
|
|
tokens = data.get("tokens", []) if isinstance(data, dict) else data
|
|
|
|
|
|
formatted_tokens = []
|
|
|
for token in tokens[:limit]:
|
|
|
formatted_tokens.append({
|
|
|
"name": token.get("name"),
|
|
|
"symbol": token.get("abbr"),
|
|
|
"contractAddress": token.get("contractAddress"),
|
|
|
"price": token.get("priceInTrx"),
|
|
|
"priceUsd": token.get("priceInUsd"),
|
|
|
"volume24h": token.get("volume24hInTrx"),
|
|
|
"holders": token.get("holders"),
|
|
|
"marketCap": token.get("marketcap")
|
|
|
})
|
|
|
|
|
|
return create_success_response(
|
|
|
self.name,
|
|
|
{
|
|
|
"chain": "tron",
|
|
|
"tokens": formatted_tokens,
|
|
|
"count": len(formatted_tokens)
|
|
|
}
|
|
|
)
|
|
|
|