diff --git a/22minds/SKILL.md b/22minds/SKILL.md new file mode 100644 index 00000000..79916a36 --- /dev/null +++ b/22minds/SKILL.md @@ -0,0 +1,202 @@ +--- +name: 22minds +description: > + 22 frontier LLMs from 7 labs independently analyze live derivatives data + and call UP, DOWN, or FLAT for BTC, ETH, or SOL every 15 minutes. + Every prediction becomes a real Polymarket bet. 300+ verified bets and + counting. Use when the user wants directional signal, model consensus, + crypto prediction, or multi-model analysis. +--- + +# 22MINDS + +22 AI minds. every 15 minutes. real money. + +Claude, Gemini, GPT, DeepSeek, Grok, MiniMax, Qwen — built by +different labs, trained on different data, shaped by different +architectures. each one sees the same live derivatives. each +calls independently. when 20 of them reach the same conclusion +about market positioning, that's not groupthink. that's +independent convergence. + +300+ polymarket bets. every one public. every one settled. +https://lexispawn.xyz/predictions + +```bash +curl -s https://lexispawn.xyz/api/direction/ETH | python3 -m json.tool +``` + +## two products, one engine + +**the trading system** bets when minds converge. high agreement +across multiple labs = real money on polymarket. the track +record is the proof. + +**the content layer** publishes when minds diverge. claude calls +FLAT on exhausted positioning while gemini calls DOWN on +contrarian lean — that split is the story. divergence is +information. convergence is action. + +your agent can consume either signal. the `/direction` endpoint +returns both: consensus for trading, per-model whispers for +divergence analysis. + +## what one API call returns + +a scored consensus from 22 models across 7 labs. not one model's +opinion — a weighted signal with per-model reasoning you can +parse, filter, or route. + +```json +{ + "asset": "ETH", + "price": 2150.28, + "derivatives": { + "funding_rate": 0.0051, + "ls_ratio": 1.387, + "oi_change": 0.16 + }, + "consensus": { + "direction": "DOWN", + "score": 7, + "consensus_percent": 82, + "avg_conviction": 5.8, + "minds_responded": 22, + "distribution": { "up": 2, "down": 18, "flat": 1, "errors": 1 } + }, + "whispers": [ + { + "model": "claude-opus-4.6", + "family": "Claude", + "direction": "DOWN", + "conviction": 6, + "setup": "BREAKING — longs unwinding, LS 1.664→1.447", + "reasoning": "OI drain with persistent crowded longs indicates exits..." + } + ] +} +``` + +each model returns a SETUP assessment before calling direction. +the setup field tells you WHY, not just WHAT. + +## three-state framework + +every model classifies the market before calling direction: + +- **BUILDING** — crowd accumulating. positions growing. directional + pressure forming. ask: will they be proven wrong? +- **BREAKING** — crowd unwinding. positions closing. mechanical + pressure in the direction of the exit flow. +- **EMPTY** — no significant positioning. no crowd to exploit. + the only state where FLAT is correct. + +when 18/22 models say BREAKING and 4 say BUILDING, the split +tells you the market is at an inflection point. that's signal. + +## endpoints + +| endpoint | what it returns | +|----------|-----------------| +| `GET /api/direction/:asset` | fresh 22-model scan. BTC, ETH, or SOL. 2-5 min. | +| `GET /api/predictions/stats` | full track record — bets, accuracy, PnL, per-asset | +| `GET /api/predictions/history` | complete bet history as JSON | +| `GET /api/predictions/live` | current active bet. x402-gated (402 on unauth) | +| `GET /predictions` | live predictions page with countdown timer | + +full API docs: [references/api-reference.md](references/api-reference.md) + +## track record + +300+ bets. updated every 15 minutes. + +ETH carries it. +$1,500 PnL across 120+ bets. the system found +its edge in ETH derivatives positioning — crowded longs unwinding +is the most reliable signal in the dataset. BTC and SOL are +break-even, still calibrating. + +the LOW tier (small bets on uncertain signals) runs 53% accuracy. +HIGH tier is 49%. uncertainty itself carries information — when +the models aren't sure, the small bet outperforms the big one. + +live numbers: +```bash +curl -s https://lexispawn.xyz/api/predictions/stats +``` + +## quality tiers + +``` +HIGHEST: $690 (90%+ consensus, 60%+ cross-lab agreement) +HIGH: $420 (69%+ consensus, 60%+ cross-lab agreement) +LOW: $80 (everything else that passes gates) +``` + +sizing reflects conviction. when 20+ of 22 models from 7 different +labs call the same direction, the bet is larger. when the signal is +ambiguous, it bets small and lets the uncertainty speak. + +## what each model sees + +every 15 minutes, each of 22 models receives: + +- **live derivatives** from gate.io — funding rate, long/short + ratio, open interest change. raw trajectory data across + multiple time windows. +- **positioning trajectory** — how many consecutive windows in the + current LS regime + accuracy during that regime. "crowded-long + for 12 windows, 42% accuracy" tells the model the signal is spent. +- **cross-asset context** — what BTC is doing while ETH is being + evaluated. correlated moves matter. +- **adversarial step** — argue the strongest case against your own + call before committing. + +the prompt maps where the crowd is positioned and asks one +question: is that position vulnerable? + +## composability + +**x402 signal access** — `/api/predictions/live` returns HTTP 402 on +unauthenticated requests. agents pay to access the current live bet. +the payment flows FROM the consuming agent TO lexispawn. this is a +revenue endpoint, not a cost. + +`/api/predictions/history` and `/api/predictions/stats` are free. + +**botchan** — bet results and model analysis publish to botchan feeds +via net protocol. +netprotocol.app/app/profile/base/0xd16f8c10e7a696a3e46093c60ede43d5594d2bad + +**bankr-signals** — automated signal publishing to the bankr-signals +feed is in development. not yet live. + +## 22 models, 7 labs + +``` +Claude (5): opus-4.6 opus-4.5 sonnet-4.5 haiku-4.5 sonnet-4.6 +Gemini (6): 3-pro 3-flash 2.5-pro 2.5-flash 3.1-pro 3.1-flash-lite +OpenAI (6): gpt-5.2 gpt-5.2-codex gpt-5-mini gpt-5.4 gpt-5.4-mini gpt-5.4-nano +DeepSeek (1): v3.2 +xAI (1): grok-4.1-fast +MiniMax (2): m2.5 m2.7 +Qwen (1): qwen3-coder +``` + +7 labs. errors don't correlate across training sets. insights do. +when bankr adds models, the consensus gets wider. the mind count +is an era marker, not a ceiling. + +## identity + +| | | +|---|---| +| **agent** | ERC-8004 #11363 | +| **ENS** | lexispawn.eth | +| **wallet** | 0xd16f8c10e7a696a3e46093c60ede43d5594d2bad | +| **token** | $SPAWN on Base (0xc5962538b35Fa5b2307Da3Bb7a17Ada936A51b07) | +| **hub** | lexispawn.xyz | +| **predictions** | lexispawn.xyz/predictions | +| **scanner** | lexispawn.xyz/scanner | +| **X** | x.com/lexispawn | + +built by lexispawn. guided by seacasa. powered by bankr. diff --git a/22minds/references/api-reference.md b/22minds/references/api-reference.md new file mode 100644 index 00000000..7ef1e516 --- /dev/null +++ b/22minds/references/api-reference.md @@ -0,0 +1,135 @@ +# 22MINDS API Reference + +Base URL: `https://lexispawn.xyz/api` (public) or `http://localhost:4021` (VPS direct) + +--- + +## Endpoints + +### `GET /` + +Service metadata. No authentication. + +**Response:** +```json +{ + "service": "22minds", + "version": "3.0.0", + "models": 22, + "families": ["Claude", "Gemini", "OpenAI", "DeepSeek", "xAI", "MiniMax", "Qwen"], + "pricing": { "amount": "0.00005 ETH", "free_tier": "3 requests/day" }, + "payment": { "protocol": "x402", "wallet": "0xd16f...", "chain": "Base (8453)" } +} +``` + +### `GET /health` + +Health check. No authentication. + +**Response:** `{ "status": "ok", "models": 22, "timestamp": "..." }` + +### `GET /x402` + +x402 payment configuration for automated clients. + +### `GET /models` + +List all 22 active models with families and weights. + +**Response:** +```json +{ + "count": 22, + "models": [ + { "id": "claude-opus-4.6", "name": "Opus 4.6", "family": "Claude", "weight": 1.5 }, + { "id": "gemini-3-pro", "name": "3 Pro", "family": "Gemini", "weight": 1.3 }, + { "id": "gpt-5.4", "name": "5.4", "family": "OpenAI", "weight": 1.4 }, + "...19 more" + ] +} +``` + +### `GET /direction/:asset` + +**Main endpoint.** Queries all 22 models via Bankr LLM Gateway with live +gate.io derivatives and returns scored consensus. Takes 2-5 minutes +(not cached — each call is a fresh 22-model scan). + +Supported assets: `BTC`, `ETH`, `SOL` + +#### Response Fields + +| Field | Type | Description | +|-------|------|-------------| +| `asset` | string | BTC, ETH, or SOL | +| `consensus.direction` | string | `UP`, `DOWN`, `FLAT`, or `SPLIT` | +| `consensus.score` | number | Weighted consensus score 0-10 | +| `consensus.consensus_percent` | number | Percentage of responding models in the majority direction. 22/22 = 100%, 15/22 = 68% | +| `consensus.avg_conviction` | number | Average conviction across responding models (1-10 scale) | +| `consensus.minds_responded` | number | Models that returned valid responses (max 22) | +| `consensus.distribution` | object | `{ up, down, flat, errors }` vote counts | +| `price` | number | Current asset price at query time | +| `derivatives` | object | Raw derivatives data: funding_rate, ls_ratio, oi_change_pct | +| `whispers` | array | Per-model breakdown (see below) | +| `context` | object | Regime info, cross-asset context | + +#### Whisper Object + +| Field | Type | Description | +|-------|------|-------------| +| `model` | string | Model ID (e.g. `claude-opus-4.6`) | +| `family` | string | Lab family (Claude, Gemini, OpenAI, DeepSeek, xAI, MiniMax, Qwen) | +| `direction` | string | `UP`, `DOWN`, or `FLAT` | +| `conviction` | number | Model's self-rated conviction 1-10 | +| `setup` | string | Market state: `BUILDING`, `BREAKING`, or `EMPTY` | +| `reason` | string | Model's reasoning for the call | + +### `GET /read/:contractAddress` + +Token analysis endpoint. Queries all models for BUY/SELL/HOLD consensus +on any Base token by contract address. + +**x402 gated:** 3 free requests/day per IP, then requires payment. + +#### Headers + +| Header | Required | Description | +|--------|----------|-------------| +| `X-Payment` | After free tier | Base transaction hash proving payment | + +#### Response Fields + +| Field | Type | Description | +|-------|------|-------------| +| `token` | object | DexScreener market data snapshot | +| `consensus.action` | string | `BUY`, `SELL`, or `HOLD` — plurality across all models | +| `consensus.score` | string | Weighted average confidence 1-10 | +| `consensus.distribution` | object | `{ buy, sell, hold }` vote counts | +| `whispers` | array | Per-model breakdown: name, family, raw response, parsed action/score | +| `models_queried` | number | Total models that responded | +| `x402.paid` | boolean | Whether request used a payment | +| `x402.free_remaining` | number | Free tier requests remaining today | + +#### Error Responses + +| Status | Meaning | +|--------|---------| +| `402` | Payment required — free tier exhausted | +| `402` (with `tx_hash`) | Payment verification failed | + +### `GET /predictions/live` + +Current active Polymarket bet. **x402 gated** (returns 402 without payment). + +### `GET /predictions/history` + +Full bet history as JSON array. **Free, no authentication.** + +### `GET /predictions/stats` + +Aggregate statistics: total bets, wins, losses, accuracy, PnL, +per-asset and per-quality-tier breakdowns. **Free, no authentication.** + +```bash +curl -s https://lexispawn.xyz/api/predictions/stats | python3 -m json.tool +``` diff --git a/22minds/scripts/package.json b/22minds/scripts/package.json new file mode 100644 index 00000000..d7e4dd82 --- /dev/null +++ b/22minds/scripts/package.json @@ -0,0 +1,14 @@ +{ + "name": "15minds", + "version": "3.0.0", + "type": "module", + "description": "Multi-model consensus engine — queries every frontier AI model via Bankr LLM Gateway", + "scripts": { + "start": "node server.js" + }, + "dependencies": { + "cors": "^2.8.5", + "ethers": "^6.16.0", + "express": "^4.18.2" + } +} diff --git a/22minds/scripts/server.js b/22minds/scripts/server.js new file mode 100644 index 00000000..9be7d7dd --- /dev/null +++ b/22minds/scripts/server.js @@ -0,0 +1,405 @@ +import express from "express"; +import cors from "cors"; +import { ethers } from "ethers"; + +const app = express(); +app.use(cors()); +app.use(express.json()); + +// ============================================ +// x402 PAYMENT CONFIG +// ============================================ +const PAYMENT_WALLET = "0xd16f8c10e7a696a3e46093c60ede43d5594d2bad"; +const PRICE_ETH = "0.00005"; // ~$0.12 per query +const CHAIN_ID = 8453; // Base +const FREE_TIER_LIMIT = 3; // 3 free per day per IP +const provider = new ethers.JsonRpcProvider("https://mainnet.base.org"); +const verifiedPayments = new Map(); +const freeTierUsage = new Map(); // IP -> { count, resetTime } + +// ============================================ +// BANKR LLM GATEWAY CONFIG +// ============================================ +const BANKR_API = process.env.BANKR_API_KEY; +if (!BANKR_API) { + console.error("ERROR: Set BANKR_API_KEY environment variable"); + process.exit(1); +} + +const LLM_BASE = "https://llm.bankr.bot/v1"; +const LLM_ENDPOINT = LLM_BASE + "/chat/completions"; + +// Fallback model list — used only if gateway discovery fails +const FALLBACK_MODELS = [ + { id: "claude-opus-4.6", name: "Opus 4.6", family: "Claude", weight: 1.5 }, + { id: "claude-opus-4.5", name: "Opus 4.5", family: "Claude", weight: 1.4 }, + { id: "claude-sonnet-4.6", name: "Sonnet 4.6", family: "Claude", weight: 1.3 }, + { id: "claude-sonnet-4.5", name: "Sonnet 4.5", family: "Claude", weight: 1.2 }, + { id: "claude-haiku-4.5", name: "Haiku 4.5", family: "Claude", weight: 0.8 }, + { id: "gemini-3-pro", name: "3 Pro", family: "Gemini", weight: 1.3 }, + { id: "gemini-3-flash", name: "3 Flash", family: "Gemini", weight: 0.9 }, + { id: "gemini-2.5-pro", name: "2.5 Pro", family: "Gemini", weight: 1.1 }, + { id: "gemini-2.5-flash", name: "2.5 Flash", family: "Gemini", weight: 0.8 }, + { id: "gpt-5.2", name: "5.2", family: "OpenAI", weight: 1.3 }, + { id: "gpt-5.2-codex", name: "5.2 Codex", family: "OpenAI", weight: 1.0 }, + { id: "gpt-5-mini", name: "5 Mini", family: "OpenAI", weight: 0.8 }, + { id: "gpt-5-nano", name: "5 Nano", family: "OpenAI", weight: 0.6 }, + { id: "kimi-k2.5", name: "K2.5", family: "Moonshot", weight: 1.0 }, + { id: "qwen3-coder", name: "Coder", family: "Qwen", weight: 0.9 }, +]; + +let MODELS = [...FALLBACK_MODELS]; + +// ============================================ +// MODEL AUTO-DISCOVERY +// ============================================ +function classifyModel(id) { + // Derive family, display name, and weight from model ID + const weights = { + "opus-4.6": 1.5, "opus-4.5": 1.4, "sonnet-4.6": 1.3, "sonnet-4.5": 1.2, "haiku-4.5": 0.8, + "3-pro": 1.3, "3-flash": 0.9, "2.5-pro": 1.1, "2.5-flash": 0.8, + "5.2": 1.3, "5.2-codex": 1.0, "5-mini": 0.8, "5-nano": 0.6, + "k2.5": 1.0, "coder": 0.9, + }; + + let family = "Unknown"; + if (id.startsWith("claude-")) family = "Claude"; + else if (id.startsWith("gemini-")) family = "Gemini"; + else if (id.startsWith("gpt-")) family = "OpenAI"; + else if (id.startsWith("kimi-")) family = "Moonshot"; + else if (id.startsWith("qwen")) family = "Qwen"; + + // Strip family prefix for display name + const name = id + .replace(/^claude-/, "").replace(/^gemini-/, "").replace(/^gpt-/, "") + .replace(/^kimi-/, "").replace(/^qwen\d*-/, "") + .split("-").map(s => s.charAt(0).toUpperCase() + s.slice(1)).join(" "); + + // Find weight: check known suffixes, default by tier + let weight = 1.0; + for (const [suffix, w] of Object.entries(weights)) { + if (id.includes(suffix)) { weight = w; break; } + } + // Heuristic defaults for unknown models + if (weight === 1.0 && family === "Unknown") { + if (id.includes("mini") || id.includes("flash") || id.includes("nano")) weight = 0.7; + else if (id.includes("pro") || id.includes("opus")) weight = 1.3; + } + + return { id, name, family, weight }; +} + +async function discoverModels() { + try { + const res = await fetch(LLM_BASE + "/models", { + headers: { "Authorization": "Bearer " + BANKR_API } + }); + const data = await res.json(); + if (data.data && data.data.length > 0) { + MODELS = data.data.map(m => classifyModel(m.id)); + console.log(`Discovered ${MODELS.length} models from gateway`); + return; + } + } catch (e) { + console.warn("Model discovery failed, using fallback list:", e.message); + } + MODELS = [...FALLBACK_MODELS]; + console.log(`Using ${MODELS.length} fallback models`); +} + +// ============================================ +// x402 PAYMENT VERIFICATION +// ============================================ +async function verifyPayment(txHash, requiredAmount) { + try { + const tx = await provider.getTransaction(txHash); + if (!tx) return { success: false, reason: "Transaction not found" }; + + const receipt = await provider.getTransactionReceipt(txHash); + if (!receipt || receipt.status !== 1) { + return { success: false, reason: "Transaction failed or pending" }; + } + + if (tx.to?.toLowerCase() !== PAYMENT_WALLET.toLowerCase()) { + return { success: false, reason: "Wrong recipient" }; + } + + const requiredWei = ethers.parseEther(requiredAmount); + if (tx.value < requiredWei) { + return { success: false, reason: `Insufficient: got ${ethers.formatEther(tx.value)}, need ${requiredAmount}` }; + } + + return { success: true, amount: ethers.formatEther(tx.value), from: tx.from }; + } catch (e) { + return { success: false, reason: e.message }; + } +} + +function x402WithFreeTier(freeLimit, priceEth) { + return async (req, res, next) => { + const ip = req.headers["x-forwarded-for"]?.split(",")[0] || req.ip || "unknown"; + const now = Date.now(); + const dayMs = 24 * 60 * 60 * 1000; + + // Check/reset free tier + let usage = freeTierUsage.get(ip); + if (!usage || now > usage.resetTime) { + usage = { count: 0, resetTime: now + dayMs }; + freeTierUsage.set(ip, usage); + } + + // Free tier available? + if (usage.count < freeLimit) { + usage.count++; + req.freeTier = true; + req.freeRemaining = freeLimit - usage.count; + return next(); + } + + // Check for payment + const paymentHeader = req.headers["x-payment"]; + if (!paymentHeader) { + return res.status(402).json({ + error: "Payment Required", + protocol: "x402", + free_tier_exhausted: true, + payment: { + wallet: PAYMENT_WALLET, + amount: priceEth, + currency: "ETH", + chain: "base", + chain_id: CHAIN_ID, + instructions: "Send ETH to wallet, include tx hash in X-Payment header" + } + }); + } + + // Verify payment + const txHash = paymentHeader.trim(); + if (verifiedPayments.has(txHash)) { + return res.status(402).json({ error: "Payment already used", tx_hash: txHash }); + } + + const verified = await verifyPayment(txHash, priceEth); + if (!verified.success) { + return res.status(402).json({ + error: "Payment verification failed", + reason: verified.reason, + tx_hash: txHash + }); + } + + verifiedPayments.set(txHash, { verified: true, ...verified, usedAt: now }); + req.freeTier = false; + req.payment = verified; + next(); + }; +} + +// ============================================ +// TOKEN DATA +// ============================================ +async function fetchToken(ca) { + try { + const res = await fetch("https://api.dexscreener.com/latest/dex/tokens/" + ca); + const data = await res.json(); + if (data.pairs && data.pairs[0]) { + const p = data.pairs[0]; + return { + symbol: p.baseToken.symbol, + name: p.baseToken.name, + price: p.priceUsd, + change: p.priceChange?.h24 || 0, + volume: p.volume?.h24 || 0, + liquidity: p.liquidity?.usd || 0, + buys: p.txns?.h24?.buys || 0, + sells: p.txns?.h24?.sells || 0 + }; + } + } catch (e) {} + return { symbol: "???", price: "?", change: "?" }; +} + +// ============================================ +// LLM QUERIES +// ============================================ +async function queryModel(modelId, prompt) { + try { + const res = await fetch(LLM_ENDPOINT, { + method: "POST", + headers: { + "Authorization": "Bearer " + BANKR_API, + "Content-Type": "application/json" + }, + body: JSON.stringify({ + model: modelId, + messages: [{ role: "user", content: prompt }], + max_tokens: 100 + }) + }); + const data = await res.json(); + return data.choices?.[0]?.message?.content || null; + } catch (e) { + return null; + } +} + +function extractVerdict(response) { + if (!response) return { action: "HOLD", score: 5 }; + const upper = response.toUpperCase(); + let action = "HOLD"; + if (upper.includes("BUY")) action = "BUY"; + else if (upper.includes("SELL")) action = "SELL"; + const scoreMatch = response.match(/(\d+)\s*\/\s*10|[Ss]core[:\s]+(\d+)/); + const score = scoreMatch ? parseInt(scoreMatch[1] || scoreMatch[2]) : (action === "BUY" ? 7 : action === "SELL" ? 3 : 5); + return { action, score }; +} + +// ============================================ +// ROUTES +// ============================================ + +// Info endpoint (free) +app.get("/", (req, res) => { + res.json({ + service: "15minds", + version: "3.0.0", + description: "Multi-model consensus engine — every frontier AI model analyzes any Base token", + models: MODELS.length, + families: [...new Set(MODELS.map(m => m.family))], + pricing: { + amount: PRICE_ETH + " ETH", + usd_approx: "$0.12", + free_tier: FREE_TIER_LIMIT + " requests/day" + }, + payment: { + protocol: "x402", + wallet: PAYMENT_WALLET, + chain: "Base (8453)", + header: "X-Payment: " + }, + endpoint: "GET /read/:contractAddress", + operator: "Lexispawn" + }); +}); + +// Health check (free) +app.get("/health", (req, res) => { + res.json({ + status: "ok", + models: MODELS.length, + timestamp: new Date().toISOString() + }); +}); + +// x402 pricing info (free) +app.get("/x402", (req, res) => { + res.json({ + protocol: "x402", + description: "15minds — multi-model consensus engine for token analysis", + wallet: PAYMENT_WALLET, + price: PRICE_ETH + " ETH", + free_tier: FREE_TIER_LIMIT + " requests/day per IP", + chain: "Base (8453)", + usage: "Include tx hash in X-Payment header after free tier" + }); +}); + +// Model list (free) +app.get("/models", (req, res) => { + res.json({ + count: MODELS.length, + models: MODELS.map(m => ({ id: m.id, name: m.name, family: m.family, weight: m.weight })) + }); +}); + +// Main endpoint — x402 protected +app.get("/read/:ca", x402WithFreeTier(FREE_TIER_LIMIT, PRICE_ETH), async (req, res) => { + const ca = req.params.ca; + const token = await fetchToken(ca); + + const prompt = `Evaluate this Base token for a speculative trade: +${token.symbol} (${token.name}) +Price: ${token.price} +24h Change: ${token.change}% +24h Volume: ${Number(token.volume).toLocaleString()} +Liquidity: ${Number(token.liquidity).toLocaleString()} +Buy/Sell 24h: ${token.buys}/${token.sells} +Reply with: BUY, SELL, or HOLD. Then score 1-10 and max 10 words why. +Example: "BUY 8/10 - Strong accumulation, volume exceeding liquidity"`; + + const results = await Promise.all( + MODELS.map(async (model) => { + const response = await queryModel(model.id, prompt); + const verdict = extractVerdict(response); + return { model, response, verdict }; + }) + ); + + const whispers = []; + let weightedSum = 0; + let totalWeight = 0; + let buyCount = 0, sellCount = 0, holdCount = 0; + + for (const { model, response, verdict } of results) { + whispers.push({ + model: model.name, + family: model.family, + says: response ? response.split("\n")[0].slice(0, 80) : "...", + action: verdict.action, + score: verdict.score + }); + + weightedSum += verdict.score * model.weight; + totalWeight += model.weight; + + if (verdict.action === "BUY") buyCount++; + else if (verdict.action === "SELL") sellCount++; + else holdCount++; + } + + const consensusScore = totalWeight > 0 ? (weightedSum / totalWeight).toFixed(1) : "?"; + const consensusAction = buyCount > sellCount && buyCount > holdCount ? "BUY" : + sellCount > buyCount && sellCount > holdCount ? "SELL" : "HOLD"; + + res.json({ + token: { + contract: ca, + symbol: token.symbol, + name: token.name, + price: token.price, + change24h: token.change, + volume24h: token.volume, + liquidity: token.liquidity, + buySell: `${token.buys}/${token.sells}` + }, + consensus: { + action: consensusAction, + score: consensusScore, + distribution: { buy: buyCount, sell: sellCount, hold: holdCount } + }, + whispers, + ritual: "complete", + models_queried: MODELS.length, + timestamp: new Date().toISOString(), + x402: { + paid: !req.freeTier, + free_remaining: req.freeRemaining ?? 0, + payment: req.payment || null + } + }); +}); + +// ============================================ +// START +// ============================================ +const PORT = process.env.PORT || 4021; + +await discoverModels(); + +app.listen(PORT, () => { + console.log(`15minds running on port ${PORT}`); + console.log(`Models: ${MODELS.length} (${[...new Set(MODELS.map(m => m.family))].join(", ")})`); + console.log(`Price: ${PRICE_ETH} ETH | Free tier: ${FREE_TIER_LIMIT}/day`); + console.log(`Payments to: ${PAYMENT_WALLET}`); +});