tokenstats

GitHub Repo | tokenstats.ai | Updated: November 2026

tokenstats watches the market for AI inference. It listens on Nostr for Routstr nodes announcing themselves, polls their model catalogs alongside OpenRouter and OrcaRouter, prices every model in USD and sats from a live BTC/USD rate, and serves the result as a live price board and a JSON API. It runs at tokenstats.ai.

Tech Stack: - Rust CLI with an Axum dashboard and HTTP API - Nostr (Routstr RIP-02 provider discovery, kind 38421) - SQLite (persistence and price history) - Coinbase BTC/USD spot price (USD and sats normalization) - Docker image on GitHub Container Registry, Caddy for HTTPS

License: Mozilla Public License 2.0 (MPL-2.0)

Why I Built This

tokenstats is the feed behind arbstr's routing. arbstr picks the cheapest qualified provider for each request, and tokenstats is where most of "qualified" comes from: arbstr 2.5 added require_healthy, which skips any endpoint tokenstats marks unhealthy. The two stay separate on purpose. tokenstats observes, arbstr executes. Observing is free, too: tokenstats never spends a sat or needs a key to measure a node.

The market it watches is messy. On any given cycle, roughly 70 Routstr nodes are announced on Nostr and fewer than half answer, and relays keep replaying announcements from nodes that are long gone.

How It Works

Nostr (kind 38421) ──► store.nodes ──► poll /v1/models ──┐
                                                         ├──► store.quotes ──► dashboard / API
OpenRouter + OrcaRouter /v1/models ─────────────────────┘
Coinbase BTC-USD ──► oracle (USD ↔ sats)
SQLite snapshot every 30s
  • Discovery and polling. Routstr nodes are discovered from Nostr announcements, and each node's /v1/models is polled concurrently alongside the OpenRouter and OrcaRouter catalogs.
  • Signals. Every quote says how old it is, whether it's stale, and whether it's an outlier (more than 10x off that model's median). Reliability over 1 hour, 24 hours, and 7 days, plus p50 and p95 latency, are kept in SQLite, so they survive restarts. A healthy flag on every quote combines the last fetch, 24 hour reliability of at least 90%, and freshness.
  • Same model, every source. Model ids are mapped to one canonical id, and the compare view (/compare, /api/compare) shows one model's offers from every source side by side, with the spread and the cheapest healthy offer.
  • Capabilities. Quotes carry tools, reasoning, and vision flags. When a Routstr node doesn't publish them, they are filled from OpenRouter or OrcaRouter and marked as inferred.
  • Price history. A point is recorded whenever a catalog's published price for an offer changes: USD when the catalog lists USD, sats for sats-only nodes. Moves under 0.1% don't count, and prices converted at the BTC/USD rate are ignored, since they move on every poll. /history draws each offer's price as a step chart over 1, 7, 30, or 90 days with a log of the changes, and /api/history serves the same points as JSON, grouped like /api/compare. Points are kept for 90 days by default. Quotes also carry price_changed_at and the blended price change over 24 hours, 7 days, and 30 days. Tracking started November 1, 2026, so the longer windows are still filling in.
  • Safety. Nostr-announced nodes on private, loopback, or link-local addresses are never requested (SSRF guard), the node list is capped at 512, and the dashboard loads no third-party code unless analytics are configured.

What I Learned

My first pass at expiring dead nodes went by last-seen time alone, and it dropped 27 of 40 nodes in two cycles. Plenty of live nodes last announced months ago, and relays replay those announcements on every connect. The fix was to expire a node only after it has been neither announced nor answering for a full week of attempts. A market feed has to be skeptical of what it's told without throwing out what's still working.


A router is only as good as what it believes about the market.