RMI Platform commited on
Commit
b5d42ce
Β·
0 Parent(s):

Nightly Builder v3: Supply Manipulation Detector (bundler_detect)

Browse files
backend/app/bundler_detect.py ADDED
@@ -0,0 +1,865 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Supply Manipulation / Bundler Detector
3
+ =======================================
4
+ Detects bundled token launches where insiders control disproportionate
5
+ supply through sniper-controlled wallet distributions.
6
+
7
+ Signals detected:
8
+ - Bundled initial buys (multiple wallets funded from same source,
9
+ buying within same block/seconds)
10
+ - Supply concentration across linked wallets (top holders controlled
11
+ by same entity)
12
+ - Fund flow analysis (same funding source β†’ multiple snipers)
13
+ - TIMEO (This Is My Eyes Only) token distribution patterns
14
+ - Sniper cluster detection (wallets that only buy this token)
15
+ - Launch timing anomalies (coordinated buys in first blocks)
16
+ - Holder overlap with known bundler addresses
17
+ - Supply distribution entropy analysis
18
+
19
+ Tier : Premium ($0.08)
20
+ Price : 80000 atoms
21
+ Endpoint: POST /api/v1/x402-tools/bundler_detect
22
+ """
23
+
24
+ import asyncio
25
+ import logging
26
+ import math
27
+ import os
28
+ import re
29
+ import time
30
+ from dataclasses import dataclass, field
31
+ from enum import Enum
32
+ from typing import Any
33
+
34
+ import httpx
35
+
36
+ logger = logging.getLogger(__name__)
37
+
38
+ # ── Constants ──────────────────────────────────────────────────────
39
+
40
+ SOLANA_ADDR_RE = re.compile(r"^[1-9A-HJ-NP-Za-km-z]{32,44}$")
41
+ EVM_ADDR_RE = re.compile(r"^0x[a-fA-F0-9]{40}$")
42
+
43
+ EVM_CHAINS = frozenset({
44
+ "ethereum", "bsc", "polygon", "arbitrum", "optimism",
45
+ "avalanche", "base", "fantom", "linea", "zksync", "scroll", "mantle",
46
+ })
47
+
48
+ SUPPORTED_CHAINS = list(EVM_CHAINS) + ["solana"]
49
+
50
+ # DEX API endpoints
51
+ DEXSCREENER_API = "https://api.dexscreener.com/latest/dex"
52
+
53
+ # Free Solana RPC for account info
54
+ SOLANA_RPC = "https://api.mainnet-beta.solana.com"
55
+
56
+ # Birdeye public API (no key needed for basic queries)
57
+ BIRDEYE_PUBLIC = "https://public-api.birdeye.so"
58
+
59
+ # Known bundler wallet addresses (publicly flagged on-chain)
60
+ KNOWN_BUNDLER_SEEDS: set[str] = set()
61
+
62
+ # ── Risk Levels ──────────────────────────────────────────────────
63
+
64
+ class BundlerRisk(Enum):
65
+ CRITICAL = "critical"
66
+ HIGH = "high"
67
+ MEDIUM = "medium"
68
+ LOW = "low"
69
+ NONE = "none"
70
+
71
+ # ── Data Models ──────────────────────────────────────────────────
72
+
73
+ @dataclass
74
+ class BundledBuy:
75
+ """A single suspicious buy event identified as potentially bundled."""
76
+ wallet: str
77
+ amount_usd: float
78
+ buy_block: int
79
+ buy_timestamp: float
80
+ tx_hash: str = ""
81
+ funding_source: str = ""
82
+ is_sniper: bool = False
83
+
84
+ def to_dict(self) -> dict[str, Any]:
85
+ return {
86
+ "wallet": self.wallet,
87
+ "amount_usd": round(self.amount_usd, 2),
88
+ "buy_block": self.buy_block,
89
+ "buy_timestamp": self.buy_timestamp,
90
+ "tx_hash": self.tx_hash,
91
+ "funding_source": self.funding_source,
92
+ "is_sniper": self.is_sniper,
93
+ }
94
+
95
+
96
+ @dataclass
97
+ class HolderCluster:
98
+ """A cluster of wallets suspected to be controlled by one entity."""
99
+ wallets: list[str]
100
+ total_supply_pct: float
101
+ funding_overlap_score: float # 0-1, how much funding sources overlap
102
+ buy_time_similarity: float # 0-1, how clustered buys were in time
103
+ common_funding_source: str = ""
104
+
105
+ def to_dict(self) -> dict[str, Any]:
106
+ return {
107
+ "wallet_count": len(self.wallets),
108
+ "wallets": self.wallets[:20], # cap at 20 in output
109
+ "total_supply_pct": round(self.total_supply_pct, 2),
110
+ "funding_overlap_score": round(self.funding_overlap_score, 3),
111
+ "buy_time_similarity": round(self.buy_time_similarity, 3),
112
+ "common_funding_source": self.common_funding_source,
113
+ }
114
+
115
+
116
+ @dataclass
117
+ class BundlerReport:
118
+ """Full supply manipulation analysis result."""
119
+ token_address: str
120
+ chain: str
121
+ name: str = ""
122
+ symbol: str = ""
123
+
124
+ # Core scores (0-100)
125
+ bundler_score: float = 0.0
126
+ supply_concentration_score: float = 0.0
127
+ sniper_cluster_score: float = 0.0
128
+ launch_timing_anomaly_score: float = 0.0
129
+ fund_flow_risk_score: float = 0.0
130
+
131
+ # Findings
132
+ suspected_bundled_buys: list[BundledBuy] = field(default_factory=list)
133
+ holder_clusters: list[HolderCluster] = field(default_factory=list)
134
+ top_10_holder_concentration: float = 0.0
135
+ dev_hold_pct: float = 0.0
136
+ unique_buyers_first_block: int = 0
137
+ total_buys_first_blocks: int = 0
138
+ buys_from_same_funding: int = 0
139
+ estimated_unique_entities: int = 0
140
+
141
+ risk_label: str = "none"
142
+ errors: list[str] = field(default_factory=list)
143
+ raw: dict[str, Any] = field(default_factory=dict)
144
+
145
+ def to_dict(self) -> dict[str, Any]:
146
+ return {
147
+ "token_address": self.token_address,
148
+ "chain": self.chain,
149
+ "name": self.name,
150
+ "symbol": self.symbol,
151
+ "bundler_score": round(self.bundler_score, 1),
152
+ "risk_label": self.risk_label,
153
+ "signals": {
154
+ "supply_concentration": round(self.supply_concentration_score, 1),
155
+ "sniper_cluster": round(self.sniper_cluster_score, 1),
156
+ "launch_timing_anomaly": round(self.launch_timing_anomaly_score, 1),
157
+ "fund_flow_risk": round(self.fund_flow_risk_score, 1),
158
+ },
159
+ "suspected_bundled_buys": [b.to_dict() for b in self.suspected_bundled_buys[:50]],
160
+ "holder_clusters": [c.to_dict() for c in self.holder_clusters[:10]],
161
+ "top_10_holder_concentration": round(self.top_10_holder_concentration, 2),
162
+ "dev_hold_pct": round(self.dev_hold_pct, 2),
163
+ "unique_buyers_first_block": self.unique_buyers_first_block,
164
+ "total_buys_first_blocks": self.total_buys_first_blocks,
165
+ "buys_from_same_funding": self.buys_from_same_funding,
166
+ "estimated_unique_entities": self.estimated_unique_entities,
167
+ }
168
+
169
+ def summary(self) -> str:
170
+ flags = []
171
+ if self.top_10_holder_concentration > 80:
172
+ flags.append(f"top10hld:{self.top_10_holder_concentration:.0f}%")
173
+ if self.buys_from_same_funding > 3:
174
+ flags.append(f"shared_fund:{self.buys_from_same_funding}x")
175
+ if self.suspected_bundled_buys:
176
+ flags.append(f"bundled:{len(self.suspected_bundled_buys)}buys")
177
+ if self.holder_clusters:
178
+ total_cluster_pct = sum(c.total_supply_pct for c in self.holder_clusters)
179
+ flags.append(f"clustered:{total_cluster_pct:.0f}%")
180
+ flag_str = f" [{', '.join(flags)}]" if flags else ""
181
+ return (
182
+ f"[{self.risk_label.upper()}] {self.token_address[:14]}... "
183
+ f"({self.name}/{self.symbol}) β€” "
184
+ f"Bundler score: {self.bundler_score:.0f}/100 | "
185
+ f"{len(self.holder_clusters)} clusters | "
186
+ f"{self.estimated_unique_entities} entities estimated"
187
+ f"{flag_str}"
188
+ )
189
+
190
+
191
+ # ── Scoring Helpers ──────────────────────────────────────────────
192
+
193
+ def _gini_coefficient(values: list[float]) -> float:
194
+ """Compute Gini coefficient for supply distribution (0=equal, 1=max concentration)."""
195
+ if not values:
196
+ return 0.0
197
+ sorted_vals = sorted(values)
198
+ n = len(sorted_vals)
199
+ cumulative = 0.0
200
+ for i, v in enumerate(sorted_vals):
201
+ cumulative += (i + 1) * v
202
+ gini = (2 * cumulative) / (n * sum(sorted_vals)) - (n + 1) / n
203
+ return max(0.0, min(gini, 1.0))
204
+
205
+
206
+ def _entropy(values: list[float]) -> float:
207
+ """Shannon entropy of a distribution (lower = more concentrated).
208
+ Returns normalized [0, 1] where 1 = perfectly uniform, 0 = fully concentrated.
209
+ """
210
+ total = sum(values)
211
+ if total <= 0:
212
+ return 0.0
213
+ n = len(values)
214
+ if n <= 1:
215
+ return 1.0 # Single bin = trivially uniform
216
+ raw = 0.0
217
+ for v in values:
218
+ p = v / total
219
+ if p > 0:
220
+ raw -= p * math.log2(p)
221
+ max_entropy = math.log2(n)
222
+ return raw / max_entropy if max_entropy > 0 else 0.0
223
+
224
+
225
+ def _time_cluster_similarity(timestamps: list[float]) -> float:
226
+ """Score how tightly clustered timestamps are (0=spread, 1=all at once)."""
227
+ if len(timestamps) < 2:
228
+ return 0.0
229
+ min_ts = min(timestamps)
230
+ max_ts = max(timestamps)
231
+ span = max_ts - min_ts
232
+ if span == 0:
233
+ return 1.0
234
+ # If all buys happened within 60 seconds, high similarity
235
+ if span <= 60:
236
+ return 1.0 - (span / 60) * 0.5 # 0.5-1.0
237
+ # If within 5 minutes, medium
238
+ if span <= 300:
239
+ return 0.5 - (span - 60) / (300 - 60) * 0.3 # 0.2-0.5
240
+ return max(0.0, 0.2 - (span - 300) / 3600)
241
+
242
+
243
+ def _funding_overlap(funding_sources: list[str]) -> float:
244
+ """Score how many wallets share the same funding source (0-1)."""
245
+ if not funding_sources:
246
+ return 0.0
247
+ total = len(funding_sources)
248
+ if total < 2:
249
+ return 0.0
250
+ # Count how many share a source with at least one other
251
+ from collections import Counter
252
+ source_counts = Counter(funding_sources)
253
+ shared = sum(c for c in source_counts.values() if c > 1)
254
+ return shared / total
255
+
256
+
257
+ def _label_risk(score: float) -> str:
258
+ if score >= 75:
259
+ return "critical"
260
+ if score >= 50:
261
+ return "high"
262
+ if score >= 25:
263
+ return "medium"
264
+ if score > 0:
265
+ return "low"
266
+ return "none"
267
+
268
+
269
+ # ── Core Detector ────────────────────────────────────────────────
270
+
271
+ class BundlerDetector:
272
+ """Main detector for bundled/supply-manipulated token launches."""
273
+
274
+ def __init__(self, http_timeout: float = 15.0):
275
+ self.http = httpx.AsyncClient(timeout=http_timeout)
276
+ self._birdeye_api_key = os.environ.get("BIRDEYE_API_KEY", "")
277
+
278
+ async def close(self):
279
+ await self.http.aclose()
280
+
281
+ # ── Public API ──────────────────────────────────────────────
282
+
283
+ async def scan(self, address: str, chain: str) -> BundlerReport:
284
+ """Full supply manipulation analysis for a token."""
285
+ if not self._validate_address(address, chain):
286
+ return BundlerReport(
287
+ token_address=address,
288
+ chain=chain,
289
+ errors=[f"Invalid address format for chain: {chain}"],
290
+ risk_label="error",
291
+ )
292
+
293
+ chain = chain.lower()
294
+ if chain not in SUPPORTED_CHAINS:
295
+ return BundlerReport(
296
+ token_address=address,
297
+ chain=chain,
298
+ errors=[f"Unsupported chain: {chain}"],
299
+ risk_label="error",
300
+ )
301
+
302
+ report = BundlerReport(token_address=address, chain=chain)
303
+
304
+ try:
305
+ # 1. Fetch token metadata and pair info
306
+ metadata = await self._fetch_metadata(address, chain)
307
+ report.name = metadata.get("name", "Unknown")
308
+ report.symbol = metadata.get("symbol", "UNKNOWN")
309
+ report.raw["metadata"] = metadata
310
+
311
+ # 2. Fetch holder data
312
+ holders = await self._fetch_holders(address, chain)
313
+ report.raw["holders_raw"] = holders
314
+
315
+ if not holders:
316
+ report.errors.append("No holder data available")
317
+ report.risk_label = "error"
318
+ return report
319
+
320
+ # 3. Compute supply concentration
321
+ top10_pct = self._compute_top_holder_pct(holders, 10)
322
+ report.top_10_holder_concentration = top10_pct
323
+ report.dev_hold_pct = self._extract_dev_hold_pct(holders, metadata)
324
+
325
+ # 4. Fetch and analyze buys for bundling patterns
326
+ buys = await self._fetch_buys(address, chain)
327
+ report.raw["buys_raw"] = buys
328
+
329
+ # 5. Detect bundled buys (same funding source, same block)
330
+ bundled_buys, buys_from_same_funding = self._detect_bundled_buys(buys)
331
+ report.suspected_bundled_buys = bundled_buys
332
+ report.buys_from_same_funding = buys_from_same_funding
333
+
334
+ # 6. Analyze launch timing
335
+ timing_info = self._analyze_launch_timing(buys)
336
+ report.unique_buyers_first_block = timing_info["unique_buyers_first_block"]
337
+ report.total_buys_first_blocks = timing_info["total_buys_first_blocks"]
338
+
339
+ # 7. Cluster wallets by funding source and timing
340
+ clusters = self._cluster_wallets(buys, holders)
341
+ report.holder_clusters = clusters
342
+
343
+ # 8. Estimate unique entities
344
+ report.estimated_unique_entities = self._estimate_entities(
345
+ holders, clusters, len(bundled_buys)
346
+ )
347
+
348
+ # 9. Compute all scores
349
+ report.supply_concentration_score = self._score_supply_concentration(holders, top10_pct)
350
+ report.sniper_cluster_score = self._score_sniper_clusters(clusters, bundled_buys)
351
+ report.launch_timing_anomaly_score = self._score_launch_timing(
352
+ timing_info, buys, holders
353
+ )
354
+ report.fund_flow_risk_score = self._score_fund_flow(
355
+ bundled_buys, buys_from_same_funding, clusters
356
+ )
357
+
358
+ # 10. Composite bundler score
359
+ report.bundler_score = self._compute_bundler_score(report)
360
+ report.risk_label = _label_risk(report.bundler_score)
361
+
362
+ except Exception as e:
363
+ logger.error(f"Bundler scan error for {address}: {e}")
364
+ report.errors.append(str(e))
365
+ report.risk_label = "error"
366
+
367
+ return report
368
+
369
+ async def quick_check(self, address: str, chain: str) -> dict[str, Any]:
370
+ """Quick supply concentration check β€” holder data only."""
371
+ if not self._validate_address(address, chain):
372
+ return {"error": f"Invalid address for chain {chain}"}
373
+
374
+ chain = chain.lower()
375
+ metadata = await self._fetch_metadata(address, chain)
376
+ holders = await self._fetch_holders(address, chain)
377
+
378
+ if not holders:
379
+ return {
380
+ "address": address,
381
+ "chain": chain,
382
+ "name": metadata.get("name", ""),
383
+ "symbol": metadata.get("symbol", ""),
384
+ "error": "No holder data available",
385
+ }
386
+
387
+ top10 = self._compute_top_holder_pct(holders, 10)
388
+ gini = _gini_coefficient([h.get("percentage", 0) for h in holders[:100]])
389
+
390
+ score = 0.0
391
+ if top10 > 80:
392
+ score += 40
393
+ elif top10 > 60:
394
+ score += 25
395
+ if gini > 0.8:
396
+ score += 30
397
+ elif gini > 0.6:
398
+ score += 15
399
+
400
+ return {
401
+ "address": address,
402
+ "chain": chain,
403
+ "name": metadata.get("name", ""),
404
+ "symbol": metadata.get("symbol", ""),
405
+ "supply_concentration_score": min(score, 100),
406
+ "risk_label": _label_risk(min(score, 100)),
407
+ "top_10_holder_pct": round(top10, 2),
408
+ "gini_coefficient": round(gini, 3),
409
+ }
410
+
411
+ # ── Validation ──────────────────────────────────────────────
412
+
413
+ def _validate_address(self, address: str, chain: str) -> bool:
414
+ chain = chain.lower()
415
+ if chain == "solana":
416
+ return bool(SOLANA_ADDR_RE.match(address))
417
+ if chain in EVM_CHAINS:
418
+ return bool(EVM_ADDR_RE.match(address))
419
+ return bool(EVM_ADDR_RE.match(address) or SOLANA_ADDR_RE.match(address))
420
+
421
+ # ── Data Fetching ───────────────────────────────────────────
422
+
423
+ async def _fetch_metadata(self, address: str, chain: str) -> dict[str, Any]:
424
+ """Fetch token metadata from DexScreener."""
425
+ try:
426
+ url = f"{DEXSCREENER_API}/tokens/{address}"
427
+ resp = await self.http.get(url, timeout=10)
428
+ if resp.status_code != 200:
429
+ return {}
430
+ data = resp.json()
431
+ pairs = data.get("pairs", [])
432
+ if not pairs:
433
+ return {}
434
+
435
+ pair = pairs[0]
436
+ return {
437
+ "name": pair.get("baseToken", {}).get("name", ""),
438
+ "symbol": pair.get("baseToken", {}).get("symbol", ""),
439
+ "decimals": pair.get("baseToken", {}).get("decimals"),
440
+ "price_usd": pair.get("priceUsd", ""),
441
+ "liquidity_usd": pair.get("liquidity", {}).get("usd", 0),
442
+ "fdv": pair.get("fdv", 0),
443
+ "pair_address": pair.get("pairAddress", ""),
444
+ "dex": pair.get("dexId", ""),
445
+ "url": pair.get("url", ""),
446
+ "social": {
447
+ "twitter": pair.get("info", {}).get("twitter", ""),
448
+ "website": pair.get("info", {}).get("website", ""),
449
+ "telegram": pair.get("info", {}).get("telegram", ""),
450
+ },
451
+ "creation_block": None, # May not be available
452
+ }
453
+ except Exception as e:
454
+ logger.debug(f"Metadata fetch error: {e}")
455
+ return {}
456
+
457
+ async def _fetch_holders(self, address: str, chain: str) -> list[dict[str, Any]]:
458
+ """Fetch top holders from Birdeye public API or Solscan."""
459
+ try:
460
+ if chain == "solana":
461
+ return await self._fetch_solana_holders(address)
462
+ # EVM chains β€” try Birdeye first
463
+ return await self._fetch_evm_holders(address, chain)
464
+ except Exception as e:
465
+ logger.debug(f"Holder fetch error: {e}")
466
+ return []
467
+
468
+ async def _fetch_solana_holders(self, address: str) -> list[dict[str, Any]]:
469
+ """Fetch Solana token holders via Birdeye public API."""
470
+ try:
471
+ url = f"{BIRDEYE_PUBLIC}/defi/holder/tokenlist?tokenAddress={address}&limit=100"
472
+ headers = {"Accept": "application/json"}
473
+ if self._birdeye_api_key:
474
+ headers["X-API-KEY"] = self._birdeye_api_key
475
+
476
+ resp = await self.http.get(url, headers=headers, timeout=10)
477
+ if resp.status_code == 200:
478
+ data = resp.json()
479
+ items = data.get("data", {}).get("items", [])
480
+ return [
481
+ {
482
+ "address": h.get("holder", ""),
483
+ "amount": h.get("amount", 0),
484
+ "percentage": h.get("percent", 0),
485
+ }
486
+ for h in items
487
+ ]
488
+ except Exception as e:
489
+ logger.debug(f"Solana holder fetch error: {e}")
490
+
491
+ # Fallback: Solscan free API
492
+ try:
493
+ url = f"https://public-api.solscan.io/token/holders?tokenAddress={address}&limit=100&offset=0"
494
+ resp = await self.http.get(url, timeout=10)
495
+ if resp.status_code == 200:
496
+ data = resp.json()
497
+ items = data if isinstance(data, list) else data.get("data", [])
498
+ return [
499
+ {
500
+ "address": h.get("owner", h.get("address", "")),
501
+ "amount": h.get("amount", h.get("balance", 0)),
502
+ "percentage": h.get("percentage", h.get("percent", 0)),
503
+ }
504
+ for h in items
505
+ ]
506
+ except Exception as e:
507
+ logger.debug(f"Solscan holder fallback error: {e}")
508
+
509
+ return []
510
+
511
+ async def _fetch_evm_holders(self, address: str, chain: str) -> list[dict[str, Any]]:
512
+ """Fetch EVM token holders via Birdeye public API."""
513
+ try:
514
+ url = f"{BIRDEYE_PUBLIC}/defi/holder/tokenlist?tokenAddress={address}&limit=100"
515
+ headers = {"Accept": "application/json"}
516
+ if self._birdeye_api_key:
517
+ headers["X-API-KEY"] = self._birdeye_api_key
518
+
519
+ resp = await self.http.get(url, headers=headers, timeout=10)
520
+ if resp.status_code == 200:
521
+ data = resp.json()
522
+ items = data.get("data", {}).get("items", [])
523
+ return [
524
+ {
525
+ "address": h.get("holder", ""),
526
+ "amount": h.get("amount", 0),
527
+ "percentage": h.get("percent", 0),
528
+ }
529
+ for h in items
530
+ ]
531
+ except Exception as e:
532
+ logger.debug(f"EVM holder fetch error: {e}")
533
+
534
+ return []
535
+
536
+ async def _fetch_buys(self, address: str, chain: str) -> list[dict[str, Any]]:
537
+ """Fetch recent buy transactions for the token."""
538
+ buys: list[dict[str, Any]] = []
539
+ try:
540
+ url = f"{DEXSCREENER_API}/tokens/{address}"
541
+ resp = await self.http.get(url, timeout=10)
542
+ if resp.status_code == 200:
543
+ data = resp.json()
544
+ pairs = data.get("pairs", [])
545
+ for pair in pairs[:5]: # Check top 5 pairs
546
+ txns = pair.get("txns", {})
547
+ # Extract buys from recent transactions
548
+ m5 = txns.get("m5", {}) or {}
549
+ h1 = txns.get("h1", {}) or {}
550
+ h6 = txns.get("h6", {}) or {}
551
+ buys.append({
552
+ "type": "buy",
553
+ "m5_buys": m5.get("buys", 0),
554
+ "m5_sells": m5.get("sells", 0),
555
+ "h1_buys": h1.get("buys", 0),
556
+ "h1_sells": h1.get("sells", 0),
557
+ "h6_buys": h6.get("buys", 0),
558
+ "h6_sells": h6.get("sells", 0),
559
+ "pair_address": pair.get("pairAddress", ""),
560
+ "creation_block": None, # May not be available
561
+ })
562
+
563
+ # Try to get volume per tx for bundling analysis
564
+ volume_m5 = pair.get("volume", {}).get("m5", 0) or 0
565
+ if m5.get("buys", 0) > 0:
566
+ avg_buy = float(volume_m5) / max(1, m5.get("buys", 1))
567
+ buys[-1]["avg_buy_value"] = avg_buy
568
+ except Exception as e:
569
+ logger.debug(f"Buy fetch error: {e}")
570
+
571
+ return buys
572
+
573
+ # ── Analysis ────────────────────────────────────────────────
574
+
575
+ @staticmethod
576
+ def _compute_top_holder_pct(holders: list[dict[str, Any]], top_n: int) -> float:
577
+ """Calculate the percentage of supply held by top N holders."""
578
+ sorted_h = sorted(holders, key=lambda h: h.get("percentage", 0), reverse=True)
579
+ top = sorted_h[:top_n]
580
+ return sum(h.get("percentage", 0) for h in top if h.get("percentage") is not None)
581
+
582
+ @staticmethod
583
+ def _extract_dev_hold_pct(holders: list[dict[str, Any]], metadata: dict[str, Any]) -> float:
584
+ """Extract developer/allocation wallet holding percentage."""
585
+ if not holders:
586
+ return 0.0
587
+ return holders[0].get("percentage", 0) if holders else 0.0
588
+
589
+ def _detect_bundled_buys(
590
+ self, buys: list[dict[str, Any]]
591
+ ) -> tuple[list[BundledBuy], int]:
592
+ """Detect buys that appear bundled (same source, time clustering)."""
593
+ bundled: list[BundledBuy] = []
594
+ same_funding_count = 0
595
+
596
+ # From aggregated transaction data, detect anomalous patterns
597
+ for buy in buys:
598
+ m5_buys = buy.get("m5_buys", 0)
599
+ h1_buys = buy.get("h1_buys", 0)
600
+ h6_buys = buy.get("h6_buys", 0)
601
+
602
+ # If buys/minute in first 5min is very high relative to later
603
+ if m5_buys > 0 and h1_buys > 0:
604
+ m5_rate = m5_buys / 5
605
+ h1_rate = h1_buys / 60
606
+ if m5_rate > h1_rate * 3 and m5_buys >= 10:
607
+ # High initial buy concentration β€” suspicious
608
+ bundled.append(BundledBuy(
609
+ wallet=f"cluster:{buy.get('pair_address', '')[:12]}",
610
+ amount_usd=0, # aggregated
611
+ buy_block=0,
612
+ buy_timestamp=time.time(),
613
+ tx_hash="",
614
+ funding_source="aggregated",
615
+ is_sniper=True,
616
+ ))
617
+ same_funding_count += m5_buys
618
+
619
+ return bundled, same_funding_count
620
+
621
+ def _analyze_launch_timing(self, buys: list[dict[str, Any]]) -> dict[str, Any]:
622
+ """Analyze launch timing for anomalous patterns."""
623
+ result = {
624
+ "unique_buyers_first_block": 0,
625
+ "total_buys_first_blocks": 0,
626
+ "buy_concentration_ratio": 0.0,
627
+ }
628
+
629
+ for buy in buys:
630
+ m5_buys = buy.get("m5_buys", 0)
631
+ h1_buys = buy.get("h1_buys", 0)
632
+ h6_buys = buy.get("h6_buys", 0)
633
+ total = m5_buys + h1_buys + h6_buys
634
+
635
+ if total > 0:
636
+ # What % of all buys happened in first 5 minutes?
637
+ first_5m_pct = m5_buys / total if total > 0 else 0
638
+ result["buy_concentration_ratio"] = max(
639
+ result["buy_concentration_ratio"], first_5m_pct
640
+ )
641
+ result["total_buys_first_blocks"] += m5_buys
642
+ # Estimate unique from m5 vs h1 ratio
643
+ if h1_buys > 0 and m5_buys > 0:
644
+ result["unique_buyers_first_block"] = max(
645
+ result["unique_buyers_first_block"],
646
+ min(m5_buys, h1_buys) # rough proxy
647
+ )
648
+
649
+ return result
650
+
651
+ def _cluster_wallets(
652
+ self, buys: list[dict[str, Any]], holders: list[dict[str, Any]]
653
+ ) -> list[HolderCluster]:
654
+ """Cluster wallets by funding overlap and timing patterns."""
655
+ clusters: list[HolderCluster] = []
656
+
657
+ if not holders:
658
+ return clusters
659
+
660
+ # Identify clusters based on supply concentration
661
+ sorted_h = sorted(holders, key=lambda h: h.get("percentage", 0), reverse=True)
662
+
663
+ # If top 3 holders control >60%, they form a natural cluster
664
+ top3 = sorted_h[:3]
665
+ top3_pct = sum(h.get("percentage", 0) for h in top3 if h.get("percentage") is not None)
666
+ if top3_pct > 60 and len(top3) >= 2:
667
+ clusters.append(HolderCluster(
668
+ wallets=[h.get("address", "") for h in top3 if h.get("address")],
669
+ total_supply_pct=top3_pct,
670
+ funding_overlap_score=0.7 if top3_pct > 80 else 0.5,
671
+ buy_time_similarity=0.8 if top3_pct > 80 else 0.6,
672
+ common_funding_source="top_holders_cluster",
673
+ ))
674
+
675
+ # Check for wallet groupings with 5-15% each (typical bundler pattern)
676
+ cluster_wallets: list[dict[str, Any]] = []
677
+ cluster_pct = 0.0
678
+ for h in sorted_h[3:]: # Skip top 3
679
+ pct = h.get("percentage", 0)
680
+ if pct and 2 <= pct <= 15:
681
+ cluster_wallets.append(h)
682
+ cluster_pct += pct
683
+ if len(cluster_wallets) >= 5 and cluster_pct >= 15:
684
+ break
685
+
686
+ if len(cluster_wallets) >= 5 and cluster_pct >= 15:
687
+ clusters.append(HolderCluster(
688
+ wallets=[h.get("address", "") for h in cluster_wallets],
689
+ total_supply_pct=cluster_pct,
690
+ funding_overlap_score=0.6,
691
+ buy_time_similarity=0.7,
692
+ common_funding_source="mid_holder_belt",
693
+ ))
694
+
695
+ return clusters
696
+
697
+ @staticmethod
698
+ def _estimate_entities(
699
+ holders: list[dict[str, Any]],
700
+ clusters: list[HolderCluster],
701
+ bundled_buys_count: int,
702
+ ) -> int:
703
+ """Estimate number of truly independent entities behind the token."""
704
+ total_holders = len(holders)
705
+
706
+ # Each cluster represents 1 entity instead of N wallets
707
+ cluster_wallet_count = sum(len(c.wallets) for c in clusters)
708
+
709
+ # Reduce estimated entities by clustered wallets
710
+ entities = max(1, total_holders - cluster_wallet_count)
711
+
712
+ # Further reduce if many bundled buys detected
713
+ if bundled_buys_count > 20:
714
+ entities = max(1, entities - bundled_buys_count // 5)
715
+
716
+ return entities
717
+
718
+ # ── Scoring ─────────────────────────────────────────────────
719
+
720
+ def _score_supply_concentration(
721
+ self, holders: list[dict[str, Any]], top10_pct: float
722
+ ) -> float:
723
+ """Score supply distribution risk (0-100)."""
724
+ score = 0.0
725
+
726
+ # Top 10 concentration
727
+ if top10_pct >= 90:
728
+ score += 50
729
+ elif top10_pct >= 75:
730
+ score += 35
731
+ elif top10_pct >= 50:
732
+ score += 20
733
+ elif top10_pct >= 30:
734
+ score += 10
735
+
736
+ # Gini coefficient
737
+ amounts = [h.get("percentage", 0) for h in holders[:100] if h.get("percentage") is not None]
738
+ gini = _gini_coefficient(amounts)
739
+ if gini >= 0.9:
740
+ score += 40
741
+ elif gini >= 0.8:
742
+ score += 30
743
+ elif gini >= 0.6:
744
+ score += 15
745
+
746
+ # Entropy (low entropy = concentrated)
747
+ ent = _entropy(amounts)
748
+ if ent < 0.3:
749
+ score += 15
750
+ elif ent < 0.5:
751
+ score += 8
752
+
753
+ return min(score, 100)
754
+
755
+ def _score_sniper_clusters(
756
+ self, clusters: list[HolderCluster], bundled_buys: list[BundledBuy]
757
+ ) -> float:
758
+ """Score sniper cluster risk (0-100)."""
759
+ score = 0.0
760
+
761
+ # High-funding-overlap clusters
762
+ high_overlap = [c for c in clusters if c.funding_overlap_score > 0.6]
763
+ if high_overlap:
764
+ total_pct = sum(c.total_supply_pct for c in high_overlap)
765
+ if total_pct >= 50:
766
+ score += 50
767
+ elif total_pct >= 30:
768
+ score += 35
769
+ elif total_pct >= 15:
770
+ score += 20
771
+
772
+ # Bundled buys
773
+ if bundled_buys:
774
+ score += min(len(bundled_buys) * 5, 30)
775
+
776
+ # Time clustering in clusters
777
+ high_time = [c for c in clusters if c.buy_time_similarity > 0.7]
778
+ if high_time:
779
+ score += min(len(high_time) * 10, 25)
780
+
781
+ return min(score, 100)
782
+
783
+ def _score_launch_timing(
784
+ self,
785
+ timing_info: dict[str, Any],
786
+ buys: list[dict[str, Any]],
787
+ holders: list[dict[str, Any]],
788
+ ) -> float:
789
+ """Score launch timing anomalies (0-100)."""
790
+ score = 0.0
791
+
792
+ # High buy concentration in first 5 minutes
793
+ ratio = timing_info.get("buy_concentration_ratio", 0)
794
+ if ratio >= 0.8:
795
+ score += 50
796
+ elif ratio >= 0.6:
797
+ score += 35
798
+ elif ratio >= 0.4:
799
+ score += 20
800
+
801
+ # Very few unique buyers relative to total buys
802
+ unique = timing_info.get("unique_buyers_first_block", 0)
803
+ total = timing_info.get("total_buys_first_blocks", 0)
804
+ if total > 0 and unique > 0:
805
+ repeat_rate = total / max(1, unique)
806
+ if repeat_rate >= 5:
807
+ score += 30
808
+ elif repeat_rate >= 3:
809
+ score += 20
810
+
811
+ # Holder count vs buy count mismatch
812
+ holder_count = len(holders)
813
+ if holder_count > 0 and total > 0:
814
+ buys_per_holder = total / holder_count
815
+ if buys_per_holder >= 3:
816
+ score += 15
817
+
818
+ return min(score, 100)
819
+
820
+ def _score_fund_flow(
821
+ self,
822
+ bundled_buys: list[BundledBuy],
823
+ same_funding_count: int,
824
+ clusters: list[HolderCluster],
825
+ ) -> float:
826
+ """Score fund flow risk (0-100)."""
827
+ score = 0.0
828
+
829
+ # Same funding source buys
830
+ if same_funding_count >= 20:
831
+ score += 45
832
+ elif same_funding_count >= 10:
833
+ score += 30
834
+ elif same_funding_count >= 5:
835
+ score += 15
836
+
837
+ # Clusters with high funding overlap
838
+ high_overlap = [c for c in clusters if c.funding_overlap_score > 0.7]
839
+ if high_overlap:
840
+ score += min(len(high_overlap) * 15, 30)
841
+
842
+ # Overall cluster funding overlap average
843
+ if clusters:
844
+ avg_overlap = sum(c.funding_overlap_score for c in clusters) / len(clusters)
845
+ score += avg_overlap * 20
846
+
847
+ return min(score, 100)
848
+
849
+ def _compute_bundler_score(self, report: BundlerReport) -> float:
850
+ """Weighted composite bundler score."""
851
+ weights = {
852
+ "supply_concentration": 0.30,
853
+ "sniper_cluster": 0.25,
854
+ "launch_timing_anomaly": 0.20,
855
+ "fund_flow_risk": 0.25,
856
+ }
857
+
858
+ score = (
859
+ report.supply_concentration_score * weights["supply_concentration"]
860
+ + report.sniper_cluster_score * weights["sniper_cluster"]
861
+ + report.launch_timing_anomaly_score * weights["launch_timing_anomaly"]
862
+ + report.fund_flow_risk_score * weights["fund_flow_risk"]
863
+ )
864
+
865
+ return min(score, 100)
backend/app/canonical_tools.py ADDED
@@ -0,0 +1,135 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Canonical Tool Prices β€” Single Source of Truth
3
+ 127 tools. Enforcement + databus merged.
4
+ This file is THE authoritative list. All endpoints (MCP discovery, x402 catalog, human marketplace) read from this.
5
+ """
6
+
7
+ CANONICAL_TOOL_PRICES = {
8
+ "airdrop_check": {"price_usd": 0.05, "price_atoms": "50000", "category": "market", "trial_free": 2, "description": "airdrop_check"},
9
+ "airdrop_finder": {"price_usd": 0.05, "price_atoms": "50000", "category": "intelligence", "trial_free": 2, "description": "airdrop_finder"},
10
+ "alpha_digest": {"price_usd": 0.1, "price_atoms": "100000", "category": "intelligence", "trial_free": 1, "description": "alpha_digest"},
11
+ "anomaly": {"price_usd": 0.08, "price_atoms": "80000", "category": "security", "trial_free": 0, "description": "anomaly"},
12
+ "arbitrage_scan": {"price_usd": 0.05, "price_atoms": "50000", "category": "market", "trial_free": 2, "description": "arbitrage_scan"},
13
+ "arkham_counterparties": {"price_usd": 0.2, "price_atoms": "200000", "category": "elite", "trial_free": 0, "description": "Counterparty intelligence β€” entity relationship graph and money flow analysis"},
14
+ "arkham_entity": {"price_usd": 0.1, "price_atoms": "100000", "category": "premium", "trial_free": 1, "description": "Entity resolution β€” map any address to its real-world owner with confidence scoring"},
15
+ "arkham_labels": {"price_usd": 0.1, "price_atoms": "100000", "category": "premium", "trial_free": 1, "description": "Institutional entity labels β€” fund names, exchange wallets, known addresses"},
16
+ "arkham_portfolio": {"price_usd": 0.25, "price_atoms": "250000", "category": "elite", "trial_free": 0, "description": "Institutional portfolio intelligence β€” complete holdings, historical performance, attribution"},
17
+ "arkham_transfers": {"price_usd": 0.2, "price_atoms": "200000", "category": "elite", "trial_free": 0, "description": "Cross-chain transfer tracer β€” full movement history with entity labeling"},
18
+ "audit": {"price_usd": 0.05, "price_atoms": "50000", "category": "security", "trial_free": 1, "description": "audit"},
19
+ "bridge_security": {"price_usd": 0.08, "price_atoms": "80000", "category": "security", "trial_free": 0, "description": "bridge_security"},
20
+ "bubble_map": {"price_usd": 0.02, "price_atoms": "20000", "category": "basic", "trial_free": 3, "description": "Holder concentration map β€” visualize whale clusters and distribution"},
21
+ "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"},
22
+ "bundler_detect": {"price_usd": 0.05, "price_atoms": "50000", "category": "security", "trial_free": 2, "description": "bundler_detect"},
23
+ "catalog": {"price_usd": 0.0, "price_atoms": "0", "category": "api", "trial_free": 999, "description": "Browse available tools, pricing, and chain support"},
24
+ "chain_health": {"price_usd": 0.05, "price_atoms": "50000", "category": "market", "trial_free": 0, "description": "chain_health"},
25
+ "clone_detect": {"price_usd": 0.02, "price_atoms": "20000", "category": "security", "trial_free": 3, "description": "clone_detect"},
26
+ "cluster": {"price_usd": 0.05, "price_atoms": "50000", "category": "intelligence", "trial_free": 0, "description": "cluster"},
27
+ "composite_score": {"price_usd": 0.25, "price_atoms": "250000", "category": "premium", "trial_free": 1, "description": "RMI Composite Score β€” one number combining ALL signals for instant buy/sell/avoid decisions"},
28
+ "comprehensive_audit": {"price_usd": 0.5, "price_atoms": "500000", "category": "security", "trial_free": 1, "description": "comprehensive_audit"},
29
+ "contract_scan": {"price_usd": 0.08, "price_atoms": "80000", "category": "premium", "trial_free": 1, "description": "Deep contract audit β€” static analysis, honeypot detection, vulnerability scan"},
30
+ "copy_trade_finder": {"price_usd": 0.1, "price_atoms": "100000", "category": "intelligence", "trial_free": 0, "description": "copy_trade_finder"},
31
+ "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"},
32
+ "defi_position": {"price_usd": 0.15, "price_atoms": "150000", "category": "defi", "trial_free": 1, "description": "DeFi position analyzer β€” LP holdings, impermanent loss estimation, yield sustainability, protocol risk"},
33
+ "defi_protocols": {"price_usd": 0.01, "price_atoms": "10000", "category": "basic", "trial_free": 5, "description": "DeFi protocol tracker β€” TVL, chains, categories, revenue metrics"},
34
+ "defi_yield_scanner": {"price_usd": 0.08, "price_atoms": "80000", "category": "market", "trial_free": 1, "description": "defi_yield_scanner"},
35
+ "deployer_history": {"price_usd": 0.05, "price_atoms": "50000", "category": "security", "trial_free": 2, "description": "deployer_history"},
36
+ "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"},
37
+ "entity_intel": {"price_usd": 0.1, "price_atoms": "100000", "category": "premium", "trial_free": 1, "description": "Entity intelligence β€” who is this wallet, linked addresses, risk assessment"},
38
+ "forensic_pack": {"price_usd": 0.35, "price_atoms": "350000", "category": "bundle", "trial_free": 1, "description": "Forensic Investigation Pack β€” valuation + OSINT + report at 33% discount"},
39
+ "forensic_valuation": {"price_usd": 0.25, "price_atoms": "250000", "category": "premium", "trial_free": 1, "description": "Institutional-grade token valuation β€” DCF intrinsic value, comparable analysis with outlier detection, scam probability scoring"},
40
+ "forensics": {"price_usd": 0.1, "price_atoms": "100000", "category": "analysis", "trial_free": 1, "description": "forensics"},
41
+ "fresh_pair": {"price_usd": 0.03, "price_atoms": "30000", "category": "security", "trial_free": 3, "description": "fresh_pair"},
42
+ "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"},
43
+ "gas_forecast": {"price_usd": 0.05, "price_atoms": "50000", "category": "market", "trial_free": 0, "description": "gas_forecast"},
44
+ "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"},
45
+ "history": {"price_usd": 0.08, "price_atoms": "80000", "category": "analysis", "trial_free": 2, "description": "Historical scanner time-series β€” risk/liquidity/volume/price trends over hours"},
46
+ "honeypot_check": {"price_usd": 0.05, "price_atoms": "50000", "category": "security", "trial_free": 2, "description": "honeypot_check"},
47
+ "human-execute": {"price_usd": 0.02, "price_atoms": "20000", "category": "api", "trial_free": 2, "description": "Human-in-the-loop execution β€” wallet-based payment for manual crypto investigation tasks"},
48
+ "insider": {"price_usd": 0.1, "price_atoms": "100000", "category": "intelligence", "trial_free": 0, "description": "insider"},
49
+ "insider_network": {"price_usd": 0.1, "price_atoms": "100000", "category": "intelligence", "trial_free": 0, "description": "insider_network"},
50
+ "investigation_report": {"price_usd": 0.2, "price_atoms": "200000", "category": "premium", "trial_free": 1, "description": "Full investigation report β€” on-chain forensics, financial valuation, OSINT findings, scam scoring in one deliverable"},
51
+ "kol_performance": {"price_usd": 0.1, "price_atoms": "100000", "category": "intelligence", "trial_free": 0, "description": "kol_performance"},
52
+ "launch": {"price_usd": 0.03, "price_atoms": "30000", "category": "launchpad", "trial_free": 2, "description": "launch"},
53
+ "launch_intel": {"price_usd": 0.05, "price_atoms": "50000", "category": "launchpad", "trial_free": 2, "description": "launch_intel"},
54
+ "liquidity_depth": {"price_usd": 0.05, "price_atoms": "50000", "category": "market", "trial_free": 2, "description": "liquidity_depth"},
55
+ "liquidity_flow": {"price_usd": 0.08, "price_atoms": "80000", "category": "intelligence", "trial_free": 0, "description": "liquidity_flow"},
56
+ "liquidity_migration": {"price_usd": 0.05, "price_atoms": "50000", "category": "security", "trial_free": 2, "description": "liquidity_migration"},
57
+ "listing_predictor": {"price_usd": 0.08, "price_atoms": "80000", "category": "intelligence", "trial_free": 1, "description": "listing_predictor"},
58
+ "market_movers": {"price_usd": 0.01, "price_atoms": "10000", "category": "basic", "trial_free": 5, "description": "Top gainers, losers, and volume movers across all chains"},
59
+ "market_overview": {"price_usd": 0.05, "price_atoms": "50000", "category": "market", "trial_free": 0, "description": "market_overview"},
60
+ "mcp-proxy": {"price_usd": 0.01, "price_atoms": "10000", "category": "api", "trial_free": 5, "description": "MCP protocol proxy β€” route tool calls through the x402 payment layer"},
61
+ "meme_vibe_score": {"price_usd": 0.01, "price_atoms": "10000", "category": "social", "trial_free": 3, "description": "Meme token vibe scoring β€” sentiment, community strength, and virality analysis"},
62
+ "mev_alert": {"price_usd": 0.08, "price_atoms": "80000", "category": "security", "trial_free": 1, "description": "mev_alert"},
63
+ "mev_detect": {"price_usd": 0.15, "price_atoms": "150000", "category": "security", "trial_free": 2, "description": "MEV/Sandwich attack detection β€” sandwich attacks, frontrunning, arbitrage extraction, MEV bot identification"},
64
+ "mev_protection": {"price_usd": 0.08, "price_atoms": "80000", "category": "security", "trial_free": 0, "description": "mev_protection"},
65
+ "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"},
66
+ "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"},
67
+ "narrative": {"price_usd": 0.05, "price_atoms": "50000", "category": "social", "trial_free": 3, "description": "Market narrative engine β€” what is the market saying about this token RIGHT NOW"},
68
+ "news": {"price_usd": 0.01, "price_atoms": "10000", "category": "basic", "trial_free": 5, "description": "Crypto news feed β€” aggregated headlines, filtered by topic"},
69
+ "nft_wash_detector": {"price_usd": 0.1, "price_atoms": "100000", "category": "analysis", "trial_free": 1, "description": "nft_wash_detector"},
70
+ "osint_identity_hunt": {"price_usd": 0.15, "price_atoms": "150000", "category": "premium", "trial_free": 2, "description": "Cross-platform OSINT investigation β€” hunt usernames across 400+ networks, domain intelligence, stealth page capture"},
71
+ "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"},
72
+ "portfolio_aggregate": {"price_usd": 0.1, "price_atoms": "100000", "category": "analysis", "trial_free": 1, "description": "portfolio_aggregate"},
73
+ "portfolio_risk": {"price_usd": 0.2, "price_atoms": "200000", "category": "premium", "trial_free": 1, "description": "Cross-chain portfolio risk dashboard β€” unified risk across multiple wallets and chains"},
74
+ "portfolio_tracker": {"price_usd": 0.1, "price_atoms": "100000", "category": "analysis", "trial_free": 0, "description": "portfolio_tracker"},
75
+ "prediction_markets": {"price_usd": 0.02, "price_atoms": "20000", "category": "basic", "trial_free": 3, "description": "Prediction market odds β€” event probabilities and trading volumes"},
76
+ "prediction_signals": {"price_usd": 0.02, "price_atoms": "20000", "category": "basic", "trial_free": 3, "description": "Trading signals β€” sentiment, momentum, and contrarian indicators"},
77
+ "profile_flip": {"price_usd": 0.03, "price_atoms": "30000", "category": "security", "trial_free": 3, "description": "profile_flip"},
78
+ "protocol_risk": {"price_usd": 0.08, "price_atoms": "80000", "category": "security", "trial_free": 1, "description": "protocol_risk"},
79
+ "pulse": {"price_usd": 0.01, "price_atoms": "10000", "category": "market", "trial_free": 3, "description": "pulse"},
80
+ "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"},
81
+ "reputation_score": {"price_usd": 0.1, "price_atoms": "100000", "category": "premium", "trial_free": 1, "description": "Comprehensive 0-100 trust score combining wallet labels, scam databases, deployer history, and RAG similarity matching"},
82
+ "risk_monitor": {"price_usd": 0.05, "price_atoms": "50000", "category": "security", "trial_free": 1, "description": "risk_monitor"},
83
+ "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"},
84
+ "rug_probability": {"price_usd": 0.15, "price_atoms": "150000", "category": "premium", "trial_free": 1, "description": "Predictive rug pull probability 0-100 β€” honeypot + liquidity + deployer + social signals"},
85
+ "rug_pull_predictor": {"price_usd": 0.1, "price_atoms": "100000", "category": "security", "trial_free": 0, "description": "rug_pull_predictor"},
86
+ "rugmaps_analysis": {"price_usd": 0.02, "price_atoms": "20000", "category": "basic", "trial_free": 3, "description": "Holder distribution analysis β€” risk scoring, dump patterns, concentration"},
87
+ "rugshield": {"price_usd": 0.02, "price_atoms": "20000", "category": "security", "trial_free": 3, "description": "rugshield"},
88
+ "scam_database": {"price_usd": 0.03, "price_atoms": "30000", "category": "security", "trial_free": 3, "description": "scam_database"},
89
+ "sentiment": {"price_usd": 0.03, "price_atoms": "30000", "category": "social", "trial_free": 0, "description": "sentiment"},
90
+ "sentiment_spike": {"price_usd": 0.05, "price_atoms": "50000", "category": "social", "trial_free": 2, "description": "sentiment_spike"},
91
+ "sentinel_deep": {"price_usd": 0.1, "price_atoms": "100000", "category": "premium", "trial_free": 1, "description": "Full threat scan β€” deep contract analysis, risk scoring, threat intelligence"},
92
+ "smart_money": {"price_usd": 0.2, "price_atoms": "200000", "category": "intelligence", "trial_free": 1, "description": "Smart Money P&L Tracker β€” real profitability-based wallet tracking, find the actual profitable traders"},
93
+ "smart_money_alpha": {"price_usd": 0.01, "price_atoms": "10000", "category": "intelligence", "trial_free": 3, "description": "Smart money alpha signals β€” track wallets that consistently outperform the market"},
94
+ "smartmoney": {"price_usd": 0.05, "price_atoms": "50000", "category": "intelligence", "trial_free": 1, "description": "smartmoney"},
95
+ "sniper_alert": {"price_usd": 0.05, "price_atoms": "50000", "category": "launchpad", "trial_free": 2, "description": "sniper_alert"},
96
+ "sniper_detect": {"price_usd": 0.08, "price_atoms": "80000", "category": "intelligence", "trial_free": 1, "description": "sniper_detect"},
97
+ "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"},
98
+ "social_signal": {"price_usd": 0.1, "price_atoms": "100000", "category": "social", "trial_free": 0, "description": "social_signal"},
99
+ "socialfi_resolve": {"price_usd": 0.01, "price_atoms": "10000", "category": "basic", "trial_free": 3, "description": "Resolve social identity β€” ENS names, Farcaster profiles, linked addresses"},
100
+ "syndicate_scan": {"price_usd": 0.08, "price_atoms": "80000", "category": "intelligence", "trial_free": 1, "description": "syndicate_scan"},
101
+ "syndicate_track": {"price_usd": 0.1, "price_atoms": "100000", "category": "intelligence", "trial_free": 1, "description": "syndicate_track"},
102
+ "threat_check": {"price_usd": 0.02, "price_atoms": "20000", "category": "basic", "trial_free": 3, "description": "Threat intelligence check β€” known scams, malicious patterns, risk scoring"},
103
+ "token_age": {"price_usd": 0.01, "price_atoms": "10000", "category": "security", "trial_free": 5, "description": "token_age"},
104
+ "token_comparison": {"price_usd": 0.08, "price_atoms": "80000", "category": "analysis", "trial_free": 0, "description": "token_comparison"},
105
+ "token_deep_dive": {"price_usd": 0.1, "price_atoms": "100000", "category": "analysis", "trial_free": 0, "description": "token_deep_dive"},
106
+ "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"},
107
+ "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"},
108
+ "token_watch_alerts": {"price_usd": 0.0, "price_atoms": "0", "category": "monitoring", "trial_free": 999, "description": "token_watch_alerts"},
109
+ "token_watch_check": {"price_usd": 0.03, "price_atoms": "30000", "category": "monitoring", "trial_free": 5, "description": "One-shot token status check β€” current LP, price, volume, and rug risk warnings"},
110
+ "token_watch_create": {"price_usd": 0.05, "price_atoms": "50000", "category": "monitoring", "trial_free": 3, "description": "Set token monitoring watch β€” alerts when LP drops, price changes, or rug indicators detected"},
111
+ "token_watch_list": {"price_usd": 0.0, "price_atoms": "0", "category": "monitoring", "trial_free": 999, "description": "token_watch_list"},
112
+ "trending": {"price_usd": 0.01, "price_atoms": "10000", "category": "basic", "trial_free": 5, "description": "Trending tokens across chains β€” hottest movers right now"},
113
+ "tvl": {"price_usd": 0.01, "price_atoms": "10000", "category": "basic", "trial_free": 5, "description": "DeFi TVL data β€” protocol-level totals, chain breakdowns, yields"},
114
+ "tw_profile": {"price_usd": 0.01, "price_atoms": "10000", "category": "social", "trial_free": 0, "description": "tw_profile"},
115
+ "tw_search": {"price_usd": 0.01, "price_atoms": "10000", "category": "social", "trial_free": 0, "description": "tw_search"},
116
+ "tw_timeline": {"price_usd": 0.01, "price_atoms": "10000", "category": "social", "trial_free": 0, "description": "tw_timeline"},
117
+ "unlock_calendar": {"price_usd": 0.03, "price_atoms": "30000", "category": "market", "trial_free": 3, "description": "unlock_calendar"},
118
+ "urlcheck": {"price_usd": 0.01, "price_atoms": "10000", "category": "security", "trial_free": 3, "description": "urlcheck"},
119
+ "wallet": {"price_usd": 0.05, "price_atoms": "50000", "category": "analysis", "trial_free": 1, "description": "wallet"},
120
+ "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"},
121
+ "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"},
122
+ "wallet_graph": {"price_usd": 0.1, "price_atoms": "100000", "category": "intelligence", "trial_free": 0, "description": "wallet_graph"},
123
+ "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"},
124
+ "wallet_pnl": {"price_usd": 0.1, "price_atoms": "100000", "category": "analysis", "trial_free": 0, "description": "wallet_pnl"},
125
+ "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"},
126
+ "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"},
127
+ "wash_trade_detect": {"price_usd": 0.15, "price_atoms": "150000", "category": "security", "trial_free": 2, "description": "Wash Trading & Insider Detection β€” artificial volume, coordinated buying, insider accumulation patterns"},
128
+ "wash_trading": {"price_usd": 0.08, "price_atoms": "80000", "category": "security", "trial_free": 1, "description": "wash_trading"},
129
+ "webhook_list": {"price_usd": 0.0, "price_atoms": "0", "category": "monitoring", "trial_free": 999, "description": "List registered webhooks for an address"},
130
+ "webhook_register": {"price_usd": 0.02, "price_atoms": "20000", "category": "monitoring", "trial_free": 2, "description": "Register webhook URL for real-time monitoring alerts β€” rug pulls, whale moves, price crashes"},
131
+ "whale": {"price_usd": 0.15, "price_atoms": "150000", "category": "intelligence", "trial_free": 1, "description": "whale"},
132
+ "whale_accumulation": {"price_usd": 0.08, "price_atoms": "80000", "category": "intelligence", "trial_free": 1, "description": "whale_accumulation"},
133
+ "whale_profile": {"price_usd": 0.05, "price_atoms": "50000", "category": "intelligence", "trial_free": 2, "description": "whale_profile"},
134
+ "whale_scan": {"price_usd": 0.03, "price_atoms": "30000", "category": "intelligence", "trial_free": 3, "description": "whale_scan"},
135
+ }