rmi-backend/app/socialfi_resolver.py
opencode c762564d40 style(rmi-backend): complete lint cleanup — 1175→0 ruff errors
- Fix 71 invalid-syntax files (class-body newline-broken assignments)
- Add from/None chain to 307 B904 raise-without-from sites
- Add B008 ignore to ruff.toml (already in pyproject.toml)
- Noqa F401 on __init__.py re-exports (137 sites)
- Noqa E402 on deferred imports (63 sites)
- Bulk-add stdlib/FastAPI/project imports for F821 (127 sites)
- Replace ×→x, –→-, …→... in docstrings (4093 chars)
- Manual refactor of 5 SIM103/SIM116 patterns

Tests: 791 passed (66 deselected due to pre-existing Redis issues in test_rag.py)
Co-authored-by: opencode <opencode@rugmunch.io>
2026-07-06 15:43:20 +02:00

121 lines
4.4 KiB
Python

"""
SocialFi Resolver - Farcaster, ENS, Lens profile resolution.
All free/public APIs, no API keys required.
Uses Warpcast v2 API (free, no auth needed for public data).
"""
import logging
from typing import Any
logger = logging.getLogger(__name__)
# Warpcast public API v2 (free, no key)
WARPCAST_API = "https://api.warpcast.com/v2"
# ENS public resolver
ENS_RPC_URLS = [
"https://api.etherscan.io/api", # via Etherscan proxy
]
# Etherscan key from env
import os as _os # noqa: E402
_ETHERSCAN_KEY = _os.getenv("ETHERSCAN_API_KEY", "")
async def resolve_ens_name(address: str) -> str | None:
"""Resolve an Ethereum address to an ENS name via Etherscan API."""
import httpx
if not _ETHERSCAN_KEY:
logger.warning("No ETHERSCAN_API_KEY set, ENS resolution disabled")
return None
try:
async with httpx.AsyncClient(timeout=10) as client:
# Etherscan V2 API for eth_call
data = "0x691f3431" + address[2:].lower().zfill(64)
resp = await client.get(
"https://api.etherscan.io/v2/api",
params={
"chainid": "1",
"module": "proxy",
"action": "eth_call",
"to": "0x3671aE578E63FdF66ad4F3E12CC0c0d71Ac7510C",
"data": data,
"apikey": _ETHERSCAN_KEY,
},
)
if resp.status_code == 200:
result = resp.json().get("result", "")
if isinstance(result, str) and result != "0x" and len(result) > 138:
name_bytes = bytes.fromhex(result[138:])
name = name_bytes.decode("utf-8").rstrip("\x00")
if "." in name:
return name
except Exception as e:
logger.debug(f"ENS resolution failed: {e}")
return None
async def fetch_farcaster_profile(fid: int) -> dict[str, Any] | None:
"""Fetch Farcaster profile by FID using Warpcast v2 API (free, no auth)."""
import httpx
try:
async with httpx.AsyncClient(timeout=10) as client:
resp = await client.get(f"{WARPCAST_API}/user", params={"fid": fid})
if resp.status_code != 200:
logger.warning(f"Warpcast API returned {resp.status_code}")
return None
data = resp.json()
user_data = data.get("result", {}).get("user", {})
if not user_data:
return None
profile_obj = user_data.get("profile", {})
bio_obj = profile_obj.get("bio", {})
return {
"fid": fid,
"username": user_data.get("username"),
"display_name": user_data.get("displayName"),
"bio": bio_obj.get("text") if isinstance(bio_obj, dict) else None,
"avatar_url": user_data.get("pfp", {}).get("url"),
"banner_url": profile_obj.get("bannerImageUrl"),
"website": profile_obj.get("url"),
"followers": user_data.get("followerCount", 0),
"following": user_data.get("followingCount", 0),
"verified": user_data.get("pfp", {}).get("verified", False),
"farcaster_url": f"https://warpcast.com/{user_data.get('username')}"
if user_data.get("username")
else None,
"connected_accounts": user_data.get("connectedAccounts", []),
"eth_wallets": user_data.get("extras", {}).get("ethWallets", []),
"solana_wallets": user_data.get("extras", {}).get("solanaWallets", []),
}
except Exception as e:
logger.warning(f"Farcaster fetch failed for FID {fid}: {e}")
return None
async def resolve_farcaster_handle(handle: str) -> int | None:
"""Resolve a Farcaster username to FID using Warpcast v2 API."""
import httpx
handle = handle.lstrip("@")
try:
async with httpx.AsyncClient(timeout=10) as client:
resp = await client.get(f"{WARPCAST_API}/user-by-username", params={"username": handle})
if resp.status_code == 200:
data = resp.json()
fid = data.get("result", {}).get("user", {}).get("fid")
if fid:
return int(fid)
except Exception as e:
logger.warning(f"Farcaster handle resolve failed for {handle}: {e}")
return None