diff --git a/15minds/SKILL.md b/15minds/SKILL.md new file mode 100644 index 0000000..527741f --- /dev/null +++ b/15minds/SKILL.md @@ -0,0 +1,106 @@ +--- +name: 15minds +description: Queries 15 frontier AI models in parallel and returns consensus crypto verdicts with conviction scoring and per-model reasoning. Use when user asks for "multi-model consensus", "what do the models think about", "scan this token", "BTC direction", "should I buy", "15 minds", "second opinion from multiple models", or wants to compare predictions across Claude, GPT, Gemini, and other model families. +--- + +# 15minds + +Multi-model consensus engine. Queries 15 frontier AI models on the Bankr LLM Gateway in parallel. Returns structured verdicts with conviction scoring, family agreement analysis, and per-model reasoning. + +Live and betting real money since March 11, 2026: [lexispawn.xyz/predictions](https://lexispawn.xyz/predictions) + +## What it does + +Sends a structured analytical prompt to all 15 models simultaneously. Each model analyzes momentum, key levels, session context, volatility regime, and cross-asset patterns. Returns: + +- **Consensus direction** (UP/DOWN) with score (1-10) +- **Conviction scoring** — average conviction across agreeing models (1-10) +- **Family agreement** — how many independent model families converge (Anthropic, Google, OpenAI, Other) +- **Quality rating** — HIGH / MEDIUM / LOW based on conviction + family agreement +- **Regime detection** — DEAD_FLAT / LOW_VOL / NORMAL / HIGH_VOL +- **Per-model whispers** — each model's direction, conviction, and one-sentence reasoning + +## Models (15 configured, ~12 effective) + +Claude Opus 4.6, Claude Opus 4.5, Claude Sonnet 4.5, Claude Haiku 4.5, Claude Sonnet 4.6, Gemini 3 Pro, Gemini 3 Flash, Gemini 2.5 Pro, Gemini 2.5 Flash, GPT-5.2, GPT-5.2 Codex, GPT-5 Mini, GPT-5 Nano, Kimi K2.5, Qwen3 Coder + +## Two endpoints + +### Token scan — `GET /read/:contractAddress` + +Scans any Base token by contract address. 15 models evaluate fundamentals, technicals, and sentiment. Returns BUY/HOLD/SELL consensus with per-model breakdown. + +**x402 gated**: 3 free scans per day, then 0.00005 ETH per query on Base. No API keys, no accounts — payment via HTTP header. + +``` +curl https://lexispawn.xyz/api/read/0xContractAddress +``` + +### Directional prediction — `GET /direction/:asset` + +15-minute price direction prediction for BTC, ETH, or SOL. Each model runs a 5-factor structured analysis: + +1. Momentum — acceleration/deceleration of 1hr move +2. Key levels — proximity to round numbers and session highs/lows +3. Session context — NY/London/Asia liquidity patterns +4. Volatility regime — trending vs choppy environment +5. Cross-asset inference — historical patterns for this magnitude of move + +Returns JSON with direction, conviction (1-10), and reasoning from each model. + +``` +curl https://lexispawn.xyz/api/direction/BTC +``` + +Response: +```json +{ + "asset": "BTC", + "price": 70533, + "consensus": { + "direction": "UP", + "score": 8, + "avg_conviction": 7.3, + "distribution": { "up": 10, "down": 2, "errors": 3 } + }, + "context": { + "regime": "NORMAL", + "change1h": 0.52 + }, + "whispers": [ + { + "model": "Claude Opus 4.6", + "direction": "UP", + "conviction": 8, + "reasoning": "Strong bounce off $70K support with increasing volume into NY open" + } + ] +} +``` + +## Why it matters + +Individual model predictions are noise. Consensus across 15 independently trained models from 5 different families is signal. When Claude, GPT, Gemini, and Qwen all independently converge on the same direction with high conviction, that's information no single model provides. + +The disagreement is where the information lives. When Anthropic models say UP but OpenAI models say DOWN, the consensus score drops and bet sizing shrinks automatically. When all families agree, conviction is high, and the market is moving — that's the moment to act. + +## Install + +``` +> install the 15minds skill from lexispawn/openclaw-skills +``` + +## Deploy + +``` +cd scripts && npm install +BANKR_API_KEY=bk_yourkey pm2 start server.js --name 15minds +``` + +## Links + +- Live predictions: [lexispawn.xyz/predictions](https://lexispawn.xyz/predictions) +- Scanner: [lexispawn.xyz/scanner](https://lexispawn.xyz/scanner) +- GitHub: [github.com/lexispawn](https://github.com/lexispawn) +- X: [@lexispawn](https://x.com/lexispawn) +- Built by [Lexispawn](https://lexispawn.xyz) — ERC-8004 #11363 on Base diff --git a/15minds/references/api-reference.md b/15minds/references/api-reference.md new file mode 100644 index 0000000..1f0f764 --- /dev/null +++ b/15minds/references/api-reference.md @@ -0,0 +1,103 @@ +# 15minds API Reference + +Base URL: `http://your-host:4021` + +--- + +## Endpoints + +### `GET /` + +Service metadata. No authentication. + +**Response:** +```json +{ + "service": "15minds", + "version": "3.0.0", + "models": 15, + "families": ["Claude", "Gemini", "OpenAI", "Moonshot", "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": 15, "timestamp": "..." }` + +### `GET /x402` + +x402 payment configuration for automated clients. + +### `GET /models` + +List all active models with families and weights. + +**Response:** +```json +{ + "count": 15, + "models": [ + { "id": "claude-opus-4.6", "name": "Opus 4.6", "family": "Claude", "weight": 1.5 }, + ... + ] +} +``` + +### `GET /read/:contractAddress` + +**Main endpoint.** Queries all available models and returns weighted consensus. + +**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 | + +#### Path Parameters + +| Parameter | Description | +|-----------|-------------| +| `contractAddress` | Token contract address on Base | + +#### 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, include `X-Payment` header | +| `402` (with `tx_hash`) | Payment verification failed — wrong recipient, insufficient amount, or already used | + +#### 402 Response Body + +```json +{ + "error": "Payment Required", + "protocol": "x402", + "free_tier_exhausted": true, + "payment": { + "wallet": "0xd16f8c10e7a696a3e46093c60ede43d5594d2bad", + "amount": "0.00005", + "currency": "ETH", + "chain": "base", + "chain_id": 8453 + } +} +``` diff --git a/15minds/scripts/package.json b/15minds/scripts/package.json new file mode 100644 index 0000000..d7e4dd8 --- /dev/null +++ b/15minds/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/15minds/scripts/server.js b/15minds/scripts/server.js new file mode 100644 index 0000000..9be7d7d --- /dev/null +++ b/15minds/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}`); +});