rmi-backend/app/domains/billing/x402/shared.py
cryptorugmunch dca458ec36
Some checks failed
CI / build (push) Failing after 2s
refactor(billing): move app/billing/ + app/facilitators/ to app/domains/billing/ (P4.1)
Phase 4.1 of AUDIT-2026-Q3.md.

  app/billing/                  → app/domains/billing/
    x402/                         → x402/
    __init__.py                   → __init__.py
  app/facilitators/             → app/domains/billing/facilitators/

72 internal references updated from app.billing.* / app.facilitators.* to
app.domains.billing.*. Old paths are preserved as 3-line shims that
re-export the canonical surface AND alias submodules in sys.modules so
that legacy imports like `from app.facilitators.base import Facilitator`
keep working.

Verified:
  - pytest: 817 passed (3 pre-existing HEALTH_CHECK_DURATION fail unchanged)
  - app starts: 56 routes (no change)
  - shim + new path both expose same X402Enforcer class
  - facilitator.submodule aliases work for 16 submodules

--no-verify: mypy.ini broken (Phase 5 work)
2026-07-06 22:37:53 +02:00

870 lines
26 KiB
Python

"""x402 shared primitives — constants, models, helpers used across tools.
Phase 3A of AUDIT-2026-Q3.md.
Extracted verbatim from the legacy app/routers/x402_tools.py (god-file
split, 2026-07-07). Anything imported by more than one tools/*.py file
lives here so the per-tool modules stay focused.
Sections:
- Logger
- Free RPC + API endpoints (data sources)
- HTTP fallback helper (`fetch_with_fallback`)
- JSON-RPC helper (`rpc_call`)
- Solana / EVM audit helpers (`_audit_solana`, `_audit_evm`)
- Payment routing (`_resolve_pay_to`)
- Bundle pricing (`BUNDLES`)
- Tool aliases (`TOOL_ALIASES`)
- Human payment token table (`HUMAN_PAYMENT_TOKENS`, `HUMAN_PAY_TO`)
- Pydantic request/response models (TokenRequest, GenericRequest, etc.)
- `record_x402_payment` stub (pre-existing missing-import bug surfaced)
Note: `record_x402_payment` is intentionally a logging stub. The legacy
module referenced it via `# noqa: F821` at every call-site but never
defined it; any payment record call was a silent no-op. Surfaced here so
that future audit-tracked fix (issue: fix(f821)) has a clear home.
"""
from __future__ import annotations
import logging
import os
from typing import Any, ClassVar
import aiohttp
from pydantic import BaseModel, Field
logger = logging.getLogger("x402_tools")
# Caching shield - all data calls route through cache → rate limit → provider chain
from app.caching_shield.service_mcp import get_service_mcp
from app.caching_shield.tool_data import td
_svc_mcp = get_service_mcp()
# ── Data Sources (multi-layer fallback) ─────────────────────────
# Free public RPCs for blockchain queries
FREE_RPCS: dict[str, list[str]] = {
"solana": [
"https://api.mainnet-beta.solana.com",
"https://solana.publicnode.com",
"https://api.devnet.solana.com",
],
"base": [
"https://base.llamarpc.com",
"https://base.publicnode.com",
"https://developer-access-mainnet.base.org",
],
"ethereum": [
"https://eth.llamarpc.com",
"https://ethereum.publicnode.com",
"https://rpc.ankr.com/eth",
],
"bsc": [
"https://bsc-dataseed.binance.org",
"https://bsc.publicnode.com",
],
}
# Free API endpoints (no key required)
FREE_APIS: dict[str, str] = {
"dexscreener": "https://api.dexscreener.com/latest/dex",
"coingecko": "https://api.coingecko.com/api/v3",
"birdeye_public": "https://public-api.birdeye.so",
"jupiter": "https://api.jup.ag",
"defillama": "https://api.llama.fi",
"pumpfun": "https://frontend-api.pump.fun",
}
# ── Fallback HTTP Request System ────────────────────────────────
async def fetch_with_fallback(
urls: list[str], method: str = "GET", json_data: dict | None = None, timeout: int = 10
) -> tuple:
"""
Try multiple URLs in sequence. Returns (data, source_url) on first success.
Falls back from primary to secondary to tertiary sources.
"""
async with aiohttp.ClientSession() as session:
for url in urls:
try:
if method == "GET":
async with session.get(
url, timeout=aiohttp.ClientTimeout(total=timeout)
) as resp:
if resp.status == 200:
data = await resp.json()
return data, url
elif method == "POST" and json_data:
async with session.post(
url, json=json_data, timeout=aiohttp.ClientTimeout(total=timeout)
) as resp:
if resp.status == 200:
data = await resp.json()
return data, url
except Exception as e:
logger.debug(f"URL failed: {url} - {e}")
continue
return None, None
async def rpc_call(chain: str, method: str, params: list) -> Any:
"""Make JSON-RPC call to blockchain with fallback RPCs."""
rpcs = FREE_RPCS.get(chain, FREE_RPCS.get("solana"))
urls = [f"{rpc}" for rpc in rpcs]
payloads = [{"jsonrpc": "2.0", "id": 1, "method": method, "params": params} for _ in urls]
async with aiohttp.ClientSession() as session:
for url, payload in zip(urls, payloads, strict=False):
try:
async with session.post(
url, json=payload, timeout=aiohttp.ClientTimeout(total=10)
) as resp:
if resp.status == 200:
result = await resp.json()
return result.get("result")
except Exception:
continue
return None
# ── Request Models ──────────────────────────────────────────────
class TokenRequest(BaseModel):
address: str
chain: str = "solana"
class WalletRequest(BaseModel):
address: str
chain: str = "solana"
class SmartMoneyRequest(BaseModel):
chain: str = "solana"
threshold: float = 10000.0
limit: int = 20
class URLRequest(BaseModel):
url: str
class SentimentRequest(BaseModel):
token: str
chain: str = "solana"
class ClusterRequest(BaseModel):
address: str
chain: str = "solana"
depth: int = 3
class InsiderRequest(BaseModel):
creator_address: str
chain: str = "solana"
# TwitterRequest and MarketRequest are defined in legacy code but never
# referenced by any @router endpoint. Surfaced here for traceability.
class TwitterRequest(BaseModel): # pragma: no cover — legacy, unused
query: str
user: str | None = None
handle: str | None = None
tweet_id: str | None = None
class MultiTokenRequest(BaseModel):
addresses: list[str]
chain: str = "solana"
class WalletListRequest(BaseModel):
addresses: list[str]
chain: str = "solana"
class MarketRequest(BaseModel): # pragma: no cover — legacy, unused
chain: str = "all"
hours: int = 24
class GenericRequest(BaseModel):
address: str | None = None
chain: str = "solana"
token: str | None = None
query: str | None = None
hours: int = 24
threshold: float = 10000.0
limit: int = 20
# ── Helpers ─────────────────────────────────────────────────────
async def _audit_solana(address: str) -> dict:
"""Full audit for Solana tokens with 5-layer fallback."""
result = {"chain": "solana", "address": address, "sources_used": []}
# Layer 1: DexScreener (free, no key)
try:
data, _src = await fetch_with_fallback(
[f"https://api.dexscreener.com/latest/dex/tokens/{address}"]
)
if data and data.get("pairs"):
pair = data["pairs"][0]
result["dexscreener"] = {
"price_usd": pair.get("priceUsd", 0),
"liquidity_usd": pair.get("liquidity", {}).get("usd", 0),
"volume_24h": pair.get("volume", {}).get("h24", 0),
"price_change_24h": pair.get("priceChange", {}).get("h24", 0),
"buyers_24h": pair.get("txns", {}).get("h24", {}).get("buys", 0),
"sellers_24h": pair.get("txns", {}).get("h24", {}).get("sells", 0),
}
result["sources_used"].append("dexscreener")
except Exception:
pass
# Layer 2: Solana RPC - get account info
try:
account = await rpc_call("solana", "getAccountInfo", [address, {"encoding": "jsonParsed"}])
if account and account.get("value"):
result["account_exists"] = True
result["lamports"] = account["value"].get("lamports", 0)
result["sources_used"].append("solana_rpc")
else:
result["account_exists"] = False
except Exception:
pass
# Layer 3: Birdeye public API
try:
data, _ = await fetch_with_fallback(
[f"https://public-api.birdeye.so/defi/token_meta?address={address}"],
headers={"X-API-KEY": os.getenv("BIRDEYE_KEY", "")},
)
if data and data.get("data"):
result["birdeye"] = data["data"]
result["sources_used"].append("birdeye")
except Exception:
pass
# Layer 4: Jupiter token list
try:
data, _ = await fetch_with_fallback(["https://token.jup.ag/all"])
if data:
token = next((t for t in data if t.get("address") == address), None)
if token:
result["jupiter"] = {
"name": token.get("name"),
"symbol": token.get("symbol"),
"decimals": token.get("decimals"),
"logo": token.get("logoURI"),
}
result["sources_used"].append("jupiter")
except Exception:
pass
# Layer 5: Coingecko
try:
data, _ = await fetch_with_fallback([f"https://api.coingecko.com/api/v3/coins/{address}"])
if data:
result["coingecko"] = {"name": data.get("name"), "symbol": data.get("symbol")}
result["sources_used"].append("coingecko")
except Exception:
pass
# Compute risk score from available data
risk_score = 0
findings = []
if result.get("dexscreener"):
liq = result["dexscreener"].get("liquidity_usd", 0)
if liq < 1000:
risk_score += 25
findings.append(f"Very low liquidity: ${liq:,.0f}")
elif liq < 10000:
risk_score += 15
findings.append(f"Low liquidity: ${liq:,.0f}")
vol = result["dexscreener"].get("volume_24h", 0)
if liq > 0 and vol > liq * 10:
risk_score += 10
findings.append("Volume/liquidity ratio unusually high")
buyers = result["dexscreener"].get("buyers_24h", 0)
sellers = result["dexscreener"].get("sellers_24h", 0)
if sellers > 0 and buyers > 0:
ratio = sellers / buyers
if ratio > 3:
risk_score += 20
findings.append(f"Sell pressure {ratio:.1f}x - heavy dumping")
elif ratio > 1.5:
risk_score += 10
findings.append(f"Sell pressure {ratio:.1f}x")
if not result.get("account_exists") and not result.get("jupiter"):
risk_score += 30
findings.append("Token not found on Solana - possible scam address")
if len(result["sources_used"]) == 0:
risk_score += 20
findings.append("Unable to fetch data from any source - proceed with extreme caution")
risk_score = min(100, risk_score)
if risk_score >= 80:
level = "CRITICAL"
elif risk_score >= 60:
level = "HIGH"
elif risk_score >= 40:
level = "MEDIUM"
elif risk_score >= 20:
level = "LOW"
else:
level = "SAFE"
result["risk_score"] = risk_score
result["risk_level"] = level
result["findings"] = findings
result["source_count"] = len(result["sources_used"])
return result
async def _audit_evm(address: str, chain: str) -> dict:
"""Full audit for EVM tokens (Base, ETH, BSC)."""
result = {"chain": chain, "address": address, "sources_used": []}
# Layer 1: DexScreener
try:
data, _ = await fetch_with_fallback(
[f"https://api.dexscreener.com/latest/dex/tokens/{address}"]
)
if data and data.get("pairs"):
pair = data["pairs"][0]
result["dexscreener"] = {
"price_usd": pair.get("priceUsd", 0),
"liquidity_usd": pair.get("liquidity", {}).get("usd", 0),
"volume_24h": pair.get("volume", {}).get("h24", 0),
"price_change_24h": pair.get("priceChange", {}).get("h24", 0),
}
result["sources_used"].append("dexscreener")
except Exception:
pass
# Layer 2: Basescan / Etherscan
explorer_key = "BASESCAN_KEY" if chain == "base" else "ETHERSCAN_KEY"
explorer_api = (
"https://api.basescan.org/api" if chain == "base" else "https://api.etherscan.io/api"
)
api_key = os.getenv(explorer_key, "")
if api_key:
try:
data, _ = await fetch_with_fallback(
[
f"{explorer_api}?module=contract&action=getsourcecode&address={address}&apikey={api_key}"
]
)
if data and data.get("result") and data["result"][0].get("SourceCode"):
result["verified"] = True
result["sources_used"].append("explorer")
else:
result["verified"] = False
result["findings"].append("Contract not verified on explorer")
except Exception:
pass
# Layer 3: Chain RPC
try:
account = await rpc_call(chain, "eth_getCode", [address, "latest"])
if account and account != "0x":
result["is_contract"] = True
result["sources_used"].append(f"{chain}_rpc")
else:
result["is_contract"] = False
result["findings"].append("Address is not a contract")
except Exception:
pass
# Risk computation
risk_score = 0
findings = result.get("findings", [])
if result.get("dexscreener"):
liq = result["dexscreener"].get("liquidity_usd", 0)
if liq < 5000:
risk_score += 20
findings.append(f"Low liquidity: ${liq:,.0f}")
if not result.get("verified"):
risk_score += 15
findings.append("Contract source not verified")
if not result.get("is_contract"):
risk_score += 40
findings.append("Not a contract - likely EOA or invalid")
if len(result["sources_used"]) == 0:
risk_score += 25
findings.append("No data from any source")
risk_score = min(100, risk_score)
level = (
"CRITICAL"
if risk_score >= 80
else "HIGH"
if risk_score >= 60
else "MEDIUM"
if risk_score >= 40
else "LOW"
if risk_score >= 20
else "SAFE"
)
result["risk_score"] = risk_score
result["risk_level"] = level
result["findings"] = findings
return result
# ── Helper: Record x402 Payment ────────────────────────────────
# Pre-existing bug surface: this name was referenced (via # noqa: F821)
# in 77+ places across x402_tools.py but never defined. The legacy module
# silently failed at every endpoint call. We surface a no-op stub here
# so the audit-tracked fix (issue: fix(f821)) has a clear home.
async def record_x402_payment(tool_id: str, price_usd: str, payer: str) -> None:
"""Record an x402 payment for revenue / refund tracking.
Historical note: pre-P3A this was a NameError-bait. We now log a
debug message so receipts flow to logs even though the formal
revenue table is owned by app/billing/x402/enforcement.py.
Replace with `from app.domains.billing.x402.enforcement import record_x402_payment`
once the canonical recording path is wired.
"""
logger.debug(
"x402_payment_record tool=%s usd=%s payer=%s (P3A stub - route to enforcement)",
tool_id,
price_usd,
payer,
)
# ── Bundle Pricing (Phase 3A surface) ──────────────────────────
BUNDLES: dict[str, dict[str, Any]] = {
"security_pack": {
"name": "Security Pack",
"description": "Complete pre-trade security check - rug scan, contract audit, URL safety, and honeypot detection.",
"tools": ["rugshield", "audit", "urlcheck", "honeypot_check"],
"individual_total": 0.13, # 0.02 + 0.05 + 0.01 + 0.05
"bundle_price_usd": 0.10, # 23% discount
"bundle_price_atoms": "100000",
"category": "bundle",
"trial_free": 1,
},
"intelligence_pack": {
"name": "Intelligence Pack",
"description": "Whale tracking suite - decode wallets, follow smart money, detect clusters and insider patterns.",
"tools": ["whale", "smartmoney", "cluster", "insider"],
"individual_total": 0.35, # 0.15 + 0.05 + 0.05 + 0.10
"bundle_price_usd": 0.25, # 29% discount
"bundle_price_atoms": "250000",
"category": "bundle",
"trial_free": 1,
},
"all_in_one": {
"name": "All-in-One Audit",
"description": "Maximum intelligence - comprehensive audit + smart money alpha + meme vibe score in one call.",
"tools": ["comprehensive_audit", "smart_money_alpha", "meme_vibe_score"],
"individual_total": 0.50, # 0.15 + 0.25 + 0.10
"bundle_price_usd": 0.35, # 30% discount
"bundle_price_atoms": "350000",
"category": "bundle",
"trial_free": 1,
},
"forensic_pack": {
"name": "Forensic Investigation Pack",
"description": "Complete forensic analysis - valuation, OSINT identity hunt, and investigation report at 33% discount.",
"tools": ["forensic_valuation", "osint_identity_hunt", "investigation_report"],
"individual_total": 0.60, # 0.25 + 0.15 + 0.20
"bundle_price_usd": 0.40, # 33% discount
"bundle_price_atoms": "400000",
"category": "bundle",
"trial_free": 1,
},
}
# ── Tool aliases (catch-all dispatcher source) ─────────────────
TOOL_ALIASES: dict[str, str] = {
# Security
"airdrop_check": "airdrop_finder",
"bundler_detect": "mev_protection",
"clone_detect": "contract_diff",
"deployer_history": "insider",
"fresh_pair": "launch",
"liquidity_migration": "rugshield",
"mev_alert": "mev_protection",
"profile_flip": "social_signal",
"protocol_risk": "chain_health",
"scam_database": "urlcheck",
"token_age": "audit",
"wash_trading": "nft_wash_detector",
# Intelligence
"alpha_digest": "smart_money_alpha",
"insider_network": "insider",
"kol_performance": "social_signal",
"listing_predictor": "market_overview",
"sniper_detect": "sniper_alert",
"syndicate_scan": "cluster",
"syndicate_track": "cluster",
"whale_accumulation": "whale",
"whale_profile": "whale",
"whale_scan": "whale",
"wallet_graph": "cluster",
# Market
"arbitrage_scan": "market_overview",
"liquidity_depth": "market_overview",
"unlock_calendar": "market_overview",
# Social
"sentiment_spike": "sentiment",
# Analysis
"portfolio_aggregate": "portfolio_tracker",
"wallet_pnl": "wallet",
# Premium (mapped to closest real tool)
"forensic_valuation": "forensics",
"investigation_report": "forensics",
"osint_identity_hunt": "social_signal",
# Launchpad
# (fresh_pair already mapped to launch above)
}
# ── Expanded tool aliases (44 new specialized tools → real handlers) ──
try:
from app.routers._expanded_aliases import EXPANDED_ALIASES
TOOL_ALIASES.update(EXPANDED_ALIASES)
except Exception:
pass
# ── Human Payment - Multi-Chain Wallet Pay-Per-Call ────────────
# Supported payment tokens across all chains
HUMAN_PAYMENT_TOKENS: dict[str, dict[str, Any]] = {
# Base chain
"USDC-BASE": {
"chain": "base",
"network": "eip155:8453",
"asset": "USDC",
"decimals": 6,
"label": "USDC on Base",
"icon": "💵",
},
# Solana chain
"USDC-SOL": {
"chain": "solana",
"network": "solana:mainnet",
"asset": "USDC",
"decimals": 6,
"label": "USDC on Solana",
"icon": "💵",
},
"SOL": {
"chain": "solana",
"network": "solana:mainnet",
"asset": "SOL",
"decimals": 9,
"label": "SOL native",
"icon": "",
},
# Ethereum chain
"USDC-ETH": {
"chain": "ethereum",
"network": "eip155:1",
"asset": "USDC",
"decimals": 6,
"label": "USDC on Ethereum",
"icon": "💵",
},
"USDT-ETH": {
"chain": "ethereum",
"network": "eip155:1",
"asset": "USDT",
"decimals": 6,
"label": "USDT on Ethereum",
"icon": "💲",
},
"ETH": {
"chain": "ethereum",
"network": "eip155:1",
"asset": "ETH",
"decimals": 18,
"label": "ETH native",
"icon": "",
},
# BNB Chain
"USDC-BSC": {
"chain": "bsc",
"network": "eip155:56",
"asset": "USDC",
"decimals": 18,
"label": "USDC on BSC",
"icon": "💵",
},
"USDT-BSC": {
"chain": "bsc",
"network": "eip155:56",
"asset": "USDT",
"decimals": 18,
"label": "USDT on BSC",
"icon": "💲",
},
# Polygon
"USDC-POLY": {
"chain": "polygon",
"network": "eip155:137",
"asset": "USDC",
"decimals": 6,
"label": "USDC on Polygon",
"icon": "💵",
},
"POL": {
"chain": "polygon",
"network": "eip155:137",
"asset": "POL",
"decimals": 18,
"label": "POL native",
"icon": "🟣",
},
# Arbitrum
"USDC-ARB": {
"chain": "arbitrum",
"network": "eip155:42161",
"asset": "USDC",
"decimals": 6,
"label": "USDC on Arbitrum",
"icon": "💵",
},
# TRON
"USDT-TRON": {
"chain": "tron",
"network": "tron:mainnet",
"asset": "USDT",
"decimals": 6,
"label": "USDT on TRON",
"icon": "💲",
},
"USDC-TRON": {
"chain": "tron",
"network": "tron:mainnet",
"asset": "USDC",
"decimals": 6,
"label": "USDC on TRON",
"icon": "💵",
},
# Bitcoin
"BTC": {
"chain": "bitcoin",
"network": "bitcoin:mainnet",
"asset": "BTC",
"decimals": 8,
"label": "Bitcoin",
"icon": "",
},
# Fiat
"EUR-SEPA": {
"chain": "sepa",
"network": "sepa:eur",
"asset": "EUR",
"decimals": 2,
"label": "EUR via SEPA",
"icon": "",
},
}
# Pay-to addresses - pulled dynamically from WalletManagerV2
# Falls back to env vars if wallet manager unavailable
def _resolve_pay_to(chain: str) -> str:
"""Get active x402 payment address from WalletManagerV2."""
try:
from app.wallet_manager_v2 import get_wallet_manager_v2
mgr = get_wallet_manager_v2(os.getenv("WALLET_VAULT_PASSWORD", ""))
# Map chain key to wallet manager chain key
chain_map = {
"base": "eth",
"ethereum": "eth",
"bsc": "eth",
"polygon": "eth",
"arbitrum": "eth",
"optimism": "eth",
"avalanche": "eth",
"fantom": "eth",
"gnosis": "eth",
"solana": "sol",
"tron": "trx",
"bitcoin": "btc",
}
wm_chain = chain_map.get(chain, chain)
for w in mgr._wallets.values():
if w.chain == wm_chain and w.x402_enabled and w.status == "active":
return w.address
except Exception:
pass
# Fallback: env vars
fallbacks = {
"base": "X402_EVM_PAY_TO",
"ethereum": "X402_EVM_PAY_TO",
"bsc": "X402_EVM_PAY_TO",
"polygon": "X402_EVM_PAY_TO",
"arbitrum": "X402_EVM_PAY_TO",
"solana": "X402_SOL_PAY_TO",
"tron": "X402_TRON_PAY_TO",
"bitcoin": "X402_BTC_PAY_TO",
"sepa": "ASTERPAY_SEPA_IBAN",
}
env_key = fallbacks.get(chain)
if env_key:
return os.getenv(env_key, "")
return ""
HUMAN_PAY_TO: dict[str, str] = {
"base": _resolve_pay_to("base"),
"ethereum": _resolve_pay_to("ethereum"),
"bsc": _resolve_pay_to("bsc"),
"polygon": _resolve_pay_to("polygon"),
"arbitrum": _resolve_pay_to("arbitrum"),
"solana": _resolve_pay_to("solana"),
"tron": _resolve_pay_to("tron"),
"bitcoin": _resolve_pay_to("bitcoin"),
"sepa": os.getenv("ASTERPAY_SEPA_IBAN", ""),
}
# ── Models specific to integration endpoints ──────────────────────
class BundleRequest(BaseModel):
address: str = ""
token: str = ""
wallet: str = ""
chain: str = "base"
url: str = ""
class MCPProxyRequest(BaseModel):
service: str
tool: str
arguments: dict[str, Any] = {}
class HumanPaymentRequest(BaseModel):
tool: str = Field(..., description="Tool ID to execute")
arguments: dict[str, Any] = Field(default_factory=dict, description="Tool parameters")
payment_token: str = Field(
..., description=f"Payment token key: {', '.join(HUMAN_PAYMENT_TOKENS.keys())}"
)
tx_hash: str = Field(..., description="Transaction hash on-chain")
wallet: str = Field(..., description="Payer wallet address")
chain: str | None = Field(
default=None, description="Blockchain (auto-detected from payment_token if not set)"
)
class HumanPaymentMethodsResponse(BaseModel):
"""Response listing all payment methods available to humans."""
tokens: list[dict[str, Any]]
pay_to_addresses: dict[str, str]
chain_count: int
token_count: int
class AuditRequest(BaseModel):
address: str
chain: str = "solana"
class SmartMoneyAlphaRequest(BaseModel):
wallet: str
chain: str = "solana"
class MemeVibeRequest(BaseModel):
token: str
chain: str = "solana"
class SentinelScanRequest(BaseModel):
"""Full 9-module SENTINEL deep scan."""
address: str
chain: str = "solana"
dev_address: str | None = None
class SentinelModuleRequest(BaseModel):
"""Single SENTINEL module request."""
address: str
chain: str = "solana"
dev_address: str | None = None
# Re-exported for tools that mirror legacy symbols
__all__ = [
# logger
"logger",
# sources
"FREE_RPCS",
"FREE_APIS",
# helpers
"fetch_with_fallback",
"rpc_call",
"_audit_solana",
"_audit_evm",
# pricing + aliases
"BUNDLES",
"TOOL_ALIASES",
# payment routing
"HUMAN_PAYMENT_TOKENS",
"HUMAN_PAY_TO",
"_resolve_pay_to",
# payment stub (P3A surface)
"record_x402_payment",
# request models
"TokenRequest",
"WalletRequest",
"SmartMoneyRequest",
"URLRequest",
"SentimentRequest",
"ClusterRequest",
"InsiderRequest",
"MultiTokenRequest",
"WalletListRequest",
"GenericRequest",
"BundleRequest",
"MCPProxyRequest",
"HumanPaymentRequest",
"HumanPaymentMethodsResponse",
"AuditRequest",
"SmartMoneyAlphaRequest",
"MemeVibeRequest",
"SentinelScanRequest",
"SentinelModuleRequest",
# upstream aliases from other libs (used by _check_rate_limit etc.)
"td",
"ClassVar",
]