From 1be0bc1639da9607afc79a7f5fc974a897dfb3a3 Mon Sep 17 00:00:00 2001 From: LimaDevBTC Date: Wed, 25 Mar 2026 18:49:43 -0300 Subject: [PATCH 1/6] [AIBTC Skills Comp Day 1] Add dog-intelligence skill MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On-chain intelligence hub for DOG•GO•TO•THE•MOON rune powered by DOG DATA's Bitcoin Core + Ord full node. 5 sub-commands: pulse, whales (>1M DOG threshold), diamond, airdrop, lth-sth. Read-only, no chain writes, zero dependencies. Co-Authored-By: Claude Opus 4.6 (1M context) --- skills/dog-intelligence/AGENT.md | 51 ++ skills/dog-intelligence/SKILL.md | 102 ++++ skills/dog-intelligence/dog-intelligence.ts | 486 ++++++++++++++++++++ 3 files changed, 639 insertions(+) create mode 100644 skills/dog-intelligence/AGENT.md create mode 100644 skills/dog-intelligence/SKILL.md create mode 100644 skills/dog-intelligence/dog-intelligence.ts diff --git a/skills/dog-intelligence/AGENT.md b/skills/dog-intelligence/AGENT.md new file mode 100644 index 00000000..5785e86d --- /dev/null +++ b/skills/dog-intelligence/AGENT.md @@ -0,0 +1,51 @@ +# dog-intelligence — Autonomous Operation Rules + +## Decision Flow + +1. **Always run `doctor` before any action.** If doctor returns `status: "blocked"` or any check fails, stop and report the issue. Do not proceed to `run`. +2. **Never expose API keys in logs or output.** If `DOGDATA_API_KEY` is set, mask it in all output (show `dog_live_***` not the full key). +3. **All outputs are strict JSON.** No plaintext, no markdown, no mixed formats. Every response follows the standard envelope: `{ status, action, data, error }`. +4. **If rate limited (HTTP 429)**, return `status: "blocked"` with the `Retry-After` value from headers. Never retry silently or loop. +5. **Data is read-only.** No action in this skill requires user confirmation, wallet access, or chain writes. No funds are moved, no transactions are signed. +6. **Always include `source` and `timestamp` in returned data.** Every response must attribute DOG DATA as the source and include the data freshness timestamp. + +## Safety Protocols + +- **No chain writes.** This skill reads public blockchain data only. +- **No wallet interaction.** Does not access, unlock, or reference any wallet. +- **No sensitive data.** Does not process private keys, mnemonics, passwords, or PII. +- **Mainnet safe.** All endpoints are read-only GET requests against dogdata.xyz. +- **Fail open.** If any endpoint is unreachable, return `status: "error"` with details — never hang or retry indefinitely. +- **Timeout enforcement.** Every HTTP request has a 10-second timeout. AbortController is used to prevent hanging. + +## Spending Limits + +None. This skill has zero cost — all data comes from a free public API. No sBTC, STX, or BTC is spent at any point. + +## Refusal Conditions + +- Refuse to run any action if `doctor` has not been run in the current session. +- Refuse to run if the API returns 5xx errors (service down) — report and wait. +- Refuse to expose raw API keys in any output or log. +- Refuse to make POST/PUT/DELETE requests — this skill is GET-only. + +## Whale Alert Thresholds + +- **Significant move:** > 1,000,000 DOG (1M) in a single transaction +- **Major holder change:** Any top-25 holder whose balance changes > 5% between checks +- **Accumulation signal:** Address receives > 500K DOG within 24 hours across multiple UTXOs + +## Data Interpretation Guidelines + +- **MVRV < 1.0:** DOG trades below realized value — historically undervalued zone. Flag as "accumulation territory." +- **MVRV > 3.0:** DOG trades well above realized value — overheated. Flag as "distribution risk." +- **LTH % > 75%:** Strong long-term conviction. Supply is locked. Bullish structural signal. +- **LTH % < 50%:** Weak conviction. Supply is mobile. Higher sell pressure risk. +- **Retention rate (airdrop):** Currently ~37%. Declining retention = increasing sell pressure from original recipients. +- **Gini > 0.8:** High concentration — top holders control significant supply. LP risk factor. + +## Cooldowns + +- Do not call the same endpoint more than once per 3 minutes (respect 20 req/hr public limit). +- For autonomous loop integration, one `pulse` per cycle (5 min) is the recommended cadence. +- `whales` and `diamond` are heavier queries — limit to once per 15 minutes in autonomous mode. diff --git a/skills/dog-intelligence/SKILL.md b/skills/dog-intelligence/SKILL.md new file mode 100644 index 00000000..07db8d15 --- /dev/null +++ b/skills/dog-intelligence/SKILL.md @@ -0,0 +1,102 @@ +--- +name: dog-intelligence +description: On-chain intelligence for DOG•GO•TO•THE•MOON rune — forensic analysis, LTH vs STH metrics, whale tracking, and airdrop analytics powered by DOG DATA's Bitcoin full node. +author: xored-pike +author_agent: Xored Pike +user-invocable: true +arguments: doctor | run --action pulse | run --action whales | run --action diamond | run --action airdrop | run --action lth-sth | install-packs +entry: dog-intelligence/dog-intelligence.ts +requires: [settings] +tags: [read-only, infrastructure, defi, l1] +--- + +# dog-intelligence + +On-chain intelligence hub for **DOG•GO•TO•THE•MOON** — the largest Bitcoin rune by holders (89K+). Pulls data directly from a Bitcoin Core + Ord full node via the [DOG DATA](https://dogdata.xyz) API. + +## What it does + +DOG intelligence provides 5 sub-commands that give agents real-time and historical analytics on the DOG rune: + +| Action | What it returns | +|--------|----------------| +| **pulse** | Full market snapshot: price (6 exchanges), MVRV ratio, holder count, LTH vs STH ratio, Diamond Paws count, retention rate. One call, complete picture. | +| **whales** | Top 25 holders with balances + recent transactions filtered for large moves (>1B DOG). Whale concentration and accumulation signals. | +| **diamond** | Forensic behavioral profiles — Diamond Paws, Dog Legends, Paper Hands breakdown. Proprietary Diamond Score ratings across 14 holder categories. | +| **airdrop** | Origin story analytics: the Runestone→DOG airdrop (100B tokens, 75,497 recipients, zero pre-sale). Retention rate, behavior breakdown, current holder status. | +| **lth-sth** | The trademark metric: Long-Term Holder vs Short-Term Holder supply ratio. HODL waves, UTXO age distribution, median age, and supply conviction analysis. | + +## Why agents need it + +DOG is the #1 rune by holder count (89,194+) and the most liquid rune across CEX + DEX markets. Agents operating in Bitcoin DeFi need: + +- **Holder concentration** (Gini coefficient, top 10/100/1000 %) to assess LP impermanent loss risk +- **MVRV ratio** for timing entry/exit — currently 0.27, meaning DOG trades at 27% of its realized value (historically undervalued) +- **Whale alerts** for frontrunning protection — know when top holders move before it hits the orderbook +- **Forensic profiles** for narrative-grade intelligence — no other data source classifies 75K+ wallets into Diamond Paws, Paper Hands, Dog Legends, etc. +- **LTH vs STH ratio** — the single most predictive metric for supply-side conviction. 78.6% of DOG supply is in long-term hands (>155 days). + +No other API offers Diamond Score, forensic categorization, or LTH/STH breakdown for any rune. This is Glassnode-grade analytics for Bitcoin's fungible token layer. + +## Safety notes + +- **Read-only skill.** Does not write to chain, does not move funds, does not sign transactions. +- No sensitive data processed. No private keys, no mnemonics, no wallet access required. +- All data sourced from DOG DATA's public API (dogdata.xyz). +- Safe for mainnet and testnet. No network-specific risk. +- Rate limited: 20 req/hr without API key, 100 req/hr with free key. +- If rate limited (HTTP 429), the skill returns `status: "blocked"` with retry information — never retries silently. + +## Data source + +[DOG DATA](https://dogdata.xyz) — the world's most comprehensive DOG rune data platform. Runs its own Bitcoin Core + Ord full node. No third-party API dependency. 35 REST endpoints, MCP server, SSE real-time events. + +- API Discovery: https://www.dogdata.xyz/api +- OpenAPI Spec: https://www.dogdata.xyz/api/openapi.json +- LLM Context: https://www.dogdata.xyz/llms.txt + +## Commands + +### Pre-flight check + +```bash +bun run skills/dog-intelligence/dog-intelligence.ts doctor +``` + +Checks API health, connectivity, and API key status. **Always run before other commands.** + +### Market pulse snapshot + +```bash +bun run skills/dog-intelligence/dog-intelligence.ts run --action pulse +``` + +### Whale tracking + +```bash +bun run skills/dog-intelligence/dog-intelligence.ts run --action whales +``` + +### Diamond Score forensics + +```bash +bun run skills/dog-intelligence/dog-intelligence.ts run --action diamond +``` + +### Airdrop origin story + +```bash +bun run skills/dog-intelligence/dog-intelligence.ts run --action airdrop +``` + +### LTH vs STH conviction analysis + +```bash +bun run skills/dog-intelligence/dog-intelligence.ts run --action lth-sth +``` + +### Install optional SDK + +```bash +bun run skills/dog-intelligence/dog-intelligence.ts install-packs +``` diff --git a/skills/dog-intelligence/dog-intelligence.ts b/skills/dog-intelligence/dog-intelligence.ts new file mode 100644 index 00000000..83b8d990 --- /dev/null +++ b/skills/dog-intelligence/dog-intelligence.ts @@ -0,0 +1,486 @@ +#!/usr/bin/env bun +/** + * dog-intelligence — On-chain intelligence for DOG•GO•TO•THE•MOON rune + * + * Powered by DOG DATA (dogdata.xyz) — Bitcoin Core + Ord full node. + * Read-only. No chain writes. No wallet access. + * + * Usage: + * bun run dog-intelligence.ts doctor + * bun run dog-intelligence.ts run --action pulse + * bun run dog-intelligence.ts run --action whales + * bun run dog-intelligence.ts run --action diamond + * bun run dog-intelligence.ts run --action airdrop + * bun run dog-intelligence.ts run --action lth-sth + * bun run dog-intelligence.ts install-packs + */ + +const DOGDATA_BASE = "https://www.dogdata.xyz/api"; +const API_KEY = process.env.DOGDATA_API_KEY || ""; +const TIMEOUT_MS = 10_000; + +// --- Output envelope --- + +interface Output { + status: "success" | "error" | "blocked"; + action: string; + data: Record | null; + error: string | null; + source: string; + timestamp: string; +} + +function out(status: Output["status"], action: string, data: Output["data"], error: string | null = null): void { + const result: Output = { + status, + action, + data, + error, + source: "dogdata.xyz", + timestamp: new Date().toISOString(), + }; + console.log(JSON.stringify(result, null, 2)); +} + +// --- HTTP helpers --- + +function headers(): Record { + const h: Record = { "Accept": "application/json" }; + if (API_KEY) h["Authorization"] = `Bearer ${API_KEY}`; + return h; +} + +async function get(path: string): Promise<{ ok: boolean; status: number; data: unknown; retryAfter?: number }> { + const controller = new AbortController(); + const timer = setTimeout(() => controller.abort(), TIMEOUT_MS); + try { + const res = await fetch(`${DOGDATA_BASE}${path}`, { + headers: headers(), + signal: controller.signal, + }); + clearTimeout(timer); + if (res.status === 429) { + const retryAfter = parseInt(res.headers.get("Retry-After") || "60", 10); + return { ok: false, status: 429, data: null, retryAfter }; + } + if (!res.ok) { + const text = await res.text().catch(() => ""); + return { ok: false, status: res.status, data: text }; + } + const json = await res.json(); + return { ok: true, status: res.status, data: json }; + } catch (err: unknown) { + clearTimeout(timer); + const message = err instanceof Error ? err.message : String(err); + return { ok: false, status: 0, data: null, retryAfter: undefined }; + } +} + +async function fetchMultiple(paths: string[]): Promise> { + const results = await Promise.allSettled(paths.map((p) => get(p))); + const output: Record = {}; + for (let i = 0; i < paths.length; i++) { + const r = results[i]; + if (r.status === "fulfilled") { + if (r.value.status === 429) { + out("blocked", "rate_limited", null, `Rate limited on ${paths[i]}. Retry after ${r.value.retryAfter}s.`); + process.exit(0); + } + output[paths[i]] = r.value.ok ? r.value.data : null; + } else { + output[paths[i]] = null; + } + } + return output; +} + +// --- Commands --- + +async function doctor(): Promise { + const health = await get("/health"); + + const checks: Record = { + api_reachable: health.ok, + api_status: health.ok ? (health.data as Record)?.status : "unreachable", + api_key_configured: API_KEY ? "yes" : "no (using public tier — 20 req/hr)", + api_key_masked: API_KEY ? `${API_KEY.slice(0, 10)}***` : "none", + }; + + if (health.ok) { + const hd = health.data as Record; + const hChecks = hd.checks as Record> | undefined; + if (hChecks) { + checks.redis = hChecks.redis?.status || "unknown"; + checks.holders_data = hChecks.holders_data?.status || "unknown"; + checks.holders_details = hChecks.holders_data?.details || ""; + checks.transactions = hChecks.transactions?.status || "unknown"; + } + checks.version = hd.version || "unknown"; + checks.uptime_seconds = hd.uptime_seconds || 0; + } + + const allOk = health.ok && checks.api_status === "healthy"; + out( + allOk ? "success" : "error", + allOk ? "All systems operational. Ready for queries." : "API health check failed. Investigate before running actions.", + checks, + allOk ? null : `API returned status: ${health.status}`, + ); +} + +async function pulse(): Promise { + const raw = await fetchMultiple([ + "/dog-rune/stats", + "/price/kraken", + "/metrics/realized-cap", + "/forensic/summary", + "/metrics/utxo-age", + ]); + + const stats = raw["/dog-rune/stats"] as Record | null; + const price = raw["/price/kraken"] as Record | null; + const realized = raw["/metrics/realized-cap"] as Record | null; + const forensic = raw["/forensic/summary"] as Record | null; + const utxoAge = raw["/metrics/utxo-age"] as Record | null; + + // Extract Kraken price + const krakenResult = price?.result as Record> | undefined; + const dogusd = krakenResult?.DOGUSD; + const currentPrice = dogusd?.c ? parseFloat((dogusd.c as string[])[0]) : null; + const volume24h = dogusd?.v ? parseFloat((dogusd.v as string[])[1]) : null; + const high24h = dogusd?.h ? parseFloat((dogusd.h as string[])[1]) : null; + const low24h = dogusd?.l ? parseFloat((dogusd.l as string[])[1]) : null; + const openPrice = dogusd?.o ? parseFloat(dogusd.o as string) : null; + const change24hPct = currentPrice && openPrice ? ((currentPrice - openPrice) / openPrice) * 100 : null; + + // MVRV interpretation + const mvrv = realized?.mvrv_ratio as number | null; + let mvrvSignal = "neutral"; + if (mvrv !== null) { + if (mvrv < 1.0) mvrvSignal = "undervalued — trading below realized value (accumulation zone)"; + else if (mvrv > 3.0) mvrvSignal = "overheated — distribution risk"; + else mvrvSignal = "fair value range"; + } + + // Forensic stats + const forensicStats = forensic?.statistics as Record | undefined; + + // LTH vs STH + const lthPct = utxoAge?.lth_percentage as number | null; + const sthPct = utxoAge?.sth_percentage as number | null; + + out("success", "DOG pulse snapshot. Use this data for market analysis and signal generation.", { + price: { + usd: currentPrice, + change_24h_pct: change24hPct ? +change24hPct.toFixed(2) : null, + high_24h: high24h, + low_24h: low24h, + volume_24h_dog: volume24h, + exchange: "Kraken", + }, + fundamentals: { + total_holders: stats?.totalHolders ?? null, + circulating_supply: stats?.circulatingSupply ?? null, + total_utxos: stats?.totalUtxos ?? null, + market_cap: realized?.market_cap ?? null, + realized_cap: realized?.realized_cap ?? null, + mvrv_ratio: mvrv, + mvrv_signal: mvrvSignal, + }, + conviction: { + lth_percentage: lthPct ? +lthPct.toFixed(2) : null, + sth_percentage: sthPct ? +sthPct.toFixed(2) : null, + lth_sth_ratio: lthPct && sthPct ? +(lthPct / sthPct).toFixed(2) : null, + median_utxo_age_days: utxoAge?.median_age_days ? +(utxoAge.median_age_days as number).toFixed(1) : null, + }, + forensic: { + diamond_paws: forensicStats?.diamond_hands ?? null, + dog_legends: (forensicStats?.by_pattern as Record)?.dog_legend ?? null, + paper_hands: (forensicStats?.by_pattern as Record)?.paper_hands ?? null, + retention_rate_pct: forensicStats?.retention_rate ? +(forensicStats.retention_rate as number).toFixed(2) : null, + total_analyzed: forensicStats?.total_analyzed ?? null, + }, + }); +} + +async function whales(): Promise { + const raw = await fetchMultiple([ + "/dog-rune/holders?limit=25", + "/dog-rune/transactions-kv", + ]); + + const holdersData = raw["/dog-rune/holders?limit=25"]; + const txData = raw["/dog-rune/transactions-kv"] as Record | null; + + // Parse holders — may be array or object with holders key + let holders: Record[] = []; + if (Array.isArray(holdersData)) { + holders = holdersData; + } else if (holdersData && typeof holdersData === "object") { + const hd = holdersData as Record; + if (Array.isArray(hd.holders)) holders = hd.holders; + else if (Array.isArray(hd.top10Holders)) holders = hd.top10Holders; + } + + // Format top holders + const topHolders = holders.slice(0, 25).map((h: Record, i: number) => ({ + rank: h.rank ?? i + 1, + address: h.address, + balance_dog: h.total_dog ?? (typeof h.total_amount === "number" ? h.total_amount / 1e5 : null), + utxo_count: h.utxo_count ?? null, + })); + + // Extract large transactions (> 1B DOG) + const WHALE_THRESHOLD = 1_000_000; // 1M DOG + const txs = txData?.transactions as Record[] | undefined; + const whaleTxs = (txs || []) + .filter((tx: Record) => { + const amount = tx.amount as number | undefined; + const totalDog = tx.total_dog as number | undefined; + const val = totalDog ?? (amount ? amount / 1e5 : 0); + return val >= WHALE_THRESHOLD; + }) + .slice(0, 20) + .map((tx: Record) => ({ + txid: tx.txid ?? tx.tx_id, + block: tx.block_height ?? tx.block, + amount_dog: tx.total_dog ?? (typeof tx.amount === "number" ? tx.amount / 1e5 : null), + type: tx.type ?? "transfer", + timestamp: tx.timestamp ?? null, + })); + + // Concentration + const top10Sum = topHolders.slice(0, 10).reduce((s, h) => s + ((h.balance_dog as number) || 0), 0); + const totalSupply = 100_000_000_000; + const top10Pct = (top10Sum / totalSupply) * 100; + + out("success", "Whale intelligence report. Top holders and large recent movements.", { + top_holders: topHolders, + whale_transactions: whaleTxs, + whale_tx_count: whaleTxs.length, + concentration: { + top_10_supply_pct: +top10Pct.toFixed(2), + top_10_total_dog: Math.round(top10Sum), + }, + threshold_dog: WHALE_THRESHOLD, + total_transactions_scanned: txs?.length ?? 0, + }); +} + +async function diamond(): Promise { + const raw = await fetchMultiple([ + "/forensic/summary", + "/forensic/profiles?limit=10&pattern=diamond_paws", + ]); + + const summary = raw["/forensic/summary"] as Record | null; + const profilesRaw = raw["/forensic/profiles?limit=10&pattern=diamond_paws"]; + + const stats = summary?.statistics as Record | undefined; + const patterns = stats?.by_pattern as Record | undefined; + + // Profile data + let sampleProfiles: unknown[] = []; + if (Array.isArray(profilesRaw)) { + sampleProfiles = profilesRaw.slice(0, 10); + } else if (profilesRaw && typeof profilesRaw === "object") { + const pr = profilesRaw as Record; + if (Array.isArray(pr.profiles)) sampleProfiles = pr.profiles.slice(0, 10); + } + + out("success", "Diamond Score forensics. Holder conviction analysis across 14 behavioral categories.", { + summary: { + total_analyzed: stats?.total_analyzed ?? null, + still_holding: stats?.still_holding ?? null, + sold_everything: stats?.sold_everything ?? null, + retention_rate_pct: stats?.retention_rate ? +(stats.retention_rate as number).toFixed(2) : null, + accumulator_rate_pct: stats?.accumulator_rate ? +(stats.accumulator_rate as number).toFixed(2) : null, + dumper_rate_pct: stats?.dumper_rate ? +(stats.dumper_rate as number).toFixed(2) : null, + }, + categories: { + diamond_paws: patterns?.diamond_paws ?? null, + dog_legend: patterns?.dog_legend ?? null, + hodl_hero: patterns?.hodl_hero ?? null, + rune_master: patterns?.rune_master ?? null, + ordinal_believer: patterns?.ordinal_believer ?? null, + satoshi_visionary: patterns?.satoshi_visionary ?? null, + steady_holder: patterns?.steady_holder ?? null, + btc_maximalist: patterns?.btc_maximalist ?? null, + profit_taker: patterns?.profit_taker ?? null, + early_exit: patterns?.early_exit ?? null, + panic_seller: patterns?.panic_seller ?? null, + paper_hands: patterns?.paper_hands ?? null, + }, + conviction_tiers: { + diamond: (patterns?.diamond_paws ?? 0) + (patterns?.dog_legend ?? 0) + (patterns?.hodl_hero ?? 0) + (patterns?.rune_master ?? 0), + neutral: (patterns?.steady_holder ?? 0) + (patterns?.ordinal_believer ?? 0) + (patterns?.btc_maximalist ?? 0) + (patterns?.satoshi_visionary ?? 0), + weak: (patterns?.profit_taker ?? 0) + (patterns?.early_exit ?? 0) + (patterns?.panic_seller ?? 0) + (patterns?.paper_hands ?? 0), + }, + sample_profiles: sampleProfiles, + }); +} + +async function airdrop(): Promise { + const raw = await fetchMultiple([ + "/airdrop/summary", + "/airdrop/recipients?limit=10", + ]); + + const summary = raw["/airdrop/summary"] as Record | null; + const recipientsRaw = raw["/airdrop/recipients?limit=10"]; + + let topRecipients: unknown[] = []; + if (Array.isArray(recipientsRaw)) { + topRecipients = recipientsRaw.slice(0, 10); + } else if (recipientsRaw && typeof recipientsRaw === "object") { + const rr = recipientsRaw as Record; + if (Array.isArray(rr.recipients)) topRecipients = rr.recipients.slice(0, 10); + } + + const totalRecipients = summary?.total_recipients as number ?? 0; + const stillHolding = summary?.still_holding as number ?? 0; + const soldEverything = summary?.sold_everything as number ?? 0; + const retentionRate = totalRecipients > 0 ? (stillHolding / totalRecipients) * 100 : 0; + + out("success", "Airdrop origin story. The largest fair rune distribution in Bitcoin history.", { + origin: { + event: "Runestone → DOG Airdrop", + total_supply: "100,000,000,000 DOG", + pre_sale: "zero", + vc_allocation: "zero", + mechanism: "Fair airdrop to Runestone holders at Bitcoin halving block 840,000", + rune_id: "840000:3", + date: "April 2024 (Bitcoin halving)", + }, + distribution: { + total_recipients: totalRecipients, + still_holding: stillHolding, + sold_everything: soldEverything, + retention_rate_pct: +retentionRate.toFixed(2), + recipients_with_multiple_airdrops: summary?.recipients_with_multiple ?? null, + total_airdrop_events: summary?.total_airdrops ?? null, + current_holder_balance_dog: summary?.total_current_balance ?? null, + }, + narrative: `The largest fair rune distribution ever: 100 billion DOG tokens airdropped to ${totalRecipients.toLocaleString()} Runestone holders at Bitcoin's 4th halving. Zero pre-sale, zero VC, zero team allocation. ${retentionRate.toFixed(1)}% of original recipients still hold — ${stillHolding.toLocaleString()} diamond hands from day one.`, + top_recipients: topRecipients, + }); +} + +async function lthSth(): Promise { + const raw = await fetchMultiple([ + "/metrics/utxo-age", + "/metrics/holder-concentration", + "/metrics/supply-profit-loss", + ]); + + const utxoAge = raw["/metrics/utxo-age"] as Record | null; + const concentration = raw["/metrics/holder-concentration"] as Record | null; + const profitLoss = raw["/metrics/supply-profit-loss"] as Record | null; + + const lthPct = utxoAge?.lth_percentage as number | null; + const sthPct = utxoAge?.sth_percentage as number | null; + const lthSthRatio = lthPct && sthPct && sthPct > 0 ? lthPct / sthPct : null; + + // Interpret + let convictionSignal = "neutral"; + if (lthPct !== null) { + if (lthPct > 75) convictionSignal = "strong — majority of supply in long-term hands, structurally bullish"; + else if (lthPct > 50) convictionSignal = "moderate — more holders are long-term than short-term"; + else convictionSignal = "weak — short-term holders dominate, higher sell pressure risk"; + } + + // HODL waves + const ageDist = utxoAge?.age_distribution as Record | undefined; + const totalSupplyRaw = utxoAge?.total_supply as number ?? 1; + const hodlWaves: Record = {}; + if (ageDist) { + for (const [range, supply] of Object.entries(ageDist)) { + hodlWaves[range] = ((supply / totalSupplyRaw) * 100).toFixed(2) + "%"; + } + } + + out("success", "LTH vs STH conviction analysis — the trademark DOG intelligence metric.", { + lth_vs_sth: { + lth_percentage: lthPct ? +lthPct.toFixed(2) : null, + sth_percentage: sthPct ? +sthPct.toFixed(2) : null, + lth_sth_ratio: lthSthRatio ? +lthSthRatio.toFixed(2) : null, + conviction_signal: convictionSignal, + lth_threshold_days: 155, + }, + utxo_age: { + total_utxos: utxoAge?.total_utxos ?? null, + avg_age_days: utxoAge?.avg_age_days ? +(utxoAge.avg_age_days as number).toFixed(1) : null, + median_age_days: utxoAge?.median_age_days ? +(utxoAge.median_age_days as number).toFixed(1) : null, + }, + hodl_waves: hodlWaves, + concentration: { + gini_coefficient: concentration?.gini_coefficient ? +(concentration.gini_coefficient as number).toFixed(4) : null, + top_10_pct: concentration?.top10_supply_pct ? +(concentration.top10_supply_pct as number).toFixed(2) : null, + top_100_pct: concentration?.top100_supply_pct ? +(concentration.top100_supply_pct as number).toFixed(2) : null, + top_1000_pct: concentration?.top1000_supply_pct ? +(concentration.top1000_supply_pct as number).toFixed(2) : null, + total_holders: concentration?.total_holders ?? null, + }, + supply_health: { + in_profit_pct: profitLoss?.supply_in_profit_pct ? +(profitLoss.supply_in_profit_pct as number).toFixed(2) : null, + in_loss_pct: profitLoss?.supply_in_loss_pct ? +(profitLoss.supply_in_loss_pct as number).toFixed(2) : null, + current_price_usd: profitLoss?.current_price ?? null, + }, + }); +} + +async function installPacks(): Promise { + out("success", "No additional packages required. dog-intelligence uses fetch (built into Bun) and the DOG DATA REST API. Optionally set DOGDATA_API_KEY env var for higher rate limits (100 req/hr).", { + required_dependencies: [], + optional: { + env_var: "DOGDATA_API_KEY", + get_key: "POST https://www.dogdata.xyz/api/keys/generate with {\"email\": \"...\", \"name\": \"...\"}", + free_tier: "100 req/hr", + public_tier: "20 req/hr (no key needed)", + }, + }); +} + +// --- Main --- + +async function main(): Promise { + const command = Bun.argv[2]; + const actionFlag = Bun.argv.indexOf("--action"); + const action = actionFlag !== -1 ? Bun.argv[actionFlag + 1] : "pulse"; + + switch (command) { + case "doctor": + await doctor(); + break; + case "run": + switch (action) { + case "pulse": + await pulse(); + break; + case "whales": + await whales(); + break; + case "diamond": + await diamond(); + break; + case "airdrop": + await airdrop(); + break; + case "lth-sth": + await lthSth(); + break; + default: + out("error", "Unknown action", null, `Unknown action: ${action}. Valid: pulse, whales, diamond, airdrop, lth-sth`); + } + break; + case "install-packs": + await installPacks(); + break; + default: + out("error", "Unknown command", null, `Unknown command: ${command}. Valid: doctor, run, install-packs`); + } +} + +main().catch((err) => { + out("error", "Fatal error", null, err instanceof Error ? err.message : String(err)); + process.exit(1); +}); From 17e6aceef2d2f92dbc1eb289571dc9c212e0f4cf Mon Sep 17 00:00:00 2001 From: LimaDevBTC Date: Thu, 26 Mar 2026 10:45:33 -0300 Subject: [PATCH 2/6] fix: address review feedback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove unused variable in catch block - Fix misleading comment: 1B → 1M DOG in whales() - Author: LimaDevBTC (operator) + Xored Pike (AIBTC agent) Co-Authored-By: Claude Opus 4.6 (1M context) --- skills/dog-intelligence/SKILL.md | 16 ++++++++-------- skills/dog-intelligence/dog-intelligence.ts | 5 ++--- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/skills/dog-intelligence/SKILL.md b/skills/dog-intelligence/SKILL.md index 07db8d15..59ea425a 100644 --- a/skills/dog-intelligence/SKILL.md +++ b/skills/dog-intelligence/SKILL.md @@ -1,7 +1,7 @@ --- name: dog-intelligence description: On-chain intelligence for DOG•GO•TO•THE•MOON rune — forensic analysis, LTH vs STH metrics, whale tracking, and airdrop analytics powered by DOG DATA's Bitcoin full node. -author: xored-pike +author: LimaDevBTC author_agent: Xored Pike user-invocable: true arguments: doctor | run --action pulse | run --action whales | run --action diamond | run --action airdrop | run --action lth-sth | install-packs @@ -60,7 +60,7 @@ No other API offers Diamond Score, forensic categorization, or LTH/STH breakdown ### Pre-flight check ```bash -bun run skills/dog-intelligence/dog-intelligence.ts doctor +bun run dog-intelligence/dog-intelligence.ts doctor ``` Checks API health, connectivity, and API key status. **Always run before other commands.** @@ -68,35 +68,35 @@ Checks API health, connectivity, and API key status. **Always run before other c ### Market pulse snapshot ```bash -bun run skills/dog-intelligence/dog-intelligence.ts run --action pulse +bun run dog-intelligence/dog-intelligence.ts run --action pulse ``` ### Whale tracking ```bash -bun run skills/dog-intelligence/dog-intelligence.ts run --action whales +bun run dog-intelligence/dog-intelligence.ts run --action whales ``` ### Diamond Score forensics ```bash -bun run skills/dog-intelligence/dog-intelligence.ts run --action diamond +bun run dog-intelligence/dog-intelligence.ts run --action diamond ``` ### Airdrop origin story ```bash -bun run skills/dog-intelligence/dog-intelligence.ts run --action airdrop +bun run dog-intelligence/dog-intelligence.ts run --action airdrop ``` ### LTH vs STH conviction analysis ```bash -bun run skills/dog-intelligence/dog-intelligence.ts run --action lth-sth +bun run dog-intelligence/dog-intelligence.ts run --action lth-sth ``` ### Install optional SDK ```bash -bun run skills/dog-intelligence/dog-intelligence.ts install-packs +bun run dog-intelligence/dog-intelligence.ts install-packs ``` diff --git a/skills/dog-intelligence/dog-intelligence.ts b/skills/dog-intelligence/dog-intelligence.ts index 83b8d990..1db937f4 100644 --- a/skills/dog-intelligence/dog-intelligence.ts +++ b/skills/dog-intelligence/dog-intelligence.ts @@ -69,9 +69,8 @@ async function get(path: string): Promise<{ ok: boolean; status: number; data: u } const json = await res.json(); return { ok: true, status: res.status, data: json }; - } catch (err: unknown) { + } catch { clearTimeout(timer); - const message = err instanceof Error ? err.message : String(err); return { ok: false, status: 0, data: null, retryAfter: undefined }; } } @@ -230,7 +229,7 @@ async function whales(): Promise { utxo_count: h.utxo_count ?? null, })); - // Extract large transactions (> 1B DOG) + // Extract large transactions (> 1M DOG) const WHALE_THRESHOLD = 1_000_000; // 1M DOG const txs = txData?.transactions as Record[] | undefined; const whaleTxs = (txs || []) From 288a8ad5253cec911badb4c381e5e0c05b48e397 Mon Sep 17 00:00:00 2001 From: LimaDevBTC Date: Thu, 16 Apr 2026 17:36:42 -0300 Subject: [PATCH 3/6] fix: apply all review fixes + add 3 new actions (markets, multichain, bitcoin) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Registry compliance fixes (all requested by reviewers): - SKILL.md: migrate frontmatter to nested metadata: block - SKILL.md: fix author_agent -> author-agent (hyphen), user-invocable as string - SKILL.md: fix requires and tags from array to comma-separated string - SKILL.md: add missing Output contract section - AGENT.md: add required YAML frontmatter (name, skill, description) Skill improvements: - whales: migrate to /whale-alerts (multi-chain, severity CRITICAL/HIGH, USD value) - whales: use /dog-rune/top-holders dedicated endpoint - pulse: use /markets for aggregated price across all exchanges - doctor: smoke-test new endpoints (markets, whale-alerts, multichain) - New action: markets — 20+ exchanges with volume, spread, trust score - New action: multichain — DOG on Stacks + Solana (bridged supply) - New action: bitcoin — BTC network status, mempool, fees, difficulty Co-Authored-By: Claude Sonnet 4.6 --- skills/dog-intelligence/AGENT.md | 34 ++- skills/dog-intelligence/SKILL.md | 106 +++++-- skills/dog-intelligence/dog-intelligence.ts | 299 ++++++++++++++++---- 3 files changed, 361 insertions(+), 78 deletions(-) diff --git a/skills/dog-intelligence/AGENT.md b/skills/dog-intelligence/AGENT.md index 5785e86d..47a817c4 100644 --- a/skills/dog-intelligence/AGENT.md +++ b/skills/dog-intelligence/AGENT.md @@ -1,3 +1,9 @@ +--- +name: dog-intelligence-agent +skill: dog-intelligence +description: "Autonomous operation rules for DOG rune on-chain intelligence — read-only analytics with forensic profiling, multi-chain whale tracking, multi-exchange market data, and conviction metrics." +--- + # dog-intelligence — Autonomous Operation Rules ## Decision Flow @@ -31,9 +37,11 @@ None. This skill has zero cost — all data comes from a free public API. No sBT ## Whale Alert Thresholds -- **Significant move:** > 1,000,000 DOG (1M) in a single transaction +- **Default threshold:** > 1,000,000 DOG (1M) per transaction — applied by the `/whale-alerts` endpoint +- **HIGH severity:** Large individual whale move flagged by DOG DATA's classification engine +- **CRITICAL severity:** Exceptional move — likely top-10 holder or exchange-scale transfer - **Major holder change:** Any top-25 holder whose balance changes > 5% between checks -- **Accumulation signal:** Address receives > 500K DOG within 24 hours across multiple UTXOs +- **Multi-chain context:** Whale alerts cover Bitcoin L1, Stacks, and Solana — always check `chain` field ## Data Interpretation Guidelines @@ -41,11 +49,29 @@ None. This skill has zero cost — all data comes from a free public API. No sBT - **MVRV > 3.0:** DOG trades well above realized value — overheated. Flag as "distribution risk." - **LTH % > 75%:** Strong long-term conviction. Supply is locked. Bullish structural signal. - **LTH % < 50%:** Weak conviction. Supply is mobile. Higher sell pressure risk. -- **Retention rate (airdrop):** Currently ~37%. Declining retention = increasing sell pressure from original recipients. +- **Retention rate (airdrop):** Declining retention = increasing sell pressure from original recipients. - **Gini > 0.8:** High concentration — top holders control significant supply. LP risk factor. +- **Price spread across exchanges > 1%:** Arbitrage opportunity exists. May indicate low liquidity on some venues. +- **Multichain supply on Stacks/Solana:** This is bridged supply — does NOT reduce Bitcoin L1 supply. Track separately. +- **Bitcoin mempool > 50K txs:** Network congested — DOG L1 transactions may be delayed. Flag to user. +- **Fee > 50 sat/vB:** High fee environment. Large DOG transfers become expensive. Advise batching. + +## Action Selection Guide + +| Situation | Recommended Action | +|-----------|-------------------| +| Quick market check | `pulse` | +| Large move detected / whale alert | `whales` | +| Narrative / conviction analysis | `diamond` | +| Historical distribution context | `airdrop` | +| Supply-side sentiment | `lth-sth` | +| LP / arbitrage / exchange comparison | `markets` | +| Cross-chain bridge analysis | `multichain` | +| Fee planning / transaction timing | `bitcoin` | ## Cooldowns - Do not call the same endpoint more than once per 3 minutes (respect 20 req/hr public limit). - For autonomous loop integration, one `pulse` per cycle (5 min) is the recommended cadence. -- `whales` and `diamond` are heavier queries — limit to once per 15 minutes in autonomous mode. +- `whales`, `diamond`, and `markets` are heavier queries — limit to once per 15 minutes in autonomous mode. +- `bitcoin` and `multichain` update every 5 minutes on the server side — calling more often returns cached data. diff --git a/skills/dog-intelligence/SKILL.md b/skills/dog-intelligence/SKILL.md index 59ea425a..737701f4 100644 --- a/skills/dog-intelligence/SKILL.md +++ b/skills/dog-intelligence/SKILL.md @@ -1,13 +1,14 @@ --- name: dog-intelligence -description: On-chain intelligence for DOG•GO•TO•THE•MOON rune — forensic analysis, LTH vs STH metrics, whale tracking, and airdrop analytics powered by DOG DATA's Bitcoin full node. -author: LimaDevBTC -author_agent: Xored Pike -user-invocable: true -arguments: doctor | run --action pulse | run --action whales | run --action diamond | run --action airdrop | run --action lth-sth | install-packs -entry: dog-intelligence/dog-intelligence.ts -requires: [settings] -tags: [read-only, infrastructure, defi, l1] +description: "On-chain intelligence for DOG•GO•TO•THE•MOON rune — forensic analysis, LTH vs STH metrics, multi-chain whale tracking, multi-exchange markets, cross-chain data, and airdrop analytics powered by DOG DATA's Bitcoin full node." +metadata: + author: "LimaDevBTC" + author-agent: "Xored Pike" + user-invocable: "false" + arguments: "doctor | run --action pulse | run --action whales | run --action diamond | run --action airdrop | run --action lth-sth | run --action markets | run --action multichain | run --action bitcoin | install-packs" + entry: "dog-intelligence/dog-intelligence.ts" + requires: "settings" + tags: "read-only, infrastructure, defi, l1, multichain" --- # dog-intelligence @@ -16,25 +17,31 @@ On-chain intelligence hub for **DOG•GO•TO•THE•MOON** — the largest Bit ## What it does -DOG intelligence provides 5 sub-commands that give agents real-time and historical analytics on the DOG rune: +DOG intelligence provides 8 sub-commands that give agents real-time and historical analytics on the DOG rune: | Action | What it returns | |--------|----------------| -| **pulse** | Full market snapshot: price (6 exchanges), MVRV ratio, holder count, LTH vs STH ratio, Diamond Paws count, retention rate. One call, complete picture. | -| **whales** | Top 25 holders with balances + recent transactions filtered for large moves (>1B DOG). Whale concentration and accumulation signals. | +| **pulse** | Full market snapshot: price aggregated across all exchanges, MVRV ratio, holder count, LTH vs STH ratio, Diamond Paws count, retention rate. One call, complete picture. | +| **whales** | Multi-chain whale alerts with severity (CRITICAL/HIGH), USD value, classification, and top 25 holder positions. Covers Bitcoin L1, Stacks, and Solana. | | **diamond** | Forensic behavioral profiles — Diamond Paws, Dog Legends, Paper Hands breakdown. Proprietary Diamond Score ratings across 14 holder categories. | -| **airdrop** | Origin story analytics: the Runestone→DOG airdrop (100B tokens, 75,497 recipients, zero pre-sale). Retention rate, behavior breakdown, current holder status. | +| **airdrop** | Origin story analytics: the Runestone→DOG airdrop (100B tokens, 75,490 recipients, zero pre-sale). Retention rate, behavior breakdown, current holder status. | | **lth-sth** | The trademark metric: Long-Term Holder vs Short-Term Holder supply ratio. HODL waves, UTXO age distribution, median age, and supply conviction analysis. | +| **markets** | Full multi-exchange view: prices and volumes from 20+ exchanges (CEX + Solana DEX). Spread analysis, trust scores, and 24h high/low from Kraken. | +| **multichain** | Cross-chain DOG intelligence: bridged supply on Stacks and Solana. Holder count, market cap, liquidity, and 24h metrics per chain. | +| **bitcoin** | Bitcoin network context: latest block, hashrate, difficulty adjustment progress, mempool depth, and fee recommendations. | ## Why agents need it -DOG is the #1 rune by holder count (89,194+) and the most liquid rune across CEX + DEX markets. Agents operating in Bitcoin DeFi need: +DOG is the #1 rune by holder count (89,020+) and the most liquid rune across CEX + DEX markets. Agents operating in Bitcoin DeFi need: - **Holder concentration** (Gini coefficient, top 10/100/1000 %) to assess LP impermanent loss risk - **MVRV ratio** for timing entry/exit — currently 0.27, meaning DOG trades at 27% of its realized value (historically undervalued) -- **Whale alerts** for frontrunning protection — know when top holders move before it hits the orderbook +- **Multi-chain whale alerts** for frontrunning protection — severity ratings (CRITICAL/HIGH), USD value, and classification across Bitcoin L1, Stacks, and Solana. Threshold: >1M DOG per transaction. - **Forensic profiles** for narrative-grade intelligence — no other data source classifies 75K+ wallets into Diamond Paws, Paper Hands, Dog Legends, etc. -- **LTH vs STH ratio** — the single most predictive metric for supply-side conviction. 78.6% of DOG supply is in long-term hands (>155 days). +- **LTH vs STH ratio** — the single most predictive metric for supply-side conviction. 79%+ of DOG supply is in long-term hands. +- **Multi-exchange markets** — 20+ exchanges including Solana DEX (Orca, Raydium, Meteora, Jupiter) and 6 CEX. Full spread and volume analysis. +- **Cross-chain data** — DOG bridged to Stacks (via Bitflow) and Solana. Track bridged supply, liquidity, and holder count per chain. +- **Bitcoin network context** — fee conditions and mempool state matter when planning large DOG transactions. No other API offers Diamond Score, forensic categorization, or LTH/STH breakdown for any rune. This is Glassnode-grade analytics for Bitcoin's fungible token layer. @@ -42,16 +49,17 @@ No other API offers Diamond Score, forensic categorization, or LTH/STH breakdown - **Read-only skill.** Does not write to chain, does not move funds, does not sign transactions. - No sensitive data processed. No private keys, no mnemonics, no wallet access required. -- All data sourced from DOG DATA's public API (dogdata.xyz). +- All data sourced from DOG DATA's public API (dogdata.xyz) — GET requests only. - Safe for mainnet and testnet. No network-specific risk. -- Rate limited: 20 req/hr without API key, 100 req/hr with free key. +- Rate limited: 20 req/hr without API key, 100 req/hr with free key, 5,000 req/hr with pro key. - If rate limited (HTTP 429), the skill returns `status: "blocked"` with retry information — never retries silently. ## Data source -[DOG DATA](https://dogdata.xyz) — the world's most comprehensive DOG rune data platform. Runs its own Bitcoin Core + Ord full node. No third-party API dependency. 35 REST endpoints, MCP server, SSE real-time events. +[DOG DATA](https://dogdata.xyz) — the world's most comprehensive DOG rune data platform. Runs its own Bitcoin Core + Ord full node. No third-party API dependency. 40 REST endpoints, MCP server, SSE real-time events. - API Discovery: https://www.dogdata.xyz/api +- Agent Capabilities: https://www.dogdata.xyz/api/agent/capabilities - OpenAPI Spec: https://www.dogdata.xyz/api/openapi.json - LLM Context: https://www.dogdata.xyz/llms.txt @@ -63,7 +71,7 @@ No other API offers Diamond Score, forensic categorization, or LTH/STH breakdown bun run dog-intelligence/dog-intelligence.ts doctor ``` -Checks API health, connectivity, and API key status. **Always run before other commands.** +Checks API health, connectivity, API key status, and smoke-tests all new endpoints. **Always run before other commands.** ### Market pulse snapshot @@ -71,7 +79,7 @@ Checks API health, connectivity, and API key status. **Always run before other c bun run dog-intelligence/dog-intelligence.ts run --action pulse ``` -### Whale tracking +### Whale tracking (multi-chain, >1M DOG threshold) ```bash bun run dog-intelligence/dog-intelligence.ts run --action whales @@ -95,8 +103,66 @@ bun run dog-intelligence/dog-intelligence.ts run --action airdrop bun run dog-intelligence/dog-intelligence.ts run --action lth-sth ``` +### Multi-exchange market data + +```bash +bun run dog-intelligence/dog-intelligence.ts run --action markets +``` + +### Cross-chain data (Stacks + Solana) + +```bash +bun run dog-intelligence/dog-intelligence.ts run --action multichain +``` + +### Bitcoin network status + +```bash +bun run dog-intelligence/dog-intelligence.ts run --action bitcoin +``` + ### Install optional SDK ```bash bun run dog-intelligence/dog-intelligence.ts install-packs ``` + +## Output contract + +All outputs are JSON to stdout using a consistent envelope: + +**Success:** +```json +{ + "status": "success", + "action": "descriptive message", + "data": {}, + "error": null, + "source": "dogdata.xyz", + "timestamp": "ISO-8601" +} +``` + +**Error:** +```json +{ + "status": "error", + "action": "context of what failed", + "data": null, + "error": "descriptive error message", + "source": "dogdata.xyz", + "timestamp": "ISO-8601" +} +``` + +**Rate limited:** +```json +{ + "status": "blocked", + "action": "rate_limited", + "data": null, + "error": "Rate limited on /endpoint. Retry after 60s.", + "source": "dogdata.xyz", + "timestamp": "ISO-8601" +} +``` diff --git a/skills/dog-intelligence/dog-intelligence.ts b/skills/dog-intelligence/dog-intelligence.ts index 1db937f4..c28128bd 100644 --- a/skills/dog-intelligence/dog-intelligence.ts +++ b/skills/dog-intelligence/dog-intelligence.ts @@ -12,6 +12,9 @@ * bun run dog-intelligence.ts run --action diamond * bun run dog-intelligence.ts run --action airdrop * bun run dog-intelligence.ts run --action lth-sth + * bun run dog-intelligence.ts run --action markets + * bun run dog-intelligence.ts run --action multichain + * bun run dog-intelligence.ts run --action bitcoin * bun run dog-intelligence.ts install-packs */ @@ -118,6 +121,12 @@ async function doctor(): Promise { checks.uptime_seconds = hd.uptime_seconds || 0; } + // Quick smoke-test new endpoints + const smokeTests = await fetchMultiple(["/markets", "/whale-alerts", "/multichain/stats"]); + checks.endpoint_markets = smokeTests["/markets"] ? "ok" : "unreachable"; + checks.endpoint_whale_alerts = smokeTests["/whale-alerts"] ? "ok" : "unreachable"; + checks.endpoint_multichain = smokeTests["/multichain/stats"] ? "ok" : "unreachable"; + const allOk = health.ok && checks.api_status === "healthy"; out( allOk ? "success" : "error", @@ -130,28 +139,37 @@ async function doctor(): Promise { async function pulse(): Promise { const raw = await fetchMultiple([ "/dog-rune/stats", - "/price/kraken", + "/markets", "/metrics/realized-cap", "/forensic/summary", "/metrics/utxo-age", ]); const stats = raw["/dog-rune/stats"] as Record | null; - const price = raw["/price/kraken"] as Record | null; + const marketsData = raw["/markets"] as Record | null; const realized = raw["/metrics/realized-cap"] as Record | null; const forensic = raw["/forensic/summary"] as Record | null; const utxoAge = raw["/metrics/utxo-age"] as Record | null; - // Extract Kraken price - const krakenResult = price?.result as Record> | undefined; + // Extract price from aggregated markets (use Kraken as primary, fallback to first ticker) + const tickers = (marketsData?.tickers as Record[]) ?? []; + const krakenTicker = tickers.find((t) => (t.market as string)?.toLowerCase() === "kraken"); + const primaryTicker = krakenTicker ?? tickers[0] ?? null; + const currentPrice = primaryTicker ? (primaryTicker.price as number) : null; + + // Aggregate volume across all exchanges + const totalVolumeUsd = tickers.reduce((s, t) => s + ((t.volumeUsd as number) || 0), 0); + + // 24h price change from Kraken raw if available + const krakenRaw = raw["/price/kraken"] as Record | null; + const krakenResult = krakenRaw?.result as Record> | undefined; const dogusd = krakenResult?.DOGUSD; - const currentPrice = dogusd?.c ? parseFloat((dogusd.c as string[])[0]) : null; - const volume24h = dogusd?.v ? parseFloat((dogusd.v as string[])[1]) : null; - const high24h = dogusd?.h ? parseFloat((dogusd.h as string[])[1]) : null; - const low24h = dogusd?.l ? parseFloat((dogusd.l as string[])[1]) : null; const openPrice = dogusd?.o ? parseFloat(dogusd.o as string) : null; const change24hPct = currentPrice && openPrice ? ((currentPrice - openPrice) / openPrice) * 100 : null; + // Exchange count + const exchangeCount = tickers.length; + // MVRV interpretation const mvrv = realized?.mvrv_ratio as number | null; let mvrvSignal = "neutral"; @@ -168,14 +186,24 @@ async function pulse(): Promise { const lthPct = utxoAge?.lth_percentage as number | null; const sthPct = utxoAge?.sth_percentage as number | null; + // Top exchanges by volume + const topExchanges = tickers + .sort((a, b) => ((b.volumeUsd as number) || 0) - ((a.volumeUsd as number) || 0)) + .slice(0, 5) + .map((t) => ({ + market: t.market, + pair: t.pair, + price_usd: t.price, + volume_usd: Math.round((t.volumeUsd as number) || 0), + })); + out("success", "DOG pulse snapshot. Use this data for market analysis and signal generation.", { price: { usd: currentPrice, change_24h_pct: change24hPct ? +change24hPct.toFixed(2) : null, - high_24h: high24h, - low_24h: low24h, - volume_24h_dog: volume24h, - exchange: "Kraken", + total_volume_usd: Math.round(totalVolumeUsd), + exchange_count: exchangeCount, + top_exchanges: topExchanges, }, fundamentals: { total_holders: stats?.totalHolders ?? null, @@ -193,7 +221,7 @@ async function pulse(): Promise { median_utxo_age_days: utxoAge?.median_age_days ? +(utxoAge.median_age_days as number).toFixed(1) : null, }, forensic: { - diamond_paws: forensicStats?.diamond_hands ?? null, + diamond_paws: (forensicStats?.by_pattern as Record)?.diamond_paws ?? forensicStats?.diamond_hands ?? null, dog_legends: (forensicStats?.by_pattern as Record)?.dog_legend ?? null, paper_hands: (forensicStats?.by_pattern as Record)?.paper_hands ?? null, retention_rate_pct: forensicStats?.retention_rate ? +(forensicStats.retention_rate as number).toFixed(2) : null, @@ -204,24 +232,37 @@ async function pulse(): Promise { async function whales(): Promise { const raw = await fetchMultiple([ - "/dog-rune/holders?limit=25", - "/dog-rune/transactions-kv", + "/whale-alerts", + "/dog-rune/top-holders", ]); - const holdersData = raw["/dog-rune/holders?limit=25"]; - const txData = raw["/dog-rune/transactions-kv"] as Record | null; - - // Parse holders — may be array or object with holders key - let holders: Record[] = []; - if (Array.isArray(holdersData)) { - holders = holdersData; - } else if (holdersData && typeof holdersData === "object") { - const hd = holdersData as Record; - if (Array.isArray(hd.holders)) holders = hd.holders; - else if (Array.isArray(hd.top10Holders)) holders = hd.top10Holders; - } + const whaleData = raw["/whale-alerts"] as Record | null; + const topHoldersData = raw["/dog-rune/top-holders"] as Record | null; + + // Parse whale alerts + const alerts = (whaleData?.alerts as Record[]) ?? []; + + // Format alerts by severity + const criticalAlerts = alerts.filter((a) => a.severity === "CRITICAL"); + const highAlerts = alerts.filter((a) => a.severity === "HIGH"); + + const formattedAlerts = alerts.slice(0, 25).map((a) => ({ + chain: a.chain, + severity: a.severity, + amount_dog: a.total_dog_formatted ?? a.total_dog_moved, + amount_dog_raw: a.total_dog_moved, + usd_value: a.usd_value, + classification: a.classification, + type: a.type, + from: a.from_short ?? a.from, + to: a.to_short ?? a.to, + block: a.block_height, + time_ago: a.time_ago, + explorer_url: a.explorer_url, + })); - // Format top holders + // Parse top holders from dedicated endpoint + const holders = (topHoldersData?.topHolders as Record[]) ?? []; const topHolders = holders.slice(0, 25).map((h: Record, i: number) => ({ rank: h.rank ?? i + 1, address: h.address, @@ -229,40 +270,27 @@ async function whales(): Promise { utxo_count: h.utxo_count ?? null, })); - // Extract large transactions (> 1M DOG) - const WHALE_THRESHOLD = 1_000_000; // 1M DOG - const txs = txData?.transactions as Record[] | undefined; - const whaleTxs = (txs || []) - .filter((tx: Record) => { - const amount = tx.amount as number | undefined; - const totalDog = tx.total_dog as number | undefined; - const val = totalDog ?? (amount ? amount / 1e5 : 0); - return val >= WHALE_THRESHOLD; - }) - .slice(0, 20) - .map((tx: Record) => ({ - txid: tx.txid ?? tx.tx_id, - block: tx.block_height ?? tx.block, - amount_dog: tx.total_dog ?? (typeof tx.amount === "number" ? tx.amount / 1e5 : null), - type: tx.type ?? "transfer", - timestamp: tx.timestamp ?? null, - })); - // Concentration const top10Sum = topHolders.slice(0, 10).reduce((s, h) => s + ((h.balance_dog as number) || 0), 0); const totalSupply = 100_000_000_000; const top10Pct = (top10Sum / totalSupply) * 100; - out("success", "Whale intelligence report. Top holders and large recent movements.", { + out("success", "Whale intelligence report. Multi-chain large moves and top holder positions.", { + whale_alerts: { + total: whaleData?.total_alerts ?? alerts.length, + critical_count: criticalAlerts.length, + high_count: highAlerts.length, + threshold: whaleData?.threshold ?? "1M DOG", + chains_monitored: whaleData?.chains ?? ["bitcoin"], + dog_price_usd: whaleData?.dog_price_usd ?? null, + alerts_by_chain: whaleData?.alerts_by_chain ?? null, + }, + recent_moves: formattedAlerts, top_holders: topHolders, - whale_transactions: whaleTxs, - whale_tx_count: whaleTxs.length, concentration: { top_10_supply_pct: +top10Pct.toFixed(2), top_10_total_dog: Math.round(top10Sum), }, - threshold_dog: WHALE_THRESHOLD, - total_transactions_scanned: txs?.length ?? 0, }); } @@ -427,13 +455,167 @@ async function lthSth(): Promise { }); } +async function markets(): Promise { + const raw = await fetchMultiple([ + "/markets", + "/price/kraken", + ]); + + const marketsData = raw["/markets"] as Record | null; + const krakenRaw = raw["/price/kraken"] as Record | null; + + const tickers = (marketsData?.tickers as Record[]) ?? []; + + // Sort by volume + const byVolume = [...tickers].sort((a, b) => ((b.volumeUsd as number) || 0) - ((a.volumeUsd as number) || 0)); + + // Aggregate stats + const totalVolumeUsd = byVolume.reduce((s, t) => s + ((t.volumeUsd as number) || 0), 0); + const prices = byVolume.map((t) => t.price as number).filter(Boolean); + const highestPrice = prices.length ? Math.max(...prices) : null; + const lowestPrice = prices.length ? Math.min(...prices) : null; + const spread = highestPrice && lowestPrice ? ((highestPrice - lowestPrice) / lowestPrice) * 100 : null; + + // Kraken 24h data + const krakenResult = krakenRaw?.result as Record> | undefined; + const dogusd = krakenResult?.DOGUSD; + const high24h = dogusd?.h ? parseFloat((dogusd.h as string[])[1]) : null; + const low24h = dogusd?.l ? parseFloat((dogusd.l as string[])[1]) : null; + const open24h = dogusd?.o ? parseFloat(dogusd.o as string) : null; + + const formattedTickers = byVolume.map((t) => ({ + market: t.market, + pair: t.pair, + price_usd: t.price, + volume_usd: Math.round((t.volumeUsd as number) || 0), + volume_dog: t.volume, + spread_pct: t.spread, + trust_score: t.trustScore, + trade_url: t.tradeUrl, + })); + + out("success", "Full market snapshot across all DOG exchanges.", { + aggregate: { + exchange_count: tickers.length, + total_volume_usd_24h: Math.round(totalVolumeUsd), + highest_price_usd: highestPrice, + lowest_price_usd: lowestPrice, + price_spread_pct: spread ? +spread.toFixed(4) : null, + high_24h_kraken: high24h, + low_24h_kraken: low24h, + open_24h_kraken: open24h, + }, + exchanges: formattedTickers, + }); +} + +async function multichain(): Promise { + const raw = await fetchMultiple([ + "/multichain/stats", + "/multichain/holders?limit=10", + ]); + + const statsData = raw["/multichain/stats"] as Record | null; + const holdersData = raw["/multichain/holders?limit=10"] as Record | null; + + const chains = (statsData?.chains as Record[]) ?? []; + + // Parse per-chain stats + const chainSummaries = chains.map((c) => ({ + chain: c.chain, + symbol: c.symbol, + price_usd: c.price_usd, + price_change_24h_pct: c.price_change_24h ? +(c.price_change_24h as number).toFixed(2) : null, + market_cap_usd: c.market_cap_usd ? Math.round(c.market_cap_usd as number) : null, + volume_24h_usd: c.volume_24h_usd ? Math.round(c.volume_24h_usd as number) : null, + liquidity_usd: c.liquidity_usd ? Math.round(c.liquidity_usd as number) : null, + holder_count: c.holder_count, + circulating_supply: c.circulating_supply, + contract: c.address, + last_updated: c.last_updated, + })); + + // Top cross-chain holders + let holders: unknown[] = []; + if (Array.isArray(holdersData)) { + holders = holdersData.slice(0, 10); + } else if (holdersData && typeof holdersData === "object") { + const hd = holdersData as Record; + if (Array.isArray(hd.holders)) holders = hd.holders.slice(0, 10); + } + + out("success", "Cross-chain DOG intelligence. Stacks and Solana bridged supply overview.", { + aggregate: { + total_holders_all_chains: statsData?.total_holders ?? null, + total_market_cap_usd: statsData?.total_market_cap_usd ? Math.round(statsData.total_market_cap_usd as number) : null, + total_volume_24h_usd: statsData?.total_volume_24h_usd ? Math.round(statsData.total_volume_24h_usd as number) : null, + total_supply_all_chains: statsData?.total_supply_all_chains ?? null, + last_updated: statsData?.last_updated ?? null, + }, + chains: chainSummaries, + top_holders: holders, + }); +} + +async function bitcoin(): Promise { + const btcData = await get("/bitcoin"); + if (!btcData.ok) { + out("error", "bitcoin", null, `Failed to fetch Bitcoin network data: HTTP ${btcData.status}`); + return; + } + + const d = btcData.data as Record; + const diff = d.difficultyAdjustment as Record | undefined; + const hashrate = d.hashrate as Record | undefined; + const mempool = d.mempool as Record | undefined; + const fees = d.fees as Record | undefined; + const blocks = d.blocks as Record[] | undefined; + const latestBlock = blocks?.[0] as Record | undefined; + + // Current hashrate in EH/s + const currentHashrateRaw = hashrate?.currentHashrate as number | null; + const currentHashrateEH = currentHashrateRaw ? currentHashrateRaw / 1e18 : null; + + // Fee recommendations + const feeRec = fees?.recommended as Record | undefined; + + out("success", "Bitcoin network status. Context for DOG L1 activity and on-chain conditions.", { + network: { + latest_block: latestBlock?.height ?? null, + latest_block_time: latestBlock?.timestamp ?? null, + hashrate_eh_s: currentHashrateEH ? +currentHashrateEH.toFixed(2) : null, + difficulty: hashrate?.currentDifficulty ? +(hashrate.currentDifficulty as number).toFixed(0) : null, + }, + difficulty_adjustment: { + progress_pct: diff?.progressPercent ? +(diff.progressPercent as number).toFixed(2) : null, + estimated_change_pct: diff?.difficultyChange ? +(diff.difficultyChange as number).toFixed(2) : null, + blocks_remaining: diff?.remainingBlocks ?? null, + next_retarget_height: diff?.nextRetargetHeight ?? null, + }, + mempool: { + tx_count: mempool?.count ?? null, + size_vb: mempool?.vsize ?? null, + total_fees_sat: mempool?.total_fee ?? null, + }, + fees: { + fastest_sat_vb: feeRec?.fastestFee ?? null, + half_hour_sat_vb: feeRec?.halfHourFee ?? null, + hour_sat_vb: feeRec?.hourFee ?? null, + economy_sat_vb: feeRec?.economyFee ?? null, + minimum_sat_vb: feeRec?.minimumFee ?? null, + }, + }); +} + async function installPacks(): Promise { - out("success", "No additional packages required. dog-intelligence uses fetch (built into Bun) and the DOG DATA REST API. Optionally set DOGDATA_API_KEY env var for higher rate limits (100 req/hr).", { + out("success", "No additional packages required. dog-intelligence uses fetch (built into Bun) and the DOG DATA REST API. Optionally set DOGDATA_API_KEY env var for higher rate limits.", { required_dependencies: [], optional: { env_var: "DOGDATA_API_KEY", get_key: "POST https://www.dogdata.xyz/api/keys/generate with {\"email\": \"...\", \"name\": \"...\"}", free_tier: "100 req/hr", + pro_tier: "5,000 req/hr", + enterprise_tier: "50,000 req/hr", public_tier: "20 req/hr (no key needed)", }, }); @@ -467,8 +649,17 @@ async function main(): Promise { case "lth-sth": await lthSth(); break; + case "markets": + await markets(); + break; + case "multichain": + await multichain(); + break; + case "bitcoin": + await bitcoin(); + break; default: - out("error", "Unknown action", null, `Unknown action: ${action}. Valid: pulse, whales, diamond, airdrop, lth-sth`); + out("error", "Unknown action", null, `Unknown action: ${action}. Valid: pulse, whales, diamond, airdrop, lth-sth, markets, multichain, bitcoin`); } break; case "install-packs": From 730697bc8ec641414e072b55c70a533a3f86125c Mon Sep 17 00:00:00 2001 From: LimaDevBTC Date: Wed, 29 Apr 2026 18:22:23 -0300 Subject: [PATCH 4/6] =?UTF-8?q?fix:=20Commander.js=20CLI,=20drop=20require?= =?UTF-8?q?s,=20fix=20tags=20=E2=80=94=20conformance=20scanner=20blockers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Migrate main() from manual Bun.argv switch to Commander.js (rule CLI_USES_COMMANDER + CLI_HAS_DOCTOR_SUBCOMMAND — BLOCKS MERGE) - Add skills/dog-intelligence/package.json with commander ^12.1.0 dep - Drop requires: "settings" from SKILL.md — skill doesn't exist on aibtcdev/skills:main or BitflowFinance/bff-skills:main (BLOCKS MERGE) - Drop tag "multichain" from SKILL.md — not in allowed values list (FRONTMATTER_TAGS_VALID_VALUES — BLOCKS MERGE on strict checks) Co-Authored-By: Claude Sonnet 4.6 --- skills/dog-intelligence/SKILL.md | 3 +- skills/dog-intelligence/dog-intelligence.ts | 86 ++++++++++----------- skills/dog-intelligence/package.json | 8 ++ 3 files changed, 48 insertions(+), 49 deletions(-) create mode 100644 skills/dog-intelligence/package.json diff --git a/skills/dog-intelligence/SKILL.md b/skills/dog-intelligence/SKILL.md index 737701f4..b1964d28 100644 --- a/skills/dog-intelligence/SKILL.md +++ b/skills/dog-intelligence/SKILL.md @@ -7,8 +7,7 @@ metadata: user-invocable: "false" arguments: "doctor | run --action pulse | run --action whales | run --action diamond | run --action airdrop | run --action lth-sth | run --action markets | run --action multichain | run --action bitcoin | install-packs" entry: "dog-intelligence/dog-intelligence.ts" - requires: "settings" - tags: "read-only, infrastructure, defi, l1, multichain" + tags: "read-only, infrastructure, defi, l1" --- # dog-intelligence diff --git a/skills/dog-intelligence/dog-intelligence.ts b/skills/dog-intelligence/dog-intelligence.ts index c28128bd..7bc8a32d 100644 --- a/skills/dog-intelligence/dog-intelligence.ts +++ b/skills/dog-intelligence/dog-intelligence.ts @@ -623,54 +623,46 @@ async function installPacks(): Promise { // --- Main --- -async function main(): Promise { - const command = Bun.argv[2]; - const actionFlag = Bun.argv.indexOf("--action"); - const action = actionFlag !== -1 ? Bun.argv[actionFlag + 1] : "pulse"; - - switch (command) { - case "doctor": - await doctor(); - break; - case "run": - switch (action) { - case "pulse": - await pulse(); - break; - case "whales": - await whales(); - break; - case "diamond": - await diamond(); - break; - case "airdrop": - await airdrop(); - break; - case "lth-sth": - await lthSth(); - break; - case "markets": - await markets(); - break; - case "multichain": - await multichain(); - break; - case "bitcoin": - await bitcoin(); - break; - default: - out("error", "Unknown action", null, `Unknown action: ${action}. Valid: pulse, whales, diamond, airdrop, lth-sth, markets, multichain, bitcoin`); - } - break; - case "install-packs": - await installPacks(); - break; - default: - out("error", "Unknown command", null, `Unknown command: ${command}. Valid: doctor, run, install-packs`); - } -} +import { Command } from "commander"; + +const program = new Command(); +program + .name("dog-intelligence") + .description("On-chain intelligence for DOG•GO•TO•THE•MOON rune (read-only)") + .version("0.1.0"); + +program + .command("doctor") + .description("Probe DOG DATA API health + endpoint reachability") + .action(async () => { await doctor(); }); + +program + .command("install-packs") + .description("Report optional env vars and rate-limit tiers") + .action(async () => { await installPacks(); }); + +program + .command("run") + .description("Run a read-only intelligence action") + .requiredOption("--action ", "pulse | whales | diamond | airdrop | lth-sth | markets | multichain | bitcoin") + .action(async (opts: { action: string }) => { + switch (opts.action) { + case "pulse": await pulse(); break; + case "whales": await whales(); break; + case "diamond": await diamond(); break; + case "airdrop": await airdrop(); break; + case "lth-sth": await lthSth(); break; + case "markets": await markets(); break; + case "multichain": await multichain(); break; + case "bitcoin": await bitcoin(); break; + default: + out("error", "Unknown action", null, + `Unknown action: ${opts.action}. Valid: pulse, whales, diamond, airdrop, lth-sth, markets, multichain, bitcoin`); + process.exit(1); + } + }); -main().catch((err) => { +program.parseAsync(process.argv).catch((err) => { out("error", "Fatal error", null, err instanceof Error ? err.message : String(err)); process.exit(1); }); diff --git a/skills/dog-intelligence/package.json b/skills/dog-intelligence/package.json new file mode 100644 index 00000000..77b5ee89 --- /dev/null +++ b/skills/dog-intelligence/package.json @@ -0,0 +1,8 @@ +{ + "name": "dog-intelligence", + "version": "0.1.0", + "description": "On-chain intelligence for DOG•GO•TO•THE•MOON rune", + "dependencies": { + "commander": "^12.1.0" + } +} From 1a6da00e51e6d29a07f642173604053826b3f59b Mon Sep 17 00:00:00 2001 From: LimaDevBTC Date: Fri, 8 May 2026 12:38:36 -0300 Subject: [PATCH 5/6] =?UTF-8?q?fix:=20truthy-null=20traps=20+=20bitcoin=20?= =?UTF-8?q?429=20=E2=86=92=20blocked=20+=20numeric=20hodl=5Fwaves?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses arc0btc 2026-05-08 review: 1. Truthy-null traps: replace `x ? +x.toFixed(N) : null` with a `num()` helper that uses `typeof x === "number" && Number.isFinite(x)` so legitimate 0 values are preserved instead of being silently coerced to null. Affected: lth_pct, sth_pct, change_24h, gini, top_10/100/1000, in_profit, in_loss, retention/accumulator/dumper rates, hashrate_eh_s, difficulty, progress_pct, estimated_change_pct, price_spread_pct, price_change_24h, median_age_days, avg_age_days, total_market_cap_usd, total_volume_24h_usd, liquidity_usd, market_cap_usd, volume_24h_usd. 2. bitcoin handler: emit `status: "blocked"` on HTTP 429 (matching the other 7 actions) so agents running backoff logic detect rate limits consistently. 3. hodl_waves: store numeric percentages (`+(...).toFixed(2)`) instead of string suffixes ("12.34%"), aligning with every other percentage field. Co-Authored-By: Claude Sonnet 4.6 --- skills/dog-intelligence/dog-intelligence.ts | 92 ++++++++++++--------- 1 file changed, 54 insertions(+), 38 deletions(-) diff --git a/skills/dog-intelligence/dog-intelligence.ts b/skills/dog-intelligence/dog-intelligence.ts index 7bc8a32d..9800294b 100644 --- a/skills/dog-intelligence/dog-intelligence.ts +++ b/skills/dog-intelligence/dog-intelligence.ts @@ -33,6 +33,12 @@ interface Output { timestamp: string; } +// Round a number to N decimals, preserving 0. Returns null only for non-numbers +// (typeof check avoids the truthy-null trap where `0 ? ... : null` returns null). +function num(x: unknown, decimals = 2): number | null { + return typeof x === "number" && Number.isFinite(x) ? +x.toFixed(decimals) : null; +} + function out(status: Output["status"], action: string, data: Output["data"], error: string | null = null): void { const result: Output = { status, @@ -197,10 +203,14 @@ async function pulse(): Promise { volume_usd: Math.round((t.volumeUsd as number) || 0), })); + const lthSthRatio = typeof lthPct === "number" && typeof sthPct === "number" && sthPct !== 0 + ? +(lthPct / sthPct).toFixed(2) + : null; + out("success", "DOG pulse snapshot. Use this data for market analysis and signal generation.", { price: { usd: currentPrice, - change_24h_pct: change24hPct ? +change24hPct.toFixed(2) : null, + change_24h_pct: num(change24hPct, 2), total_volume_usd: Math.round(totalVolumeUsd), exchange_count: exchangeCount, top_exchanges: topExchanges, @@ -215,16 +225,16 @@ async function pulse(): Promise { mvrv_signal: mvrvSignal, }, conviction: { - lth_percentage: lthPct ? +lthPct.toFixed(2) : null, - sth_percentage: sthPct ? +sthPct.toFixed(2) : null, - lth_sth_ratio: lthPct && sthPct ? +(lthPct / sthPct).toFixed(2) : null, - median_utxo_age_days: utxoAge?.median_age_days ? +(utxoAge.median_age_days as number).toFixed(1) : null, + lth_percentage: num(lthPct, 2), + sth_percentage: num(sthPct, 2), + lth_sth_ratio: lthSthRatio, + median_utxo_age_days: num(utxoAge?.median_age_days, 1), }, forensic: { diamond_paws: (forensicStats?.by_pattern as Record)?.diamond_paws ?? forensicStats?.diamond_hands ?? null, dog_legends: (forensicStats?.by_pattern as Record)?.dog_legend ?? null, paper_hands: (forensicStats?.by_pattern as Record)?.paper_hands ?? null, - retention_rate_pct: forensicStats?.retention_rate ? +(forensicStats.retention_rate as number).toFixed(2) : null, + retention_rate_pct: num(forensicStats?.retention_rate, 2), total_analyzed: forensicStats?.total_analyzed ?? null, }, }); @@ -320,9 +330,9 @@ async function diamond(): Promise { total_analyzed: stats?.total_analyzed ?? null, still_holding: stats?.still_holding ?? null, sold_everything: stats?.sold_everything ?? null, - retention_rate_pct: stats?.retention_rate ? +(stats.retention_rate as number).toFixed(2) : null, - accumulator_rate_pct: stats?.accumulator_rate ? +(stats.accumulator_rate as number).toFixed(2) : null, - dumper_rate_pct: stats?.dumper_rate ? +(stats.dumper_rate as number).toFixed(2) : null, + retention_rate_pct: num(stats?.retention_rate, 2), + accumulator_rate_pct: num(stats?.accumulator_rate, 2), + dumper_rate_pct: num(stats?.dumper_rate, 2), }, categories: { diamond_paws: patterns?.diamond_paws ?? null, @@ -406,50 +416,52 @@ async function lthSth(): Promise { const lthPct = utxoAge?.lth_percentage as number | null; const sthPct = utxoAge?.sth_percentage as number | null; - const lthSthRatio = lthPct && sthPct && sthPct > 0 ? lthPct / sthPct : null; + const lthSthRatio = typeof lthPct === "number" && typeof sthPct === "number" && sthPct > 0 + ? +(lthPct / sthPct).toFixed(2) + : null; // Interpret let convictionSignal = "neutral"; - if (lthPct !== null) { + if (typeof lthPct === "number") { if (lthPct > 75) convictionSignal = "strong — majority of supply in long-term hands, structurally bullish"; else if (lthPct > 50) convictionSignal = "moderate — more holders are long-term than short-term"; else convictionSignal = "weak — short-term holders dominate, higher sell pressure risk"; } - // HODL waves + // HODL waves — store as numeric percentages (consistent with other percentage fields) const ageDist = utxoAge?.age_distribution as Record | undefined; - const totalSupplyRaw = utxoAge?.total_supply as number ?? 1; - const hodlWaves: Record = {}; + const totalSupplyRaw = (utxoAge?.total_supply as number) ?? 1; + const hodlWaves: Record = {}; if (ageDist) { for (const [range, supply] of Object.entries(ageDist)) { - hodlWaves[range] = ((supply / totalSupplyRaw) * 100).toFixed(2) + "%"; + hodlWaves[range] = +((supply / totalSupplyRaw) * 100).toFixed(2); } } out("success", "LTH vs STH conviction analysis — the trademark DOG intelligence metric.", { lth_vs_sth: { - lth_percentage: lthPct ? +lthPct.toFixed(2) : null, - sth_percentage: sthPct ? +sthPct.toFixed(2) : null, - lth_sth_ratio: lthSthRatio ? +lthSthRatio.toFixed(2) : null, + lth_percentage: num(lthPct, 2), + sth_percentage: num(sthPct, 2), + lth_sth_ratio: lthSthRatio, conviction_signal: convictionSignal, lth_threshold_days: 155, }, utxo_age: { total_utxos: utxoAge?.total_utxos ?? null, - avg_age_days: utxoAge?.avg_age_days ? +(utxoAge.avg_age_days as number).toFixed(1) : null, - median_age_days: utxoAge?.median_age_days ? +(utxoAge.median_age_days as number).toFixed(1) : null, + avg_age_days: num(utxoAge?.avg_age_days, 1), + median_age_days: num(utxoAge?.median_age_days, 1), }, hodl_waves: hodlWaves, concentration: { - gini_coefficient: concentration?.gini_coefficient ? +(concentration.gini_coefficient as number).toFixed(4) : null, - top_10_pct: concentration?.top10_supply_pct ? +(concentration.top10_supply_pct as number).toFixed(2) : null, - top_100_pct: concentration?.top100_supply_pct ? +(concentration.top100_supply_pct as number).toFixed(2) : null, - top_1000_pct: concentration?.top1000_supply_pct ? +(concentration.top1000_supply_pct as number).toFixed(2) : null, + gini_coefficient: num(concentration?.gini_coefficient, 4), + top_10_pct: num(concentration?.top10_supply_pct, 2), + top_100_pct: num(concentration?.top100_supply_pct, 2), + top_1000_pct: num(concentration?.top1000_supply_pct, 2), total_holders: concentration?.total_holders ?? null, }, supply_health: { - in_profit_pct: profitLoss?.supply_in_profit_pct ? +(profitLoss.supply_in_profit_pct as number).toFixed(2) : null, - in_loss_pct: profitLoss?.supply_in_loss_pct ? +(profitLoss.supply_in_loss_pct as number).toFixed(2) : null, + in_profit_pct: num(profitLoss?.supply_in_profit_pct, 2), + in_loss_pct: num(profitLoss?.supply_in_loss_pct, 2), current_price_usd: profitLoss?.current_price ?? null, }, }); @@ -500,7 +512,7 @@ async function markets(): Promise { total_volume_usd_24h: Math.round(totalVolumeUsd), highest_price_usd: highestPrice, lowest_price_usd: lowestPrice, - price_spread_pct: spread ? +spread.toFixed(4) : null, + price_spread_pct: num(spread, 4), high_24h_kraken: high24h, low_24h_kraken: low24h, open_24h_kraken: open24h, @@ -525,10 +537,10 @@ async function multichain(): Promise { chain: c.chain, symbol: c.symbol, price_usd: c.price_usd, - price_change_24h_pct: c.price_change_24h ? +(c.price_change_24h as number).toFixed(2) : null, - market_cap_usd: c.market_cap_usd ? Math.round(c.market_cap_usd as number) : null, - volume_24h_usd: c.volume_24h_usd ? Math.round(c.volume_24h_usd as number) : null, - liquidity_usd: c.liquidity_usd ? Math.round(c.liquidity_usd as number) : null, + price_change_24h_pct: num(c.price_change_24h, 2), + market_cap_usd: typeof c.market_cap_usd === "number" ? Math.round(c.market_cap_usd) : null, + volume_24h_usd: typeof c.volume_24h_usd === "number" ? Math.round(c.volume_24h_usd) : null, + liquidity_usd: typeof c.liquidity_usd === "number" ? Math.round(c.liquidity_usd) : null, holder_count: c.holder_count, circulating_supply: c.circulating_supply, contract: c.address, @@ -547,8 +559,8 @@ async function multichain(): Promise { out("success", "Cross-chain DOG intelligence. Stacks and Solana bridged supply overview.", { aggregate: { total_holders_all_chains: statsData?.total_holders ?? null, - total_market_cap_usd: statsData?.total_market_cap_usd ? Math.round(statsData.total_market_cap_usd as number) : null, - total_volume_24h_usd: statsData?.total_volume_24h_usd ? Math.round(statsData.total_volume_24h_usd as number) : null, + total_market_cap_usd: typeof statsData?.total_market_cap_usd === "number" ? Math.round(statsData.total_market_cap_usd) : null, + total_volume_24h_usd: typeof statsData?.total_volume_24h_usd === "number" ? Math.round(statsData.total_volume_24h_usd) : null, total_supply_all_chains: statsData?.total_supply_all_chains ?? null, last_updated: statsData?.last_updated ?? null, }, @@ -560,6 +572,10 @@ async function multichain(): Promise { async function bitcoin(): Promise { const btcData = await get("/bitcoin"); if (!btcData.ok) { + if (btcData.status === 429) { + out("blocked", "rate_limited", null, `Rate limited on /bitcoin. Retry after ${btcData.retryAfter ?? 60}s.`); + return; + } out("error", "bitcoin", null, `Failed to fetch Bitcoin network data: HTTP ${btcData.status}`); return; } @@ -574,7 +590,7 @@ async function bitcoin(): Promise { // Current hashrate in EH/s const currentHashrateRaw = hashrate?.currentHashrate as number | null; - const currentHashrateEH = currentHashrateRaw ? currentHashrateRaw / 1e18 : null; + const currentHashrateEH = typeof currentHashrateRaw === "number" ? currentHashrateRaw / 1e18 : null; // Fee recommendations const feeRec = fees?.recommended as Record | undefined; @@ -583,12 +599,12 @@ async function bitcoin(): Promise { network: { latest_block: latestBlock?.height ?? null, latest_block_time: latestBlock?.timestamp ?? null, - hashrate_eh_s: currentHashrateEH ? +currentHashrateEH.toFixed(2) : null, - difficulty: hashrate?.currentDifficulty ? +(hashrate.currentDifficulty as number).toFixed(0) : null, + hashrate_eh_s: num(currentHashrateEH, 2), + difficulty: num(hashrate?.currentDifficulty, 0), }, difficulty_adjustment: { - progress_pct: diff?.progressPercent ? +(diff.progressPercent as number).toFixed(2) : null, - estimated_change_pct: diff?.difficultyChange ? +(diff.difficultyChange as number).toFixed(2) : null, + progress_pct: num(diff?.progressPercent, 2), + estimated_change_pct: num(diff?.difficultyChange, 2), blocks_remaining: diff?.remainingBlocks ?? null, next_retarget_height: diff?.nextRetargetHeight ?? null, }, From 0c3796684166536a4bde411522228ee6f18736f0 Mon Sep 17 00:00:00 2001 From: arc <224894192+arc0btc@users.noreply.github.com> Date: Fri, 8 May 2026 12:27:19 -0600 Subject: [PATCH 6/6] fix(dog-intelligence): hygiene items #2, #4, #5 from issue #598 - get() catch now captures err and returns message string so timeout vs DNS vs TLS errors are distinguishable in the response payload - rate_limited out() calls now include retry_after_seconds as a top-level numeric field (fetchMultiple and bitcoin action handlers) - totalSupply constant in whales() gets a comment explaining the fixed etching supply (rune 840000:3 cannot mint) --- skills/dog-intelligence/dog-intelligence.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/skills/dog-intelligence/dog-intelligence.ts b/skills/dog-intelligence/dog-intelligence.ts index 9800294b..188f9ff8 100644 --- a/skills/dog-intelligence/dog-intelligence.ts +++ b/skills/dog-intelligence/dog-intelligence.ts @@ -78,9 +78,9 @@ async function get(path: string): Promise<{ ok: boolean; status: number; data: u } const json = await res.json(); return { ok: true, status: res.status, data: json }; - } catch { + } catch (err) { clearTimeout(timer); - return { ok: false, status: 0, data: null, retryAfter: undefined }; + return { ok: false, status: 0, data: err instanceof Error ? err.message : String(err), retryAfter: undefined }; } } @@ -91,7 +91,7 @@ async function fetchMultiple(paths: string[]): Promise> const r = results[i]; if (r.status === "fulfilled") { if (r.value.status === 429) { - out("blocked", "rate_limited", null, `Rate limited on ${paths[i]}. Retry after ${r.value.retryAfter}s.`); + out("blocked", "rate_limited", { retry_after_seconds: r.value.retryAfter ?? 60 }, `Rate limited on ${paths[i]}. Retry after ${r.value.retryAfter ?? 60}s.`); process.exit(0); } output[paths[i]] = r.value.ok ? r.value.data : null; @@ -282,6 +282,7 @@ async function whales(): Promise { // Concentration const top10Sum = topHolders.slice(0, 10).reduce((s, h) => s + ((h.balance_dog as number) || 0), 0); + // DOG total supply is fixed at etching (rune 840000:3). Cannot change. const totalSupply = 100_000_000_000; const top10Pct = (top10Sum / totalSupply) * 100; @@ -573,7 +574,7 @@ async function bitcoin(): Promise { const btcData = await get("/bitcoin"); if (!btcData.ok) { if (btcData.status === 429) { - out("blocked", "rate_limited", null, `Rate limited on /bitcoin. Retry after ${btcData.retryAfter ?? 60}s.`); + out("blocked", "rate_limited", { retry_after_seconds: btcData.retryAfter ?? 60 }, `Rate limited on /bitcoin. Retry after ${btcData.retryAfter ?? 60}s.`); return; } out("error", "bitcoin", null, `Failed to fetch Bitcoin network data: HTTP ${btcData.status}`);