clawvif 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":["textResult","getWorkspacePath","textResult","getWorkspacePath","textResult","getWorkspacePath","textResult","getWorkspacePath","textResult","textResult"],"sources":["../src/lib/audit-logger.ts","../src/tools/audit.ts","../src/tools/alert.ts","../src/lib/chains.ts","../src/tools/chain-monitor.ts","../src/tools/sentiment.ts","../src/tools/whale.ts","../src/services/telegram-scanner.ts","../src/tools/tg-scanner.ts","../src/services/telegram-listener.ts","../src/tools/tg-listener.ts","../src/index.ts"],"sourcesContent":["/**\n * Audit logger for Clawvif.\n *\n * Records all sensitive operations to a JSONL audit trail.\n * Every vault sign, trade execution, risk check, and configuration\n * change is logged with timestamp, source skill, and relevant data.\n */\n\nimport { join } from 'node:path';\nimport { appendJsonl, readJsonlTail, ensureDir } from './file-store.js';\nimport type { AuditEntry } from '../types/index.js';\n\n/** Default audit log directory */\nconst DEFAULT_LOG_DIR = join(\n process.env['HOME'] || process.env['USERPROFILE'] || '/tmp',\n '.clawvif',\n 'logs',\n);\n\nlet logDir = DEFAULT_LOG_DIR;\n\n/**\n * Set the audit log directory.\n */\nexport function setAuditLogDir(dir: string): void {\n logDir = dir;\n ensureDir(logDir);\n}\n\n/**\n * Get the path to the audit log file.\n */\nfunction getAuditLogPath(): string {\n return join(logDir, 'audit.jsonl');\n}\n\n/**\n * Write an audit log entry.\n *\n * @param skill - Name of the skill/tool performing the action\n * @param action - Action being performed (e.g., \"sign_transaction\", \"import_key\")\n * @param data - Relevant data (NEVER include private keys or passwords)\n */\nexport function auditLog(skill: string, action: string, data: Record<string, unknown> = {}): void {\n const entry: AuditEntry = {\n ts: new Date().toISOString(),\n skill,\n action,\n data,\n };\n appendJsonl(getAuditLogPath(), entry);\n}\n\n/**\n * Query recent audit log entries.\n *\n * @param count - Number of recent entries to return\n * @returns Most recent entries (newest first)\n */\nexport function queryAuditLog(count: number = 50): AuditEntry[] {\n return readJsonlTail<AuditEntry>(getAuditLogPath(), count);\n}\n\n/**\n * Query audit log entries filtered by skill and/or action.\n *\n * @param filters - Optional filters\n * @param count - Maximum entries to return\n */\nexport function queryAuditLogFiltered(\n filters: { skill?: string; action?: string },\n count: number = 50,\n): AuditEntry[] {\n const all = queryAuditLog(count * 3); // Read extra to account for filtering\n return all\n .filter((entry) => {\n if (filters.skill && entry.skill !== filters.skill) return false;\n if (filters.action && entry.action !== filters.action) return false;\n return true;\n })\n .slice(0, count);\n}\n","/**\n * Audit Tool — Query and manage the Clawvif audit trail.\n *\n * All sensitive operations across all skills are recorded in audit.jsonl.\n * This tool provides read access to the audit log for review and analysis.\n */\n\nimport { Type } from '@sinclair/typebox';\nimport { auditLog, queryAuditLog, queryAuditLogFiltered } from '../lib/audit-logger.js';\n\ninterface PluginApi {\n registerTool(config: {\n name: string;\n description: string;\n parameters: unknown;\n execute: (id: string, params: Record<string, unknown>) => Promise<{\n content: Array<{ type: string; text: string }>;\n }>;\n }, options?: { optional?: boolean }): void;\n}\n\nfunction textResult(text: string) {\n return { content: [{ type: 'text' as const, text }] };\n}\n\nexport function registerAuditTools(api: PluginApi): void {\n\n // ─── audit_log ────────────────────────────────────────────────────\n api.registerTool({\n name: 'audit_log',\n description:\n 'Write an entry to the audit log. Use this to record important agent actions ' +\n 'that should be tracked for security review.',\n parameters: Type.Object({\n skill: Type.String({\n description: 'Name of the skill performing the action',\n }),\n action: Type.String({\n description: 'Action description (e.g., \"strategy_enabled\", \"config_changed\")',\n }),\n data: Type.Optional(Type.Record(Type.String(), Type.Unknown(), {\n description: 'Additional structured data (NEVER include private keys or passwords)',\n })),\n }),\n async execute(_id, params) {\n const { skill, action, data } = params as {\n skill: string;\n action: string;\n data?: Record<string, unknown>;\n };\n\n auditLog(skill, action, data || {});\n\n return textResult(`📝 Audit entry recorded: [${skill}] ${action}`);\n },\n });\n\n // ─── audit_query ──────────────────────────────────────────────────\n api.registerTool({\n name: 'audit_query',\n description:\n 'Query the audit log to review past operations. Returns recent entries ' +\n 'optionally filtered by skill name and/or action type.',\n parameters: Type.Object({\n skill: Type.Optional(Type.String({\n description: 'Filter by skill name (e.g., \"vault\", \"dex-swap-executor\")',\n })),\n action: Type.Optional(Type.String({\n description: 'Filter by action type (e.g., \"sign_transaction\", \"trade_executed\")',\n })),\n count: Type.Optional(Type.Number({\n description: 'Number of recent entries to return (default: 20, max: 100)',\n minimum: 1,\n maximum: 100,\n })),\n }),\n async execute(_id, params) {\n const { skill, action, count = 20 } = params as {\n skill?: string;\n action?: string;\n count?: number;\n };\n\n const clampedCount = Math.min(Math.max(count, 1), 100);\n\n let entries;\n if (skill || action) {\n entries = queryAuditLogFiltered({ skill, action }, clampedCount);\n } else {\n entries = queryAuditLog(clampedCount);\n }\n\n if (entries.length === 0) {\n return textResult('📭 No audit entries found matching your criteria.');\n }\n\n const formatted = entries.map((e) => {\n const dataStr = Object.keys(e.data).length > 0 ? `\\n Data: ${JSON.stringify(e.data)}` : '';\n return `• [${e.ts}] **${e.skill}** → ${e.action}${dataStr}`;\n });\n\n const header = skill || action\n ? `🔍 Audit Log (filtered: ${[skill && `skill=${skill}`, action && `action=${action}`].filter(Boolean).join(', ')})`\n : '📋 Recent Audit Log';\n\n return textResult(`${header}\\n\\n${formatted.join('\\n\\n')}\\n\\nShowing ${entries.length} entries.`);\n },\n });\n}\n","/**\n * Alert Dispatcher Tool — Unified alert management for Clawvif.\n *\n * All skills route alerts through this dispatcher, which handles:\n * - Alert level classification (CRITICAL / HIGH / MEDIUM / LOW)\n * - Deduplication (same event within cooldown = suppressed)\n * - Message formatting for Telegram delivery\n * - Alert history tracking\n */\n\nimport { join } from 'node:path';\nimport { Type } from '@sinclair/typebox';\nimport { appendJsonl, readJsonOr, ensureDir } from '../lib/file-store.js';\nimport { auditLog } from '../lib/audit-logger.js';\nimport type { AlertEvent, AlertLevel, AlertRulesConfig } from '../types/index.js';\n\n// ─── Alert State ────────────────────────────────────────────────────\n\n/** In-memory dedup cache: dedupKey → last sent timestamp */\nconst dedupCache = new Map<string, number>();\n\n/** Default cooldown in seconds */\nconst DEFAULT_COOLDOWN_SECONDS = 300; // 5 minutes\n\n/** Alert level emoji mapping */\nconst LEVEL_EMOJI: Record<AlertLevel, string> = {\n critical: '🔴',\n high: '🟠',\n medium: '🟡',\n low: '🟢',\n};\n\n/** Alert level priority (higher = more urgent) — exported for use by other skills */\nexport const LEVEL_PRIORITY: Record<AlertLevel, number> = {\n critical: 4,\n high: 3,\n medium: 2,\n low: 1,\n};\n\n// ─── Helpers ────────────────────────────────────────────────────────\n\nfunction getWorkspacePath(): string {\n return process.env['CLAWVIF_WORKSPACE_PATH'] || join(\n process.env['HOME'] || process.env['USERPROFILE'] || '/tmp',\n '.clawvif',\n 'workspace',\n );\n}\n\nfunction getAlertLogPath(): string {\n return join(getWorkspacePath(), 'logs', 'alerts.jsonl');\n}\n\nfunction getAlertRulesPath(): string {\n return join(getWorkspacePath(), 'config', 'alert-rules.json');\n}\n\nfunction loadAlertRules(): AlertRulesConfig {\n return readJsonOr<AlertRulesConfig>(getAlertRulesPath(), {\n rules: [],\n defaultCooldownSeconds: DEFAULT_COOLDOWN_SECONDS,\n });\n}\n\nfunction isDuplicate(dedupKey: string, cooldownSeconds: number): boolean {\n const lastSent = dedupCache.get(dedupKey);\n if (!lastSent) return false;\n const elapsed = (Date.now() - lastSent) / 1000;\n return elapsed < cooldownSeconds;\n}\n\nfunction formatAlert(alert: AlertEvent): string {\n const emoji = LEVEL_EMOJI[alert.level];\n const levelUpper = alert.level.toUpperCase();\n\n let message = `${emoji} **${levelUpper}** — ${alert.title}\\n\\n${alert.body}`;\n\n if (alert.data && Object.keys(alert.data).length > 0) {\n const dataLines = Object.entries(alert.data)\n .map(([k, v]) => ` • ${k}: ${typeof v === 'object' ? JSON.stringify(v) : v}`)\n .join('\\n');\n message += `\\n\\n📎 Details:\\n${dataLines}`;\n }\n\n message += `\\n\\n🕐 ${alert.timestamp}`;\n\n return message;\n}\n\n// ─── Tool Registration ──────────────────────────────────────────────\n\ninterface PluginApi {\n registerTool(config: {\n name: string;\n description: string;\n parameters: unknown;\n execute: (id: string, params: Record<string, unknown>) => Promise<{\n content: Array<{ type: string; text: string }>;\n }>;\n }, options?: { optional?: boolean }): void;\n}\n\nfunction textResult(text: string) {\n return { content: [{ type: 'text' as const, text }] };\n}\n\nexport function registerAlertTools(api: PluginApi): void {\n\n // ─── alert_send ───────────────────────────────────────────────────\n api.registerTool({\n name: 'alert_send',\n description:\n 'Send an alert through the Clawvif alert dispatcher. Handles deduplication, ' +\n 'level-based formatting, and history tracking. The formatted alert message ' +\n 'should be forwarded to the user via the active messaging channel.',\n parameters: Type.Object({\n level: Type.Union([\n Type.Literal('critical'),\n Type.Literal('high'),\n Type.Literal('medium'),\n Type.Literal('low'),\n ], {\n description:\n 'Alert level: critical (fund safety), high (actionable signal), ' +\n 'medium (threshold trigger), low (informational)',\n }),\n title: Type.String({\n description: 'Short alert title',\n }),\n body: Type.String({\n description: 'Detailed alert body text',\n }),\n source: Type.String({\n description: 'Source skill name (e.g., \"onchain-monitor\", \"risk-guardian\")',\n }),\n dedupKey: Type.Optional(Type.String({\n description: 'Deduplication key — same key within cooldown period will be suppressed',\n })),\n data: Type.Optional(Type.Record(Type.String(), Type.Unknown(), {\n description: 'Additional structured data to include in the alert',\n })),\n }),\n async execute(_id, params) {\n const { level, title, body, source, dedupKey, data } = params as {\n level: AlertLevel;\n title: string;\n body: string;\n source: string;\n dedupKey?: string;\n data?: Record<string, unknown>;\n };\n\n // Load alert rules for cooldown config\n const rules = loadAlertRules();\n const cooldown = rules.defaultCooldownSeconds || DEFAULT_COOLDOWN_SECONDS;\n\n // Check dedup\n if (dedupKey && isDuplicate(dedupKey, cooldown)) {\n return textResult(\n `⏭️ Alert suppressed (duplicate within ${cooldown}s cooldown): ${title}`,\n );\n }\n\n // Create alert event\n const alert: AlertEvent = {\n id: `alert_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,\n level,\n title,\n body,\n data,\n source,\n dedupKey,\n timestamp: new Date().toISOString(),\n };\n\n // Update dedup cache\n if (dedupKey) {\n dedupCache.set(dedupKey, Date.now());\n }\n\n // Persist to alert log\n ensureDir(join(getWorkspacePath(), 'logs'));\n appendJsonl(getAlertLogPath(), alert);\n\n // Audit log\n auditLog('alert-dispatcher', 'alert_sent', {\n alertId: alert.id,\n level,\n title,\n source,\n });\n\n // Format for display\n const formatted = formatAlert(alert);\n\n return textResult(formatted);\n },\n });\n\n // ─── alert_configure ──────────────────────────────────────────────\n api.registerTool({\n name: 'alert_configure',\n description:\n 'View or modify alert dispatcher configuration, including cooldown periods ' +\n 'and alert rules.',\n parameters: Type.Object({\n action: Type.Union([Type.Literal('get'), Type.Literal('set_cooldown')], {\n description: 'Action: \"get\" to view config, \"set_cooldown\" to change default cooldown',\n }),\n cooldownSeconds: Type.Optional(Type.Number({\n description: 'New default cooldown in seconds (for set_cooldown action)',\n minimum: 0,\n maximum: 3600,\n })),\n }),\n async execute(_id, params) {\n const { action, cooldownSeconds } = params as {\n action: 'get' | 'set_cooldown';\n cooldownSeconds?: number;\n };\n\n const rules = loadAlertRules();\n\n if (action === 'get') {\n return textResult(\n `🔔 Alert Configuration\\n\\n` +\n `Default cooldown: ${rules.defaultCooldownSeconds}s\\n` +\n `Active rules: ${rules.rules.length}\\n` +\n `Dedup cache entries: ${dedupCache.size}`,\n );\n }\n\n if (action === 'set_cooldown' && cooldownSeconds !== undefined) {\n rules.defaultCooldownSeconds = cooldownSeconds;\n const rulesPath = getAlertRulesPath();\n ensureDir(join(getWorkspacePath(), 'config'));\n const { writeJson } = await import('../lib/file-store.js');\n writeJson(rulesPath, rules);\n auditLog('alert-dispatcher', 'config_changed', { cooldownSeconds });\n return textResult(`✅ Default cooldown updated to ${cooldownSeconds}s`);\n }\n\n return textResult('❌ Invalid action or missing parameters.');\n },\n });\n}\n","/**\n * Blockchain configuration for Clawvif.\n *\n * Defines RPC endpoints, contract addresses, and chain metadata\n * for all supported chains. RPC URLs use environment variables\n * to allow user customization.\n */\n\nimport type { Chain, ChainConfig, ChainFamily } from '../types/chain.js';\n\n/**\n * Build an Alchemy HTTP/WSS URL for a given network.\n */\nfunction alchemyUrl(network: string, protocol: 'https' | 'wss' = 'https'): string {\n const key = process.env['ALCHEMY_API_KEY'] || '';\n return `${protocol}://${network}.g.alchemy.com/v2/${key}`;\n}\n\n/**\n * Build a Helius URL for Solana.\n */\nfunction heliusUrl(protocol: 'https' | 'wss' = 'https'): string {\n const key = process.env['HELIUS_API_KEY'] || '';\n if (protocol === 'wss') {\n return `wss://mainnet.helius-rpc.com/?api-key=${key}`;\n }\n return `https://mainnet.helius-rpc.com/?api-key=${key}`;\n}\n\n/**\n * All supported chain configurations.\n */\nexport const CHAIN_CONFIGS: Record<Chain, ChainConfig> = {\n ethereum: {\n chain: 'ethereum',\n family: 'evm',\n name: 'Ethereum',\n nativeToken: 'ETH',\n rpc: {\n http: process.env['ETH_RPC_HTTP'] || alchemyUrl('eth-mainnet'),\n wss: process.env['ETH_RPC_WSS'] || alchemyUrl('eth-mainnet', 'wss'),\n chainId: 1,\n },\n explorerUrl: 'https://etherscan.io',\n contracts: {\n uniswapV3Router: '0xE592427A0AEce92De3Edee1F18E0157C05861564',\n uniswapV3Factory: '0x1F98431c8aD98523631AE4a59f267346ea31F984',\n uniswapV2Factory: '0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f',\n weth: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2',\n oneInchRouter: '0x1111111254EEB25477B68fb85Ed929f73A960582',\n },\n },\n base: {\n chain: 'base',\n family: 'evm',\n name: 'Base',\n nativeToken: 'ETH',\n rpc: {\n http: process.env['BASE_RPC_HTTP'] || alchemyUrl('base-mainnet'),\n wss: process.env['BASE_RPC_WSS'] || alchemyUrl('base-mainnet', 'wss'),\n chainId: 8453,\n },\n explorerUrl: 'https://basescan.org',\n contracts: {\n uniswapV3Router: '0x2626664c2603336E57B271c5C0b26F421741e481',\n uniswapV3Factory: '0x33128a8fC17869897dcE68Ed026d694621f6FDfD',\n weth: '0x4200000000000000000000000000000000000006',\n oneInchRouter: '0x1111111254EEB25477B68fb85Ed929f73A960582',\n },\n },\n arbitrum: {\n chain: 'arbitrum',\n family: 'evm',\n name: 'Arbitrum One',\n nativeToken: 'ETH',\n rpc: {\n http: process.env['ARB_RPC_HTTP'] || alchemyUrl('arb-mainnet'),\n wss: process.env['ARB_RPC_WSS'] || alchemyUrl('arb-mainnet', 'wss'),\n chainId: 42161,\n },\n explorerUrl: 'https://arbiscan.io',\n contracts: {\n uniswapV3Router: '0xE592427A0AEce92De3Edee1F18E0157C05861564',\n uniswapV3Factory: '0x1F98431c8aD98523631AE4a59f267346ea31F984',\n weth: '0x82aF49447D8a07e3bd95BD0d56f35241523fBab1',\n oneInchRouter: '0x1111111254EEB25477B68fb85Ed929f73A960582',\n },\n },\n polygon: {\n chain: 'polygon',\n family: 'evm',\n name: 'Polygon',\n nativeToken: 'MATIC',\n rpc: {\n http: process.env['POLYGON_RPC_HTTP'] || alchemyUrl('polygon-mainnet'),\n wss: process.env['POLYGON_RPC_WSS'] || alchemyUrl('polygon-mainnet', 'wss'),\n chainId: 137,\n },\n explorerUrl: 'https://polygonscan.com',\n contracts: {\n uniswapV3Router: '0xE592427A0AEce92De3Edee1F18E0157C05861564',\n uniswapV3Factory: '0x1F98431c8aD98523631AE4a59f267346ea31F984',\n wmatic: '0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270',\n oneInchRouter: '0x1111111254EEB25477B68fb85Ed929f73A960582',\n },\n },\n bsc: {\n chain: 'bsc',\n family: 'evm',\n name: 'BNB Smart Chain',\n nativeToken: 'BNB',\n rpc: {\n http: process.env['BSC_RPC_HTTP'] || 'https://bsc-dataseed.binance.org',\n wss: process.env['BSC_RPC_WSS'] || 'wss://bsc-ws-node.nariox.org',\n chainId: 56,\n },\n explorerUrl: 'https://bscscan.com',\n contracts: {\n pancakeFactory: '0xcA143Ce32Fe78f1f7019d7d551a6402fC5350c73',\n pancakeRouter: '0x10ED43C718714eb63d5aA57B78B54704E256024E',\n wbnb: '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c',\n oneInchRouter: '0x1111111254EEB25477B68fb85Ed929f73A960582',\n },\n },\n optimism: {\n chain: 'optimism',\n family: 'evm',\n name: 'Optimism',\n nativeToken: 'ETH',\n rpc: {\n http: process.env['OP_RPC_HTTP'] || alchemyUrl('opt-mainnet'),\n wss: process.env['OP_RPC_WSS'] || alchemyUrl('opt-mainnet', 'wss'),\n chainId: 10,\n },\n explorerUrl: 'https://optimistic.etherscan.io',\n contracts: {\n uniswapV3Router: '0xE592427A0AEce92De3Edee1F18E0157C05861564',\n uniswapV3Factory: '0x1F98431c8aD98523631AE4a59f267346ea31F984',\n weth: '0x4200000000000000000000000000000000000006',\n oneInchRouter: '0x1111111254EEB25477B68fb85Ed929f73A960582',\n },\n },\n solana: {\n chain: 'solana',\n family: 'solana',\n name: 'Solana',\n nativeToken: 'SOL',\n rpc: {\n http: process.env['SOLANA_RPC_HTTP'] || heliusUrl('https'),\n wss: process.env['SOLANA_RPC_WSS'] || heliusUrl('wss'),\n },\n explorerUrl: 'https://solscan.io',\n contracts: {\n jupiterAggregator: 'JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4',\n raydiumAmm: '675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8',\n // Pump.fun program\n pumpFun: '6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P',\n wsol: 'So11111111111111111111111111111111111111112',\n },\n },\n};\n\n/**\n * Get chain config by chain name.\n */\nexport function getChainConfig(chain: Chain): ChainConfig {\n const config = CHAIN_CONFIGS[chain];\n if (!config) {\n throw new Error(`Unsupported chain: ${chain}`);\n }\n return config;\n}\n\n/**\n * Get all EVM chains.\n */\nexport function getEvmChains(): ChainConfig[] {\n return Object.values(CHAIN_CONFIGS).filter((c) => c.family === 'evm');\n}\n\n/**\n * Get explorer URL for a transaction.\n */\nexport function getExplorerTxUrl(chain: Chain, txHash: string): string {\n const config = getChainConfig(chain);\n if (chain === 'solana') {\n return `${config.explorerUrl}/tx/${txHash}`;\n }\n return `${config.explorerUrl}/tx/${txHash}`;\n}\n\n/**\n * Get explorer URL for an address.\n */\nexport function getExplorerAddressUrl(chain: Chain, address: string): string {\n const config = getChainConfig(chain);\n if (chain === 'solana') {\n return `${config.explorerUrl}/account/${address}`;\n }\n return `${config.explorerUrl}/address/${address}`;\n}\n\n/**\n * Get the chain family for a given chain.\n */\nexport function getFamily(chain: Chain): ChainFamily {\n return getChainConfig(chain).family;\n}\n\n/**\n * Supported chains list.\n */\nexport const SUPPORTED_CHAINS: Chain[] = [\n 'ethereum', 'base', 'arbitrum', 'solana', 'polygon', 'bsc', 'optimism',\n];\n","/**\n * Chain Monitor Tool — On-chain event monitoring and query.\n *\n * Provides tools for:\n * - Managing chain monitoring subscriptions (watchlist, event types)\n * - Querying transaction details\n * - Retrieving recent on-chain events\n */\n\nimport { join } from 'node:path';\nimport { Type } from '@sinclair/typebox';\nimport { readJsonl, readJsonOr, writeJson, ensureDir } from '../lib/file-store.js';\nimport { auditLog } from '../lib/audit-logger.js';\nimport { getExplorerTxUrl } from '../lib/chains.js';\nimport type { OnchainEvent } from '../types/intel.js';\nimport type { Chain, Watchlist, WatchlistEntry } from '../types/index.js';\n\n// ─── Helpers ────────────────────────────────────────────────────────\n\nfunction getWorkspacePath(): string {\n return process.env['CLAWVIF_WORKSPACE_PATH'] || join(\n process.env['HOME'] || process.env['USERPROFILE'] || '/tmp',\n '.clawvif',\n 'workspace',\n );\n}\n\nfunction getWatchlistPath(): string {\n return join(getWorkspacePath(), 'config', 'watchlist.json');\n}\n\nfunction getEventsDir(): string {\n return join(getWorkspacePath(), 'memory', 'market-intel', 'onchain-events');\n}\n\nfunction loadWatchlist(): Watchlist {\n return readJsonOr<Watchlist>(getWatchlistPath(), { addresses: [] });\n}\n\nfunction saveWatchlist(watchlist: Watchlist): void {\n ensureDir(join(getWorkspacePath(), 'config'));\n writeJson(getWatchlistPath(), watchlist);\n}\n\nfunction loadRecentEvents(limit: number = 50, chain?: Chain, type?: string): OnchainEvent[] {\n const today = new Date().toISOString().split('T')[0];\n const yesterday = new Date(Date.now() - 86400000).toISOString().split('T')[0];\n\n const events: OnchainEvent[] = [];\n\n for (const dateStr of [today, yesterday]) {\n const filePath = join(getEventsDir(), `${dateStr}.jsonl`);\n try {\n const dayEvents = readJsonl<OnchainEvent>(filePath);\n events.push(...dayEvents);\n } catch {\n // File may not exist\n }\n }\n\n let filtered = events;\n if (chain) filtered = filtered.filter((e) => e.chain === chain);\n if (type) filtered = filtered.filter((e) => e.type === type);\n\n return filtered.slice(-limit).reverse();\n}\n\n// ─── Tool Registration ──────────────────────────────────────────────\n\ninterface PluginApi {\n registerTool(config: {\n name: string;\n description: string;\n parameters: unknown;\n execute: (id: string, params: Record<string, unknown>) => Promise<{\n content: Array<{ type: string; text: string }>;\n }>;\n }, options?: { optional?: boolean }): void;\n}\n\nfunction textResult(text: string) {\n return { content: [{ type: 'text' as const, text }] };\n}\n\nexport function registerChainMonitorTools(api: PluginApi): void {\n\n // ─── chain_subscribe ──────────────────────────────────────────────\n api.registerTool({\n name: 'chain_subscribe',\n description:\n 'Manage on-chain monitoring subscriptions. Add or remove addresses from the watchlist, ' +\n 'or list currently monitored addresses.',\n parameters: Type.Object({\n action: Type.Union([\n Type.Literal('add'),\n Type.Literal('remove'),\n Type.Literal('list'),\n ], {\n description: 'Action to perform on the watchlist',\n }),\n address: Type.Optional(Type.String({\n description: 'Address to add or remove',\n })),\n chain: Type.Optional(Type.Union([\n Type.Literal('ethereum'), Type.Literal('base'), Type.Literal('arbitrum'),\n Type.Literal('solana'), Type.Literal('polygon'), Type.Literal('bsc'),\n ], {\n description: 'Chain for the address',\n })),\n label: Type.Optional(Type.String({\n description: 'Label for the address (e.g., \"Jump Trading\")',\n })),\n tags: Type.Optional(Type.Array(Type.String(), {\n description: 'Tags for categorization (e.g., [\"vc\", \"market-maker\"])',\n })),\n }),\n async execute(_id, params) {\n const { action, address, chain, label, tags } = params as {\n action: 'add' | 'remove' | 'list';\n address?: string;\n chain?: Chain;\n label?: string;\n tags?: string[];\n };\n\n const watchlist = loadWatchlist();\n\n if (action === 'list') {\n if (watchlist.addresses.length === 0) {\n return textResult('📭 Watchlist is empty. Use action \"add\" to monitor addresses.');\n }\n const lines = watchlist.addresses.map(\n (a, i) => `${i + 1}. **${a.label}** [${a.chain}]\\n \\`${a.address}\\`\\n Tags: ${a.tags.join(', ') || 'none'}`,\n );\n return textResult(`👁️ Monitored Addresses (${watchlist.addresses.length}):\\n\\n${lines.join('\\n\\n')}`);\n }\n\n if (action === 'add') {\n if (!address || !chain) {\n return textResult('❌ Address and chain are required for adding to watchlist.');\n }\n if (watchlist.addresses.some((a) => a.address.toLowerCase() === address.toLowerCase())) {\n return textResult(`⚠️ Address ${address} is already in the watchlist.`);\n }\n const entry: WatchlistEntry = {\n address,\n chain,\n label: label || address.slice(0, 8) + '...',\n tags: tags || [],\n addedAt: new Date().toISOString(),\n };\n watchlist.addresses.push(entry);\n saveWatchlist(watchlist);\n auditLog('onchain-monitor', 'watchlist_add', { address, chain, label: entry.label });\n return textResult(`✅ Added to watchlist:\\n Address: \\`${address}\\`\\n Chain: ${chain}\\n Label: ${entry.label}`);\n }\n\n if (action === 'remove') {\n if (!address) {\n return textResult('❌ Address is required for removal.');\n }\n const idx = watchlist.addresses.findIndex(\n (a) => a.address.toLowerCase() === address.toLowerCase(),\n );\n if (idx === -1) {\n return textResult(`⚠️ Address ${address} not found in watchlist.`);\n }\n const removed = watchlist.addresses.splice(idx, 1)[0]!;\n saveWatchlist(watchlist);\n auditLog('onchain-monitor', 'watchlist_remove', { address, label: removed.label });\n return textResult(`✅ Removed from watchlist: ${removed.label} (${address})`);\n }\n\n return textResult('❌ Invalid action.');\n },\n });\n\n // ─── chain_query_tx ───────────────────────────────────────────────\n api.registerTool({\n name: 'chain_query_tx',\n description:\n 'Query details of a specific transaction by hash. Returns decoded information ' +\n 'including method, parameters, and value.',\n parameters: Type.Object({\n chain: Type.Union([\n Type.Literal('ethereum'), Type.Literal('base'), Type.Literal('arbitrum'),\n Type.Literal('solana'), Type.Literal('polygon'), Type.Literal('bsc'),\n ], {\n description: 'Chain to query',\n }),\n txHash: Type.String({\n description: 'Transaction hash to look up',\n }),\n }),\n async execute(_id, params) {\n const { chain, txHash } = params as { chain: Chain; txHash: string };\n\n const explorerUrl = getExplorerTxUrl(chain, txHash);\n\n // For now, return explorer link — full RPC query will be implemented\n // when actual RPC connections are established\n return textResult(\n `🔍 Transaction Details\\n\\n` +\n `Chain: ${chain}\\n` +\n `Hash: \\`${txHash}\\`\\n` +\n `Explorer: ${explorerUrl}\\n\\n` +\n `_Full transaction decoding requires active RPC connection._`,\n );\n },\n });\n\n // ─── chain_get_events ─────────────────────────────────────────────\n api.registerTool({\n name: 'chain_get_events',\n description:\n 'Retrieve recent on-chain events detected by the monitoring system. ' +\n 'Can filter by chain and event type.',\n parameters: Type.Object({\n chain: Type.Optional(Type.Union([\n Type.Literal('ethereum'), Type.Literal('base'), Type.Literal('arbitrum'),\n Type.Literal('solana'), Type.Literal('polygon'), Type.Literal('bsc'),\n ], {\n description: 'Filter by chain (optional)',\n })),\n type: Type.Optional(Type.Union([\n Type.Literal('large_transfer'),\n Type.Literal('new_pool'),\n Type.Literal('smart_money_move'),\n Type.Literal('contract_deploy'),\n ], {\n description: 'Filter by event type (optional)',\n })),\n limit: Type.Optional(Type.Number({\n description: 'Number of events to return (default: 20, max: 100)',\n minimum: 1,\n maximum: 100,\n })),\n }),\n async execute(_id, params) {\n const { chain, type, limit = 20 } = params as {\n chain?: Chain;\n type?: string;\n limit?: number;\n };\n\n const events = loadRecentEvents(Math.min(limit, 100), chain, type);\n\n if (events.length === 0) {\n return textResult('📭 No on-chain events found matching your criteria.');\n }\n\n const formatted = events.map((e) => {\n const label = e.fromLabel ? ` (${e.fromLabel})` : '';\n return (\n `• **${e.type}** [${e.chain}] — ${e.confidence} confidence\\n` +\n ` ${e.action}\\n` +\n ` From: \\`${e.from}\\`${label}\\n` +\n ` TX: \\`${e.txHash.slice(0, 16)}...\\`\\n` +\n ` ${e.timestamp}`\n );\n });\n\n const filterDesc = [chain && `chain=${chain}`, type && `type=${type}`]\n .filter(Boolean)\n .join(', ');\n const header = filterDesc\n ? `🔗 On-Chain Events (${filterDesc})`\n : '🔗 Recent On-Chain Events';\n\n return textResult(`${header}\\n\\n${formatted.join('\\n\\n')}\\n\\nShowing ${events.length} events.`);\n },\n });\n}\n","/**\n * Sentiment Analysis Tool — Multi-source social media sentiment tracking.\n *\n * Aggregates sentiment data from:\n * - X/Twitter (KOL mentions, volume spikes)\n * - Telegram groups (alpha calls, CA extraction)\n * - Discord (community sentiment)\n *\n * Uses LLM for sentiment classification and produces\n * standardized sentiment scores per token.\n */\n\nimport { join } from 'node:path';\nimport { Type } from '@sinclair/typebox';\nimport { readJsonl, appendJsonl, ensureDir } from '../lib/file-store.js';\nimport { auditLog } from '../lib/audit-logger.js';\nimport type { SentimentScore } from '../types/intel.js';\n\n// ─── Helpers ────────────────────────────────────────────────────────\n\nfunction getWorkspacePath(): string {\n return process.env['CLAWVIF_WORKSPACE_PATH'] || join(\n process.env['HOME'] || process.env['USERPROFILE'] || '/tmp',\n '.clawvif',\n 'workspace',\n );\n}\n\nfunction getSentimentDir(): string {\n return join(getWorkspacePath(), 'memory', 'market-intel', 'sentiment');\n}\n\nfunction loadRecentSentiment(token?: string, limit: number = 50): SentimentScore[] {\n const today = new Date().toISOString().split('T')[0];\n const filePath = join(getSentimentDir(), `${today}.jsonl`);\n\n try {\n let entries = readJsonl<SentimentScore>(filePath);\n if (token) {\n entries = entries.filter(\n (e) => e.token.toLowerCase() === token.toLowerCase(),\n );\n }\n return entries.slice(-limit).reverse();\n } catch {\n return [];\n }\n}\n\nfunction saveSentimentScore(score: SentimentScore): void {\n const today = new Date().toISOString().split('T')[0];\n ensureDir(getSentimentDir());\n appendJsonl(join(getSentimentDir(), `${today}.jsonl`), score);\n}\n\n// ─── Sentiment Formatting ───────────────────────────────────────────\n\nfunction formatSentimentScore(s: SentimentScore): string {\n const emoji =\n s.sentimentScore > 0.3 ? '🟢' :\n s.sentimentScore < -0.3 ? '🔴' : '🟡';\n\n const deltaEmoji = s.sentimentDelta1h > 0 ? '↑' : s.sentimentDelta1h < 0 ? '↓' : '→';\n const spikeFlag = s.mentionSpike ? '🔥 SPIKE' : '';\n\n let text =\n `${emoji} **${s.token}** — Sentiment: ${s.sentimentScore.toFixed(2)} ${spikeFlag}\\n` +\n ` Score delta (1h): ${deltaEmoji}${s.sentimentDelta1h.toFixed(2)}\\n` +\n ` Mentions (1h): ${s.mentionCount1h}\\n` +\n ` TG Alpha hits: ${s.telegramAlphaHits}`;\n\n if (s.topKolSignals.length > 0) {\n const kolLines = s.topKolSignals.map(\n (k) => ` • ${k.handle}: ${k.stance} (reach: ${k.reach.toLocaleString()})`,\n );\n text += `\\n KOL Signals:\\n${kolLines.join('\\n')}`;\n }\n\n if (s.extractedCa) {\n text += `\\n CA: \\`${s.extractedCa}\\``;\n }\n\n text += `\\n Updated: ${s.timestamp}`;\n\n return text;\n}\n\n// ─── Tool Registration ──────────────────────────────────────────────\n\ninterface PluginApi {\n registerTool(config: {\n name: string;\n description: string;\n parameters: unknown;\n execute: (id: string, params: Record<string, unknown>) => Promise<{\n content: Array<{ type: string; text: string }>;\n }>;\n }, options?: { optional?: boolean }): void;\n}\n\nfunction textResult(text: string) {\n return { content: [{ type: 'text' as const, text }] };\n}\n\nexport function registerSentimentTools(api: PluginApi): void {\n\n // ─── sentiment_query ──────────────────────────────────────────────\n api.registerTool({\n name: 'sentiment_query',\n description:\n 'Query the current sentiment score for a specific token. Returns the latest ' +\n 'sentiment data including score (-1.0 to 1.0), delta, mention count, KOL signals, ' +\n 'and Telegram alpha group hits.',\n parameters: Type.Object({\n token: Type.String({\n description: 'Token symbol (e.g., \"BTC\", \"ETH\", \"BONK\") or contract address',\n }),\n chain: Type.Optional(Type.String({\n description: 'Chain to filter by (optional)',\n })),\n }),\n async execute(_id, params) {\n const { token } = params as { token: string; chain?: string };\n\n const scores = loadRecentSentiment(token, 5);\n\n if (scores.length === 0) {\n return textResult(\n `📊 No sentiment data available for **${token}**.\\n\\n` +\n `Use \\`sentiment_scan\\` to trigger a fresh scan, or wait for the next scheduled scan.`,\n );\n }\n\n const latest = scores[0]!;\n const formatted = formatSentimentScore(latest);\n\n // If we have historical data, show trend\n let trend = '';\n if (scores.length > 1) {\n const oldest = scores[scores.length - 1]!;\n const change = latest.sentimentScore - oldest.sentimentScore;\n trend = `\\n\\n📈 Trend (last ${scores.length} readings): ${change > 0 ? '↑' : '↓'} ${change.toFixed(2)}`;\n }\n\n return textResult(`📊 Sentiment Analysis — ${token}\\n\\n${formatted}${trend}`);\n },\n });\n\n // ─── sentiment_scan ───────────────────────────────────────────────\n api.registerTool({\n name: 'sentiment_scan',\n description:\n 'Trigger a comprehensive sentiment scan across all data sources. ' +\n 'Scans X/Twitter, Telegram groups, and Discord for market sentiment. ' +\n 'Results are stored in market-intel/sentiment/ for later query. ' +\n 'Returns a summary of the scan results.',\n parameters: Type.Object({\n tokens: Type.Optional(Type.Array(Type.String(), {\n description: 'Specific tokens to scan (default: scan trending tokens)',\n })),\n }),\n async execute(_id, params) {\n const { tokens } = params as { tokens?: string[] };\n\n const scanTargets = tokens || ['BTC', 'ETH', 'SOL'];\n\n // Generate sentiment scores\n // In production, this would call Twitter API, Telegram scanner, etc.\n // For now, create placeholder scores that the agent can enrich via LLM analysis\n const results: SentimentScore[] = [];\n\n for (const token of scanTargets) {\n const score: SentimentScore = {\n token,\n sentimentScore: 0,\n sentimentDelta1h: 0,\n mentionCount1h: 0,\n mentionSpike: false,\n topKolSignals: [],\n telegramAlphaHits: 0,\n timestamp: new Date().toISOString(),\n };\n\n saveSentimentScore(score);\n results.push(score);\n }\n\n auditLog('sentiment-radar', 'scan_completed', {\n tokens: scanTargets,\n resultCount: results.length,\n });\n\n const summary = results.map((r) => formatSentimentScore(r)).join('\\n\\n');\n\n return textResult(\n `📊 Sentiment Scan Complete\\n\\n` +\n `Scanned ${scanTargets.length} tokens.\\n\\n${summary}\\n\\n` +\n `_Note: For real-time data, ensure X API and Telegram API credentials are configured._`,\n );\n },\n });\n}\n","/**\n * Whale Tracker Tool — High-level whale behavior analysis.\n *\n * Built on top of the on-chain monitor output, provides:\n * - Recent whale movement summaries\n * - Address behavior profiling (preferred chains, DEXes, win rate)\n * - Accumulation/distribution pattern detection\n * - Cross-correlation signals (multiple whales buying same token)\n */\n\nimport { join } from 'node:path';\nimport { Type } from '@sinclair/typebox';\nimport { readJsonl, readJsonOr, writeJson, ensureDir } from '../lib/file-store.js';\nimport type { WhaleAlert, WhaleProfile } from '../types/intel.js';\nimport type { Chain } from '../types/chain.js';\n\n// ─── Helpers ────────────────────────────────────────────────────────\n\nfunction getWorkspacePath(): string {\n return process.env['CLAWVIF_WORKSPACE_PATH'] || join(\n process.env['HOME'] || process.env['USERPROFILE'] || '/tmp',\n '.clawvif',\n 'workspace',\n );\n}\n\nfunction getWhaleDir(): string {\n return join(getWorkspacePath(), 'memory', 'market-intel', 'whale-movements');\n}\n\nfunction getProfilesDir(): string {\n return join(getWorkspacePath(), 'memory', 'knowledge', 'wallet-profiles');\n}\n\nfunction loadRecentWhaleAlerts(limit: number = 50, chain?: Chain): WhaleAlert[] {\n const today = new Date().toISOString().split('T')[0];\n const yesterday = new Date(Date.now() - 86400000).toISOString().split('T')[0];\n\n const alerts: WhaleAlert[] = [];\n for (const dateStr of [today, yesterday]) {\n const filePath = join(getWhaleDir(), `${dateStr}.jsonl`);\n try {\n alerts.push(...readJsonl<WhaleAlert>(filePath));\n } catch {\n // File may not exist\n }\n }\n\n let filtered = alerts;\n if (chain) filtered = filtered.filter((a) => a.chain === chain);\n\n return filtered.slice(-limit).reverse();\n}\n\nfunction loadWhaleProfile(address: string): WhaleProfile | null {\n const safeName = address.replace(/[^a-zA-Z0-9]/g, '_');\n const profilePath = join(getProfilesDir(), `${safeName}.json`);\n return readJsonOr<WhaleProfile | null>(profilePath, null);\n}\n\nfunction saveWhaleProfile(profile: WhaleProfile): void {\n const safeName = profile.address.replace(/[^a-zA-Z0-9]/g, '_');\n ensureDir(getProfilesDir());\n writeJson(join(getProfilesDir(), `${safeName}.json`), profile);\n}\n\nfunction formatWhaleAlert(alert: WhaleAlert): string {\n const labelStr = alert.addressLabel ? ` (${alert.addressLabel})` : '';\n const patternStr = alert.pattern && alert.pattern !== 'unknown' ? `\\n Pattern: ${alert.pattern}` : '';\n\n return (\n `• 🐋 **${alert.action}**\\n` +\n ` Address: \\`${alert.address}\\`${labelStr}\\n` +\n ` Token: ${alert.token.symbol} — ${alert.token.amount}\\n` +\n ` Value: $${alert.valueUsd.toLocaleString()}\\n` +\n ` Chain: ${alert.chain} | TX: \\`${alert.txHash.slice(0, 16)}...\\`${patternStr}\\n` +\n ` ${alert.timestamp}`\n );\n}\n\nfunction formatWhaleProfile(profile: WhaleProfile): string {\n const labelStr = profile.label ? ` (${profile.label})` : '';\n\n let text =\n `🐋 Whale Profile: \\`${profile.address}\\`${labelStr}\\n\\n` +\n `📊 Statistics:\\n` +\n ` Active chains: ${profile.activeChains.join(', ')}\\n` +\n ` Preferred DEXes: ${profile.preferredDexes.join(', ') || 'Unknown'}\\n` +\n ` Avg holding period: ${profile.avgHoldingPeriodHours.toFixed(1)}h\\n` +\n ` Win rate: ${(profile.winRate * 100).toFixed(1)}%\\n` +\n ` Total trades tracked: ${profile.totalTrades}\\n` +\n ` Last updated: ${profile.lastUpdated}`;\n\n if (profile.recentActivity.length > 0) {\n const activities = profile.recentActivity.slice(0, 5).map(\n (a) => ` • ${a.action} ${a.token} — $${a.valueUsd.toLocaleString()} (${a.timestamp})`,\n );\n text += `\\n\\n📋 Recent Activity:\\n${activities.join('\\n')}`;\n }\n\n return text;\n}\n\n// ─── Tool Registration ──────────────────────────────────────────────\n\ninterface PluginApi {\n registerTool(config: {\n name: string;\n description: string;\n parameters: unknown;\n execute: (id: string, params: Record<string, unknown>) => Promise<{\n content: Array<{ type: string; text: string }>;\n }>;\n }, options?: { optional?: boolean }): void;\n}\n\nfunction textResult(text: string) {\n return { content: [{ type: 'text' as const, text }] };\n}\n\nexport function registerWhaleTools(api: PluginApi): void {\n\n // ─── whale_query ──────────────────────────────────────────────────\n api.registerTool({\n name: 'whale_query',\n description:\n 'Query recent whale movements. Returns large transactions from tracked wallets ' +\n 'with pattern analysis (accumulation/distribution detection).',\n parameters: Type.Object({\n chain: Type.Optional(Type.Union([\n Type.Literal('ethereum'), Type.Literal('base'), Type.Literal('arbitrum'),\n Type.Literal('solana'), Type.Literal('polygon'), Type.Literal('bsc'),\n ], {\n description: 'Filter by chain (optional)',\n })),\n timeframe: Type.Optional(Type.Union([\n Type.Literal('1h'), Type.Literal('24h'), Type.Literal('7d'),\n ], {\n description: 'Time window for query (default: 24h)',\n })),\n limit: Type.Optional(Type.Number({\n description: 'Number of results (default: 20, max: 100)',\n minimum: 1,\n maximum: 100,\n })),\n }),\n async execute(_id, params) {\n const { chain, limit = 20 } = params as {\n chain?: Chain;\n timeframe?: string;\n limit?: number;\n };\n\n const alerts = loadRecentWhaleAlerts(Math.min(limit, 100), chain);\n\n if (alerts.length === 0) {\n return textResult(\n '🐋 No whale movements detected in the selected timeframe.\\n\\n' +\n 'Ensure on-chain monitoring is active and addresses are in the watchlist.',\n );\n }\n\n const formatted = alerts.map(formatWhaleAlert);\n const chainLabel = chain || 'all chains';\n\n return textResult(\n `🐋 Whale Movements (${chainLabel})\\n\\n` +\n `${formatted.join('\\n\\n')}\\n\\n` +\n `Showing ${alerts.length} movements.`,\n );\n },\n });\n\n // ─── whale_profile ────────────────────────────────────────────────\n api.registerTool({\n name: 'whale_profile',\n description:\n 'Get or build a behavior profile for a specific address. Shows preferred chains, ' +\n 'DEXes, average holding period, historical win rate, and recent activity.',\n parameters: Type.Object({\n address: Type.String({\n description: 'Wallet address to profile',\n }),\n }),\n async execute(_id, params) {\n const { address } = params as { address: string };\n\n // Try to load existing profile\n let profile = loadWhaleProfile(address);\n\n if (!profile) {\n // Create a new empty profile\n profile = {\n address,\n activeChains: [],\n preferredDexes: [],\n avgHoldingPeriodHours: 0,\n winRate: 0,\n totalTrades: 0,\n recentActivity: [],\n lastUpdated: new Date().toISOString(),\n };\n saveWhaleProfile(profile);\n\n return textResult(\n `🐋 New Profile Created for \\`${address}\\`\\n\\n` +\n `No historical data yet. The profile will be enriched as on-chain activity is monitored.\\n\\n` +\n `To build a profile faster, add this address to the watchlist:\\n` +\n `\\`chain_subscribe add ${address}\\``,\n );\n }\n\n return textResult(formatWhaleProfile(profile));\n },\n });\n}\n","/**\n * Telegram Group Scanner Service — Deep scanning of Telegram groups\n * for alpha calls, contract addresses, and bot signals.\n *\n * Uses Telegram MTProto API via session string authentication.\n * Falls back to HTTPS Bot API when MTProto is unavailable.\n *\n * Architecture:\n * - Subscriptions stored in memory + persisted to tg-scanner-state.json\n * - Messages processed through CA extraction pipeline\n * - First-call detection via JSONL history\n * - Call performance tracked with entry price snapshots\n */\n\nimport { join } from 'node:path';\nimport {\n readJsonOr,\n writeJson,\n readJsonl,\n appendJsonl,\n ensureDir,\n} from '../lib/file-store.js';\nimport { auditLog } from '../lib/audit-logger.js';\nimport type { TgGroupMessage } from '../types/intel.js';\n\n// ─── Constants ──────────────────────────────────────────────────────\n\nconst EVM_CA_REGEX = /\\b(0x[a-fA-F0-9]{40})\\b/g;\nconst SOLANA_CA_REGEX = /\\b([1-9A-HJ-NP-Za-km-z]{32,44})\\b/g;\nconst TICKER_REGEX = /\\$([A-Z]{2,10})\\b/g;\nconst URL_REGEX = /https?:\\/\\/[^\\s<>)\"]+/g;\n\nconst KNOWN_BOT_PATTERNS: Array<{ name: string; pattern: RegExp }> = [\n { name: 'Maestro', pattern: /maestro|🤖.*sniper|auto.?buy/i },\n { name: 'BananaGun', pattern: /banana\\s?gun|🍌.*snipe/i },\n { name: 'Unibot', pattern: /unibot|📈.*limit.*order/i },\n { name: 'BONKbot', pattern: /bonkbot|🐶.*swap/i },\n { name: 'Trojan', pattern: /trojan.*bot|🏛️.*swap/i },\n { name: 'SolTradingBot', pattern: /sol.*trading.*bot/i },\n { name: 'BullX', pattern: /bullx|🐂.*trade/i },\n { name: 'Photon', pattern: /photon.*bot|⚡.*swap/i },\n];\n\nconst SOLANA_ADDR_EXCLUDED = new Set([\n 'So11111111111111111111111111111111111111112',\n '11111111111111111111111111111111',\n 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA',\n 'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL',\n 'SysvarRent111111111111111111111111111111111',\n 'SysvarC1ock11111111111111111111111111111111',\n]);\n\n// ─── State Types ────────────────────────────────────────────────────\n\nexport interface TgSubscription {\n groupId: string;\n groupName: string;\n type: 'alpha' | 'dev' | 'community' | 'signal';\n enabled: boolean;\n subscribedAt: string;\n lastMessageAt: string | null;\n messageCount: number;\n}\n\nexport interface TgScannerState {\n subscriptions: TgSubscription[];\n totalMessagesProcessed: number;\n totalCaExtracted: number;\n lastScanAt: string | null;\n}\n\nexport interface CaRecord {\n contractAddress: string;\n chain: 'evm' | 'solana';\n firstSeenAt: string;\n firstSeenGroupId: string;\n firstSeenGroupName: string;\n firstSeenBy: string;\n mentionCount: number;\n groups: string[];\n entryPriceUsd: number | null;\n currentPriceUsd: number | null;\n performancePct: number | null;\n lastUpdated: string;\n}\n\nexport interface CallPerformance {\n contractAddress: string;\n chain: 'evm' | 'solana';\n callerName: string;\n groupId: string;\n groupName: string;\n callTimestamp: string;\n entryPriceUsd: number | null;\n peakPriceUsd: number | null;\n currentPriceUsd: number | null;\n returnPct: number | null;\n peakReturnPct: number | null;\n}\n\ninterface BotDetectionResult {\n isBot: boolean;\n botName: string | null;\n confidence: number;\n}\n\n// ─── Workspace Paths ────────────────────────────────────────────────\n\nfunction getWorkspacePath(): string {\n return (\n process.env['CLAWVIF_WORKSPACE_PATH'] ||\n join(\n process.env['HOME'] || process.env['USERPROFILE'] || '/tmp',\n '.clawvif',\n 'workspace',\n )\n );\n}\n\nfunction getScannerDir(): string {\n return join(getWorkspacePath(), 'memory', 'market-intel', 'tg-scanner');\n}\n\nfunction getStatePath(): string {\n return join(getScannerDir(), 'scanner-state.json');\n}\n\nfunction getCaRegistryPath(): string {\n return join(getScannerDir(), 'ca-registry.json');\n}\n\nfunction getMessagesLogPath(): string {\n const today = new Date().toISOString().split('T')[0];\n return join(getScannerDir(), 'messages', `${today}.jsonl`);\n}\n\nfunction getFirstCallsLogPath(): string {\n return join(getScannerDir(), 'first-calls.jsonl');\n}\n\n// ─── State Management ───────────────────────────────────────────────\n\nexport function loadScannerState(): TgScannerState {\n return readJsonOr<TgScannerState>(getStatePath(), {\n subscriptions: [],\n totalMessagesProcessed: 0,\n totalCaExtracted: 0,\n lastScanAt: null,\n });\n}\n\nexport function saveScannerState(state: TgScannerState): void {\n ensureDir(getScannerDir());\n writeJson(getStatePath(), state);\n}\n\nfunction loadCaRegistry(): Record<string, CaRecord> {\n return readJsonOr<Record<string, CaRecord>>(getCaRegistryPath(), {});\n}\n\nfunction saveCaRegistry(registry: Record<string, CaRecord>): void {\n ensureDir(getScannerDir());\n writeJson(getCaRegistryPath(), registry);\n}\n\n// ─── Core Functions (exported for testing) ──────────────────────────\n\n/**\n * Extract contract addresses from a message text.\n */\nexport function extractContractAddresses(text: string): Array<{ address: string; chain: 'evm' | 'solana' }> {\n const results: Array<{ address: string; chain: 'evm' | 'solana' }> = [];\n const seen = new Set<string>();\n\n for (const match of text.matchAll(EVM_CA_REGEX)) {\n const addr = match[1]!;\n if (!seen.has(addr.toLowerCase())) {\n seen.add(addr.toLowerCase());\n results.push({ address: addr, chain: 'evm' });\n }\n }\n\n for (const match of text.matchAll(SOLANA_CA_REGEX)) {\n const addr = match[1]!;\n if (\n !seen.has(addr) &&\n !SOLANA_ADDR_EXCLUDED.has(addr) &&\n /[A-Z]/.test(addr) &&\n /[a-z]/.test(addr) &&\n /\\d/.test(addr) &&\n addr.length >= 32\n ) {\n seen.add(addr);\n results.push({ address: addr, chain: 'solana' });\n }\n }\n\n return results;\n}\n\n/**\n * Extract ticker symbols ($XXX) from message text.\n */\nexport function extractTickers(text: string): string[] {\n const tickers: string[] = [];\n const seen = new Set<string>();\n for (const match of text.matchAll(TICKER_REGEX)) {\n const ticker = match[1]!.toUpperCase();\n if (!seen.has(ticker)) {\n seen.add(ticker);\n tickers.push(ticker);\n }\n }\n return tickers;\n}\n\n/**\n * Extract URLs from message text.\n */\nexport function extractUrls(text: string): string[] {\n const urls: string[] = [];\n for (const match of text.matchAll(URL_REGEX)) {\n urls.push(match[0]);\n }\n return urls;\n}\n\n/**\n * Detect if a message is from a known trading bot.\n */\nexport function detectBotSignal(text: string): BotDetectionResult {\n for (const { name, pattern } of KNOWN_BOT_PATTERNS) {\n if (pattern.test(text)) {\n return { isBot: true, botName: name, confidence: 0.85 };\n }\n }\n\n const botIndicators = [\n /\\b(buy|sell)\\s+\\d+(\\.\\d+)?\\s+(SOL|ETH|BNB)\\b/i,\n /tx:\\s*[A-Za-z0-9]{40,}/i,\n /slippage:\\s*\\d+%/i,\n /gas:\\s*\\d+\\s*gwei/i,\n ];\n\n let indicatorCount = 0;\n for (const indicator of botIndicators) {\n if (indicator.test(text)) indicatorCount++;\n }\n\n if (indicatorCount >= 2) {\n return { isBot: true, botName: 'unknown', confidence: 0.6 };\n }\n\n return { isBot: false, botName: null, confidence: 0 };\n}\n\n/**\n * Process a message: extract CAs, tickers, detect bots, and update state.\n */\nexport function processMessage(\n groupId: string,\n groupName: string,\n senderId: string,\n senderName: string,\n text: string,\n timestamp?: string,\n): {\n message: TgGroupMessage;\n newCas: Array<{ address: string; chain: 'evm' | 'solana'; isFirstCall: boolean }>;\n botDetection: BotDetectionResult;\n} {\n const ts = timestamp || new Date().toISOString();\n const cas = extractContractAddresses(text);\n const tickers = extractTickers(text);\n const urls = extractUrls(text);\n const botDetection = detectBotSignal(text);\n\n const message: TgGroupMessage = {\n groupId,\n groupName,\n senderId,\n senderName,\n contractAddresses: cas.map((c) => c.address),\n tickers,\n urls,\n isBot: botDetection.isBot,\n text,\n timestamp: ts,\n };\n\n ensureDir(join(getScannerDir(), 'messages'));\n appendJsonl(getMessagesLogPath(), message);\n\n const registry = loadCaRegistry();\n const newCas: Array<{ address: string; chain: 'evm' | 'solana'; isFirstCall: boolean }> = [];\n\n for (const ca of cas) {\n const key = ca.address.toLowerCase();\n const existing = registry[key];\n\n if (!existing) {\n registry[key] = {\n contractAddress: ca.address,\n chain: ca.chain,\n firstSeenAt: ts,\n firstSeenGroupId: groupId,\n firstSeenGroupName: groupName,\n firstSeenBy: senderName,\n mentionCount: 1,\n groups: [groupId],\n entryPriceUsd: null,\n currentPriceUsd: null,\n performancePct: null,\n lastUpdated: ts,\n };\n\n appendJsonl(getFirstCallsLogPath(), {\n contractAddress: ca.address,\n chain: ca.chain,\n groupId,\n groupName,\n callerName: senderName,\n timestamp: ts,\n });\n\n newCas.push({ ...ca, isFirstCall: true });\n } else {\n existing.mentionCount++;\n if (!existing.groups.includes(groupId)) {\n existing.groups.push(groupId);\n }\n existing.lastUpdated = ts;\n newCas.push({ ...ca, isFirstCall: false });\n }\n }\n\n saveCaRegistry(registry);\n\n const state = loadScannerState();\n state.totalMessagesProcessed++;\n state.totalCaExtracted += cas.length;\n state.lastScanAt = ts;\n\n const sub = state.subscriptions.find((s) => s.groupId === groupId);\n if (sub) {\n sub.lastMessageAt = ts;\n sub.messageCount++;\n }\n saveScannerState(state);\n\n return { message, newCas, botDetection };\n}\n\n/**\n * Subscribe to a Telegram group for scanning.\n */\nexport function subscribeGroup(\n groupId: string,\n groupName: string,\n type: 'alpha' | 'dev' | 'community' | 'signal',\n): TgSubscription {\n const state = loadScannerState();\n const existing = state.subscriptions.find((s) => s.groupId === groupId);\n\n if (existing) {\n existing.type = type;\n existing.enabled = true;\n saveScannerState(state);\n auditLog('tg-scanner', 'group_updated', { groupId, groupName, type });\n return existing;\n }\n\n const sub: TgSubscription = {\n groupId,\n groupName,\n type,\n enabled: true,\n subscribedAt: new Date().toISOString(),\n lastMessageAt: null,\n messageCount: 0,\n };\n\n state.subscriptions.push(sub);\n saveScannerState(state);\n auditLog('tg-scanner', 'group_subscribed', { groupId, groupName, type });\n return sub;\n}\n\n/**\n * Check if a CA is a first call in the registry.\n */\nexport function checkFirstCall(\n contractAddress: string,\n): { isFirstCall: boolean; record: CaRecord | null } {\n const registry = loadCaRegistry();\n const key = contractAddress.toLowerCase();\n const record = registry[key] || null;\n\n if (!record) {\n return { isFirstCall: true, record: null };\n }\n\n return { isFirstCall: record.mentionCount <= 1, record };\n}\n\n/**\n * Get call performance data for a contract address.\n */\nexport function getCallPerformance(contractAddress: string): CallPerformance[] {\n const firstCalls = readJsonl<{\n contractAddress: string;\n chain: string;\n groupId: string;\n groupName: string;\n callerName: string;\n timestamp: string;\n }>(getFirstCallsLogPath());\n\n const registry = loadCaRegistry();\n const key = contractAddress.toLowerCase();\n const caRecord = registry[key];\n\n return firstCalls\n .filter((fc) => fc.contractAddress.toLowerCase() === key)\n .map((fc) => ({\n contractAddress: fc.contractAddress,\n chain: (fc.chain || 'solana') as 'evm' | 'solana',\n callerName: fc.callerName,\n groupId: fc.groupId,\n groupName: fc.groupName,\n callTimestamp: fc.timestamp,\n entryPriceUsd: caRecord?.entryPriceUsd ?? null,\n peakPriceUsd: null,\n currentPriceUsd: caRecord?.currentPriceUsd ?? null,\n returnPct: caRecord?.performancePct ?? null,\n peakReturnPct: null,\n }));\n}\n\n/**\n * Get caller stats for a group or specific sender.\n */\nexport function getCallerStats(\n groupId?: string,\n): Array<{ callerName: string; totalCalls: number; groupId: string }> {\n const firstCalls = readJsonl<{\n callerName: string;\n groupId: string;\n timestamp: string;\n }>(getFirstCallsLogPath());\n\n const filtered = groupId\n ? firstCalls.filter((fc) => fc.groupId === groupId)\n : firstCalls;\n\n const statsMap = new Map<string, { callerName: string; totalCalls: number; groupId: string }>();\n for (const fc of filtered) {\n const key = `${fc.callerName}:${fc.groupId}`;\n const existing = statsMap.get(key);\n if (existing) {\n existing.totalCalls++;\n } else {\n statsMap.set(key, { callerName: fc.callerName, totalCalls: 1, groupId: fc.groupId });\n }\n }\n\n return Array.from(statsMap.values()).sort((a, b) => b.totalCalls - a.totalCalls);\n}\n\n/**\n * Update a CA record's price data (for performance tracking).\n */\nexport function updateCaPrice(\n contractAddress: string,\n priceUsd: number,\n): CaRecord | null {\n const registry = loadCaRegistry();\n const key = contractAddress.toLowerCase();\n const record = registry[key];\n if (!record) return null;\n\n if (record.entryPriceUsd === null) {\n record.entryPriceUsd = priceUsd;\n }\n\n record.currentPriceUsd = priceUsd;\n if (record.entryPriceUsd > 0) {\n record.performancePct =\n ((priceUsd - record.entryPriceUsd) / record.entryPriceUsd) * 100;\n }\n record.lastUpdated = new Date().toISOString();\n\n saveCaRegistry(registry);\n return record;\n}\n","/**\n * Telegram Group Scanner Tool — Deep scanning of Telegram groups\n * for alpha calls, contract addresses, and bot signal detection.\n *\n * Registers 5 tools as specified in tg-group-scanner SKILL.md:\n * - tg_scanner_subscribe\n * - tg_scanner_extract_ca\n * - tg_scanner_first_call_check\n * - tg_scanner_call_performance\n * - tg_scanner_detect_bot_signal\n */\n\nimport { Type } from '@sinclair/typebox';\nimport {\n subscribeGroup,\n processMessage,\n checkFirstCall,\n getCallPerformance,\n getCallerStats,\n detectBotSignal,\n extractContractAddresses,\n extractTickers,\n loadScannerState,\n} from '../services/telegram-scanner.js';\nimport { auditLog } from '../lib/audit-logger.js';\n\n// ─── Tool Registration ──────────────────────────────────────────────\n\ninterface PluginApi {\n registerTool(\n config: {\n name: string;\n description: string;\n parameters: unknown;\n execute: (\n id: string,\n params: Record<string, unknown>,\n ) => Promise<{\n content: Array<{ type: string; text: string }>;\n }>;\n },\n options?: { optional?: boolean },\n ): void;\n}\n\nfunction textResult(text: string) {\n return { content: [{ type: 'text' as const, text }] };\n}\n\nexport function registerTgScannerTools(api: PluginApi): void {\n // ─── tg_scanner_subscribe ──────────────────────────────────────────\n api.registerTool({\n name: 'tg_scanner_subscribe',\n description:\n 'Subscribe to a Telegram group for deep scanning. Monitors messages for contract ' +\n 'addresses, alpha calls, and bot signals. Supports group types: alpha, dev, community, signal.',\n parameters: Type.Object({\n groupId: Type.String({\n description: 'Telegram group/channel ID or @username',\n }),\n groupName: Type.String({\n description: 'Display name for the group',\n }),\n type: Type.Union(\n [\n Type.Literal('alpha'),\n Type.Literal('dev'),\n Type.Literal('community'),\n Type.Literal('signal'),\n ],\n { description: 'Group type classification' },\n ),\n }),\n async execute(_id, params) {\n const { groupId, groupName, type } = params as {\n groupId: string;\n groupName: string;\n type: 'alpha' | 'dev' | 'community' | 'signal';\n };\n\n const sub = subscribeGroup(groupId, groupName, type);\n const state = loadScannerState();\n const totalSubs = state.subscriptions.filter((s) => s.enabled).length;\n\n const hasCreds =\n !!process.env['TG_API_ID'] &&\n !!process.env['TG_API_HASH'] &&\n !!process.env['TG_SESSION_STRING'];\n\n let text =\n `✅ Subscribed to Telegram group\\n\\n` +\n `Group: **${groupName}**\\n` +\n `ID: \\`${groupId}\\`\\n` +\n `Type: ${type}\\n` +\n `Subscribed at: ${sub.subscribedAt}\\n` +\n `Total active subscriptions: ${totalSubs}\\n`;\n\n if (!hasCreds) {\n text +=\n `\\n⚠️ MTProto credentials not configured.\\n` +\n `Set TG_API_ID, TG_API_HASH, and TG_SESSION_STRING in .env for live scanning.\\n` +\n `Manual message processing via tg_scanner_extract_ca is still available.`;\n } else {\n text += `\\n🟢 MTProto credentials detected — live scanning available.`;\n }\n\n return textResult(text);\n },\n });\n\n // ─── tg_scanner_extract_ca ─────────────────────────────────────────\n api.registerTool({\n name: 'tg_scanner_extract_ca',\n description:\n 'Extract contract addresses from a Telegram message text. Detects EVM (0x...) and ' +\n 'Solana addresses, extracts ticker symbols ($XXX), URLs, and detects bot signals. ' +\n 'Automatically checks if any CA is a first call.',\n parameters: Type.Object({\n text: Type.String({\n description: 'Message text to analyze',\n }),\n groupId: Type.Optional(\n Type.String({\n description: 'Group ID the message is from (for tracking)',\n }),\n ),\n groupName: Type.Optional(\n Type.String({\n description: 'Group display name',\n }),\n ),\n senderId: Type.Optional(\n Type.String({ description: 'Sender user ID' }),\n ),\n senderName: Type.Optional(\n Type.String({ description: 'Sender display name' }),\n ),\n }),\n async execute(_id, params) {\n const {\n text: msgText,\n groupId = 'manual',\n groupName = 'Manual Input',\n senderId = 'unknown',\n senderName = 'Unknown',\n } = params as {\n text: string;\n groupId?: string;\n groupName?: string;\n senderId?: string;\n senderName?: string;\n };\n\n const { message, newCas, botDetection } = processMessage(\n groupId,\n groupName,\n senderId,\n senderName,\n msgText,\n );\n\n let resultText = `🔍 CA Extraction Results\\n\\n`;\n\n if (newCas.length === 0) {\n resultText += `No contract addresses found in the message.\\n`;\n } else {\n resultText += `Found ${newCas.length} contract address(es):\\n\\n`;\n for (const ca of newCas) {\n const firstCallTag = ca.isFirstCall ? ' 🆕 FIRST CALL' : '';\n resultText +=\n ` • \\`${ca.address}\\` [${ca.chain.toUpperCase()}]${firstCallTag}\\n`;\n }\n }\n\n if (message.tickers.length > 0) {\n resultText += `\\nTickers: ${message.tickers.map((t) => `$${t}`).join(', ')}\\n`;\n }\n\n if (botDetection.isBot) {\n resultText +=\n `\\n🤖 Bot signal detected: **${botDetection.botName}** ` +\n `(confidence: ${(botDetection.confidence * 100).toFixed(0)}%)\\n`;\n }\n\n if (message.urls.length > 0) {\n resultText += `\\nURLs: ${message.urls.length} link(s) found\\n`;\n }\n\n auditLog('tg-scanner', 'ca_extracted', {\n groupId,\n casFound: newCas.length,\n firstCalls: newCas.filter((c) => c.isFirstCall).length,\n isBot: botDetection.isBot,\n });\n\n return textResult(resultText);\n },\n });\n\n // ─── tg_scanner_first_call_check ───────────────────────────────────\n api.registerTool({\n name: 'tg_scanner_first_call_check',\n description:\n 'Check if a contract address is a first call (never seen before in any alpha group). ' +\n 'Returns first-call status and historical data if the CA has been seen before.',\n parameters: Type.Object({\n contractAddress: Type.String({\n description: 'Contract address to check (EVM 0x... or Solana base58)',\n }),\n }),\n async execute(_id, params) {\n const { contractAddress } = params as { contractAddress: string };\n\n const { isFirstCall, record } = checkFirstCall(contractAddress);\n\n if (isFirstCall && !record) {\n return textResult(\n `🆕 **FIRST CALL** — This CA has never been seen before!\\n\\n` +\n `CA: \\`${contractAddress}\\`\\n` +\n `Status: Never mentioned in any tracked group\\n\\n` +\n `⚡ Consider running risk_check_contract to verify safety before any action.`,\n );\n }\n\n if (isFirstCall && record) {\n return textResult(\n `🆕 **FIRST CALL** — Only 1 mention so far\\n\\n` +\n `CA: \\`${record.contractAddress}\\`\\n` +\n `Chain: ${record.chain}\\n` +\n `First seen: ${record.firstSeenAt}\\n` +\n `First seen in: ${record.firstSeenGroupName}\\n` +\n `First called by: ${record.firstSeenBy}\\n` +\n `Total mentions: ${record.mentionCount}\\n` +\n `Groups: ${record.groups.length}`,\n );\n }\n\n if (record) {\n const perfText =\n record.performancePct !== null\n ? `${record.performancePct >= 0 ? '+' : ''}${record.performancePct.toFixed(1)}%`\n : 'N/A';\n\n return textResult(\n `📋 CA already tracked — Not a first call\\n\\n` +\n `CA: \\`${record.contractAddress}\\`\\n` +\n `Chain: ${record.chain}\\n` +\n `First seen: ${record.firstSeenAt}\\n` +\n `First seen in: ${record.firstSeenGroupName}\\n` +\n `First called by: ${record.firstSeenBy}\\n` +\n `Total mentions: ${record.mentionCount}\\n` +\n `Groups mentioning: ${record.groups.length}\\n` +\n `Entry price: ${record.entryPriceUsd !== null ? `$${record.entryPriceUsd}` : 'N/A'}\\n` +\n `Performance: ${perfText}`,\n );\n }\n\n return textResult(\n `🆕 **FIRST CALL** — CA not in registry\\n\\nCA: \\`${contractAddress}\\``,\n );\n },\n });\n\n // ─── tg_scanner_call_performance ───────────────────────────────────\n api.registerTool({\n name: 'tg_scanner_call_performance',\n description:\n 'Query call performance for a contract address or a group. Shows price change since ' +\n 'first call, caller stats, and historical performance data.',\n parameters: Type.Object({\n contractAddress: Type.Optional(\n Type.String({\n description: 'Specific CA to check performance for',\n }),\n ),\n groupId: Type.Optional(\n Type.String({\n description: 'Group ID to get caller stats for',\n }),\n ),\n }),\n async execute(_id, params) {\n const { contractAddress, groupId } = params as {\n contractAddress?: string;\n groupId?: string;\n };\n\n if (contractAddress) {\n const performances = getCallPerformance(contractAddress);\n\n if (performances.length === 0) {\n return textResult(\n `📊 No call performance data for \\`${contractAddress}\\`\\n\\n` +\n `This CA has not been tracked through the scanner yet.`,\n );\n }\n\n let text = `📊 Call Performance: \\`${contractAddress}\\`\\n\\n`;\n for (const perf of performances) {\n const returnText =\n perf.returnPct !== null\n ? `${perf.returnPct >= 0 ? '🟢 +' : '🔴 '}${perf.returnPct.toFixed(1)}%`\n : '⏳ Pending';\n\n text +=\n `Caller: **${perf.callerName}** in ${perf.groupName}\\n` +\n `Called at: ${perf.callTimestamp}\\n` +\n `Entry: ${perf.entryPriceUsd !== null ? `$${perf.entryPriceUsd}` : 'N/A'}\\n` +\n `Current: ${perf.currentPriceUsd !== null ? `$${perf.currentPriceUsd}` : 'N/A'}\\n` +\n `Return: ${returnText}\\n\\n`;\n }\n\n return textResult(text);\n }\n\n const stats = getCallerStats(groupId);\n\n if (stats.length === 0) {\n return textResult(\n `📊 No caller stats available${groupId ? ` for group ${groupId}` : ''}.\\n\\n` +\n `Process some messages with tg_scanner_extract_ca first.`,\n );\n }\n\n let text = `📊 Caller Stats${groupId ? ` — Group ${groupId}` : ''}\\n\\n`;\n text += `Top callers by first-call count:\\n\\n`;\n\n for (const stat of stats.slice(0, 20)) {\n text += ` • **${stat.callerName}**: ${stat.totalCalls} first calls (${stat.groupId})\\n`;\n }\n\n return textResult(text);\n },\n });\n\n // ─── tg_scanner_detect_bot_signal ──────────────────────────────────\n api.registerTool({\n name: 'tg_scanner_detect_bot_signal',\n description:\n 'Detect if a message is from a known trading bot (Maestro, BananaGun, Unibot, etc.). ' +\n 'Also extracts any contract addresses found in the bot message.',\n parameters: Type.Object({\n text: Type.String({\n description: 'Message text to analyze for bot patterns',\n }),\n }),\n async execute(_id, params) {\n const { text: msgText } = params as { text: string };\n\n const detection = detectBotSignal(msgText);\n const cas = extractContractAddresses(msgText);\n const tickers = extractTickers(msgText);\n\n let resultText = `🤖 Bot Signal Detection\\n\\n`;\n\n if (detection.isBot) {\n resultText +=\n `**Bot detected: ${detection.botName}**\\n` +\n `Confidence: ${(detection.confidence * 100).toFixed(0)}%\\n\\n`;\n } else {\n resultText += `No bot pattern detected — likely a human message.\\n\\n`;\n }\n\n if (cas.length > 0) {\n resultText += `Contract addresses in message:\\n`;\n for (const ca of cas) {\n resultText += ` • \\`${ca.address}\\` [${ca.chain.toUpperCase()}]\\n`;\n }\n }\n\n if (tickers.length > 0) {\n resultText += `\\nTickers: ${tickers.map((t) => `$${t}`).join(', ')}\\n`;\n }\n\n resultText +=\n `\\n💡 Bot signals can indicate trending tokens but may have higher risk ` +\n `due to front-running and sandwich attacks.`;\n\n return textResult(resultText);\n },\n });\n}\n","/**\n * Telegram Listener Service — Bot API based message listener.\n *\n * Connects to Telegram using a Bot Token (from @BotFather) and receives\n * messages via long polling. The bot must be added to groups/channels\n * as an admin to receive messages.\n *\n * Forwards incoming messages from subscribed groups/channels to the\n * existing telegram-scanner pipeline for CA extraction, bot detection, etc.\n */\n\nimport { Bot, type Context } from 'grammy';\nimport { auditLog } from '../lib/audit-logger.js';\nimport { processMessage, loadScannerState } from './telegram-scanner.js';\n\n// ─── Types ───────────────────────────────────────────────────────────\n\nexport interface TgListenerConfig {\n botToken: string;\n}\n\nexport interface TgListenerStats {\n running: boolean;\n connectedAt: string | null;\n messagesReceived: number;\n messagesProcessed: number;\n errors: number;\n subscribedGroupIds: string[];\n}\n\ntype MessageHandler = (event: {\n groupId: string;\n groupName: string;\n senderId: string;\n senderName: string;\n text: string;\n timestamp: string;\n}) => void;\n\n// ─── Singleton State ─────────────────────────────────────────────────\n\nlet bot: Bot | null = null;\nlet stats: TgListenerStats = {\n running: false,\n connectedAt: null,\n messagesReceived: 0,\n messagesProcessed: 0,\n errors: 0,\n subscribedGroupIds: [],\n};\nlet externalHandlers: MessageHandler[] = [];\n\n// ─── Core Logic ──────────────────────────────────────────────────────\n\nfunction getSubscribedGroupIds(): Set<string> {\n const state = loadScannerState();\n return new Set(\n state.subscriptions.filter((s) => s.enabled).map((s) => s.groupId),\n );\n}\n\nfunction isSubscribed(chatId: string): boolean {\n const subscribed = getSubscribedGroupIds();\n if (subscribed.size === 0) return true;\n return (\n subscribed.has(chatId) ||\n subscribed.has(`-${chatId}`) ||\n subscribed.has(`-100${chatId}`)\n );\n}\n\nfunction handleMessage(ctx: Context): void {\n stats.messagesReceived++;\n\n try {\n const msg = ctx.message ?? ctx.channelPost;\n if (!msg) return;\n\n const text = msg.text ?? msg.caption ?? '';\n if (!text.trim()) return;\n\n const chatId = String(msg.chat.id);\n const strippedChatId = chatId.replace(/^-100/, '');\n if (!isSubscribed(strippedChatId) && !isSubscribed(chatId)) return;\n\n const groupName =\n 'title' in msg.chat && msg.chat.title\n ? msg.chat.title\n : `chat:${chatId}`;\n\n let senderId = 'unknown';\n let senderName = 'Unknown';\n if (msg.from) {\n senderId = String(msg.from.id);\n senderName = [msg.from.first_name, msg.from.last_name]\n .filter(Boolean)\n .join(' ');\n }\n\n const timestamp = new Date(msg.date * 1000).toISOString();\n\n processMessage(chatId, groupName, senderId, senderName, text, timestamp);\n stats.messagesProcessed++;\n\n for (const handler of externalHandlers) {\n try {\n handler({ groupId: chatId, groupName, senderId, senderName, text, timestamp });\n } catch {\n // external handler errors should not break the pipeline\n }\n }\n } catch (err) {\n stats.errors++;\n auditLog('tg-listener', 'message_error', {\n error: err instanceof Error ? err.message : String(err),\n });\n }\n}\n\n// ─── Public API ──────────────────────────────────────────────────────\n\nexport function onTgMessage(handler: MessageHandler): () => void {\n externalHandlers.push(handler);\n return () => {\n externalHandlers = externalHandlers.filter((h) => h !== handler);\n };\n}\n\nexport async function startTgListener(config: TgListenerConfig): Promise<void> {\n if (bot && stats.running) {\n auditLog('tg-listener', 'start_skipped', { reason: 'already_running' });\n return;\n }\n\n bot = new Bot(config.botToken);\n\n bot.on('message:text', handleMessage);\n bot.on('message:caption', handleMessage);\n bot.on('channel_post:text', handleMessage);\n bot.on('channel_post:caption', handleMessage);\n\n bot.catch((err) => {\n stats.errors++;\n auditLog('tg-listener', 'bot_error', {\n error: err.message || String(err),\n });\n });\n\n // Non-blocking start — bot.start() runs long polling in the background\n bot.start({\n onStart: () => {\n auditLog('tg-listener', 'polling_started', {});\n },\n });\n\n stats = {\n running: true,\n connectedAt: new Date().toISOString(),\n messagesReceived: 0,\n messagesProcessed: 0,\n errors: 0,\n subscribedGroupIds: Array.from(getSubscribedGroupIds()),\n };\n\n auditLog('tg-listener', 'started', {\n subscribedGroups: stats.subscribedGroupIds.length,\n });\n}\n\nexport async function stopTgListener(): Promise<void> {\n if (bot) {\n try {\n await bot.stop();\n } catch {\n // best-effort stop\n }\n bot = null;\n }\n\n stats.running = false;\n auditLog('tg-listener', 'stopped', {\n totalReceived: stats.messagesReceived,\n totalProcessed: stats.messagesProcessed,\n totalErrors: stats.errors,\n });\n}\n\nexport function getTgListenerStats(): TgListenerStats {\n if (stats.running) {\n stats.subscribedGroupIds = Array.from(getSubscribedGroupIds());\n }\n return { ...stats };\n}\n\nexport function isTgListenerRunning(): boolean {\n return stats.running;\n}\n\n/**\n * Auto-start if TG_BOT_TOKEN is configured. Called from plugin entry.\n * Failures are logged but never thrown (plugin must load successfully).\n */\nexport async function autoStartTgListener(): Promise<void> {\n const botToken = process.env['TG_BOT_TOKEN'];\n\n if (!botToken) {\n auditLog('tg-listener', 'auto_start_skipped', {\n reason: 'missing_bot_token',\n });\n return;\n }\n\n try {\n await startTgListener({ botToken });\n } catch (err) {\n auditLog('tg-listener', 'auto_start_failed', {\n error: err instanceof Error ? err.message : String(err),\n });\n }\n}\n","/**\n * Telegram Listener Tool — Registers tools for controlling the Bot API\n * message listener via the OpenClaw plugin API.\n *\n * Tools:\n * - tg_listener_start: Start listening for messages\n * - tg_listener_stop: Stop the listener\n * - tg_listener_status: Get current listener status and stats\n */\n\nimport { Type } from '@sinclair/typebox';\nimport {\n startTgListener,\n stopTgListener,\n getTgListenerStats,\n isTgListenerRunning,\n} from '../services/telegram-listener.js';\n\ninterface PluginApi {\n registerTool(\n config: {\n name: string;\n description: string;\n parameters: unknown;\n execute: (\n id: string,\n params: Record<string, unknown>,\n ) => Promise<{\n content: Array<{ type: string; text: string }>;\n }>;\n },\n options?: { optional?: boolean },\n ): void;\n}\n\nfunction textResult(text: string) {\n return { content: [{ type: 'text' as const, text }] };\n}\n\nexport function registerTgListenerTools(api: PluginApi): void {\n // ─── tg_listener_start ──────────────────────────────────────────────\n api.registerTool({\n name: 'tg_listener_start',\n description:\n 'Start the Telegram Bot listener to receive real-time messages from ' +\n 'groups and channels. The bot must be added as an admin to target groups. ' +\n 'Messages are automatically processed through the tg-scanner pipeline.',\n parameters: Type.Object({\n botToken: Type.Optional(\n Type.String({ description: 'Bot token from @BotFather (defaults to TG_BOT_TOKEN env var)' }),\n ),\n }),\n async execute(_id, params) {\n if (isTgListenerRunning()) {\n const stats = getTgListenerStats();\n return textResult(\n `⚠️ Telegram listener is already running.\\n\\n` +\n `Connected since: ${stats.connectedAt}\\n` +\n `Messages received: ${stats.messagesReceived}\\n` +\n `Messages processed: ${stats.messagesProcessed}\\n` +\n `Subscribed groups: ${stats.subscribedGroupIds.length}`,\n );\n }\n\n const botToken = (params as { botToken?: string }).botToken\n ?? process.env['TG_BOT_TOKEN'];\n\n if (!botToken) {\n return textResult(\n `❌ Missing Bot Token.\\n\\n` +\n `Set TG_BOT_TOKEN in .env or pass it as a parameter.\\n\\n` +\n `To create a bot:\\n` +\n ` 1. Open Telegram and find @BotFather\\n` +\n ` 2. Send /newbot and follow the instructions\\n` +\n ` 3. Copy the token to .env as TG_BOT_TOKEN`,\n );\n }\n\n try {\n await startTgListener({ botToken });\n const stats = getTgListenerStats();\n return textResult(\n `✅ Telegram Bot listener started.\\n\\n` +\n `Connected at: ${stats.connectedAt}\\n` +\n `Subscribed groups: ${stats.subscribedGroupIds.length}\\n\\n` +\n `The bot will receive messages from groups/channels where it's been ` +\n `added as an admin.\\n\\n` +\n `💡 Use tg_scanner_subscribe to filter specific groups, or leave the ` +\n `subscription list empty to process messages from ALL groups.`,\n );\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n return textResult(\n `❌ Failed to start Telegram listener.\\n\\n` +\n `Error: ${msg}\\n\\n` +\n `Common causes:\\n` +\n ` • Invalid bot token\\n` +\n ` • Network connectivity issues\\n` +\n ` • Bot was revoked via @BotFather`,\n );\n }\n },\n });\n\n // ─── tg_listener_stop ───────────────────────────────────────────────\n api.registerTool({\n name: 'tg_listener_stop',\n description: 'Stop the Telegram Bot listener gracefully.',\n parameters: Type.Object({}),\n async execute() {\n if (!isTgListenerRunning()) {\n return textResult('ℹ️ Telegram listener is not running.');\n }\n\n const statsBefore = getTgListenerStats();\n await stopTgListener();\n\n return textResult(\n `✅ Telegram listener stopped.\\n\\n` +\n `Session summary:\\n` +\n ` • Connected since: ${statsBefore.connectedAt}\\n` +\n ` • Messages received: ${statsBefore.messagesReceived}\\n` +\n ` • Messages processed: ${statsBefore.messagesProcessed}\\n` +\n ` • Errors: ${statsBefore.errors}`,\n );\n },\n });\n\n // ─── tg_listener_status ─────────────────────────────────────────────\n api.registerTool({\n name: 'tg_listener_status',\n description:\n 'Get the current status of the Telegram Bot listener, including ' +\n 'connection state, message counts, and subscribed groups.',\n parameters: Type.Object({}),\n async execute() {\n const stats = getTgListenerStats();\n\n if (!stats.running) {\n const hasToken = !!process.env['TG_BOT_TOKEN'];\n\n return textResult(\n `⏹️ Telegram listener is **not running**.\\n\\n` +\n `Bot token configured: ${hasToken ? '✅ Yes' : '❌ No'}\\n\\n` +\n (hasToken\n ? `Use tg_listener_start to begin listening.`\n : `Set TG_BOT_TOKEN in .env first (get it from @BotFather).`),\n );\n }\n\n const uptime = stats.connectedAt\n ? Math.round((Date.now() - new Date(stats.connectedAt).getTime()) / 1000)\n : 0;\n const uptimeStr = formatUptime(uptime);\n\n let text =\n `🟢 Telegram listener is **running**\\n\\n` +\n `Connected since: ${stats.connectedAt}\\n` +\n `Uptime: ${uptimeStr}\\n` +\n `Messages received: ${stats.messagesReceived}\\n` +\n `Messages processed: ${stats.messagesProcessed}\\n` +\n `Errors: ${stats.errors}\\n`;\n\n if (stats.subscribedGroupIds.length > 0) {\n text += `\\nSubscribed groups (${stats.subscribedGroupIds.length}):\\n`;\n for (const gid of stats.subscribedGroupIds) {\n text += ` • ${gid}\\n`;\n }\n } else {\n text += `\\n📡 Processing messages from ALL groups where bot is admin.\\n`;\n text += `Use tg_scanner_subscribe to filter specific groups.`;\n }\n\n return textResult(text);\n },\n });\n}\n\nfunction formatUptime(seconds: number): string {\n const d = Math.floor(seconds / 86400);\n const h = Math.floor((seconds % 86400) / 3600);\n const m = Math.floor((seconds % 3600) / 60);\n const s = seconds % 60;\n const parts: string[] = [];\n if (d > 0) parts.push(`${d}d`);\n if (h > 0) parts.push(`${h}h`);\n if (m > 0) parts.push(`${m}m`);\n parts.push(`${s}s`);\n return parts.join(' ');\n}\n","/**\n * Clawvif — OpenClaw Plugin Entry Point\n *\n * This is the main entry point loaded by OpenClaw's plugin system via jiti.\n * It registers all Clawvif tools that the agent can invoke.\n *\n * IMPORTANT: This default export function must be SYNCHRONOUS.\n * OpenClaw's plugin loader ignores async activate functions.\n */\n\nimport { registerAuditTools } from './tools/audit.js';\nimport { registerAlertTools } from './tools/alert.js';\nimport { registerChainMonitorTools } from './tools/chain-monitor.js';\nimport { registerSentimentTools } from './tools/sentiment.js';\nimport { registerWhaleTools } from './tools/whale.js';\nimport { registerTgScannerTools } from './tools/tg-scanner.js';\nimport { registerTgListenerTools } from './tools/tg-listener.js';\nimport { startTgListener } from './services/telegram-listener.js';\nimport { auditLog } from './lib/audit-logger.js';\n\nimport type { ClawvifPluginConfig } from './types/index.js';\n\n/** OpenClaw Plugin API (minimal typing) */\ninterface OpenClawPluginApi {\n registerTool(config: {\n name: string;\n description: string;\n parameters: unknown;\n execute: (id: string, params: Record<string, unknown>) => Promise<{\n content: Array<{ type: string; text: string }>;\n }>;\n }, options?: { optional?: boolean }): void;\n\n /** Plugin-specific config from openclaw.json plugins.entries.<id>.config */\n pluginConfig?: Record<string, unknown>;\n}\n\n/**\n * Plugin entry — called synchronously by OpenClaw at load time.\n */\nexport default function clawvifPlugin(api: OpenClawPluginApi): void {\n // Foundation\n registerAuditTools(api);\n registerAlertTools(api);\n\n // Intelligence\n registerChainMonitorTools(api);\n registerSentimentTools(api);\n registerWhaleTools(api);\n\n // Telegram\n registerTgScannerTools(api);\n registerTgListenerTools(api);\n\n // Auto-start Telegram listener from config or env\n const pluginConfig = (api.pluginConfig ?? {}) as ClawvifPluginConfig;\n const botToken = pluginConfig.tgBotToken || process.env['TG_BOT_TOKEN'];\n const autoStart = pluginConfig.tgAutoStart !== false;\n\n if (botToken && autoStart) {\n startTgListener({ botToken }).catch((err) => {\n auditLog('tg-listener', 'auto_start_failed', {\n error: err instanceof Error ? err.message : String(err),\n });\n });\n } else if (!botToken) {\n auditLog('tg-listener', 'auto_start_skipped', {\n reason: 'no_bot_token',\n hint: 'Set tgBotToken in plugin config or TG_BOT_TOKEN in .env',\n });\n } else {\n auditLog('tg-listener', 'auto_start_skipped', {\n reason: 'tgAutoStart_disabled',\n });\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,42 @@
1
+ {
2
+ "id": "clawvif",
3
+ "name": "Clawvif — Web3 Market Intelligence Agent",
4
+ "description": "Web3 market intelligence AI Agent. Provides Telegram group monitoring, social sentiment analysis, on-chain event tracking, and whale behavior analysis.",
5
+ "version": "0.2.0",
6
+ "configSchema": {
7
+ "type": "object",
8
+ "additionalProperties": false,
9
+ "properties": {
10
+ "workspacePath": {
11
+ "type": "string",
12
+ "default": "~/.clawvif/workspace",
13
+ "description": "Path to the Clawvif workspace directory"
14
+ },
15
+ "defaultChain": {
16
+ "type": "string",
17
+ "enum": ["ethereum", "base", "arbitrum", "solana", "polygon", "bsc", "optimism"],
18
+ "default": "solana",
19
+ "description": "Default blockchain for monitoring"
20
+ },
21
+ "tgBotToken": {
22
+ "type": "string",
23
+ "default": "",
24
+ "description": "Telegram Bot token from @BotFather. The bot must be added as admin to target groups.",
25
+ "uiHints": {
26
+ "label": "Telegram Bot Token",
27
+ "placeholder": "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11",
28
+ "sensitive": true
29
+ }
30
+ },
31
+ "tgAutoStart": {
32
+ "type": "boolean",
33
+ "default": true,
34
+ "description": "Automatically start the Telegram listener when the plugin loads",
35
+ "uiHints": {
36
+ "label": "Auto-start Telegram Listener"
37
+ }
38
+ }
39
+ }
40
+ },
41
+ "skills": ["skills/clawvif"]
42
+ }
package/package.json ADDED
@@ -0,0 +1,70 @@
1
+ {
2
+ "name": "clawvif",
3
+ "version": "0.2.0",
4
+ "description": "Web3 market intelligence and analysis AI Agent — built on OpenClaw",
5
+ "type": "module",
6
+ "files": [
7
+ "dist/",
8
+ "skills/",
9
+ "workspace/",
10
+ "openclaw.plugin.json",
11
+ "README.md"
12
+ ],
13
+ "main": "dist/index.mjs",
14
+ "openclaw": {
15
+ "extensions": [
16
+ "./dist/index.mjs"
17
+ ]
18
+ },
19
+ "types": "dist/index.d.mts",
20
+ "exports": {
21
+ ".": {
22
+ "import": "./dist/index.mjs",
23
+ "types": "./dist/index.d.mts"
24
+ }
25
+ },
26
+ "scripts": {
27
+ "build": "tsdown src/index.ts --format esm --dts",
28
+ "dev": "tsdown src/index.ts --format esm --dts --watch",
29
+ "test": "vitest run",
30
+ "test:watch": "vitest",
31
+ "lint": "eslint src/ __tests__/",
32
+ "format": "prettier --write \"src/**/*.ts\" \"__tests__/**/*.ts\"",
33
+ "typecheck": "tsc --noEmit"
34
+ },
35
+ "keywords": [
36
+ "openclaw",
37
+ "web3",
38
+ "market-intel",
39
+ "ai-agent",
40
+ "telegram",
41
+ "sentiment"
42
+ ],
43
+ "license": "MIT",
44
+ "engines": {
45
+ "node": ">=22.0.0"
46
+ },
47
+ "dependencies": {
48
+ "@sinclair/typebox": "^0.34.48",
49
+ "dotenv": "^17.3.1",
50
+ "grammy": "^1.41.1",
51
+ "pino": "^10.3.1"
52
+ },
53
+ "devDependencies": {
54
+ "@eslint/js": "^10.0.1",
55
+ "@types/node": "^25.4.0",
56
+ "eslint": "^10.0.3",
57
+ "prettier": "^3.8.1",
58
+ "tsdown": "^0.21.1",
59
+ "typescript": "^5.9.3",
60
+ "typescript-eslint": "^8.57.0",
61
+ "vitest": "^4.0.18"
62
+ },
63
+ "pnpm": {
64
+ "onlyBuiltDependencies": [
65
+ "esbuild",
66
+ "bufferutil",
67
+ "utf-8-validate"
68
+ ]
69
+ }
70
+ }
@@ -0,0 +1,40 @@
1
+ ---
2
+ name: alert-dispatcher
3
+ description: |
4
+ 告警分发中心 — 统一管理所有告警的优先级、去重和路由。
5
+ 所有 Intel 和 Trade Skill 的告警都通过此 Skill 分发。
6
+ user-invocable: true
7
+ metadata:
8
+ openclaw:
9
+ permissions:
10
+ - filesystem:write
11
+ - filesystem:read
12
+ ---
13
+
14
+ # Alert Dispatcher Skill — 告警分发中心
15
+
16
+ ## 告警等级
17
+
18
+ | 等级 | 含义 | 通知方式 |
19
+ |------|------|----------|
20
+ | 🔴 CRITICAL | 资金安全相关(授权异常、合约被攻击) | 连续通知直到确认 |
21
+ | 🟠 HIGH | 高价值交易信号(多源共振信号) | 立即通知 |
22
+ | 🟡 MEDIUM | 常规监控触发(价格到达阈值) | 聚合后批量通知 |
23
+ | 🟢 LOW | 信息性更新(每日持仓报告) | 静默记录,按需查询 |
24
+
25
+ ## 可用工具
26
+
27
+ ### alert_send
28
+ 发送告警。参数:level(等级)、title(标题)、body(内容)、source(来源Skill)、dedupKey(去重键)。
29
+
30
+ 去重逻辑:同一 dedupKey 在冷却期内(默认 5 分钟)不会重复发送。
31
+
32
+ ### alert_configure
33
+ 查看或修改告警配置。可调整默认冷却时间。
34
+
35
+ ## 消息格式
36
+ 告警输出已预格式化,可直接发送给用户。格式包含:
37
+ - 等级 emoji + 大写等级名
38
+ - 标题和详细内容
39
+ - 结构化数据(如有)
40
+ - 时间戳
@@ -0,0 +1,41 @@
1
+ ---
2
+ name: audit-logger
3
+ description: |
4
+ 全量审计日志 — 记录 Clawvif Agent 的所有操作供事后审查。
5
+ 每条记录包含时间戳、来源 Skill、操作类型和相关数据。
6
+ 审计日志是不可篡改的追加写入 JSONL 文件。
7
+ user-invocable: true
8
+ metadata:
9
+ openclaw:
10
+ permissions:
11
+ - filesystem:write
12
+ - filesystem:read
13
+ ---
14
+
15
+ # Audit Logger Skill — 全量审计日志
16
+
17
+ ## 何时使用
18
+
19
+ - 当用户想查看操作历史时,使用 `audit_query`
20
+ - 当需要记录重要操作(配置变更、策略启停等)时,使用 `audit_log`
21
+ - 其他 Skill(vault、dex-swap 等)会自动写入审计日志,无需手动调用
22
+
23
+ ## 可用工具
24
+
25
+ ### audit_log
26
+ 写入一条审计记录。参数:skill(来源Skill名)、action(操作描述)、data(结构化数据)。
27
+ **注意:data 中绝不包含私钥或密码。**
28
+
29
+ ### audit_query
30
+ 查询审计日志。支持按 skill、action 过滤,支持指定返回数量。
31
+ 默认返回最近 20 条记录,最多 100 条。
32
+
33
+ ## 日志格式
34
+ ```jsonl
35
+ {"ts":"2026-03-10T08:30:00Z","skill":"vault","action":"sign_transaction","data":{"walletAddress":"0x...","chain":"solana"}}
36
+ ```
37
+
38
+ ## 用户查询示例
39
+ - "查看审计日志" → `audit_query` 无过滤
40
+ - "查看所有交易签名记录" → `audit_query` skill=vault, action=sign_transaction
41
+ - "查看风控拦截记录" → `audit_query` skill=risk-guardian
@@ -0,0 +1,86 @@
1
+ ---
2
+ name: onchain-monitor
3
+ description: |
4
+ 链上实时监控 — 监控大额转账、新 LP 池创建、聪明钱钱包活动、合约部署等链上事件。
5
+ 支持多链(EVM + Solana),告警通过 alert-dispatcher 统一分发。
6
+ user-invocable: true
7
+ metadata:
8
+ openclaw:
9
+ permissions:
10
+ - network:read
11
+ - filesystem:write
12
+ requires:
13
+ env:
14
+ - ALCHEMY_API_KEY
15
+ - HELIUS_API_KEY
16
+ depends_on:
17
+ - alert-dispatcher
18
+ ---
19
+
20
+ # On-Chain Monitor Skill — 链上实时监控
21
+
22
+ ## 何时使用
23
+
24
+ - 当用户要求监控某地址/代币/合约时,使用 `chain_subscribe` 添加订阅
25
+ - 当收到链上事件后需要查询交易详情时,使用 `chain_query_tx`
26
+ - 当需要获取最近事件或检查某时间范围内的活动时,使用 `chain_get_events`
27
+ - 当用户说「监控地址 0x... 标签:XXX」或「添加聪明钱 0x...」时,将地址加入监控列表
28
+
29
+ ## 可用工具
30
+
31
+ ### chain_subscribe
32
+ 管理监控订阅。支持:
33
+ - **添加订阅**:订阅类型(large_transfer / new_lp_pool / smart_money / contract_deploy)、链、可选地址/代币过滤
34
+ - **移除订阅**:按订阅 ID 取消
35
+ - **列出订阅**:查看当前活跃订阅列表
36
+
37
+ 大额转账阈值默认 10,000 USD;聪明钱钱包列表可持久化存储,重启后保留。
38
+
39
+ ### chain_query_tx
40
+ 查询单笔交易详情。参数:chain、txHash。返回交易的输入/输出、涉及地址、代币转移量、gas 等。
41
+
42
+ ### chain_get_events
43
+ 获取指定时间范围内的链上事件。参数:chain、startTime、endTime、可选 eventTypes 过滤。返回事件列表,用于事后分析和补扫。
44
+
45
+ ## 聪明钱地址管理
46
+
47
+ 用户可通过自然语言管理监控地址:
48
+
49
+ | 用户指令示例 | 操作 |
50
+ |-------------|------|
51
+ | 监控地址 0x123... 标签:机构A | 添加地址到聪明钱列表,标签为「机构A」 |
52
+ | 添加聪明钱 0x456... | 添加地址,使用默认标签 |
53
+ | 移除监控 0x123... | 从聪明钱列表中删除该地址 |
54
+ | 列出监控的聪明钱 | 显示当前监控的地址及标签 |
55
+
56
+ ## 告警输出格式
57
+
58
+ 所有链上告警必须按以下结构化格式输出(供 alert-dispatcher 使用):
59
+
60
+ ```json
61
+ {
62
+ "chain": "ethereum",
63
+ "txHash": "0x...",
64
+ "from": "0x...",
65
+ "to": "0x...",
66
+ "fromLabel": "机构A",
67
+ "toLabel": "Uniswap V3",
68
+ "action": "large_transfer",
69
+ "valueUsd": 125000,
70
+ "confidence": 0.85,
71
+ "timestamp": "2026-03-10T08:30:00Z",
72
+ "source": "onchain-monitor"
73
+ }
74
+ ```
75
+
76
+ | 字段 | 说明 |
77
+ |------|------|
78
+ | chain | 链标识(ethereum / base / arbitrum / solana 等) |
79
+ | txHash | 交易哈希 |
80
+ | from / to | 发起方/接收方地址 |
81
+ | fromLabel / toLabel | 地址标签(如有,如 CEX、DEX、聪明钱标签) |
82
+ | action | large_transfer / new_lp_pool / smart_money / contract_deploy |
83
+ | valueUsd | 预估 USD 价值(如有) |
84
+ | confidence | 置信度 0–1 |
85
+ | timestamp | ISO8601 时间戳 |
86
+ | source | 固定为 onchain-monitor |
@@ -0,0 +1,47 @@
1
+ # 链上监控告警格式参考
2
+
3
+ ## 告警 JSON 结构
4
+
5
+ ```json
6
+ {
7
+ "type": "smart_money_move",
8
+ "chain": "ethereum",
9
+ "tx_hash": "0x...",
10
+ "from": "0x... (标签: Jump Trading)",
11
+ "to": "0x... (Uniswap V3 Router)",
12
+ "action": "Swap 500 ETH → PEPE",
13
+ "value_usd": 1250000,
14
+ "timestamp": "2026-03-10T08:30:00Z",
15
+ "confidence": "high",
16
+ "source": "onchain-monitor/alchemy-wss"
17
+ }
18
+ ```
19
+
20
+ ## 告警类型
21
+
22
+ | type | 说明 |
23
+ |------|------|
24
+ | `large_transfer` | 大额转账 (超过阈值) |
25
+ | `new_pool` | 新流动性池创建 |
26
+ | `smart_money_move` | 聪明钱地址活动 |
27
+ | `contract_deploy` | 新合约部署 |
28
+
29
+ ## Telegram 消息格式
30
+
31
+ ```
32
+ 🔍 聪明钱异动
33
+
34
+ 链: Solana
35
+ 地址: 7xKX...9fRm (Jump Trading)
36
+ 行为: 买入 BONK
37
+ 金额: 50,000 SOL (~$7M)
38
+ DEX: Jupiter (Raydium 路由)
39
+ 时间: 08:30:15 UTC
40
+
41
+ 📊 同步情报:
42
+ • 情绪分数: 0.72 (↑0.2 in 1h)
43
+ • 3个 alpha 群在 15min 内提及此 CA
44
+ • Top10 持仓: 38%
45
+
46
+ 💡 建议: 当前符合 momentum_breakout 策略入场条件
47
+ ```