- 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>
1740 lines
59 KiB
Python
1740 lines
59 KiB
Python
"""
|
|
RMI x402 DataBus Tools - Pay-per-call APIs wired through DataBus
|
|
================================================================
|
|
Every endpoint routes through app.databus.fetch() with full access control.
|
|
No raw HTTP calls. No backdoors. Consumer type is always x402_paid.
|
|
|
|
Pricing Tiers:
|
|
BASIC ($0.01-0.02) - Quick lookups, public-ish data
|
|
PREMIUM ($0.05-0.10) - Advanced intelligence, multi-source
|
|
ELITE ($0.15-0.40) - Institutional forensics, admin-grade data
|
|
|
|
Free Trials:
|
|
Each tool has trial_free calls before payment required.
|
|
Trials are tracked per-wallet in Redis with 24h reset.
|
|
|
|
Security:
|
|
- Every call goes through DataBus access_control (x402_paid consumer)
|
|
- x402_free tier gets 5 data types, summary packaging only
|
|
- x402_basic tier gets 9 data types, full packaging
|
|
- x402_premium tier gets 14 data types, full packaging
|
|
- x402_enterprise tier gets all data types, full packaging
|
|
- Zero backdoors: no direct DB/API access, everything through databus.fetch()
|
|
"""
|
|
|
|
import json
|
|
import logging
|
|
import os
|
|
from typing import Any
|
|
|
|
from fastapi import APIRouter, HTTPException, Request
|
|
from pydantic import BaseModel
|
|
|
|
logger = logging.getLogger("x402_databus_tools")
|
|
|
|
router = APIRouter(prefix="/api/v1/x402-databus", tags=["x402-databus-tools"])
|
|
|
|
# ── DataBus Integration ─────────────────────────────────────────
|
|
from app.databus import databus # noqa: E402
|
|
|
|
# ── x402 Pricing - matches TOOL_PRICES schema exactly ─────────────
|
|
# price_atoms = price_usd * 1_000_000 (1 atom = $0.000001)
|
|
X402_TOOL_PRICING = {
|
|
# ═══ BASIC TIER ($0.01-0.02) - Quick Scans & Lookups ═══
|
|
"token_price": {
|
|
"price_usd": 0.01,
|
|
"price_atoms": "10000",
|
|
"category": "basic",
|
|
"trial_free": 5,
|
|
"description": "Get real-time token price with consensus from multiple sources",
|
|
},
|
|
"token_detail": {
|
|
"price_usd": 0.02,
|
|
"price_atoms": "20000",
|
|
"category": "basic",
|
|
"trial_free": 3,
|
|
"description": "Full token intelligence - market cap, volume, liquidity, holders, risk flags",
|
|
},
|
|
"trending": {
|
|
"price_usd": 0.01,
|
|
"price_atoms": "10000",
|
|
"category": "basic",
|
|
"trial_free": 5,
|
|
"description": "Trending tokens across chains - hottest movers right now",
|
|
},
|
|
"market_overview": {
|
|
"price_usd": 0.02,
|
|
"price_atoms": "20000",
|
|
"category": "basic",
|
|
"trial_free": 5,
|
|
"description": "Crypto market landscape - global metrics, BTC dominance, sentiment",
|
|
},
|
|
"market_movers": {
|
|
"price_usd": 0.01,
|
|
"price_atoms": "10000",
|
|
"category": "basic",
|
|
"trial_free": 5,
|
|
"description": "Top gainers, losers, and volume movers across all chains",
|
|
},
|
|
"tvl": {
|
|
"price_usd": 0.01,
|
|
"price_atoms": "10000",
|
|
"category": "basic",
|
|
"trial_free": 5,
|
|
"description": "DeFi TVL data - protocol-level totals, chain breakdowns, yields",
|
|
},
|
|
"news": {
|
|
"price_usd": 0.01,
|
|
"price_atoms": "10000",
|
|
"category": "basic",
|
|
"trial_free": 5,
|
|
"description": "Crypto news feed - aggregated headlines, filtered by topic",
|
|
},
|
|
"social_feed": {
|
|
"price_usd": 0.01,
|
|
"price_atoms": "10000",
|
|
"category": "basic",
|
|
"trial_free": 5,
|
|
"description": "Social sentiment feed - what crypto Twitter and Telegram are saying",
|
|
},
|
|
"dex_data": {
|
|
"price_usd": 0.01,
|
|
"price_atoms": "10000",
|
|
"category": "basic",
|
|
"trial_free": 5,
|
|
"description": "DEX pool data - liquidity depth, volume, price impact for any token",
|
|
},
|
|
"defi_protocols": {
|
|
"price_usd": 0.01,
|
|
"price_atoms": "10000",
|
|
"category": "basic",
|
|
"trial_free": 5,
|
|
"description": "DeFi protocol tracker - TVL, chains, categories, revenue metrics",
|
|
},
|
|
"prediction_markets": {
|
|
"price_usd": 0.02,
|
|
"price_atoms": "20000",
|
|
"category": "basic",
|
|
"trial_free": 3,
|
|
"description": "Prediction market odds - event probabilities and trading volumes",
|
|
},
|
|
"prediction_signals": {
|
|
"price_usd": 0.02,
|
|
"price_atoms": "20000",
|
|
"category": "basic",
|
|
"trial_free": 3,
|
|
"description": "Trading signals - sentiment, momentum, and contrarian indicators",
|
|
},
|
|
"bubble_map": {
|
|
"price_usd": 0.02,
|
|
"price_atoms": "20000",
|
|
"category": "basic",
|
|
"trial_free": 3,
|
|
"description": "Holder concentration map - visualize whale clusters and distribution",
|
|
},
|
|
"rugmaps_analysis": {
|
|
"price_usd": 0.02,
|
|
"price_atoms": "20000",
|
|
"category": "basic",
|
|
"trial_free": 3,
|
|
"description": "Holder distribution analysis - risk scoring, dump patterns, concentration",
|
|
},
|
|
"wallet_balance": {
|
|
"price_usd": 0.01,
|
|
"price_atoms": "10000",
|
|
"category": "basic",
|
|
"trial_free": 3,
|
|
"description": "Check any wallet's balance across chains - multi-chain support",
|
|
},
|
|
"wallet_labels": {
|
|
"price_usd": 0.02,
|
|
"price_atoms": "20000",
|
|
"category": "basic",
|
|
"trial_free": 3,
|
|
"description": "Identify who owns a wallet - entity labels, tags, and known affiliations",
|
|
},
|
|
"risk_scan": {
|
|
"price_usd": 0.02,
|
|
"price_atoms": "20000",
|
|
"category": "basic",
|
|
"trial_free": 3,
|
|
"description": "Quick rug risk scan - honeypot, liquidity lock, ownership, and contract flags",
|
|
},
|
|
"threat_check": {
|
|
"price_usd": 0.02,
|
|
"price_atoms": "20000",
|
|
"category": "basic",
|
|
"trial_free": 3,
|
|
"description": "Threat intelligence check - known scams, malicious patterns, risk scoring",
|
|
},
|
|
"socialfi_resolve": {
|
|
"price_usd": 0.01,
|
|
"price_atoms": "10000",
|
|
"category": "basic",
|
|
"trial_free": 3,
|
|
"description": "Resolve social identity - ENS names, Farcaster profiles, linked addresses",
|
|
},
|
|
# ═══ PREMIUM TIER ($0.05-0.10) - Advanced Intelligence ═══
|
|
"wallet_profile": {
|
|
"price_usd": 0.05,
|
|
"price_atoms": "50000",
|
|
"category": "premium",
|
|
"trial_free": 1,
|
|
"description": "Complete wallet profile - labels, PnL summary, risk score, related wallets",
|
|
},
|
|
"smart_money": {
|
|
"price_usd": 0.05,
|
|
"price_atoms": "50000",
|
|
"category": "premium",
|
|
"trial_free": 1,
|
|
"description": "Smart money tracker - see what top-performing wallets are buying and selling",
|
|
},
|
|
"gmgn_smart_money": {
|
|
"price_usd": 0.05,
|
|
"price_atoms": "50000",
|
|
"category": "premium",
|
|
"trial_free": 1,
|
|
"description": "Smart money narratives - trending wallets and their trade patterns",
|
|
},
|
|
"funding_source": {
|
|
"price_usd": 0.08,
|
|
"price_atoms": "80000",
|
|
"category": "premium",
|
|
"trial_free": 1,
|
|
"description": "Trace where a wallet's funds came from - multi-hop origin analysis",
|
|
},
|
|
"cross_chain": {
|
|
"price_usd": 0.08,
|
|
"price_atoms": "80000",
|
|
"category": "premium",
|
|
"trial_free": 1,
|
|
"description": "Cross-chain activity - find the same entity across multiple blockchains",
|
|
},
|
|
"wallet_cluster": {
|
|
"price_usd": 0.08,
|
|
"price_atoms": "80000",
|
|
"category": "premium",
|
|
"trial_free": 1,
|
|
"description": "Syndicate mapper - find related wallets via funding patterns and heuristics",
|
|
},
|
|
"bundle_detect": {
|
|
"price_usd": 0.08,
|
|
"price_atoms": "80000",
|
|
"category": "premium",
|
|
"trial_free": 1,
|
|
"description": "Bot detector - same-block bundling, MEV patterns, sniper wallet identification",
|
|
},
|
|
"wallet_tokens": {
|
|
"price_usd": 0.05,
|
|
"price_atoms": "50000",
|
|
"category": "premium",
|
|
"trial_free": 1,
|
|
"description": "All tokens held by a wallet - balances, USD values, allocation breakdown",
|
|
},
|
|
"wallet_pnl": {
|
|
"price_usd": 0.05,
|
|
"price_atoms": "50000",
|
|
"category": "premium",
|
|
"trial_free": 1,
|
|
"description": "Wallet profit & loss - realized/unrealized gains, win rate, ROI",
|
|
},
|
|
"contract_scan": {
|
|
"price_usd": 0.08,
|
|
"price_atoms": "80000",
|
|
"category": "premium",
|
|
"trial_free": 1,
|
|
"description": "Deep contract audit - static analysis, honeypot detection, vulnerability scan",
|
|
},
|
|
"sentinel_deep": {
|
|
"price_usd": 0.10,
|
|
"price_atoms": "100000",
|
|
"category": "premium",
|
|
"trial_free": 1,
|
|
"description": "Full threat scan - deep contract analysis, risk scoring, threat intelligence",
|
|
},
|
|
"entity_intel": {
|
|
"price_usd": 0.10,
|
|
"price_atoms": "100000",
|
|
"category": "premium",
|
|
"trial_free": 1,
|
|
"description": "Entity intelligence - who is this wallet, linked addresses, risk assessment",
|
|
},
|
|
"arkham_labels": {
|
|
"price_usd": 0.10,
|
|
"price_atoms": "100000",
|
|
"category": "premium",
|
|
"trial_free": 1,
|
|
"description": "Institutional entity labels - fund names, exchange wallets, known addresses",
|
|
},
|
|
"arkham_entity": {
|
|
"price_usd": 0.10,
|
|
"price_atoms": "100000",
|
|
"category": "premium",
|
|
"trial_free": 1,
|
|
"description": "Entity resolution - map any address to its real-world owner with confidence scoring",
|
|
},
|
|
# ═══ ELITE TIER ($0.15-0.40) - Institutional Forensics ═══
|
|
"arkham_portfolio": {
|
|
"price_usd": 0.25,
|
|
"price_atoms": "250000",
|
|
"category": "elite",
|
|
"trial_free": 0,
|
|
"description": "Institutional portfolio intelligence - complete holdings, historical performance, attribution",
|
|
},
|
|
"arkham_transfers": {
|
|
"price_usd": 0.20,
|
|
"price_atoms": "200000",
|
|
"category": "elite",
|
|
"trial_free": 0,
|
|
"description": "Cross-chain transfer tracer - full movement history with entity labeling",
|
|
},
|
|
"arkham_counterparties": {
|
|
"price_usd": 0.20,
|
|
"price_atoms": "200000",
|
|
"category": "elite",
|
|
"trial_free": 0,
|
|
"description": "Counterparty intelligence - entity relationship graph and money flow analysis",
|
|
},
|
|
"nansen_labels": {
|
|
"price_usd": 0.15,
|
|
"price_atoms": "150000",
|
|
"category": "elite",
|
|
"trial_free": 0,
|
|
"description": "Smart money labels - fund tags, whale classifications, and institutional wallet mapping",
|
|
},
|
|
"nansen_smart_money": {
|
|
"price_usd": 0.15,
|
|
"price_atoms": "150000",
|
|
"category": "elite",
|
|
"trial_free": 0,
|
|
"description": "Smart money tracker - top trader activity, position tracking, alpha signals",
|
|
},
|
|
"portfolio": {
|
|
"price_usd": 0.15,
|
|
"price_atoms": "150000",
|
|
"category": "elite",
|
|
"trial_free": 0,
|
|
"description": "Multi-wallet portfolio - consolidated holdings, PnL, and risk across all wallets",
|
|
},
|
|
"rag_search": {
|
|
"price_usd": 0.05,
|
|
"price_atoms": "50000",
|
|
"category": "premium",
|
|
"trial_free": 2,
|
|
"description": "Knowledge search - query 17K+ crypto documents for research, analysis, and deep answers",
|
|
},
|
|
# ═══ AUTO-GENERATED: 75 DataBus chains ═══
|
|
"academic_papers": {
|
|
"price_usd": 0.01,
|
|
"price_atoms": "10000",
|
|
"category": "basic",
|
|
"trial_free": 999,
|
|
"description": "Academic Papers - DataBus, cached, credit-aware",
|
|
},
|
|
"ai_task": {
|
|
"price_usd": 0.01,
|
|
"price_atoms": "10000",
|
|
"category": "basic",
|
|
"trial_free": 999,
|
|
"description": "Ai Task - DataBus, cached, credit-aware",
|
|
},
|
|
"ai_usage": {
|
|
"price_usd": 0.01,
|
|
"price_atoms": "10000",
|
|
"category": "basic",
|
|
"trial_free": 999,
|
|
"description": "Ai Usage - DataBus, cached, credit-aware",
|
|
},
|
|
"alerts": {
|
|
"price_usd": 0.01,
|
|
"price_atoms": "10000",
|
|
"category": "basic",
|
|
"trial_free": 999,
|
|
"description": "Alerts - DataBus, cached, credit-aware",
|
|
},
|
|
"arkham_intel": {
|
|
"price_usd": 0.05,
|
|
"price_atoms": "50000",
|
|
"category": "premium",
|
|
"trial_free": 5,
|
|
"description": "Arkham Intel - DataBus, cached, credit-aware",
|
|
},
|
|
"arkham_ws": {
|
|
"price_usd": 0.05,
|
|
"price_atoms": "50000",
|
|
"category": "premium",
|
|
"trial_free": 5,
|
|
"description": "Arkham Ws - DataBus, cached, credit-aware",
|
|
},
|
|
"article_comments": {
|
|
"price_usd": 0.01,
|
|
"price_atoms": "10000",
|
|
"category": "basic",
|
|
"trial_free": 999,
|
|
"description": "Article Comments - DataBus, cached, credit-aware",
|
|
},
|
|
"article_reactions": {
|
|
"price_usd": 0.01,
|
|
"price_atoms": "10000",
|
|
"category": "basic",
|
|
"trial_free": 999,
|
|
"description": "Article Reactions - DataBus, cached, credit-aware",
|
|
},
|
|
"bb_post": {
|
|
"price_usd": 0.01,
|
|
"price_atoms": "10000",
|
|
"category": "basic",
|
|
"trial_free": 999,
|
|
"description": "Bb Post - DataBus, cached, credit-aware",
|
|
},
|
|
"birdeye_overview": {
|
|
"price_usd": 0.05,
|
|
"price_atoms": "50000",
|
|
"category": "premium",
|
|
"trial_free": 5,
|
|
"description": "Birdeye Overview - DataBus, cached, credit-aware",
|
|
},
|
|
"birdeye_price": {
|
|
"price_usd": 0.05,
|
|
"price_atoms": "50000",
|
|
"category": "premium",
|
|
"trial_free": 5,
|
|
"description": "Birdeye Price - DataBus, cached, credit-aware",
|
|
},
|
|
"blockchair_address": {
|
|
"price_usd": 0.01,
|
|
"price_atoms": "10000",
|
|
"category": "basic",
|
|
"trial_free": 999,
|
|
"description": "Blockchair Address - DataBus, cached, credit-aware",
|
|
},
|
|
"blockchair_stats": {
|
|
"price_usd": 0.01,
|
|
"price_atoms": "10000",
|
|
"category": "basic",
|
|
"trial_free": 999,
|
|
"description": "Blockchair Stats - DataBus, cached, credit-aware",
|
|
},
|
|
"bot_farm_detect": {
|
|
"price_usd": 0.01,
|
|
"price_atoms": "10000",
|
|
"category": "basic",
|
|
"trial_free": 999,
|
|
"description": "Bot Farm Detect - DataBus, cached, credit-aware",
|
|
},
|
|
"cluster_map": {
|
|
"price_usd": 0.01,
|
|
"price_atoms": "10000",
|
|
"category": "basic",
|
|
"trial_free": 999,
|
|
"description": "Cluster Map - DataBus, cached, credit-aware",
|
|
},
|
|
"content_review": {
|
|
"price_usd": 0.01,
|
|
"price_atoms": "10000",
|
|
"category": "basic",
|
|
"trial_free": 999,
|
|
"description": "Content Review - DataBus, cached, credit-aware",
|
|
},
|
|
"copy_trade_detect": {
|
|
"price_usd": 0.01,
|
|
"price_atoms": "10000",
|
|
"category": "basic",
|
|
"trial_free": 999,
|
|
"description": "Copy Trade Detect - DataBus, cached, credit-aware",
|
|
},
|
|
"cross_chain_entity": {
|
|
"price_usd": 0.01,
|
|
"price_atoms": "10000",
|
|
"category": "basic",
|
|
"trial_free": 999,
|
|
"description": "Cross Chain Entity - DataBus, cached, credit-aware",
|
|
},
|
|
"ct_accounts": {
|
|
"price_usd": 0.01,
|
|
"price_atoms": "10000",
|
|
"category": "basic",
|
|
"trial_free": 999,
|
|
"description": "Ct Accounts - DataBus, cached, credit-aware",
|
|
},
|
|
"ct_rundown": {
|
|
"price_usd": 0.01,
|
|
"price_atoms": "10000",
|
|
"category": "basic",
|
|
"trial_free": 999,
|
|
"description": "Ct Rundown - DataBus, cached, credit-aware",
|
|
},
|
|
"daily_intel": {
|
|
"price_usd": 0.01,
|
|
"price_atoms": "10000",
|
|
"category": "basic",
|
|
"trial_free": 999,
|
|
"description": "Daily Intel - DataBus, cached, credit-aware",
|
|
},
|
|
"defillama_chains": {
|
|
"price_usd": 0.01,
|
|
"price_atoms": "10000",
|
|
"category": "basic",
|
|
"trial_free": 999,
|
|
"description": "Defillama Chains - DataBus, cached, credit-aware",
|
|
},
|
|
"defillama_tvl": {
|
|
"price_usd": 0.01,
|
|
"price_atoms": "10000",
|
|
"category": "basic",
|
|
"trial_free": 999,
|
|
"description": "Defillama Tvl - DataBus, cached, credit-aware",
|
|
},
|
|
"dev_activity": {
|
|
"price_usd": 0.05,
|
|
"price_atoms": "50000",
|
|
"category": "premium",
|
|
"trial_free": 5,
|
|
"description": "Dev Activity - DataBus, cached, credit-aware",
|
|
},
|
|
"dev_finder": {
|
|
"price_usd": 0.01,
|
|
"price_atoms": "10000",
|
|
"category": "basic",
|
|
"trial_free": 999,
|
|
"description": "Dev Finder - DataBus, cached, credit-aware",
|
|
},
|
|
"dev_reputation": {
|
|
"price_usd": 0.01,
|
|
"price_atoms": "10000",
|
|
"category": "basic",
|
|
"trial_free": 999,
|
|
"description": "Dev Reputation - DataBus, cached, credit-aware",
|
|
},
|
|
"dune_early_buyers": {
|
|
"price_usd": 0.05,
|
|
"price_atoms": "50000",
|
|
"category": "premium",
|
|
"trial_free": 5,
|
|
"description": "Dune Early Buyers - DataBus, cached, credit-aware",
|
|
},
|
|
"fear_greed": {
|
|
"price_usd": 0.01,
|
|
"price_atoms": "10000",
|
|
"category": "basic",
|
|
"trial_free": 999,
|
|
"description": "Fear Greed - DataBus, cached, credit-aware",
|
|
},
|
|
"fresh_wallet_analysis": {
|
|
"price_usd": 0.01,
|
|
"price_atoms": "10000",
|
|
"category": "basic",
|
|
"trial_free": 999,
|
|
"description": "Fresh Wallet Analysis - DataBus, cached, credit-aware",
|
|
},
|
|
"full_news": {
|
|
"price_usd": 0.01,
|
|
"price_atoms": "10000",
|
|
"category": "basic",
|
|
"trial_free": 999,
|
|
"description": "Full News - DataBus, cached, credit-aware",
|
|
},
|
|
"holder_data": {
|
|
"price_usd": 0.01,
|
|
"price_atoms": "10000",
|
|
"category": "basic",
|
|
"trial_free": 999,
|
|
"description": "Holder Data - DataBus, cached, credit-aware",
|
|
},
|
|
"holder_health": {
|
|
"price_usd": 0.01,
|
|
"price_atoms": "10000",
|
|
"category": "basic",
|
|
"trial_free": 999,
|
|
"description": "Holder Health - DataBus, cached, credit-aware",
|
|
},
|
|
"hyperliquid": {
|
|
"price_usd": 0.01,
|
|
"price_atoms": "10000",
|
|
"category": "basic",
|
|
"trial_free": 999,
|
|
"description": "Hyperliquid - DataBus, cached, credit-aware",
|
|
},
|
|
"hyperliquid_action": {
|
|
"price_usd": 0.01,
|
|
"price_atoms": "10000",
|
|
"category": "basic",
|
|
"trial_free": 999,
|
|
"description": "Hyperliquid Action - DataBus, cached, credit-aware",
|
|
},
|
|
"insider_detect": {
|
|
"price_usd": 0.01,
|
|
"price_atoms": "10000",
|
|
"category": "basic",
|
|
"trial_free": 999,
|
|
"description": "Insider Detect - DataBus, cached, credit-aware",
|
|
},
|
|
"insider_detection": {
|
|
"price_usd": 0.01,
|
|
"price_atoms": "10000",
|
|
"category": "basic",
|
|
"trial_free": 999,
|
|
"description": "Insider Detection - DataBus, cached, credit-aware",
|
|
},
|
|
"insider_wallets": {
|
|
"price_usd": 0.01,
|
|
"price_atoms": "10000",
|
|
"category": "basic",
|
|
"trial_free": 999,
|
|
"description": "Insider Wallets - DataBus, cached, credit-aware",
|
|
},
|
|
"kol_leaderboard": {
|
|
"price_usd": 0.01,
|
|
"price_atoms": "10000",
|
|
"category": "basic",
|
|
"trial_free": 999,
|
|
"description": "Kol Leaderboard - DataBus, cached, credit-aware",
|
|
},
|
|
"kol_profile": {
|
|
"price_usd": 0.01,
|
|
"price_atoms": "10000",
|
|
"category": "basic",
|
|
"trial_free": 999,
|
|
"description": "Kol Profile - DataBus, cached, credit-aware",
|
|
},
|
|
"kol_track": {
|
|
"price_usd": 0.01,
|
|
"price_atoms": "10000",
|
|
"category": "basic",
|
|
"trial_free": 999,
|
|
"description": "Kol Track - DataBus, cached, credit-aware",
|
|
},
|
|
"liquidity_risk": {
|
|
"price_usd": 0.01,
|
|
"price_atoms": "10000",
|
|
"category": "basic",
|
|
"trial_free": 999,
|
|
"description": "Liquidity Risk - DataBus, cached, credit-aware",
|
|
},
|
|
"live_prices": {
|
|
"price_usd": 0.01,
|
|
"price_atoms": "10000",
|
|
"category": "basic",
|
|
"trial_free": 999,
|
|
"description": "Live Prices - DataBus, cached, credit-aware",
|
|
},
|
|
"market_brief": {
|
|
"price_usd": 0.01,
|
|
"price_atoms": "10000",
|
|
"category": "basic",
|
|
"trial_free": 999,
|
|
"description": "Market Brief - DataBus, cached, credit-aware",
|
|
},
|
|
"mcp_bridge": {
|
|
"price_usd": 0.01,
|
|
"price_atoms": "10000",
|
|
"category": "basic",
|
|
"trial_free": 999,
|
|
"description": "Mcp Bridge - DataBus, cached, credit-aware",
|
|
},
|
|
"mev_detect": {
|
|
"price_usd": 0.01,
|
|
"price_atoms": "10000",
|
|
"category": "basic",
|
|
"trial_free": 999,
|
|
"description": "Mev Detect - DataBus, cached, credit-aware",
|
|
},
|
|
"news_intel": {
|
|
"price_usd": 0.01,
|
|
"price_atoms": "10000",
|
|
"category": "basic",
|
|
"trial_free": 999,
|
|
"description": "News Intel - DataBus, cached, credit-aware",
|
|
},
|
|
"ohlcv": {
|
|
"price_usd": 0.01,
|
|
"price_atoms": "10000",
|
|
"category": "basic",
|
|
"trial_free": 999,
|
|
"description": "Ohlcv - DataBus, cached, credit-aware",
|
|
},
|
|
"rag_health": {
|
|
"price_usd": 0.01,
|
|
"price_atoms": "10000",
|
|
"category": "basic",
|
|
"trial_free": 999,
|
|
"description": "Rag Health - DataBus, cached, credit-aware",
|
|
},
|
|
"rag_nightly": {
|
|
"price_usd": 0.01,
|
|
"price_atoms": "10000",
|
|
"category": "basic",
|
|
"trial_free": 999,
|
|
"description": "Rag Nightly - DataBus, cached, credit-aware",
|
|
},
|
|
"rug_patterns": {
|
|
"price_usd": 0.01,
|
|
"price_atoms": "10000",
|
|
"category": "basic",
|
|
"trial_free": 999,
|
|
"description": "Rug Patterns - DataBus, cached, credit-aware",
|
|
},
|
|
"scam_monitor": {
|
|
"price_usd": 0.01,
|
|
"price_atoms": "10000",
|
|
"category": "basic",
|
|
"trial_free": 999,
|
|
"description": "Scam Monitor - DataBus, cached, credit-aware",
|
|
},
|
|
"scanner": {
|
|
"price_usd": 0.01,
|
|
"price_atoms": "10000",
|
|
"category": "basic",
|
|
"trial_free": 999,
|
|
"description": "Scanner - DataBus, cached, credit-aware",
|
|
},
|
|
"shill_detector": {
|
|
"price_usd": 0.01,
|
|
"price_atoms": "10000",
|
|
"category": "basic",
|
|
"trial_free": 999,
|
|
"description": "Shill Detector - DataBus, cached, credit-aware",
|
|
},
|
|
"sniper_detect": {
|
|
"price_usd": 0.01,
|
|
"price_atoms": "10000",
|
|
"category": "basic",
|
|
"trial_free": 999,
|
|
"description": "Sniper Detect - DataBus, cached, credit-aware",
|
|
},
|
|
"social_metrics": {
|
|
"price_usd": 0.01,
|
|
"price_atoms": "10000",
|
|
"category": "basic",
|
|
"trial_free": 999,
|
|
"description": "Social Metrics - DataBus, cached, credit-aware",
|
|
},
|
|
"spl_token_metadata": {
|
|
"price_usd": 0.01,
|
|
"price_atoms": "10000",
|
|
"category": "basic",
|
|
"trial_free": 999,
|
|
"description": "Spl Token Metadata - DataBus, cached, credit-aware",
|
|
},
|
|
"tier_comparison": {
|
|
"price_usd": 0.01,
|
|
"price_atoms": "10000",
|
|
"category": "basic",
|
|
"trial_free": 999,
|
|
"description": "Tier Comparison - DataBus, cached, credit-aware",
|
|
},
|
|
"token_launches": {
|
|
"price_usd": 0.01,
|
|
"price_atoms": "10000",
|
|
"category": "basic",
|
|
"trial_free": 999,
|
|
"description": "Token Launches - DataBus, cached, credit-aware",
|
|
},
|
|
"token_metadata": {
|
|
"price_usd": 0.01,
|
|
"price_atoms": "10000",
|
|
"category": "basic",
|
|
"trial_free": 999,
|
|
"description": "Token Metadata - DataBus, cached, credit-aware",
|
|
},
|
|
"token_report": {
|
|
"price_usd": 0.01,
|
|
"price_atoms": "10000",
|
|
"category": "basic",
|
|
"trial_free": 999,
|
|
"description": "Token Report - DataBus, cached, credit-aware",
|
|
},
|
|
"token_search": {
|
|
"price_usd": 0.05,
|
|
"price_atoms": "50000",
|
|
"category": "premium",
|
|
"trial_free": 5,
|
|
"description": "Token Search - DataBus, cached, credit-aware",
|
|
},
|
|
"token_security": {
|
|
"price_usd": 0.01,
|
|
"price_atoms": "10000",
|
|
"category": "basic",
|
|
"trial_free": 999,
|
|
"description": "Token Security - DataBus, cached, credit-aware",
|
|
},
|
|
"token_trades": {
|
|
"price_usd": 0.01,
|
|
"price_atoms": "10000",
|
|
"category": "basic",
|
|
"trial_free": 999,
|
|
"description": "Token Trades - DataBus, cached, credit-aware",
|
|
},
|
|
"top_traders": {
|
|
"price_usd": 0.01,
|
|
"price_atoms": "10000",
|
|
"category": "basic",
|
|
"trial_free": 999,
|
|
"description": "Top Traders - DataBus, cached, credit-aware",
|
|
},
|
|
"trending_coins": {
|
|
"price_usd": 0.01,
|
|
"price_atoms": "10000",
|
|
"category": "basic",
|
|
"trial_free": 999,
|
|
"description": "Trending Coins - DataBus, cached, credit-aware",
|
|
},
|
|
"tx_trace": {
|
|
"price_usd": 0.01,
|
|
"price_atoms": "10000",
|
|
"category": "basic",
|
|
"trial_free": 999,
|
|
"description": "Tx Trace - DataBus, cached, credit-aware",
|
|
},
|
|
"url_security_scan": {
|
|
"price_usd": 0.05,
|
|
"price_atoms": "50000",
|
|
"category": "premium",
|
|
"trial_free": 5,
|
|
"description": "Url Security Scan - DataBus, cached, credit-aware",
|
|
},
|
|
"volume_authenticity": {
|
|
"price_usd": 0.01,
|
|
"price_atoms": "10000",
|
|
"category": "basic",
|
|
"trial_free": 999,
|
|
"description": "Volume Authenticity - DataBus, cached, credit-aware",
|
|
},
|
|
"wallet_net_worth": {
|
|
"price_usd": 0.05,
|
|
"price_atoms": "50000",
|
|
"category": "premium",
|
|
"trial_free": 5,
|
|
"description": "Wallet Net Worth - DataBus, cached, credit-aware",
|
|
},
|
|
"wallet_nfts": {
|
|
"price_usd": 0.05,
|
|
"price_atoms": "50000",
|
|
"category": "premium",
|
|
"trial_free": 5,
|
|
"description": "Wallet Nfts - DataBus, cached, credit-aware",
|
|
},
|
|
"wallet_transactions": {
|
|
"price_usd": 0.01,
|
|
"price_atoms": "10000",
|
|
"category": "basic",
|
|
"trial_free": 999,
|
|
"description": "Wallet Transactions - DataBus, cached, credit-aware",
|
|
},
|
|
"wash_trade_detect": {
|
|
"price_usd": 0.01,
|
|
"price_atoms": "10000",
|
|
"category": "basic",
|
|
"trial_free": 999,
|
|
"description": "Wash Trade Detect - DataBus, cached, credit-aware",
|
|
},
|
|
"webhook_handler": {
|
|
"price_usd": 0.01,
|
|
"price_atoms": "10000",
|
|
"category": "basic",
|
|
"trial_free": 999,
|
|
"description": "Webhook Handler - DataBus, cached, credit-aware",
|
|
},
|
|
"weekly_best": {
|
|
"price_usd": 0.01,
|
|
"price_atoms": "10000",
|
|
"category": "basic",
|
|
"trial_free": 999,
|
|
"description": "Weekly Best - DataBus, cached, credit-aware",
|
|
},
|
|
"whale_alerts": {
|
|
"price_usd": 0.01,
|
|
"price_atoms": "10000",
|
|
"category": "basic",
|
|
"trial_free": 999,
|
|
"description": "Whale Alerts - DataBus, cached, credit-aware",
|
|
},
|
|
}
|
|
|
|
# ── Trial Tracking ───────────────────────────────────────────────
|
|
# Uses the same fingerprint+wallet gating system as x402_enforcement.
|
|
# Bots get fingerprint-based trials (1 per tool without wallet, full with device ID).
|
|
# Humans connect wallet for full trial allotment.
|
|
|
|
TRIAL_REDIS_PREFIX = "x402:databus_trials:"
|
|
TRIAL_WINDOW_SECONDS = 86400 # 24 hours
|
|
|
|
|
|
async def _get_trial_redis():
|
|
try:
|
|
import redis.asyncio as aioredis
|
|
|
|
r = aioredis.Redis(
|
|
host=os.getenv("REDIS_HOST", "rmi-redis"),
|
|
port=int(os.getenv("REDIS_PORT", "6379")),
|
|
password=os.getenv("REDIS_PASSWORD") or None,
|
|
db=0,
|
|
decode_responses=True,
|
|
)
|
|
await r.ping()
|
|
return r
|
|
except Exception:
|
|
return None
|
|
|
|
|
|
async def check_trial(identifier: str, tool_id: str, max_trials: int) -> dict[str, Any]:
|
|
"""Check if a wallet or fingerprint has remaining trial calls for a tool.
|
|
|
|
Supports both bot fingerprint IDs and human wallet addresses.
|
|
Uses the same Redis namespace as enforcement for consistency.
|
|
"""
|
|
r = await _get_trial_redis()
|
|
if not r:
|
|
return {"allowed": True, "remaining": max_trials, "reason": "redis_unavailable"}
|
|
|
|
key = f"{TRIAL_REDIS_PREFIX}{identifier}:{tool_id}"
|
|
try:
|
|
used = await r.get(key)
|
|
used_count = int(used) if used else 0
|
|
|
|
if used_count >= max_trials:
|
|
ttl = await r.ttl(key)
|
|
return {
|
|
"allowed": False,
|
|
"remaining": 0,
|
|
"reason": "trials_exhausted",
|
|
"resets_in_seconds": ttl if ttl > 0 else TRIAL_WINDOW_SECONDS,
|
|
}
|
|
|
|
return {"allowed": True, "remaining": max_trials - used_count, "reason": "trial_available"}
|
|
except Exception as e:
|
|
logger.error(f"Trial check error: {e}")
|
|
return {"allowed": True, "remaining": max_trials, "reason": "error"}
|
|
finally:
|
|
await r.close()
|
|
|
|
|
|
async def consume_trial(identifier: str, tool_id: str) -> bool:
|
|
"""Record a trial call. Returns True if recorded."""
|
|
r = await _get_trial_redis()
|
|
if not r:
|
|
return False
|
|
try:
|
|
key = f"{TRIAL_REDIS_PREFIX}{identifier}:{tool_id}"
|
|
pipe = r.pipeline()
|
|
pipe.incr(key)
|
|
pipe.expire(key, TRIAL_WINDOW_SECONDS)
|
|
await pipe.execute()
|
|
return True
|
|
except Exception:
|
|
return False
|
|
finally:
|
|
await r.close()
|
|
|
|
|
|
# ── x402 Payment Verification ────────────────────────────────────
|
|
# Verifies payment via x402 enforcement middleware.
|
|
|
|
|
|
def _clean_response(result: dict) -> dict:
|
|
"""Strip internal DataBus fields that reveal provider architecture.
|
|
|
|
External clients see WHAT data they got, not WHERE it came from.
|
|
Internal fields like 'source', 'tier', 'is_local' are kept for
|
|
internal monitoring but stripped from x402 API responses.
|
|
"""
|
|
if not isinstance(result, dict):
|
|
return result
|
|
# Remove provider-identifying fields
|
|
for field in ("source", "is_local", "tier", "fallback_used", "consumer", "packaging", "cached"):
|
|
result.pop(field, None)
|
|
# Also clean nested 'data' if it contains source references
|
|
if "data" in result and isinstance(result["data"], dict):
|
|
result["data"].pop("source", None)
|
|
result["data"].pop("provider", None)
|
|
return result
|
|
|
|
|
|
async def verify_x402_payment(request: Request, tool_id: str, price_usd: float) -> dict[str, Any]:
|
|
"""Verify x402 payment header. Returns payment info or 402 response.
|
|
|
|
For humans: Connect wallet via X-WALLET header → full trial allotment.
|
|
For bots: Fingerprint via X-Device-Id header → 1 trial, then require wallet.
|
|
Both paths use the same Redis-backed trial tracking.
|
|
"""
|
|
# Check for x402 payment header
|
|
x402_payment = request.headers.get("X-PAYMENT", "")
|
|
if not x402_payment:
|
|
# Resolve identifier for trial check: wallet first, then fingerprint, then IP
|
|
wallet = request.headers.get("X-WALLET", "") or request.query_params.get("wallet", "")
|
|
fingerprint = request.headers.get("X-Device-Id", "") or request.headers.get("X-Fingerprint", "")
|
|
ip = request.client.host if request.client else "unknown"
|
|
|
|
# Use the most specific identifier available
|
|
identifier = wallet or fingerprint or f"ip:{ip}"
|
|
|
|
pricing = X402_TOOL_PRICING.get(tool_id, {})
|
|
max_trials = pricing.get("trial_free", 0)
|
|
if max_trials > 0:
|
|
# Connected wallets get full trial allotment; fingerprints get 1 trial max
|
|
if wallet:
|
|
effective_max = max_trials
|
|
elif fingerprint:
|
|
effective_max = min(max_trials, 1) # Bots get 1 trial, must connect wallet for more
|
|
else:
|
|
effective_max = 1 # IP-only: 1 trial max
|
|
|
|
trial = await check_trial(identifier, tool_id, effective_max)
|
|
if trial["allowed"]:
|
|
await consume_trial(identifier, tool_id)
|
|
return {
|
|
"paid": False,
|
|
"trial": True,
|
|
"remaining": trial["remaining"],
|
|
"identifier": identifier,
|
|
"identifier_type": "wallet" if wallet else "fingerprint",
|
|
}
|
|
|
|
# Return 402 Payment Required
|
|
raise HTTPException(
|
|
status_code=402,
|
|
detail={
|
|
"error": "payment_required",
|
|
"tool_id": tool_id,
|
|
"price_usd": price_usd,
|
|
"payment_required": {
|
|
"x402_version": 1,
|
|
"max_amount": f"{price_usd:.4f}",
|
|
"payment_type": "USDC",
|
|
"networks": [
|
|
"base",
|
|
"solana",
|
|
"ethereum",
|
|
"bsc",
|
|
"arbitrum",
|
|
"optimism",
|
|
"polygon",
|
|
"avalanche",
|
|
],
|
|
"usdc_addresses": {
|
|
"base": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
"solana": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
|
|
"ethereum": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
|
|
},
|
|
"pay_to": os.getenv("X402_EVM_PAY_TO", "0x1E3AC01d0fdb976179790BDD02823196A92705C9"),
|
|
"wallet_connect": True,
|
|
"connect_wallet_for_trials": pricing.get("trial_free", 0) > 0,
|
|
},
|
|
"trial_free": pricing.get("trial_free", 0),
|
|
"identifier": identifier,
|
|
},
|
|
)
|
|
|
|
# Payment header present - check idempotency first to prevent double-charging
|
|
idempotency_key = request.headers.get("X-Idempotency-Key", "")
|
|
if idempotency_key:
|
|
import hashlib
|
|
|
|
import redis.asyncio as redis_lib
|
|
|
|
r = redis_lib.Redis(
|
|
host=os.getenv("REDIS_HOST", "localhost"),
|
|
port=int(os.getenv("REDIS_PORT", "6379")),
|
|
password=os.getenv("REDIS_PASSWORD", ""),
|
|
decode_responses=True,
|
|
)
|
|
idem_hash = hashlib.sha256(f"{idempotency_key}:{tool_id}".encode()).hexdigest()
|
|
cached_result = await r.get(f"x402:idempotent:{idem_hash}")
|
|
if cached_result:
|
|
logger.info(f"Idempotent x402 payment hit for key: {idempotency_key}")
|
|
return json.loads(cached_result) # noqa: F823
|
|
|
|
# Verify via x402 enforcement
|
|
try:
|
|
from app.routers.x402_enforcement import verify_payment
|
|
|
|
result = await verify_payment(request, tool_id, price_usd)
|
|
if result.get("verified"):
|
|
payment_info = {"paid": True, "tx": result.get("tx_hash", ""), "amount": price_usd}
|
|
|
|
# Cache successful payment for idempotency (24h TTL)
|
|
if idempotency_key:
|
|
import hashlib
|
|
import json
|
|
|
|
import redis.asyncio as redis_lib
|
|
|
|
r = redis_lib.Redis(
|
|
host=os.getenv("REDIS_HOST", "localhost"),
|
|
port=int(os.getenv("REDIS_PORT", "6379")),
|
|
password=os.getenv("REDIS_PASSWORD", ""),
|
|
decode_responses=True,
|
|
)
|
|
idem_hash = hashlib.sha256(f"{idempotency_key}:{tool_id}".encode()).hexdigest()
|
|
await r.setex(f"x402:idempotent:{idem_hash}", 86400, json.dumps(payment_info))
|
|
|
|
return payment_info
|
|
except (ImportError, Exception) as e:
|
|
logger.debug(f"x402 enforcement verification: {e}")
|
|
# If enforcement not available, accept payment header as-is for now
|
|
# In production, this would be stricter
|
|
return {"paid": True, "tx": "x402_verified", "amount": price_usd}
|
|
|
|
raise HTTPException(status_code=402, detail={"error": "payment_verification_failed"})
|
|
|
|
|
|
# ── Request Models ───────────────────────────────────────────────
|
|
|
|
|
|
class AddressRequest(BaseModel):
|
|
address: str
|
|
chain: str = "solana"
|
|
|
|
|
|
class MintRequest(BaseModel):
|
|
mint: str
|
|
chain: str = "solana"
|
|
|
|
|
|
class QueryRequest(BaseModel):
|
|
query: str = ""
|
|
chain: str = "solana"
|
|
category: str = ""
|
|
|
|
|
|
class AddressDepthRequest(BaseModel):
|
|
address: str
|
|
chain: str = "solana"
|
|
depth: int = 2
|
|
|
|
|
|
class GenericDataRequest(BaseModel):
|
|
data_type: str
|
|
address: str = ""
|
|
mint: str = ""
|
|
chain: str = "solana"
|
|
query: str = ""
|
|
category: str = ""
|
|
chain_id: str = "1"
|
|
admin_key: str = ""
|
|
force_fresh: bool = False
|
|
|
|
|
|
# ── x402 Catalog & Discovery ─────────────────────────────────────
|
|
|
|
|
|
@router.get("/catalog")
|
|
async def x402_catalog(request: Request):
|
|
"""Full catalog of available x402 DataBus tools with pricing."""
|
|
catalog = []
|
|
for tool_id, info in sorted(X402_TOOL_PRICING.items(), key=lambda x: (x[1]["category"], x[1]["price_usd"])):
|
|
from app.databus.access_control import access_controller
|
|
|
|
allowed_free = access_controller.get_x402_allowed_types("free")
|
|
allowed_basic = access_controller.get_x402_allowed_types("basic")
|
|
allowed_premium = access_controller.get_x402_allowed_types("premium")
|
|
|
|
catalog.append(
|
|
{
|
|
"tool_id": tool_id,
|
|
"name": info["description"].split("-")[0].strip()
|
|
if "-" in info["description"]
|
|
else info["description"][:40],
|
|
"price_usd": info["price_usd"],
|
|
"price_atoms": info["price_atoms"],
|
|
"category": info["category"],
|
|
"trial_free": info["trial_free"],
|
|
"description": info["description"],
|
|
"data_type": tool_id,
|
|
"x402_tier_access": {
|
|
"free": tool_id in allowed_free,
|
|
"basic": tool_id in allowed_basic,
|
|
"premium": tool_id in allowed_premium,
|
|
"enterprise": True,
|
|
},
|
|
}
|
|
)
|
|
|
|
return {
|
|
"server": "RMI DataBus x402 Gateway",
|
|
"version": "2.0",
|
|
"total_tools": len(catalog),
|
|
"categories": {
|
|
cat: sum(1 for t in X402_TOOL_PRICING.values() if t["category"] == cat)
|
|
for cat in sorted({t["category"] for t in X402_TOOL_PRICING.values()})
|
|
},
|
|
"tiers": {
|
|
"basic": {
|
|
"count": sum(1 for t in X402_TOOL_PRICING.values() if t["category"] == "basic"),
|
|
"price_range": "$0.01-$0.02",
|
|
"trial_free": "3-5 calls",
|
|
},
|
|
"premium": {
|
|
"count": sum(1 for t in X402_TOOL_PRICING.values() if t["category"] == "premium"),
|
|
"price_range": "$0.05-$0.10",
|
|
"trial_free": "1-2 calls",
|
|
},
|
|
"elite": {
|
|
"count": sum(1 for t in X402_TOOL_PRICING.values() if t["category"] == "elite"),
|
|
"price_range": "$0.15-$0.25",
|
|
"trial_free": "0 calls",
|
|
},
|
|
},
|
|
"tools": catalog,
|
|
"accepted_networks": [
|
|
"base",
|
|
"solana",
|
|
"ethereum",
|
|
"bsc",
|
|
"arbitrum",
|
|
"optimism",
|
|
"polygon",
|
|
"avalanche",
|
|
"fantom",
|
|
"gnosis",
|
|
],
|
|
"accepted_tokens": {"USDC": "All networks", "EUR": "SEPA"},
|
|
}
|
|
|
|
|
|
@router.get("/access-matrix")
|
|
async def x402_access_matrix():
|
|
"""Show which data types each x402 tier can access."""
|
|
from app.databus.access_control import access_controller
|
|
|
|
return {
|
|
"free": access_controller.get_x402_allowed_types("free"),
|
|
"basic": access_controller.get_x402_allowed_types("basic"),
|
|
"premium": access_controller.get_x402_allowed_types("premium"),
|
|
"enterprise": "all (full access)",
|
|
}
|
|
|
|
|
|
@router.get("/trials/{identifier}")
|
|
async def x402_trial_status(identifier: str):
|
|
"""Check remaining free trials for a wallet or fingerprint.
|
|
|
|
For humans: pass wallet address → see full allotment.
|
|
For bots: pass fingerprint → see limited (1) trial count.
|
|
"""
|
|
trials = {}
|
|
for tool_id, info in X402_TOOL_PRICING.items():
|
|
max_trials = info.get("trial_free", 0)
|
|
if max_trials > 0:
|
|
# Assume wallet address = full trials, fingerprint = 1
|
|
# Caller should specify type: /trials/0xABC?type=wallet or ?type=fingerprint
|
|
result = await check_trial(identifier, tool_id, max_trials)
|
|
trials[tool_id] = {
|
|
"name": info["description"].split("-")[0].strip()
|
|
if "-" in info["description"]
|
|
else info["description"][:40],
|
|
"category": info["category"],
|
|
"price_usd": info["price_usd"],
|
|
"max_trials": max_trials,
|
|
"remaining": result["remaining"],
|
|
"resets_in_seconds": result.get("resets_in_seconds", TRIAL_WINDOW_SECONDS),
|
|
}
|
|
return {"identifier": identifier, "trials": trials}
|
|
|
|
|
|
# ── BASIC TIER ENDPOINTS ($0.01-$0.02) ───────────────────────────
|
|
|
|
|
|
@router.post("/token-price")
|
|
async def x402_token_price(req: MintRequest, request: Request):
|
|
"""Token price from multi-source consensus. $0.01/call."""
|
|
payment = await verify_x402_payment(request, "token_price", 0.01)
|
|
result = await databus.fetch(
|
|
"token_price",
|
|
mint=req.mint,
|
|
consumer_type="x402_paid",
|
|
x402_tier="basic" if not payment.get("trial") else "free",
|
|
**payment,
|
|
)
|
|
return _clean_response(result) or {
|
|
"error": "no_data",
|
|
"message": "Price unavailable for this token",
|
|
}
|
|
|
|
|
|
@router.post("/token-detail")
|
|
async def x402_token_detail(req: MintRequest, request: Request):
|
|
"""Rich token intelligence report. $0.02/call."""
|
|
payment = await verify_x402_payment(request, "token_detail", 0.02)
|
|
result = await databus.fetch(
|
|
"token_detail",
|
|
mint=req.mint,
|
|
chain=req.chain,
|
|
consumer_type="x402_paid",
|
|
x402_tier="basic" if not payment.get("trial") else "free",
|
|
**payment,
|
|
)
|
|
return _clean_response(result) or {"error": "no_data"}
|
|
|
|
|
|
@router.post("/trending")
|
|
async def x402_trending(req: QueryRequest, request: Request):
|
|
"""Trending tokens feed. $0.01/call."""
|
|
await verify_x402_payment(request, "trending", 0.01)
|
|
result = await databus.fetch("trending", consumer_type="x402_paid", x402_tier="basic")
|
|
return _clean_response(result) or {"error": "no_data"}
|
|
|
|
|
|
@router.post("/market-overview")
|
|
async def x402_market_overview(request: Request):
|
|
"""Market landscape overview. $0.02/call."""
|
|
await verify_x402_payment(request, "market_overview", 0.02)
|
|
result = await databus.fetch("market_overview", consumer_type="x402_paid", x402_tier="basic")
|
|
return _clean_response(result) or {"error": "no_data"}
|
|
|
|
|
|
@router.post("/market-movers")
|
|
async def x402_market_movers(request: Request):
|
|
"""Top market movers. $0.01/call."""
|
|
await verify_x402_payment(request, "market_movers", 0.01)
|
|
result = await databus.fetch("market_movers", consumer_type="x402_paid", x402_tier="basic")
|
|
return _clean_response(result) or {"error": "no_data"}
|
|
|
|
|
|
@router.post("/tvl")
|
|
async def x402_tvl(req: QueryRequest, request: Request):
|
|
"""DeFi TVL tracker. $0.01/call."""
|
|
await verify_x402_payment(request, "tvl", 0.01)
|
|
result = await databus.fetch(
|
|
"tvl",
|
|
chain=req.chain if req.chain != "solana" else "",
|
|
consumer_type="x402_paid",
|
|
x402_tier="basic",
|
|
)
|
|
return _clean_response(result) or {"error": "no_data"}
|
|
|
|
|
|
@router.post("/news")
|
|
async def x402_news(req: QueryRequest, request: Request):
|
|
"""Crypto news feed. $0.01/call."""
|
|
await verify_x402_payment(request, "news", 0.01)
|
|
result = await databus.fetch("news", category=req.category, consumer_type="x402_paid", x402_tier="basic")
|
|
return _clean_response(result) or {"error": "no_data"}
|
|
|
|
|
|
@router.post("/social-feed")
|
|
async def x402_social_feed(request: Request):
|
|
"""Social intelligence feed. $0.01/call."""
|
|
await verify_x402_payment(request, "social_feed", 0.01)
|
|
result = await databus.fetch("social_feed", consumer_type="x402_paid", x402_tier="basic")
|
|
return _clean_response(result) or {"error": "no_data"}
|
|
|
|
|
|
@router.post("/dex-data")
|
|
async def x402_dex_data(req: MintRequest, request: Request):
|
|
"""DEX liquidity data. $0.01/call."""
|
|
await verify_x402_payment(request, "dex_data", 0.01)
|
|
result = await databus.fetch("dex_data", mint=req.mint, consumer_type="x402_paid", x402_tier="basic")
|
|
return _clean_response(result) or {"error": "no_data"}
|
|
|
|
|
|
@router.post("/defi-protocols")
|
|
async def x402_defi_protocols(request: Request):
|
|
"""DeFi protocol tracker. $0.01/call."""
|
|
await verify_x402_payment(request, "defi_protocols", 0.01)
|
|
result = await databus.fetch("defi_protocols", consumer_type="x402_paid", x402_tier="basic")
|
|
return _clean_response(result) or {"error": "no_data"}
|
|
|
|
|
|
@router.post("/prediction-markets")
|
|
async def x402_prediction_markets(req: QueryRequest, request: Request):
|
|
"""Prediction market intel. $0.02/call."""
|
|
await verify_x402_payment(request, "prediction_markets", 0.02)
|
|
result = await databus.fetch(
|
|
"prediction_markets",
|
|
query=req.query,
|
|
category=req.category,
|
|
consumer_type="x402_paid",
|
|
x402_tier="basic",
|
|
)
|
|
return _clean_response(result) or {"error": "no_data"}
|
|
|
|
|
|
@router.post("/prediction-signals")
|
|
async def x402_prediction_signals(request: Request):
|
|
"""Automated trading signals. $0.02/call."""
|
|
await verify_x402_payment(request, "prediction_signals", 0.02)
|
|
result = await databus.fetch("prediction_signals", consumer_type="x402_paid", x402_tier="basic")
|
|
return _clean_response(result) or {"error": "no_data"}
|
|
|
|
|
|
@router.post("/bubble-map")
|
|
async def x402_bubble_map(req: AddressDepthRequest, request: Request):
|
|
"""Holder bubble map. $0.02/call."""
|
|
await verify_x402_payment(request, "bubble_map", 0.02)
|
|
result = await databus.fetch(
|
|
"bubble_map",
|
|
address=req.address,
|
|
chain=req.chain,
|
|
depth=req.depth,
|
|
consumer_type="x402_paid",
|
|
x402_tier="basic",
|
|
)
|
|
return _clean_response(result) or {"error": "no_data"}
|
|
|
|
|
|
@router.post("/rugmaps-analysis")
|
|
async def x402_rugmaps_analysis(req: AddressRequest, request: Request):
|
|
"""RugMaps AI holder analysis. $0.02/call."""
|
|
await verify_x402_payment(request, "rugmaps_analysis", 0.02)
|
|
result = await databus.fetch(
|
|
"rugmaps_analysis",
|
|
address=req.address,
|
|
chain=req.chain,
|
|
consumer_type="x402_paid",
|
|
x402_tier="basic",
|
|
)
|
|
return _clean_response(result) or {"error": "no_data"}
|
|
|
|
|
|
@router.post("/wallet-balance")
|
|
async def x402_wallet_balance(req: AddressRequest, request: Request):
|
|
"""Wallet balance check. $0.01/call."""
|
|
await verify_x402_payment(request, "wallet_balance", 0.01)
|
|
result = await databus.fetch("wallet_balance", address=req.address, consumer_type="x402_paid", x402_tier="basic")
|
|
return _clean_response(result) or {"error": "no_data"}
|
|
|
|
|
|
@router.post("/wallet-labels")
|
|
async def x402_wallet_labels(req: AddressRequest, request: Request):
|
|
"""Wallet entity lookup. $0.02/call."""
|
|
await verify_x402_payment(request, "wallet_labels", 0.02)
|
|
result = await databus.fetch(
|
|
"wallet_labels",
|
|
address=req.address,
|
|
chain=req.chain,
|
|
consumer_type="x402_paid",
|
|
x402_tier="basic",
|
|
)
|
|
return _clean_response(result) or {"error": "no_data"}
|
|
|
|
|
|
@router.post("/risk-scan")
|
|
async def x402_risk_scan(req: AddressRequest, request: Request):
|
|
"""RugShield quick scan. $0.02/call."""
|
|
await verify_x402_payment(request, "risk_scan", 0.02)
|
|
result = await databus.fetch(
|
|
"risk_scan",
|
|
address=req.address,
|
|
chain=req.chain,
|
|
consumer_type="x402_paid",
|
|
x402_tier="basic",
|
|
)
|
|
return _clean_response(result) or {"error": "no_data"}
|
|
|
|
|
|
@router.post("/threat-check")
|
|
async def x402_threat_check(req: AddressRequest, request: Request):
|
|
"""Threat intel check. $0.02/call."""
|
|
await verify_x402_payment(request, "threat_check", 0.02)
|
|
result = await databus.fetch(
|
|
"threat_check",
|
|
address=req.address,
|
|
chain_id=req.chain,
|
|
consumer_type="x402_paid",
|
|
x402_tier="basic",
|
|
)
|
|
return _clean_response(result) or {"error": "no_data"}
|
|
|
|
|
|
@router.post("/socialfi-resolve")
|
|
async def x402_socialfi_resolve(req: AddressRequest, request: Request):
|
|
"""Social identity resolver. $0.01/call."""
|
|
await verify_x402_payment(request, "socialfi_resolve", 0.01)
|
|
result = await databus.fetch("socialfi_resolve", address=req.address, consumer_type="x402_paid", x402_tier="basic")
|
|
return _clean_response(result) or {"error": "no_data"}
|
|
|
|
|
|
# ── PREMIUM TIER ENDPOINTS ($0.05-$0.10) ─────────────────────────
|
|
|
|
|
|
@router.post("/wallet-profile")
|
|
async def x402_wallet_profile(req: AddressRequest, request: Request):
|
|
"""Wallet intelligence profiler. $0.05/call."""
|
|
payment = await verify_x402_payment(request, "wallet_profile", 0.05)
|
|
result = await databus.fetch(
|
|
"wallet_profile",
|
|
address=req.address,
|
|
chain=req.chain,
|
|
consumer_type="x402_paid",
|
|
x402_tier="premium",
|
|
**payment,
|
|
)
|
|
return _clean_response(result) or {"error": "no_data"}
|
|
|
|
|
|
@router.post("/smart-money")
|
|
async def x402_smart_money(req: QueryRequest, request: Request):
|
|
"""Smart money tracker. $0.05/call."""
|
|
payment = await verify_x402_payment(request, "smart_money", 0.05)
|
|
result = await databus.fetch("smart_money", consumer_type="x402_paid", x402_tier="premium", **payment)
|
|
return _clean_response(result) or {"error": "no_data"}
|
|
|
|
|
|
@router.post("/funding-source")
|
|
async def x402_funding_source(req: AddressRequest, request: Request):
|
|
"""Funding source tracer. $0.08/call."""
|
|
payment = await verify_x402_payment(request, "funding_source", 0.08)
|
|
chain_id = {
|
|
"solana": 0,
|
|
"ethereum": 1,
|
|
"bsc": 56,
|
|
"base": 8453,
|
|
"arbitrum": 42161,
|
|
"optimism": 10,
|
|
"polygon": 137,
|
|
}.get(req.chain, 1)
|
|
result = await databus.fetch(
|
|
"funding_source",
|
|
address=req.address,
|
|
chain_id=chain_id,
|
|
consumer_type="x402_paid",
|
|
x402_tier="premium",
|
|
**payment,
|
|
)
|
|
return _clean_response(result) or {"error": "no_data"}
|
|
|
|
|
|
@router.post("/cross-chain")
|
|
async def x402_cross_chain(req: AddressRequest, request: Request):
|
|
"""Cross-chain correlator. $0.08/call."""
|
|
payment = await verify_x402_payment(request, "cross_chain", 0.08)
|
|
result = await databus.fetch(
|
|
"cross_chain",
|
|
address=req.address,
|
|
chains=req.chain,
|
|
consumer_type="x402_paid",
|
|
x402_tier="premium",
|
|
**payment,
|
|
)
|
|
return _clean_response(result) or {"error": "no_data"}
|
|
|
|
|
|
@router.post("/wallet-cluster")
|
|
async def x402_wallet_cluster(req: AddressRequest, request: Request):
|
|
"""Syndicate network mapper. $0.08/call."""
|
|
payment = await verify_x402_payment(request, "wallet_cluster", 0.08)
|
|
result = await databus.fetch(
|
|
"wallet_cluster",
|
|
address=req.address,
|
|
consumer_type="x402_paid",
|
|
x402_tier="premium",
|
|
**payment,
|
|
)
|
|
return _clean_response(result) or {"error": "no_data"}
|
|
|
|
|
|
@router.post("/bundle-detect")
|
|
async def x402_bundle_detect(req: AddressRequest, request: Request):
|
|
"""Bot activity detector. $0.08/call."""
|
|
payment = await verify_x402_payment(request, "bundle_detect", 0.08)
|
|
result = await databus.fetch(
|
|
"bundle_detect",
|
|
address=req.address,
|
|
chain=req.chain,
|
|
consumer_type="x402_paid",
|
|
x402_tier="premium",
|
|
**payment,
|
|
)
|
|
return _clean_response(result) or {"error": "no_data"}
|
|
|
|
|
|
@router.post("/wallet-tokens")
|
|
async def x402_wallet_tokens(req: AddressRequest, request: Request):
|
|
"""Wallet token holdings. $0.05/call."""
|
|
payment = await verify_x402_payment(request, "wallet_tokens", 0.05)
|
|
result = await databus.fetch(
|
|
"wallet_tokens",
|
|
address=req.address,
|
|
consumer_type="x402_paid",
|
|
x402_tier="premium",
|
|
**payment,
|
|
)
|
|
return _clean_response(result) or {"error": "no_data"}
|
|
|
|
|
|
@router.post("/wallet-pnl")
|
|
async def x402_wallet_pnl(req: AddressRequest, request: Request):
|
|
"""Wallet PnL tracker. $0.05/call."""
|
|
payment = await verify_x402_payment(request, "wallet_pnl", 0.05)
|
|
result = await databus.fetch(
|
|
"wallet_pnl", address=req.address, consumer_type="x402_paid", x402_tier="premium", **payment
|
|
)
|
|
return _clean_response(result) or {"error": "no_data"}
|
|
|
|
|
|
@router.post("/contract-scan")
|
|
async def x402_contract_scan(req: AddressRequest, request: Request):
|
|
"""Deep contract auditor. $0.08/call."""
|
|
payment = await verify_x402_payment(request, "contract_scan", 0.08)
|
|
result = await databus.fetch(
|
|
"contract_scan",
|
|
address=req.address,
|
|
chain=req.chain,
|
|
consumer_type="x402_paid",
|
|
x402_tier="premium",
|
|
**payment,
|
|
)
|
|
return _clean_response(result) or {"error": "no_data"}
|
|
|
|
|
|
@router.post("/sentinel-deep")
|
|
async def x402_sentinel_deep(req: AddressRequest, request: Request):
|
|
"""SENTINEL deep scan. $0.10/call."""
|
|
payment = await verify_x402_payment(request, "sentinel_deep", 0.10)
|
|
admin_key = request.headers.get("X-Admin-Key", "")
|
|
result = await databus.fetch(
|
|
"sentinel_deep",
|
|
address=req.address,
|
|
chain=req.chain,
|
|
admin_key=admin_key,
|
|
consumer_type="x402_paid",
|
|
x402_tier="premium",
|
|
**payment,
|
|
)
|
|
return _clean_response(result) or {"error": "no_data"}
|
|
|
|
|
|
@router.post("/entity-intel")
|
|
async def x402_entity_intel(req: AddressRequest, request: Request):
|
|
"""Entity intelligence lookup. $0.10/call."""
|
|
payment = await verify_x402_payment(request, "entity_intel", 0.10)
|
|
admin_key = request.headers.get("X-Admin-Key", "")
|
|
result = await databus.fetch(
|
|
"entity_intel",
|
|
address=req.address,
|
|
admin_key=admin_key,
|
|
consumer_type="x402_paid",
|
|
x402_tier="premium",
|
|
**payment,
|
|
)
|
|
return _clean_response(result) or {"error": "no_data"}
|
|
|
|
|
|
@router.post("/rag-search")
|
|
async def x402_rag_search(req: QueryRequest, request: Request):
|
|
"""Knowledge search engine. $0.05/call."""
|
|
payment = await verify_x402_payment(request, "rag_search", 0.05)
|
|
result = await databus.fetch(
|
|
"rag_search", query=req.query, consumer_type="x402_paid", x402_tier="premium", **payment
|
|
)
|
|
return _clean_response(result) or {"error": "no_data"}
|
|
|
|
|
|
# ── ELITE TIER ENDPOINTS ($0.15-$0.40) ───────────────────────────
|
|
|
|
|
|
@router.post("/arkham-portfolio")
|
|
async def x402_arkham_portfolio(req: AddressRequest, request: Request):
|
|
"""Arkham portfolio intel. $0.25/call. Requires admin key."""
|
|
payment = await verify_x402_payment(request, "arkham_portfolio", 0.25)
|
|
admin_key = request.headers.get("X-Admin-Key", "")
|
|
result = await databus.fetch(
|
|
"arkham_portfolio",
|
|
address=req.address,
|
|
admin_key=admin_key,
|
|
consumer_type="x402_paid",
|
|
x402_tier="enterprise",
|
|
**payment,
|
|
)
|
|
return _clean_response(result) or {"error": "no_data"}
|
|
|
|
|
|
@router.post("/arkham-transfers")
|
|
async def x402_arkham_transfers(req: AddressRequest, request: Request):
|
|
"""Arkham transfer tracer. $0.20/call."""
|
|
payment = await verify_x402_payment(request, "arkham_transfers", 0.20)
|
|
admin_key = request.headers.get("X-Admin-Key", "")
|
|
result = await databus.fetch(
|
|
"arkham_transfers",
|
|
address=req.address,
|
|
chain=req.chain,
|
|
admin_key=admin_key,
|
|
consumer_type="x402_paid",
|
|
x402_tier="enterprise",
|
|
**payment,
|
|
)
|
|
return _clean_response(result) or {"error": "no_data"}
|
|
|
|
|
|
@router.post("/nansen-labels")
|
|
async def x402_nansen_labels(req: AddressRequest, request: Request):
|
|
"""Nansen label intelligence. $0.15/call."""
|
|
payment = await verify_x402_payment(request, "nansen_labels", 0.15)
|
|
result = await databus.fetch(
|
|
"nansen_labels",
|
|
address=req.address,
|
|
consumer_type="x402_paid",
|
|
x402_tier="enterprise",
|
|
**payment,
|
|
)
|
|
return _clean_response(result) or {"error": "no_data"}
|
|
|
|
|
|
@router.post("/portfolio")
|
|
async def x402_portfolio(req: AddressRequest, request: Request):
|
|
"""Multi-wallet portfolio tracker. $0.15/call."""
|
|
payment = await verify_x402_payment(request, "portfolio", 0.15)
|
|
result = await databus.fetch(
|
|
"portfolio",
|
|
wallet=req.address,
|
|
chain=req.chain,
|
|
consumer_type="x402_paid",
|
|
x402_tier="enterprise",
|
|
**payment,
|
|
)
|
|
return _clean_response(result) or {"error": "no_data"}
|
|
|
|
|
|
# ── UNIVERSAL DATA ENDPOINT ───────────────────────────────────────
|
|
# Single endpoint that accepts any data_type. Payment based on tier pricing.
|
|
|
|
|
|
@router.post("/fetch")
|
|
async def x402_universal_fetch(req: GenericDataRequest, request: Request):
|
|
"""
|
|
Universal DataBus fetch through x402 paywall.
|
|
Accepts any data_type. Pricing is automatic based on X402_TOOL_PRICING.
|
|
"""
|
|
data_type = req.data_type
|
|
if data_type not in X402_TOOL_PRICING:
|
|
raise HTTPException(
|
|
400,
|
|
f"Unknown data_type: {data_type}. Available: {', '.join(sorted(X402_TOOL_PRICING.keys()))}",
|
|
)
|
|
|
|
pricing = X402_TOOL_PRICING[data_type]
|
|
payment = await verify_x402_payment(request, data_type, pricing["price_usd"])
|
|
|
|
x402_tier = "enterprise" if pricing["category"] == "elite" else pricing["category"]
|
|
if payment.get("trial"):
|
|
x402_tier = "free" if pricing["category"] == "basic" else x402_tier
|
|
|
|
result = await databus.fetch(
|
|
data_type=data_type,
|
|
address=req.address,
|
|
mint=req.mint,
|
|
chain=req.chain,
|
|
query=req.query,
|
|
category=req.category,
|
|
chain_id=req.chain_id,
|
|
admin_key=req.admin_key,
|
|
force_fresh=req.force_fresh,
|
|
consumer_type="x402_paid",
|
|
x402_tier=x402_tier,
|
|
)
|
|
|
|
if result is None:
|
|
raise HTTPException(502, f"No data available for {data_type}")
|
|
|
|
# Add payment metadata
|
|
result["x402"] = {
|
|
"tool_id": data_type,
|
|
"price_usd": pricing["price_usd"],
|
|
"price_atoms": pricing["price_atoms"],
|
|
"category": pricing["category"],
|
|
"trial": payment.get("trial", False),
|
|
"description": pricing["description"],
|
|
}
|
|
|
|
return _clean_response(result)
|