| """ |
| X402 Tool Data Provider β Cached, rate-limited data access for all x402 tools. |
| |
| Replace raw aiohttp/httpx calls with this provider. One import, everything cached. |
| |
| Usage in x402 routers: |
| from app.caching_shield.tool_data import td |
| |
| # Instead of: async with aiohttp.ClientSession() as s: r = await s.get(url) |
| # Use: result = await td.token_price(mint="So111...") |
| # Returns: {"price_usd": 79.5, "source": "jupiter", "cached": False} |
| """ |
|
|
| from app.caching_shield.unified_layer import get_data_layer |
|
|
|
|
| class ToolData: |
| """Cached, rate-limited data provider for x402 tool routers.""" |
|
|
| def __init__(self): |
| self._layer = get_data_layer() |
|
|
| async def token_price(self, mint: str) -> dict: |
| r = await self._layer.fetch("token_price", mint=mint) |
| return r.to_dict() if r else {"error": "no data"} |
|
|
| async def token_meta(self, mint: str) -> dict: |
| r = await self._layer.fetch("token_meta", mint=mint) |
| return r.to_dict() if r else {"error": "no data"} |
|
|
| async def wallet_balance(self, address: str) -> dict: |
| r = await self._layer.fetch("wallet_balance", address=address) |
| return r.to_dict() if r else {"error": "no data"} |
|
|
| async def risk_scan(self, address: str, chain: str = "solana") -> dict: |
| r = await self._layer.fetch("risk_scan", address=address, chain=chain) |
| return r.to_dict() if r else {"error": "no data"} |
|
|
| async def tx_history(self, address: str) -> dict: |
| r = await self._layer.fetch("tx_history", address=address) |
| return r.to_dict() if r else {"error": "no data"} |
|
|
| async def funding_source(self, address: str, chain_id: int = 1) -> dict: |
| r = await self._layer.fetch("funding_source", address=address, chain_id=chain_id) |
| return r.to_dict() if r else {"error": "no data"} |
|
|
| async def call_tool(self, tool_id: str, params: dict | None = None) -> dict: |
| """Generic tool dispatcher β calls unified_layer.fetch with tool_id and params. |
| |
| This is the primary method for trial execution and MCP tool calls. |
| Falls back to specific ToolData methods for known tools, or uses |
| the unified layer's generic fetch for all 127 tools. |
| |
| Returns None if the tool execution fails (so middleware can fall back |
| to POST route handlers). |
| """ |
| params = params or {} |
|
|
| |
| method_map = { |
| "risk_scan": self.risk_scan, |
| "token_price": self.token_price, |
| "token_meta": self.token_meta, |
| "wallet_balance": self.wallet_balance, |
| "tx_history": self.tx_history, |
| "funding_source": self.funding_source, |
| } |
| if tool_id in method_map: |
| try: |
| result = await method_map[tool_id](**params) |
| |
| if isinstance(result, dict) and set(result.keys()) <= {"error"}: |
| return None |
| return result |
| except Exception: |
| pass |
|
|
| try: |
| r = await self._layer.fetch(tool_id, **params) |
| if r: |
| result = r.to_dict() |
| result["tool"] = tool_id |
| |
| if set(result.keys()) <= {"error", "tool"}: |
| return None |
| return result |
| except Exception: |
| pass |
|
|
| |
| return None |
|
|
| def stats(self) -> dict: |
| return self._layer.stats() |
|
|
|
|
| |
| td = ToolData() |
|
|