Spaces:
Running
Running
File size: 6,813 Bytes
f104fee 9b006e9 f104fee ea46ec8 f104fee 9b006e9 f104fee c52b367 9b006e9 f104fee 9b006e9 f104fee 9b006e9 f104fee 9b006e9 f104fee 9b006e9 f104fee 9b006e9 f104fee 9b006e9 f104fee 9b006e9 f104fee 9b006e9 f104fee 9b006e9 f104fee 9b006e9 f104fee 9b006e9 f104fee |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 |
from typing import Dict, Any, Optional
from pydantic import BaseModel, PrivateAttr
from src.tools.base_tool import BaseWeb3Tool, Web3ToolInput
from src.utils.config import config
from src.utils.logger import get_logger
logger = get_logger(__name__)
class EtherscanTool(BaseWeb3Tool):
name: str = "etherscan_data"
description: str = """Get Ethereum blockchain data from Etherscan.
Useful for: transaction analysis, address information, gas prices, token data.
Input: Ethereum address, transaction hash, or general blockchain query."""
args_schema: type[BaseModel] = Web3ToolInput
enabled: bool = True # Add enabled as a Pydantic field
_base_url: str = PrivateAttr(default="https://api.etherscan.io/api")
_api_key: Optional[str] = PrivateAttr(default=None)
def __init__(self):
super().__init__()
self._api_key = config.ETHERSCAN_API_KEY
self.enabled = bool(self._api_key)
if not self.enabled:
logger.warning("Etherscan API key not configured - limited functionality")
async def _arun(self, query: str, filters: Optional[Dict[str, Any]] = None, **kwargs) -> str:
if not self.enabled:
return "β οΈ **Etherscan Service Limited**\n\nEtherscan functionality requires an API key.\nGet yours free at: https://etherscan.io/apis\n\nSet environment variable: `ETHERSCAN_API_KEY=your_key`"
try:
filters = filters or {}
if filters.get("type") == "gas_prices":
return await self._get_gas_prices()
elif filters.get("type") == "eth_stats":
return await self._get_eth_stats()
elif self._is_address(query):
return await self._get_address_info(query)
elif self._is_tx_hash(query):
return await self._get_transaction_info(query)
else:
return await self._get_gas_prices()
except Exception as e:
logger.error(f"Etherscan error: {e}")
return f"β οΈ Etherscan service temporarily unavailable"
def _is_address(self, query: str) -> bool:
return (
len(query) == 42
and query.startswith("0x")
and all(c in "0123456789abcdefABCDEF" for c in query[2:])
)
def _is_tx_hash(self, query: str) -> bool:
return (
len(query) == 66
and query.startswith("0x")
and all(c in "0123456789abcdefABCDEF" for c in query[2:])
)
async def _get_gas_prices(self) -> str:
try:
params = {
"module": "gastracker",
"action": "gasoracle",
"apikey": self._api_key
}
data = await self.make_request(self._base_url, params)
if not data or data.get("status") != "1":
error_msg = data.get("message", "Unknown error") if data else "No response"
logger.warning(f"Etherscan gas price error: {error_msg}")
return "β οΈ Gas price data temporarily unavailable"
result_data = data.get("result", {})
if not result_data:
return "β No gas price data in response"
safe_gas = result_data.get("SafeGasPrice", "N/A")
standard_gas = result_data.get("StandardGasPrice", "N/A")
fast_gas = result_data.get("FastGasPrice", "N/A")
# Validate gas prices are numeric
try:
if safe_gas != "N/A":
float(safe_gas)
if standard_gas != "N/A":
float(standard_gas)
if fast_gas != "N/A":
float(fast_gas)
except (ValueError, TypeError):
return "β οΈ Invalid gas price data received"
result = "β½ **Ethereum Gas Prices:**\n\n"
result += f"π **Safe**: {safe_gas} gwei\n"
result += f"β‘ **Standard**: {standard_gas} gwei\n"
result += f"π **Fast**: {fast_gas} gwei\n"
return result
except Exception as e:
logger.error(f"Gas prices error: {e}")
return "β οΈ Gas price service temporarily unavailable"
async def _get_eth_stats(self) -> str:
params = {
"module": "stats",
"action": "ethsupply",
"apikey": self._api_key
}
data = await self.make_request(self._base_url, params)
if data.get("status") != "1":
return "Ethereum stats unavailable"
eth_supply = int(data.get("result", 0)) / 1e18
result = "π **Ethereum Network Stats:**\n\n"
result += f"π **ETH Supply**: {eth_supply:,.0f} ETH\n"
return result
async def _get_address_info(self, address: str) -> str:
params = {
"module": "account",
"action": "txlist",
"address": address,
"startblock": "0",
"endblock": "99999999",
"page": "1",
"offset": "10",
"sort": "desc",
"apikey": self._api_key
}
data = await self.make_request(self._base_url, params)
if data.get("status") != "1":
return f"Address information unavailable for {address}"
balance_wei = int(data.get("result", 0))
balance_eth = balance_wei / 1e18
result = f"π **Address Information:**\n\n"
result += f"**Address**: {address}\n"
result += f"π° **Balance**: {balance_eth:.4f} ETH\n"
return result
async def _get_transaction_info(self, tx_hash: str) -> str:
params = {
"module": "proxy",
"action": "eth_getTransactionByHash",
"txhash": tx_hash,
"apikey": self._api_key
}
data = await self.make_request(self._base_url, params)
if not data.get("result"):
return f"Transaction not found: {tx_hash}"
tx = data.get("result", {})
value_wei = int(tx.get("value", "0x0"), 16)
value_eth = value_wei / 1e18
gas_price = int(tx.get("gasPrice", "0x0"), 16) / 1e9
result = f"π **Transaction Information:**\n\n"
result += f"**Hash**: {tx_hash}\n"
result += f"**From**: {tx.get('from', 'N/A')}\n"
result += f"**To**: {tx.get('to', 'N/A')}\n"
result += f"π° **Value**: {value_eth:.4f} ETH\n"
result += f"β½ **Gas Price**: {gas_price:.2f} gwei\n"
return result
|